Ajout du support basique pour les recettes dans l'API REST.

This commit is contained in:
FyloZ 2021-01-10 00:51:45 -05:00
parent e56f185489
commit 66cec621ac
29 changed files with 383 additions and 197 deletions

View File

@ -39,6 +39,10 @@ public class Mix implements Model {
// Casier
private String location;
public MixType getMixType() {
return mixType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -1,113 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.hibernate.validator.constraints.Length;
import org.jetbrains.annotations.Nullable;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.stream.Collectors;
@Entity
@Data
@RequiredArgsConstructor
@NoArgsConstructor
public class Recipe implements Model {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@NonNull
@NotNull
@Length(min = 2)
private String name;
@NonNull
@NotNull
@ManyToOne
private Company company;
@NonNull
@NotNull
private String description;
@NonNull
@NotNull
private Integer sample;
private String approbationDate;
private String remark;
private String note;
@JsonIgnore
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<Mix> mixes;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeStep> recipeSteps;
public Recipe(Long id, String name, Company company, String description, Integer sample, String approbationDate, String remark, String note) {
this.id = id;
this.name = name;
this.company = company;
this.description = description;
this.sample = sample;
this.approbationDate = approbationDate;
this.remark = remark;
this.note = note;
}
/**
* Récupère les mélanges triés par leur identifiant.
*
* @return Les mélanges triés par leur identifiant
*/
@JsonIgnore
public List<Mix> getMixesSortedById() {
List<Mix> sortedMixes = new ArrayList<>(mixes);
sortedMixes.sort(Comparator.comparing(Mix::getId));
return sortedMixes;
}
/**
* Récupère les types de mélange des mélanges de la recette.
*
* @return Les types de mélange contenus dans la recette
*/
@JsonIgnore
public Collection<MixType> getMixTypes() {
return mixes.stream()
.map(Mix::getMixType)
.collect(Collectors.toList());
}
/**
* Vérifie si la recette contient un type de mélange.
*
* @param mixType Le type de mélange
* @return Si la recette contient le type de mélange
*/
@JsonIgnore
public boolean hasMixType(MixType mixType) {
return getMixTypes().contains(mixType);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Recipe recipe = (Recipe) o;
return Objects.equals(name, recipe.name) &&
Objects.equals(company, recipe.company);
}
@Override
public int hashCode() {
return Objects.hash(name, company);
}
}

View File

@ -1,7 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.model.dto;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import lombok.Data;
import lombok.NoArgsConstructor;

View File

@ -4,6 +4,7 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Company;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
@Data
@ -19,7 +20,7 @@ public class RecipeEditorFormDto {
private Integer sample;
private String approbationDate;
private LocalDate approbationDate;
private String remark;
@ -32,15 +33,7 @@ public class RecipeEditorFormDto {
}
public Recipe update(Recipe original) {
original.setName(name);
original.setCompany(company);
original.setDescription(description);
original.setSample(sample);
original.setApprobationDate(approbationDate);
original.setRemark(remark);
original.setNote(note);
return original;
return new Recipe(original.getId(), name, company, description, sample, approbationDate, remark, note);
}
}

View File

@ -1,19 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository;
import dev.fyloz.trial.colorrecipesexplorer.model.Company;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RecipeRepository extends JpaRepository<Recipe, Long> {
List<Recipe> findAllByCompany(Company company);
Recipe findByName(String name);
boolean existsByCompany(Company company);
}

View File

@ -2,7 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.service.files;
import dev.fyloz.trial.colorrecipesexplorer.config.Preferences;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import dev.fyloz.trial.colorrecipesexplorer.xlsx.XlsxExporter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -16,10 +16,10 @@ import java.util.zip.ZipOutputStream;
@Service
public class XlsService {
private RecipeService recipeService;
private RecipeJavaService recipeService;
@Autowired
public XlsService(RecipeService recipeService) {
public XlsService(RecipeJavaService recipeService) {
this.recipeService = recipeService;
}

View File

@ -15,7 +15,7 @@ import javax.validation.constraints.NotNull;
@Service
public class CompanyJavaService extends AbstractJavaService<Company, CompanyRepository> {
private RecipeService recipeService;
private RecipeJavaService recipeService;
public CompanyJavaService() {
super(Company.class);
@ -29,7 +29,7 @@ public class CompanyJavaService extends AbstractJavaService<Company, CompanyRepo
// Pour éviter les dépendances circulaires
@Autowired
@Lazy
public void setRecipeService(RecipeService recipeService) {
public void setRecipeService(RecipeJavaService recipeService) {
this.recipeService = recipeService;
}

View File

@ -96,7 +96,7 @@ public class MixService extends AbstractJavaService<Mix, MixRepository> {
.withDto(formDto)
.build();
if (mix.getRecipe().hasMixType(mix.getMixType()))
if (mix.getRecipe().containsMixType(mix.getMixType()))
throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, Mix.IDENTIFIER_MIX_TYPE_NAME, mix.getMixType().getName());
mixTypeService.save(mix.getMixType());
@ -111,7 +111,7 @@ public class MixService extends AbstractJavaService<Mix, MixRepository> {
MixType mixType = mix.getMixType();
if (!formDto.getOldMixTypeName().equals(mixType.getName()) && mixTypeService.existsByName(formDto.getMixTypeName()) && mix.getRecipe().hasMixType(mixType))
if (!formDto.getOldMixTypeName().equals(mixType.getName()) && mixTypeService.existsByName(formDto.getMixTypeName()) && mix.getRecipe().containsMixType(mixType))
throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.OTHER, Mix.IDENTIFIER_MIX_TYPE_NAME, mix.getMixType().getName());
if (materialService.existsByName(mixType.getName()) && !materialService.getByName(mixType.getName()).isMixType())

View File

@ -5,9 +5,9 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeEditorFormDto;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository;
import dev.fyloz.trial.colorrecipesexplorer.service.AbstractJavaService;
import dev.fyloz.trial.colorrecipesexplorer.service.files.ImagesService;
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -17,14 +17,14 @@ import java.util.*;
import java.util.stream.Collectors;
@Service
public class RecipeService extends AbstractJavaService<Recipe, RecipeRepository> {
public class RecipeJavaService extends AbstractJavaService<Recipe, RecipeRepository> {
private CompanyJavaService companyService;
private MixService mixService;
private RecipeStepJavaService stepService;
private ImagesService imagesService;
public RecipeService() {
public RecipeJavaService() {
super(Recipe.class);
}
@ -93,8 +93,8 @@ public class RecipeService extends AbstractJavaService<Recipe, RecipeRepository>
public Recipe updateRecipeAndSteps(RecipeEditorFormDto recipeDto) {
Recipe recipe = recipeDto.update(getById(recipeDto.getId()));
stepService.deleteAll(recipe.getRecipeSteps());
recipe.setRecipeSteps(stepService.createAllForRecipe(recipe, recipeDto.getStep()));
stepService.deleteAll(recipe.getSteps());
recipe.setSteps(stepService.createAllForRecipe(recipe, recipeDto.getStep()));
return update(recipe);
}

View File

@ -3,7 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.service.PasswordService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
@ -20,10 +20,10 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.PASSWORD_VAL
@Profile("thymeleaf")
public class IndexController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
@Autowired
public IndexController(RecipeService recipeService) {
public IndexController(RecipeJavaService recipeService) {
this.recipeService = recipeService;
}

View File

@ -5,7 +5,7 @@ import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.service.files.MarkdownFilesService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.MixService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
@ -25,11 +25,11 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
public class OthersController {
private MixService mixService;
private RecipeService recipeService;
private RecipeJavaService recipeService;
private MarkdownFilesService markdownService;
@Autowired
public OthersController(MixService mixService, RecipeService recipeService, MarkdownFilesService markdownService) {
public OthersController(MixService mixService, RecipeJavaService recipeService, MarkdownFilesService markdownService) {
this.mixService = mixService;
this.recipeService = recipeService;
this.markdownService = markdownService;

View File

@ -1,13 +1,13 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.web.response.JSONResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
@ -23,10 +23,10 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Profile("thymeleaf")
public class RecipeExplorerController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
@Autowired
public RecipeExplorerController(RecipeService recipeService) {
public RecipeExplorerController(RecipeJavaService recipeService) {
this.recipeService = recipeService;
}

View File

@ -3,11 +3,11 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.creators;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.model.MixTypeKt;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.MixFormDto;
import dev.fyloz.trial.colorrecipesexplorer.service.ServiceKt;
import dev.fyloz.trial.colorrecipesexplorer.service.model.*;
@ -30,12 +30,12 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
public class MixCreatorController {
private MixService mixService;
private RecipeService recipeService;
private RecipeJavaService recipeService;
private MaterialJavaService materialService;
private MaterialTypeJavaService materialTypeService;
@Autowired
public MixCreatorController(MixService mixService, RecipeService recipeService, MaterialJavaService materialService, MixTypeJavaService mixTypeService, MaterialTypeJavaService materialTypeService) {
public MixCreatorController(MixService mixService, RecipeJavaService recipeService, MaterialJavaService materialService, MixTypeJavaService mixTypeService, MaterialTypeJavaService materialTypeService) {
this.mixService = mixService;
this.recipeService = recipeService;
this.materialService = materialService;

View File

@ -1,11 +1,11 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.creators;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.service.model.CompanyJavaService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.service.model.CompanyJavaService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
@ -22,11 +22,11 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.EDITOR_RECIP
@Profile("thymeleaf")
public class RecipeCreatorController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
private CompanyJavaService companyService;
@Autowired
public RecipeCreatorController(RecipeService recipeService, CompanyJavaService companyService) {
public RecipeCreatorController(RecipeJavaService recipeService, CompanyJavaService companyService) {
this.recipeService = recipeService;
this.companyService = companyService;
}

View File

@ -1,13 +1,13 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.editors;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.model.dto.RecipeEditorFormDto;
import dev.fyloz.trial.colorrecipesexplorer.service.model.CompanyJavaService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
@ -24,11 +24,11 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Profile("thymeleaf")
public class RecipeEditorController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
private CompanyJavaService companyService;
@Autowired
public RecipeEditorController(RecipeService recipeService, CompanyJavaService companyService) {
public RecipeEditorController(RecipeJavaService recipeService, CompanyJavaService companyService) {
this.recipeService = recipeService;
this.companyService = companyService;
}

View File

@ -1,13 +1,13 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.files;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.web.response.JSONResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.service.files.ImagesService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
@ -28,11 +28,11 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
@Profile("thymeleaf")
public class ImageFilesController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
private ImagesService imagesService;
@Autowired
public ImageFilesController(RecipeService recipeService, ImagesService imagesService) {
public ImageFilesController(RecipeJavaService recipeService, ImagesService imagesService) {
this.recipeService = recipeService;
this.imagesService = imagesService;
}

View File

@ -3,7 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.files;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.service.files.TouchUpKitService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
@ -21,10 +21,10 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.*;
public class TouchUpKitController {
private TouchUpKitService touchUpKitService;
private RecipeService recipeService;
private RecipeJavaService recipeService;
@Autowired
public TouchUpKitController(TouchUpKitService touchUpKitService, RecipeService recipeService) {
public TouchUpKitController(TouchUpKitService touchUpKitService, RecipeJavaService recipeService) {
this.touchUpKitService = touchUpKitService;
this.recipeService = recipeService;
}

View File

@ -1,11 +1,11 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller.thymeleaf.removers;
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.web.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@ -20,9 +20,9 @@ import static dev.fyloz.trial.colorrecipesexplorer.web.WebsitePaths.REMOVER_RECI
@Profile("thymeleaf")
public class RecipeRemoverController {
private RecipeService recipeService;
private RecipeJavaService recipeService;
public RecipeRemoverController(RecipeService recipeService) {
public RecipeRemoverController(RecipeJavaService recipeService) {
this.recipeService = recipeService;
}

View File

@ -52,7 +52,7 @@ public class XlsxExporter {
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.NAME, "Échantillon"));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.VALUE_NUM, recipe.getSample()));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.NAME, "Date d'approbation"));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.VALUE_STR, recipe.getApprobationDate()));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.VALUE_STR, recipe.getApprobationDate().toString()));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.NAME, "Remarque"));
sheet.registerCell(new DescriptionCell(DescriptionCell.DescriptionCellType.VALUE_STR, recipe.getRemark()));
@ -80,12 +80,12 @@ public class XlsxExporter {
}
// Étapes
Collection<RecipeStep> steps = recipe.getRecipeSteps();
Collection<RecipeStep> steps = recipe.getSteps();
if (steps.size() > 0) {
sheet.registerCell(new SectionTitleCell("Étapes"));
Catalog stepsCatalog = new Catalog();
for (RecipeStep step : recipe.getRecipeSteps()) stepsCatalog.addContent(step.getMessage());
for (RecipeStep step : recipe.getSteps()) stepsCatalog.addContent(step.getMessage());
sheet.registerAllCells(stepsCatalog.getCells());
}

View File

@ -13,3 +13,5 @@ interface EntityDto<out E> {
/** Converts the dto to an actual entity. */
fun toEntity(): E
}
fun Collection<Model>.sortedById(): Collection<Model> = this.sortedBy { it.id }

View File

@ -0,0 +1,180 @@
package dev.fyloz.trial.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrSize
import java.time.LocalDate
import java.util.*
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size
private const val RECIPE_ID_NULL_MESSAGE = "Un identifiant est requis"
private const val RECIPE_NAME_NULL_MESSAGE = "Un nom est requis"
private const val RECIPE_DESCRIPTION_NULL_MESSAGE = "Une description est requise"
private const val RECIPE_SAMPLE_NULL_MESSAGE = "Un numéro d'échantillon est requis"
private const val RECIPE_SAMPLE_TOO_SMALL_MESSAGE = "Le numéro d'échantillon doit être supérieur ou égal à 0"
private const val RECIPE_COMPANY_NULL_MESSAGE = "Une bannière est requise"
@Entity
data class Recipe(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
override val id: Long?,
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
val name: String,
val description: String,
val sample: Int,
val approbationDate: LocalDate,
/** A remark given by the creator of the recipe. */
val remark: String,
/** A note that can be altered by anybody with view permissions to the recipe. */
var note: String,
@ManyToOne
val company: Company,
@JsonIgnore
@OneToMany
val mixes: List<Mix>,
@OneToMany
var steps: List<RecipeStep>
) : Model {
constructor(
id: Long,
name: String,
company: Company,
description: String,
sample: Int,
approbationDate: LocalDate,
remark: String,
note: String
) : this(id, name, description, sample, approbationDate, remark, note, company, listOf(), listOf())
val mixesSortedById: Collection<Mix>
@JsonIgnore
get() = mixes.sortedBy { it.id }
/** The mix types contained in this recipe. */
val mixTypes: Collection<MixType>
@JsonIgnore
get() = mixes.map { it.getMixType() as MixType }
/** Checks if the recipe contains the given [mixType]. */
fun containsMixType(mixType: MixType) = mixTypes.contains(mixType)
override fun equals(other: Any?): Boolean = other is Recipe && other.name == name && other.company == company
override fun hashCode(): Int = Objects.hash(name, company)
}
open class RecipeSaveDto(
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
val name: String,
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
val description: String,
@field:NotNull(message = RECIPE_SAMPLE_NULL_MESSAGE)
@field:Size(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
val sample: Int,
val approbationDate: LocalDate,
val remark: String,
@field:NotNull(message = RECIPE_COMPANY_NULL_MESSAGE)
val company: Company,
// TODO when frontend while be done -> add mixes ?
// TODO when frontend while be done -> add steps ?
) : EntityDto<Recipe> {
override fun toEntity(): Recipe = recipe(
name = name,
description = description,
sample = sample,
approbationDate = approbationDate,
remark = remark,
company = company
)
}
open class RecipeUpdateDto(
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
val id: Long = 0L,
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
val name: String? = "name",
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
val description: String? = "description",
@field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
val sample: Int? = -1,
val approbationDate: LocalDate? = LocalDate.MIN,
val remark: String? = "remark",
val note: String? = "note",
val company: Company? = company(),
val steps: List<RecipeStep>? = listOf()
) : EntityDto<Recipe> {
override fun toEntity(): Recipe = recipe(
id,
name = name ?: "name",
description = description ?: "description",
sample = sample ?: -1,
approbationDate = approbationDate ?: LocalDate.MIN,
remark = remark ?: "remark",
note = note ?: "note",
company = company ?: company(),
steps = steps ?: listOf()
)
}
// ==== DSL ====
fun recipe(
id: Long? = null,
name: String = "name",
description: String = "description",
sample: Int = -1,
approbationDate: LocalDate = LocalDate.MIN,
remark: String = "remark",
note: String = "",
company: Company = company(),
mixes: List<Mix> = listOf(),
steps: List<RecipeStep> = listOf(),
op: Recipe.() -> Unit = {}
) = Recipe(id, name, description, sample, approbationDate, remark, note, company, mixes, steps).apply(op)
fun recipeSaveDto(
name: String = "name",
description: String = "description",
sample: Int = -1,
approbationDate: LocalDate = LocalDate.MIN,
remark: String = "remark",
company: Company = company(),
op: RecipeSaveDto.() -> Unit = {}
) = RecipeSaveDto(name, description, sample, approbationDate, remark, company).apply(op)
fun recipeUpdateDto(
id: Long = 0L,
name: String = "name",
description: String = "description",
sample: Int = -1,
approbationDate: LocalDate = LocalDate.MIN,
remark: String = "remark",
note: String = "",
company: Company = company(),
steps: List<RecipeStep> = listOf(),
op: RecipeUpdateDto.() -> Unit = {}
) = RecipeUpdateDto(id, name, description, sample, approbationDate, remark, note, company, steps).apply(op)

View File

@ -41,3 +41,6 @@ fun isNotNullAndNotBlank(value: String?): Boolean {
contract { returns(true) implies (value != null) }
return value != null && value.isNotBlank()
}
@ExperimentalContracts
fun String?.or(alternative: String): String = if (isNotNullAndNotBlank(this)) this else alternative

View File

@ -0,0 +1,13 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Company
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe
import org.springframework.data.jpa.repository.JpaRepository
interface RecipeRepository : JpaRepository<Recipe, Long> {
/** Checks if one or more recipes have the given [company]. */
fun existsByCompany(company: Company): Boolean
/** Gets all recipes with the given [company]. */
fun findAllByCompany(company: Company): Collection<Recipe>
}

View File

@ -0,0 +1,20 @@
package dev.fyloz.trial.colorrecipesexplorer.rest
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe
import dev.fyloz.trial.colorrecipesexplorer.model.RecipeSaveDto
import dev.fyloz.trial.colorrecipesexplorer.model.RecipeUpdateDto
import dev.fyloz.trial.colorrecipesexplorer.service.RecipeService
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
private const val RECIPE_CONTROLLER_PATH = "api/recipe"
@RestController
@RequestMapping(RECIPE_CONTROLLER_PATH)
class RecipeController(recipeService: RecipeService) :
AbstractModelRestApiController<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService>(
recipeService,
RECIPE_CONTROLLER_PATH
)

View File

@ -5,9 +5,8 @@ import dev.fyloz.trial.colorrecipesexplorer.model.CompanySaveDto
import dev.fyloz.trial.colorrecipesexplorer.model.CompanyUpdateDto
import dev.fyloz.trial.colorrecipesexplorer.model.company
import dev.fyloz.trial.colorrecipesexplorer.repository.CompanyRepository
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService
import org.springframework.stereotype.Service
import org.springframework.util.Assert
interface CompanyService : ExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository> {
/** Checks if the given [company] is used by one or more recipes. */
@ -15,7 +14,7 @@ interface CompanyService : ExternalNamedModelService<Company, CompanySaveDto, Co
}
@Service
class CompanyServiceImpl(companyRepository: CompanyRepository, val recipeService: RecipeService) :
class CompanyServiceImpl(companyRepository: CompanyRepository, val recipeService: RecipeJavaService) :
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository>(companyRepository),
CompanyService {
override fun isLinkedToRecipes(company: Company): Boolean = recipeService.existsByCompany(company)

View File

@ -0,0 +1,42 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.model.validation.or
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository
import org.springframework.stereotype.Service
import kotlin.contracts.ExperimentalContracts
interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository> {
/** Checks if one or more recipes have the given [company]. */
fun existsByCompany(company: Company): Boolean
/** Gets all recipes with the given [company]. */
fun getAllByCompany(company: Company): Collection<Recipe>
}
@Service
class RecipeServiceImpl(recipeRepository: RecipeRepository) :
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
RecipeService {
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
@ExperimentalContracts
override fun update(entity: RecipeUpdateDto): Recipe {
val persistedRecipe by lazy { getById(entity.id) }
return update(with(entity) {
recipe(
id = id,
name = name.or(persistedRecipe.name),
description = description.or(persistedRecipe.description),
sample = if (sample != null && sample >= 0) sample else persistedRecipe.sample,
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
remark = remark.or(persistedRecipe.remark),
note = note.or(persistedRecipe.note),
company = company ?: persistedRecipe.company,
steps = if (!steps.isNullOrEmpty()) steps else persistedRecipe.steps
)
})
}
}

View File

@ -7,7 +7,10 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.*
@DataJpaTest
abstract class AbstractNamedJpaRepositoryTest<E : NamedModel, R : NamedJpaRepository<E>>(protected val repository: R, protected val entityManager: TestEntityManager) {
abstract class AbstractNamedJpaRepositoryTest<E : NamedModel, R : NamedJpaRepository<E>>(
protected val repository: R,
protected val entityManager: TestEntityManager
) {
protected abstract fun entity(name: String = "entity"): E
@Test

View File

@ -3,14 +3,14 @@ package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.CompanyRepository
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeService
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Nested
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class CompanyServiceTest : AbstractExternalNamedModelServiceTest<Company, CompanySaveDto, CompanyUpdateDto, CompanyService, CompanyRepository>() {
private val recipeService: RecipeService = mock()
private val recipeService: RecipeJavaService = mock()
override val repository: CompanyRepository = mock()
override val service: CompanyService = spy(CompanyServiceImpl(repository, recipeService))

View File

@ -0,0 +1,59 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.whenever
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class RecipeServiceTest :
AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
override val repository: RecipeRepository = mock()
override val service: RecipeService = spy(RecipeServiceImpl(repository))
private val company: Company = company()
override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company)
override val anotherEntity: Recipe = recipe(id = 1L, name = "another recipe", company = company)
override val entitySaveDto: RecipeSaveDto = spy(recipeSaveDto(name = entity.name, company = entity.company))
override val entityUpdateDto: RecipeUpdateDto = spy(recipeUpdateDto(id = entity.id!!, name = entity.name, company = entity.company))
@Nested
inner class ExistsByCompany {
@Test
fun `returns true when at least one recipe exists for the given company`() {
whenever(repository.existsByCompany(company)).doReturn(true)
val found = service.existsByCompany(company)
assertTrue(found)
}
@Test
fun `returns false when no recipe exists for the given company`() {
whenever(repository.existsByCompany(company)).doReturn(false)
val found = service.existsByCompany(company)
assertFalse(found)
}
}
@Nested
inner class GetAllByCompany {
@Test
fun `returns the recipes with the given company`() {
val companies = listOf(entity, anotherEntity)
whenever(repository.findAllByCompany(company)).doReturn(companies)
val found = service.getAllByCompany(company)
assertEquals(companies, found)
}
}
}