first commit
This commit is contained in:
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/sausage-store.iml" filepath="$PROJECT_DIR$/.idea/sausage-store.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Chameleon" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/spring-petclinic/target/classes/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/spring-petclinic" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files.autoSave": "off",
|
||||
"editor.parameterHints.enabled": false,
|
||||
"editor.rulers": [
|
||||
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Dmitrii Sugrobov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,2 +1,36 @@
|
||||
# sausage-store
|
||||
# Sausage Store
|
||||
|
||||

|
||||
|
||||
|
||||
## Technologies used
|
||||
|
||||
* Frontend – TypeScript, Angular.
|
||||
* Backend – Java 16, Spring Boot, Spring Data.
|
||||
* Database – H2.
|
||||
|
||||
## Installation guide
|
||||
### Backend
|
||||
|
||||
Install Java 16 and maven and run:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn package
|
||||
cd target
|
||||
java -jar sausage-store-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
Install NodeJS and npm on your computer and run:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
npm install -g http-server
|
||||
sudo http-server ./dist/frontend/ -p 80 --proxy http://localhost:8080
|
||||
```
|
||||
|
||||
Then open your browser and go to [http://localhost](http://localhost)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.yandex.practicum.devops</groupId>
|
||||
<artifactId>sausage-store</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>sausage-store</name>
|
||||
<description>Backend for sausage-store</description>
|
||||
<properties>
|
||||
<java.version>16</java.version>
|
||||
<sonar.projectKey>test_manual</sonar.projectKey>
|
||||
<sonar.qualitygate.wait>true</sonar.qualitygate.wait>
|
||||
</properties>
|
||||
<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-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.yandex.practicum.devops;
|
||||
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import com.yandex.practicum.devops.service.ProductService;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SausageApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SausageApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CommandLineRunner runner(ProductService productService) {
|
||||
return args -> {
|
||||
productService.save(new Product(1L, "Сливочная", 320.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/6.jpg"));
|
||||
productService.save(new Product(2L, "Особая", 179.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/5.jpg"));
|
||||
productService.save(new Product(3L, "Молочная", 225.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/4.jpg"));
|
||||
productService.save(new Product(4L, "Нюренбергская", 315.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/3.jpg"));
|
||||
productService.save(new Product(5L, "Мюнхенская", 330.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/2.jpg"));
|
||||
productService.save(new Product(6L, "Русская", 189.00, "https://res.cloudinary.com/sugrobov/image/upload/v1623323635/repos/sausages/1.jpg"));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.yandex.practicum.devops.controller;
|
||||
|
||||
import com.yandex.practicum.devops.dto.OrderProductDto;
|
||||
import com.yandex.practicum.devops.exception.ResourceNotFoundException;
|
||||
import com.yandex.practicum.devops.model.Order;
|
||||
import com.yandex.practicum.devops.model.OrderProduct;
|
||||
import com.yandex.practicum.devops.model.OrderStatus;
|
||||
import com.yandex.practicum.devops.service.OrderProductService;
|
||||
import com.yandex.practicum.devops.service.OrderService;
|
||||
import com.yandex.practicum.devops.service.ProductService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/orders")
|
||||
public class OrderController {
|
||||
|
||||
ProductService productService;
|
||||
OrderService orderService;
|
||||
OrderProductService orderProductService;
|
||||
|
||||
public OrderController(ProductService productService, OrderService orderService, OrderProductService orderProductService) {
|
||||
this.productService = productService;
|
||||
this.orderService = orderService;
|
||||
this.orderProductService = orderProductService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public @NotNull Iterable<Order> list() {
|
||||
return this.orderService.getAllOrders();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Order> create(@RequestBody OrderForm form) {
|
||||
List<OrderProductDto> formDtos = form.getProductOrders();
|
||||
validateProductsExistence(formDtos);
|
||||
Order order = new Order();
|
||||
order.setStatus(OrderStatus.PAID.name());
|
||||
order = this.orderService.create(order);
|
||||
|
||||
List<OrderProduct> orderProducts = new ArrayList<>();
|
||||
for (OrderProductDto dto : formDtos) {
|
||||
orderProducts.add(orderProductService.create(new OrderProduct(order, productService.getProduct(dto
|
||||
.getProduct()
|
||||
.getId()), dto.getQuantity())));
|
||||
}
|
||||
|
||||
order.setOrderProducts(orderProducts);
|
||||
|
||||
this.orderService.update(order);
|
||||
|
||||
String uri = ServletUriComponentsBuilder
|
||||
.fromCurrentServletMapping()
|
||||
.path("/orders/{id}")
|
||||
.buildAndExpand(order.getId())
|
||||
.toString();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Location", uri);
|
||||
|
||||
return new ResponseEntity<>(order, headers, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
private void validateProductsExistence(List<OrderProductDto> orderProducts) {
|
||||
List<OrderProductDto> list = orderProducts
|
||||
.stream()
|
||||
.filter(op -> Objects.isNull(productService.getProduct(op
|
||||
.getProduct()
|
||||
.getId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
new ResourceNotFoundException("Product not found");
|
||||
}
|
||||
}
|
||||
|
||||
public static class OrderForm {
|
||||
|
||||
private List<OrderProductDto> productOrders;
|
||||
|
||||
public List<OrderProductDto> getProductOrders() {
|
||||
return productOrders;
|
||||
}
|
||||
|
||||
public void setProductOrders(List<OrderProductDto> productOrders) {
|
||||
this.productOrders = productOrders;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.yandex.practicum.devops.controller;
|
||||
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import com.yandex.practicum.devops.service.ProductService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/products")
|
||||
public class ProductController {
|
||||
|
||||
private ProductService productService;
|
||||
|
||||
public ProductController(ProductService productService) {
|
||||
this.productService = productService;
|
||||
}
|
||||
|
||||
@GetMapping(value = { "", "/" })
|
||||
public @NotNull Iterable<Product> getProducts() {
|
||||
return productService.getAllProducts();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.yandex.practicum.devops.dto;
|
||||
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
|
||||
public class OrderProductDto {
|
||||
|
||||
private Product product;
|
||||
private Integer quantity;
|
||||
|
||||
public Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
public void setProduct(Product product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.yandex.practicum.devops.exception;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
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 javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ApiExceptionHandler {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ErrorResponse> handle(ConstraintViolationException e) {
|
||||
ErrorResponse errors = new ErrorResponse();
|
||||
for (ConstraintViolation violation : e.getConstraintViolations()) {
|
||||
ErrorItem error = new ErrorItem();
|
||||
error.setCode(violation.getMessageTemplate());
|
||||
error.setMessage(violation.getMessage());
|
||||
errors.addError(error);
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<ErrorItem> handle(ResourceNotFoundException e) {
|
||||
ErrorItem error = new ErrorItem();
|
||||
error.setMessage(e.getMessage());
|
||||
|
||||
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
public static class ErrorItem {
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL) private String code;
|
||||
|
||||
private String message;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ErrorResponse {
|
||||
|
||||
private List<ErrorItem> errors = new ArrayList<>();
|
||||
|
||||
public List<ErrorItem> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public void setErrors(List<ErrorItem> errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public void addError(ErrorItem error) {
|
||||
this.errors.add(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.yandex.practicum.devops.exception;
|
||||
|
||||
public class ResourceNotFoundException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 5861310537366287163L;
|
||||
|
||||
public ResourceNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ResourceNotFoundException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ResourceNotFoundException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ResourceNotFoundException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.yandex.practicum.devops.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.Valid;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "orders")
|
||||
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="orderProducts")
|
||||
public class Order {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@JsonFormat(pattern = "dd/MM/yyyy") private LocalDate dateCreated;
|
||||
|
||||
private String status;
|
||||
|
||||
@OneToMany(mappedBy = "pk.order")
|
||||
@Valid
|
||||
private List<OrderProduct> orderProducts = new ArrayList<>();
|
||||
|
||||
@Transient
|
||||
public Double getTotalOrderPrice() {
|
||||
double sum = 0D;
|
||||
List<OrderProduct> orderProducts = getOrderProducts();
|
||||
for (OrderProduct op : orderProducts) {
|
||||
sum += op.getTotalPrice();
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDate getDateCreated() {
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
public void setDateCreated(LocalDate dateCreated) {
|
||||
this.dateCreated = dateCreated;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public List<OrderProduct> getOrderProducts() {
|
||||
return orderProducts;
|
||||
}
|
||||
|
||||
public void setOrderProducts(List<OrderProduct> orderProducts) {
|
||||
this.orderProducts = orderProducts;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public int getNumberOfProducts() {
|
||||
return this.orderProducts.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.yandex.practicum.devops.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
@Entity
|
||||
public class OrderProduct {
|
||||
|
||||
@EmbeddedId
|
||||
@JsonIgnore
|
||||
private OrderProductPK pk;
|
||||
|
||||
@Column(nullable = false) private Integer quantity;
|
||||
|
||||
public OrderProduct() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OrderProduct(Order order, Product product, Integer quantity) {
|
||||
pk = new OrderProductPK();
|
||||
pk.setOrder(order);
|
||||
pk.setProduct(product);
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public Product getProduct() {
|
||||
return this.pk.getProduct();
|
||||
}
|
||||
|
||||
@Transient
|
||||
public Double getTotalPrice() {
|
||||
return getProduct().getPrice() * getQuantity();
|
||||
}
|
||||
|
||||
public OrderProductPK getPk() {
|
||||
return pk;
|
||||
}
|
||||
|
||||
public void setPk(OrderProductPK pk) {
|
||||
this.pk = pk;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((pk == null) ? 0 : pk.hashCode());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OrderProduct other = (OrderProduct) obj;
|
||||
if (pk == null) {
|
||||
if (other.pk != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!pk.equals(other.pk)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.yandex.practicum.devops.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Embeddable
|
||||
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "order")
|
||||
public class OrderProductPK implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 476151177562655457L;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "order_id")
|
||||
private Order order;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "product_id")
|
||||
private Product product;
|
||||
|
||||
public Order getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(Order order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public Product getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
public void setProduct(Product product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
|
||||
result = prime * result + ((order.getId() == null)
|
||||
? 0
|
||||
: order
|
||||
.getId()
|
||||
.hashCode());
|
||||
result = prime * result + ((product.getId() == null)
|
||||
? 0
|
||||
: product
|
||||
.getId()
|
||||
.hashCode());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OrderProductPK other = (OrderProductPK) obj;
|
||||
if (order == null) {
|
||||
if (other.order != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!order.equals(other.order)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (product == null) {
|
||||
if (other.product != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!product.equals(other.product)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.yandex.practicum.devops.model;
|
||||
|
||||
public enum OrderStatus {
|
||||
PAID
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.yandex.practicum.devops.model;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Entity
|
||||
public class Product {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@NotNull(message = "Product name is required.")
|
||||
@Basic(optional = false)
|
||||
private String name;
|
||||
|
||||
private Double price;
|
||||
|
||||
private String pictureUrl;
|
||||
|
||||
public Product(Long id, @NotNull(message = "Product name is required.") String name, Double price, String pictureUrl) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.pictureUrl = pictureUrl;
|
||||
}
|
||||
|
||||
public Product() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public String getPictureUrl() {
|
||||
return pictureUrl;
|
||||
}
|
||||
|
||||
public void setPictureUrl(String pictureUrl) {
|
||||
this.pictureUrl = pictureUrl;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.yandex.practicum.devops.repository;
|
||||
|
||||
import com.yandex.practicum.devops.model.OrderProduct;
|
||||
import com.yandex.practicum.devops.model.OrderProductPK;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface OrderProductRepository extends CrudRepository<OrderProduct, OrderProductPK> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.yandex.practicum.devops.repository;
|
||||
|
||||
import com.yandex.practicum.devops.model.Order;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface OrderRepository extends CrudRepository<Order, Long> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.yandex.practicum.devops.repository;
|
||||
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface ProductRepository extends CrudRepository<Product, Long> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.model.OrderProduct;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Validated
|
||||
public interface OrderProductService {
|
||||
|
||||
OrderProduct create(@NotNull(message = "The products for order cannot be null.") @Valid OrderProduct orderProduct);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.model.OrderProduct;
|
||||
import com.yandex.practicum.devops.repository.OrderProductRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class OrderProductServiceImpl implements OrderProductService {
|
||||
|
||||
private OrderProductRepository orderProductRepository;
|
||||
|
||||
public OrderProductServiceImpl(OrderProductRepository orderProductRepository) {
|
||||
this.orderProductRepository = orderProductRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderProduct create(OrderProduct orderProduct) {
|
||||
return this.orderProductRepository.save(orderProduct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.model.Order;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Validated
|
||||
public interface OrderService {
|
||||
|
||||
@NotNull Iterable<Order> getAllOrders();
|
||||
|
||||
Order create(@NotNull(message = "The order cannot be null.") @Valid Order order);
|
||||
|
||||
void update(@NotNull(message = "The order cannot be null.") @Valid Order order);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.model.Order;
|
||||
import com.yandex.practicum.devops.repository.OrderRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
public OrderServiceImpl(OrderRepository orderRepository) {
|
||||
this.orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Order> getAllOrders() {
|
||||
return this.orderRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Order create(Order order) {
|
||||
order.setDateCreated(LocalDate.now());
|
||||
|
||||
return this.orderRepository.save(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Order order) {
|
||||
this.orderRepository.save(order);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Validated
|
||||
public interface ProductService {
|
||||
|
||||
@NotNull Iterable<Product> getAllProducts();
|
||||
|
||||
Product getProduct(@Min(value = 1L, message = "Invalid product ID.") long id);
|
||||
|
||||
Product save(Product product);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.yandex.practicum.devops.service;
|
||||
|
||||
import com.yandex.practicum.devops.exception.ResourceNotFoundException;
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import com.yandex.practicum.devops.repository.ProductRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private ProductRepository productRepository;
|
||||
|
||||
public ProductServiceImpl(ProductRepository productRepository) {
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Product> getAllProducts() {
|
||||
return productRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Product getProduct(long id) {
|
||||
return productRepository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Product not found"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Product save(Product product) {
|
||||
return productRepository.save(product);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
management.security.enabled=false
|
||||
|
||||
spring.datasource.name=ecommercedb
|
||||
spring.jpa.show-sql=false
|
||||
|
||||
#H2 settings
|
||||
spring.h2.console.enabled=true
|
||||
spring.h2.console.path=/h2-console
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package com.yandex.practicum.devops;
|
||||
|
||||
import com.yandex.practicum.devops.controller.OrderController;
|
||||
import com.yandex.practicum.devops.controller.ProductController;
|
||||
import com.yandex.practicum.devops.dto.OrderProductDto;
|
||||
import com.yandex.practicum.devops.model.Order;
|
||||
import com.yandex.practicum.devops.model.Product;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasProperty;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = { SausageApplication.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class SausageApplicationIntegrationTest {
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
|
||||
@LocalServerPort private int port;
|
||||
|
||||
@Autowired private ProductController productController;
|
||||
|
||||
@Autowired private OrderController orderController;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
Assertions
|
||||
.assertThat(productController)
|
||||
.isNotNull();
|
||||
Assertions
|
||||
.assertThat(orderController)
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenGetProductsApiCall_whenProductListRetrieved_thenSizeMatchAndListContainsProductNames() {
|
||||
ResponseEntity<Iterable<Product>> responseEntity = restTemplate.exchange("http://localhost:" + port + "/api/products", HttpMethod.GET, null, new ParameterizedTypeReference<Iterable<Product>>() {
|
||||
});
|
||||
Iterable<Product> products = responseEntity.getBody();
|
||||
Assertions
|
||||
.assertThat(products)
|
||||
.hasSize(6);
|
||||
|
||||
assertThat(products, hasItem(hasProperty("name", is("Сливочная"))));
|
||||
assertThat(products, hasItem(hasProperty("name", is("Особая"))));
|
||||
assertThat(products, hasItem(hasProperty("name", is("Молочная"))));
|
||||
assertThat(products, hasItem(hasProperty("name", is("Нюренбергская"))));
|
||||
assertThat(products, hasItem(hasProperty("name", is("Мюнхенская"))));
|
||||
assertThat(products, hasItem(hasProperty("name", is("Русская"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenGetOrdersApiCall_whenProductListRetrieved_thenSizeMatchAndListContainsProductNames() {
|
||||
ResponseEntity<Iterable<Order>> responseEntity = restTemplate.exchange("http://localhost:" + port + "/api/orders", HttpMethod.GET, null, new ParameterizedTypeReference<Iterable<Order>>() {
|
||||
});
|
||||
|
||||
Iterable<Order> orders = responseEntity.getBody();
|
||||
Assertions
|
||||
.assertThat(orders)
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPostOrder_whenBodyRequestMatcherJson_thenResponseContainsEqualObjectProperties() {
|
||||
final ResponseEntity<Order> postResponse = restTemplate.postForEntity("http://localhost:" + port + "/api/orders", prepareOrderForm(), Order.class);
|
||||
Order order = postResponse.getBody();
|
||||
Assertions
|
||||
.assertThat(postResponse.getStatusCode())
|
||||
.isEqualByComparingTo(HttpStatus.CREATED);
|
||||
|
||||
assertThat(order, hasProperty("status", is("PAID")));
|
||||
assertThat(order.getOrderProducts(), hasItem(hasProperty("quantity", is(2))));
|
||||
}
|
||||
|
||||
private OrderController.OrderForm prepareOrderForm() {
|
||||
OrderController.OrderForm orderForm = new OrderController.OrderForm();
|
||||
OrderProductDto productDto = new OrderProductDto();
|
||||
productDto.setProduct(new Product(1L, "Русская", 300.00, "http://placehold.it/200x100"));
|
||||
productDto.setQuantity(2);
|
||||
orderForm.setProductOrders(Collections.singletonList(productDto));
|
||||
|
||||
return orderForm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "frontend:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "frontend:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"frontend-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "frontend:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "frontend:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "frontend"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { AppPage } from './app.po';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to frontend!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+23139
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --proxy-config proxy-conf.json",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^6.0.3",
|
||||
"@angular/common": "^6.0.3",
|
||||
"@angular/compiler": "^6.0.3",
|
||||
"@angular/core": "^6.0.3",
|
||||
"@angular/forms": "^6.0.3",
|
||||
"@angular/http": "^6.0.3",
|
||||
"@angular/platform-browser": "^6.0.3",
|
||||
"@angular/platform-browser-dynamic": "^6.0.3",
|
||||
"@angular/router": "^6.0.3",
|
||||
"bootstrap": "^4.1.2",
|
||||
"core-js": "^2.5.4",
|
||||
"rxjs": "^6.0.0",
|
||||
"zone.js": "^0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.6.8",
|
||||
"@angular/cli": "~6.0.8",
|
||||
"@angular/compiler-cli": "^6.0.3",
|
||||
"@angular/language-service": "^6.0.3",
|
||||
"@types/jasmine": "~2.8.6",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "~8.9.4",
|
||||
"codelyzer": "~4.2.1",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~1.7.1",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.0",
|
||||
"karma-jasmine": "~1.1.1",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.3.0",
|
||||
"ts-node": "~5.0.1",
|
||||
"tslint": "~5.9.1",
|
||||
"typescript": "~2.7.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.container {
|
||||
padding-top: 65px;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="container">
|
||||
<app-ecommerce></app-ecommerce>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to frontend!');
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {EcommerceService} from "./ecommerce/services/EcommerceService";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
providers: [EcommerceService]
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
import {EcommerceComponent} from './ecommerce/ecommerce.component';
|
||||
import {ProductsComponent} from './ecommerce/products/products.component';
|
||||
import {ShoppingCartComponent} from './ecommerce/shopping-cart/shopping-cart.component';
|
||||
import {OrdersComponent} from './ecommerce/orders/orders.component';
|
||||
import {EcommerceService} from "./ecommerce/services/EcommerceService";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
EcommerceComponent,
|
||||
ProductsComponent,
|
||||
ShoppingCartComponent,
|
||||
OrdersComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [EcommerceService],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">Сосисочная у дома</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse"
|
||||
data-target="#navbarResponsive" aria-controls="navbarResponsive"
|
||||
aria-expanded="false" aria-label="Toggle navigation" (click)="toggleCollapsed()">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div id="navbarResponsive" [ngClass]="{'collapse': collapsed, 'navbar-collapse': true}">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#" (click)="reset()">Магазин
|
||||
<span class="sr-only">(current)</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<app-products #productsC [hidden]="orderFinished"></app-products>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<app-shopping-cart (onOrderFinished)=finishOrder($event) #shoppingCartC
|
||||
[hidden]="orderFinished"></app-shopping-cart>
|
||||
</div>
|
||||
<div class="col-md-6 offset-3">
|
||||
<app-orders #ordersC [hidden]="!orderFinished"></app-orders>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EcommerceComponent } from './ecommerce.component';
|
||||
|
||||
describe('EcommerceComponent', () => {
|
||||
let component: EcommerceComponent;
|
||||
let fixture: ComponentFixture<EcommerceComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ EcommerceComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EcommerceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {ProductsComponent} from "./products/products.component";
|
||||
import {ShoppingCartComponent} from "./shopping-cart/shopping-cart.component";
|
||||
import {OrdersComponent} from "./orders/orders.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-ecommerce',
|
||||
templateUrl: './ecommerce.component.html',
|
||||
styleUrls: ['./ecommerce.component.css']
|
||||
})
|
||||
export class EcommerceComponent implements OnInit {
|
||||
private collapsed = true;
|
||||
orderFinished = false;
|
||||
|
||||
@ViewChild('productsC')
|
||||
productsC: ProductsComponent;
|
||||
|
||||
@ViewChild('shoppingCartC')
|
||||
shoppingCartC: ShoppingCartComponent;
|
||||
|
||||
@ViewChild('ordersC')
|
||||
ordersC: OrdersComponent;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
toggleCollapsed(): void {
|
||||
this.collapsed = !this.collapsed;
|
||||
}
|
||||
|
||||
finishOrder(orderFinished: boolean) {
|
||||
this.orderFinished = orderFinished;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.orderFinished = false;
|
||||
this.productsC.reset();
|
||||
this.shoppingCartC.reset();
|
||||
this.ordersC.paid = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {Product} from "./product.model";
|
||||
|
||||
export class ProductOrder {
|
||||
product: Product;
|
||||
quantity: number;
|
||||
|
||||
constructor(product: Product, quantity: number) {
|
||||
this.product = product;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import {ProductOrder} from "./product-order.model";
|
||||
|
||||
export class ProductOrders {
|
||||
productOrders: ProductOrder[] = [];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class Product {
|
||||
id: number;
|
||||
name: string;
|
||||
price: number;
|
||||
pictureUrl: string;
|
||||
|
||||
constructor(id: number, name: string, price: number, pictureUrl: string) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.price = price;
|
||||
this.pictureUrl = pictureUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<h2 class="text-center">Оформление заказа</h2>
|
||||
<ul>
|
||||
<li *ngFor="let order of orders.productOrders">
|
||||
{{ order.product.name }} - {{ order.product.price }}₽ x {{ order.quantity}} шт.
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="text-right">Общая стоимость: {{ total }}₽</h3>
|
||||
|
||||
<button class="btn btn-primary btn-block" (click)="pay()" *ngIf="!paid">Оплатить</button>
|
||||
|
||||
<div class="alert alert-success" role="alert" *ngIf="paid">
|
||||
<strong>Отлично!</strong> Заказ успешно оформлен.
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OrdersComponent } from './orders.component';
|
||||
|
||||
describe('OrdersComponent', () => {
|
||||
let component: OrdersComponent;
|
||||
let fixture: ComponentFixture<OrdersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ OrdersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OrdersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ProductOrders} from "../models/product-orders.model";
|
||||
import {Subscription} from "rxjs/internal/Subscription";
|
||||
import {EcommerceService} from "../services/EcommerceService";
|
||||
|
||||
@Component({
|
||||
selector: 'app-orders',
|
||||
templateUrl: './orders.component.html',
|
||||
styleUrls: ['./orders.component.css']
|
||||
})
|
||||
export class OrdersComponent implements OnInit {
|
||||
orders: ProductOrders;
|
||||
total: number;
|
||||
paid: boolean;
|
||||
sub: Subscription;
|
||||
|
||||
constructor(private ecommerceService: EcommerceService) {
|
||||
this.orders = this.ecommerceService.ProductOrders;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.paid = false;
|
||||
this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
|
||||
this.orders = this.ecommerceService.ProductOrders;
|
||||
});
|
||||
this.loadTotal();
|
||||
}
|
||||
|
||||
pay() {
|
||||
this.paid = true;
|
||||
this.ecommerceService.saveOrder(this.orders).subscribe();
|
||||
}
|
||||
|
||||
loadTotal() {
|
||||
this.sub = this.ecommerceService.TotalChanged.subscribe(() => {
|
||||
this.total = this.ecommerceService.Total;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.padding-0 {
|
||||
padding-right: 0;
|
||||
padding-left: 1;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="row card-deck">
|
||||
<div class="col-lg-4 col-md-6 mb-4" *ngFor="let order of productOrders">
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
<h4>{{order.product.name}}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="#"><img class="card-img-top" src={{order.product.pictureUrl}} alt=""></a>
|
||||
<h5 class="card-title">{{order.product.price}}₽</h5>
|
||||
<div class="row">
|
||||
<div class="col-4 padding-0" *ngIf="!isProductSelected(order.product)">
|
||||
<input type="number" min="1" class="form-control" [(ngModel)]=order.quantity>
|
||||
</div>
|
||||
<div class="col-4 padding-0" *ngIf="!isProductSelected(order.product)">
|
||||
<button class="btn btn-primary" (click)="addToCart(order)"
|
||||
[disabled]="order.quantity <= 0">В корзину
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="isProductSelected(order.product)">
|
||||
<button class="btn btn-primary btn-block"
|
||||
(click)="removeFromCart(order)">Убрать из корзины
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProductsComponent } from './products.component';
|
||||
|
||||
describe('ProductsComponent', () => {
|
||||
let component: ProductsComponent;
|
||||
let fixture: ComponentFixture<ProductsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ProductsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProductsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ProductOrder} from "../models/product-order.model";
|
||||
import {EcommerceService} from "../services/EcommerceService";
|
||||
import {Subscription} from "rxjs/internal/Subscription";
|
||||
import {ProductOrders} from "../models/product-orders.model";
|
||||
import {Product} from "../models/product.model";
|
||||
|
||||
@Component({
|
||||
selector: 'app-products',
|
||||
templateUrl: './products.component.html',
|
||||
styleUrls: ['./products.component.css']
|
||||
})
|
||||
export class ProductsComponent implements OnInit {
|
||||
productOrders: ProductOrder[] = [];
|
||||
products: Product[] = [];
|
||||
selectedProductOrder: ProductOrder;
|
||||
private shoppingCartOrders: ProductOrders;
|
||||
sub: Subscription;
|
||||
productSelected: boolean = false;
|
||||
|
||||
constructor(private ecommerceService: EcommerceService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.productOrders = [];
|
||||
this.loadProducts();
|
||||
this.loadOrders();
|
||||
}
|
||||
|
||||
addToCart(order: ProductOrder) {
|
||||
this.ecommerceService.SelectedProductOrder = order;
|
||||
this.selectedProductOrder = this.ecommerceService.SelectedProductOrder;
|
||||
this.productSelected = true;
|
||||
}
|
||||
|
||||
removeFromCart(productOrder: ProductOrder) {
|
||||
let index = this.getProductIndex(productOrder.product);
|
||||
if (index > -1) {
|
||||
this.shoppingCartOrders.productOrders.splice(
|
||||
this.getProductIndex(productOrder.product), 1);
|
||||
}
|
||||
this.ecommerceService.ProductOrders = this.shoppingCartOrders;
|
||||
this.shoppingCartOrders = this.ecommerceService.ProductOrders;
|
||||
this.productSelected = false;
|
||||
}
|
||||
|
||||
getProductIndex(product: Product): number {
|
||||
return this.ecommerceService.ProductOrders.productOrders.findIndex(
|
||||
value => value.product === product);
|
||||
}
|
||||
|
||||
isProductSelected(product: Product): boolean {
|
||||
return this.getProductIndex(product) > -1;
|
||||
}
|
||||
|
||||
loadProducts() {
|
||||
this.ecommerceService.getAllProducts()
|
||||
.subscribe(
|
||||
(products: any[]) => {
|
||||
this.products = products;
|
||||
this.products.forEach(product => {
|
||||
this.productOrders.push(new ProductOrder(product, 0));
|
||||
})
|
||||
},
|
||||
(error) => {
|
||||
window.alert('На сайте ведутся технические работы. Приходите позже!');
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadOrders() {
|
||||
this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
|
||||
this.shoppingCartOrders = this.ecommerceService.ProductOrders;
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.productOrders = [];
|
||||
this.loadProducts();
|
||||
this.ecommerceService.ProductOrders.productOrders = [];
|
||||
this.loadOrders();
|
||||
this.productSelected = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import {ProductOrder} from "../models/product-order.model";
|
||||
import {Subject} from "rxjs/internal/Subject";
|
||||
import {ProductOrders} from "../models/product-orders.model";
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Injectable} from "@angular/core";
|
||||
|
||||
@Injectable()
|
||||
export class EcommerceService {
|
||||
private productsUrl = "/api/products";
|
||||
private ordersUrl = "/api/orders";
|
||||
|
||||
private productOrder: ProductOrder;
|
||||
private orders: ProductOrders = new ProductOrders();
|
||||
|
||||
private productOrderSubject = new Subject();
|
||||
private ordersSubject = new Subject();
|
||||
private totalSubject = new Subject();
|
||||
|
||||
private total: number;
|
||||
|
||||
ProductOrderChanged = this.productOrderSubject.asObservable();
|
||||
OrdersChanged = this.ordersSubject.asObservable();
|
||||
TotalChanged = this.totalSubject.asObservable();
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
getAllProducts() {
|
||||
return this.http.get(this.productsUrl);
|
||||
}
|
||||
|
||||
saveOrder(order: ProductOrders) {
|
||||
return this.http.post(this.ordersUrl, order);
|
||||
}
|
||||
|
||||
set SelectedProductOrder(value: ProductOrder) {
|
||||
this.productOrder = value;
|
||||
this.productOrderSubject.next();
|
||||
}
|
||||
|
||||
get SelectedProductOrder() {
|
||||
return this.productOrder;
|
||||
}
|
||||
|
||||
set ProductOrders(value: ProductOrders) {
|
||||
this.orders = value;
|
||||
this.ordersSubject.next();
|
||||
}
|
||||
|
||||
get ProductOrders() {
|
||||
return this.orders;
|
||||
}
|
||||
|
||||
get Total() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
set Total(value: number) {
|
||||
this.total = value;
|
||||
this.totalSubject.next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
|
||||
<div class="card-header text-center">Корзина</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Всего: {{total}}₽</h5>
|
||||
<hr>
|
||||
<h6 class="card-title">Товары в корзине:</h6>
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let order of orders.productOrders">
|
||||
{{ order.product.name }} - {{ order.quantity}} шт.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-light btn-block" (click)="finishOrder()"
|
||||
[disabled]="orders.productOrders.length == 0">Оформить заказ
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ShoppingCartComponent } from './shopping-cart.component';
|
||||
|
||||
describe('ShoppingCartComponent', () => {
|
||||
let component: ShoppingCartComponent;
|
||||
let fixture: ComponentFixture<ShoppingCartComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ShoppingCartComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ShoppingCartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import {Component, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core';
|
||||
import {ProductOrders} from "../models/product-orders.model";
|
||||
import {ProductOrder} from "../models/product-order.model";
|
||||
import {EcommerceService} from "../services/EcommerceService";
|
||||
import {Subscription} from "rxjs/internal/Subscription";
|
||||
|
||||
@Component({
|
||||
selector: 'app-shopping-cart',
|
||||
templateUrl: './shopping-cart.component.html',
|
||||
styleUrls: ['./shopping-cart.component.css']
|
||||
})
|
||||
export class ShoppingCartComponent implements OnInit, OnDestroy {
|
||||
orderFinished: boolean;
|
||||
orders: ProductOrders;
|
||||
total: number;
|
||||
sub: Subscription;
|
||||
|
||||
@Output() onOrderFinished: EventEmitter<boolean>;
|
||||
|
||||
constructor(private ecommerceService: EcommerceService) {
|
||||
this.total = 0;
|
||||
this.orderFinished = false;
|
||||
this.onOrderFinished = new EventEmitter<boolean>();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.orders = new ProductOrders();
|
||||
this.loadCart();
|
||||
this.loadTotal();
|
||||
}
|
||||
|
||||
private calculateTotal(products: ProductOrder[]): number {
|
||||
let sum = 0;
|
||||
products.forEach(value => {
|
||||
sum += (value.product.price * value.quantity);
|
||||
});
|
||||
return sum;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
finishOrder() {
|
||||
this.orderFinished = true;
|
||||
this.ecommerceService.Total = this.total;
|
||||
this.onOrderFinished.emit(this.orderFinished);
|
||||
}
|
||||
|
||||
loadTotal() {
|
||||
this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
|
||||
this.total = this.calculateTotal(this.orders.productOrders);
|
||||
});
|
||||
}
|
||||
|
||||
loadCart() {
|
||||
this.sub = this.ecommerceService.ProductOrderChanged.subscribe(() => {
|
||||
let productOrder = this.ecommerceService.SelectedProductOrder;
|
||||
if (productOrder) {
|
||||
this.orders.productOrders.push(new ProductOrder(
|
||||
productOrder.product, productOrder.quantity));
|
||||
}
|
||||
this.ecommerceService.ProductOrders = this.orders;
|
||||
this.orders = this.ecommerceService.ProductOrders;
|
||||
this.total = this.calculateTotal(this.orders.productOrders);
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.orderFinished = false;
|
||||
this.orders = new ProductOrders();
|
||||
this.orders.productOrders = []
|
||||
this.loadTotal();
|
||||
this.total = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
# For IE 9-11 support, please uncomment the last line of the file and adjust as needed
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
# IE 9-11
|
||||
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* In development mode, to ignore zone related error stack frames such as
|
||||
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
|
||||
* import the following file, but please comment it out in production mode
|
||||
* because it will have performance impact when throw error
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Frontend</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="spring-boot-angular/src/main/js/ecommerce/src/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../coverage'),
|
||||
reports: ['html', 'lcovonly'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/weak-map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following for the Reflect API. */
|
||||
// import 'core-js/es6/reflect';
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
|
||||
import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
**/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
*/
|
||||
|
||||
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
|
||||
/*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*/
|
||||
// (window as any).__Zone_enable_cross_context_check = true;
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@@ -0,0 +1,20 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: any;
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "es2015",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"module": "commonjs",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"rulesDirectory": [
|
||||
"node_modules/codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"arrow-return-shorthand": true,
|
||||
"callable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warn"
|
||||
},
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"interface-over-type-literal": true,
|
||||
"label-position": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-super": true,
|
||||
"no-empty": false,
|
||||
"no-empty-interface": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-misused-new": true,
|
||||
"no-non-null-assertion": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-string-throw": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unnecessary-initializer": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"unified-signatures": true,
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
"no-output-on-prefix": true,
|
||||
"use-input-property-decorator": true,
|
||||
"use-output-property-decorator": true,
|
||||
"use-host-property-decorator": true,
|
||||
"no-input-rename": true,
|
||||
"no-output-rename": true,
|
||||
"use-life-cycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"component-class-suffix": true,
|
||||
"directive-class-suffix": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user