shadaev_anton_lab_3 #76
74
shadaev_anton_lab_3/README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами
|
||||||
|
Цель:
|
||||||
|
|
||||||
|
Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
|
||||||
|
|
||||||
|
Задачи:
|
||||||
|
|
||||||
|
Создать 2 микросервиса, реализующих CRUD на связанных сущностях. Реализовать механизм синхронного обмена сообщениями между микросервисами. Реализовать шлюз на основе прозрачного прокси-сервера nginx.
|
||||||
|
|
||||||
|
## Запуск:
|
||||||
|
|
||||||
|
Чтобы запустить контейнеры в docker, необходимо выполнить следующую команду: `docker-compose -f \ docker-compose.yml up -d`, где:
|
||||||
|
|
||||||
|
-f - путь до docker-compose.yml файла
|
||||||
|
-d - фоновый режим запуска
|
||||||
|
|
||||||
|
|
||||||
|
## Демонстрация работы программы
|
||||||
|
### Сервис Aggregator
|
||||||
|
<u>POST-метод для создания вакансии</u>
|
||||||
|
|
||||||
|
![1.png](screenshots%2F1.png)
|
||||||
|
|
||||||
|
<u>GET-метод для получения вакансии по id</u>
|
||||||
|
|
||||||
|
![2.png](screenshots%2F2.png)
|
||||||
|
|
||||||
|
<u>GET-метод для получения списка вакансий</u>
|
||||||
|
|
||||||
|
![3.png](screenshots%2F3.png)
|
||||||
|
|
||||||
|
<u>PUT-метод для обновления вакансии</u>
|
||||||
|
|
||||||
|
![4.png](screenshots%2F4.png)
|
||||||
|
|
||||||
|
<u>DELETE-метод для удаления вакансии</u>
|
||||||
|
|
||||||
|
![5.png](screenshots%2F5.png)
|
||||||
|
|
||||||
|
### Метод, связывающий оба микросервиса
|
||||||
|
<u>Демонстрация работы:</u>
|
||||||
|
|
||||||
|
![img_12.png](screenshots%2Fimg_12.png)
|
||||||
|
|
||||||
|
### Сущности
|
||||||
|
Page -> Jobs (One-to-Many)
|
||||||
|
|
||||||
|
![img.png](screenshots/test/img.png)
|
||||||
|
|
||||||
|
![img_1.png](screenshots/test/img_1.png)
|
||||||
|
|
||||||
|
### Dockerfile (aggregator-api)
|
||||||
|
|
||||||
|
![img_2.png](screenshots/test/img_2.png)
|
||||||
|
|
||||||
|
### Dockerfile (parser-api)
|
||||||
|
|
||||||
|
![img_3.png](screenshots/test/img_3.png)
|
||||||
|
|
||||||
|
### Docker compose
|
||||||
|
|
||||||
|
![img_4.png](screenshots/test/img_4.png)
|
||||||
|
|
||||||
|
![img_5.png](screenshots/test/img_5.png)
|
||||||
|
|
||||||
|
![img_6.png](screenshots/test/img_6.png)
|
||||||
|
|
||||||
|
![img_7.png](screenshots/test/img_7.png)
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
![img_8.png](screenshots%2Ftest%2Fimg_8.png)
|
||||||
|
|
||||||
|
## Ссылка на видео:
|
||||||
|
https://youtu.be/oPLQxyRDlXw
|
9
shadaev_anton_lab_3/aggregator-api/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM openjdk:17-jdk-slim
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY aggregator-api-1.0-SNAPSHOT.jar /app
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "/app/aggregator-api-1.0-SNAPSHOT.jar"]
|
58
shadaev_anton_lab_3/aggregator-api/pom.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.company</groupId>
|
||||||
|
<artifactId>shadaev_anton_lab_3</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>aggregator-api</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.company;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class AggregatorApiApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AggregatorApiApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.company.controller;
|
||||||
|
|
||||||
|
import com.company.dto.JobDto;
|
||||||
|
import com.company.exceptions.JobAlreadyExistsException;
|
||||||
|
import com.company.exceptions.JobNotFoundException;
|
||||||
|
import com.company.model.Job;
|
||||||
|
import com.company.service.JobService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/jobs")
|
||||||
|
public class JobController {
|
||||||
|
private final JobService jobService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public JobController(JobService jobService) {
|
||||||
|
this.jobService = jobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> save(@RequestBody JobDto jobDto) {
|
||||||
|
Job job;
|
||||||
|
try {
|
||||||
|
if (jobService.findJobByTitle(jobDto.getTitle()).isPresent())
|
||||||
|
throw new JobAlreadyExistsException("Job already exists!");
|
||||||
|
job = jobService.save(jobDto);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(job);
|
||||||
|
} catch (JobAlreadyExistsException e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Job> findAll() {
|
||||||
|
return jobService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{uuid}")
|
||||||
|
public ResponseEntity<Job> findJobByUUID(@PathVariable UUID uuid) {
|
||||||
|
return jobService.findJobByUUID(uuid)
|
||||||
|
.map(app -> ResponseEntity.ok().body(app))
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{uuid}")
|
||||||
|
public ResponseEntity<?> updateJobByUUID(@PathVariable UUID uuid, @RequestBody JobDto jobDto) {
|
||||||
|
Job job;
|
||||||
|
try {
|
||||||
|
if (jobService.findJobByUUID(uuid).isEmpty())
|
||||||
|
throw new JobNotFoundException("Job not found!");
|
||||||
|
job = jobService.updateJobByUUID(uuid, jobDto);
|
||||||
|
return ResponseEntity.ok().body(job);
|
||||||
|
} catch (JobNotFoundException e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{uuid}")
|
||||||
|
public ResponseEntity<?> deleteJobByUUID(@PathVariable UUID uuid) {
|
||||||
|
UUID statusId;
|
||||||
|
try {
|
||||||
|
if (!jobService.findJobByUUID(uuid).isPresent())
|
||||||
|
throw new JobNotFoundException("Job not found!");
|
||||||
|
statusId = jobService.deleteJobByUUID(uuid);
|
||||||
|
return ResponseEntity.ok().body(statusId);
|
||||||
|
} catch (JobNotFoundException e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.company.dto;
|
||||||
|
|
||||||
|
import com.company.model.Job;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public record JobDto(@Getter String title, @Getter String description) {
|
||||||
|
|
||||||
|
public JobDto(String title, String description) {
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Job toJob(JobDto jobDto) {
|
||||||
|
return Job.builder()
|
||||||
|
.title(jobDto.getTitle())
|
||||||
|
.description(jobDto.getDescription())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.company.exceptions;
|
||||||
|
|
||||||
|
public class JobAlreadyExistsException extends Exception {
|
||||||
|
public JobAlreadyExistsException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobAlreadyExistsException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.company.exceptions;
|
||||||
|
|
||||||
|
public class JobNotFoundException extends Exception {
|
||||||
|
public JobNotFoundException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.company.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "jobs")
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
public class Job {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
private String title;
|
||||||
|
private String description;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.company.repo;
|
||||||
|
|
||||||
|
import com.company.model.Job;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface JobRepository extends JpaRepository<Job, UUID> {
|
||||||
|
Optional<Job> findJobByTitle(String title);
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.company.service;
|
||||||
|
|
||||||
|
import com.company.dto.JobDto;
|
||||||
|
import com.company.model.Job;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface JobService {
|
||||||
|
Job save(JobDto job);
|
||||||
|
List<Job> findAll();
|
||||||
|
Optional<Job> findJobByUUID(UUID uuid);
|
||||||
|
Optional<Job> findJobByTitle(String title);
|
||||||
|
Job updateJobByUUID(UUID uuid, JobDto jobDto);
|
||||||
|
UUID deleteJobByUUID(UUID uuid);
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.company.service.impl;
|
||||||
|
|
||||||
|
import com.company.dto.JobDto;
|
||||||
|
import com.company.model.Job;
|
||||||
|
import com.company.repo.JobRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JobService implements com.company.service.JobService {
|
||||||
|
private final JobRepository jobRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public JobService(JobRepository jobRepository) {
|
||||||
|
this.jobRepository = jobRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Job save(JobDto jobDto) {
|
||||||
|
return jobRepository.save(JobDto.toJob(jobDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public List<Job> findAll(){
|
||||||
|
return jobRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Optional<Job> findJobByUUID(UUID uuid) {
|
||||||
|
return Optional.ofNullable(jobRepository.findById(uuid).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Optional<Job> findJobByTitle(String title) {
|
||||||
|
return Optional.ofNullable(jobRepository.findJobByTitle(title)).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Job updateJobByUUID(UUID uuid, JobDto jobDto) {
|
||||||
|
Job job = jobRepository.findById(uuid).get();
|
||||||
|
job.setTitle(jobDto.getTitle());
|
||||||
|
job.setDescription(jobDto.getDescription());
|
||||||
|
return jobRepository.save(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UUID deleteJobByUUID(UUID uuid) {
|
||||||
|
jobRepository.deleteById(uuid);
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: true
|
||||||
|
datasource:
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
url: jdbc:postgresql://localhost:5433/aggregator_db
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
test...
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: true
|
||||||
|
datasource:
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
url: jdbc:postgresql://localhost:5433/aggregator_db
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
test...
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,3 @@
|
|||||||
|
artifactId=aggregator-api
|
||||||
|
groupId=com.company
|
||||||
|
version=1.0-SNAPSHOT
|
@ -0,0 +1,10 @@
|
|||||||
|
com/company/controller/JobController.class
|
||||||
|
com/company/service/JobService.class
|
||||||
|
com/company/model/Job$JobBuilder.class
|
||||||
|
com/company/exceptions/JobAlreadyExistsException.class
|
||||||
|
com/company/exceptions/JobNotFoundException.class
|
||||||
|
com/company/repo/JobRepository.class
|
||||||
|
com/company/dto/JobDto.class
|
||||||
|
com/company/service/impl/JobService.class
|
||||||
|
com/company/model/Job.class
|
||||||
|
com/company/AggregatorApiApplication.class
|
@ -0,0 +1,9 @@
|
|||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/dto/JobDto.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/model/Job.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/service/JobService.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/exceptions/JobNotFoundException.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/exceptions/JobAlreadyExistsException.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/repo/JobRepository.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/service/impl/JobService.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/AggregatorApiApplication.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/aggregator-api/src/main/java/com/company/controller/JobController.java
|
74
shadaev_anton_lab_3/docker-compose.yaml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
aggregator-api-service:
|
||||||
|
# image: kybernetique/aggregator-api:latest
|
||||||
|
build:
|
||||||
|
context: ./aggregator-api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
environment:
|
||||||
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-service/aggregator_db
|
||||||
|
- SPRING_DATASOURCE_USERNAME=postgres
|
||||||
|
- SPRING_DATASOURCE_PASSWORD=postgres
|
||||||
|
- SPRING_JPA_HIBERNATE_DDL_AUTO=create-drop
|
||||||
|
depends_on:
|
||||||
|
- postgres-service
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
aliases:
|
||||||
|
- "aggregator"
|
||||||
|
|
||||||
|
|
||||||
|
parser-api-service:
|
||||||
|
# image: kybernetique/parser-api:latest
|
||||||
|
build:
|
||||||
|
context: ./parser-api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8082:8081"
|
||||||
|
environment:
|
||||||
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-service/aggregator_db
|
||||||
|
- SPRING_DATASOURCE_USERNAME=postgres
|
||||||
|
- SPRING_DATASOURCE_PASSWORD=postgres
|
||||||
|
- SPRING_JPA_HIBERNATE_DDL_AUTO=create-drop
|
||||||
|
depends_on:
|
||||||
|
- postgres-service
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
aliases:
|
||||||
|
- "parser"
|
||||||
|
|
||||||
|
postgres-service:
|
||||||
|
image: postgres:14.1
|
||||||
|
container_name: postgres-db
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "aggregator_db"
|
||||||
|
POSTGRES_USER: "postgres"
|
||||||
|
POSTGRES_PASSWORD: "postgres"
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
aliases:
|
||||||
|
- "postgres"
|
||||||
|
|
||||||
|
nginx-service:
|
||||||
|
image: nginx:latest
|
||||||
|
ports:
|
||||||
|
- "81:80"
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
aliases:
|
||||||
|
- "nginx"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
depends_on:
|
||||||
|
- aggregator-api-service
|
||||||
|
- parser-api-service
|
||||||
|
- postgres-service
|
||||||
|
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
driver: bridge
|
19
shadaev_anton_lab_3/nginx.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location /aggregator-api/ {
|
||||||
|
proxy_pass http://aggregator-api-service:8080/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /parser-api/ {
|
||||||
|
proxy_pass http://parser-api-service:8081/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
shadaev_anton_lab_3/parser-api/Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM openjdk:17-jdk-slim
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY parser-api-1.0-SNAPSHOT.jar /app
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "/app/parser-api-1.0-SNAPSHOT.jar"]
|
BIN
shadaev_anton_lab_3/parser-api/parser-api-1.0-SNAPSHOT.jar
Normal file
58
shadaev_anton_lab_3/parser-api/pom.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.company</groupId>
|
||||||
|
<artifactId>shadaev_anton_lab_3</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>parser-api</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.company;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ParserApiApplication {
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ParserApiApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.company.controller;
|
||||||
|
|
||||||
|
import com.company.dto.PageDto;
|
||||||
|
import com.company.exception.PageNotFoundException;
|
||||||
|
import com.company.model.Page;
|
||||||
|
import com.company.service.PageService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/pages")
|
||||||
|
public class PageController {
|
||||||
|
private final PageService pageService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PageController(PageService pageService) {
|
||||||
|
this.pageService = pageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> save(@RequestBody PageDto pageDto) {
|
||||||
|
Page page;
|
||||||
|
page = pageService.save(pageDto);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Page> findAll() {
|
||||||
|
return pageService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{uuid}")
|
||||||
|
public ResponseEntity<Page> findPageByUUID(@PathVariable UUID uuid) {
|
||||||
|
return pageService.findPageByUUID(uuid)
|
||||||
|
.map(app -> ResponseEntity.ok().body(app))
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{uuid}")
|
||||||
|
public ResponseEntity<?> updatePageByUUID(@PathVariable UUID uuid, @RequestBody PageDto pageDto) {
|
||||||
|
Page page;
|
||||||
|
try {
|
||||||
|
if (pageService.findPageByUUID(uuid).isEmpty())
|
||||||
|
throw new PageNotFoundException("Page not found!");
|
||||||
|
page = pageService.updatePageByUUID(uuid, pageDto);
|
||||||
|
return ResponseEntity.ok().body(page);
|
||||||
|
} catch (PageNotFoundException e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{uuid}")
|
||||||
|
public ResponseEntity<?> deletePageByUUID(@PathVariable UUID uuid) {
|
||||||
|
UUID statusId;
|
||||||
|
try {
|
||||||
|
if (!pageService.findPageByUUID(uuid).isPresent())
|
||||||
|
throw new PageNotFoundException("Page not found!");
|
||||||
|
statusId = pageService.deletePageByUUID(uuid);
|
||||||
|
return ResponseEntity.ok().body(statusId);
|
||||||
|
} catch (PageNotFoundException e) {
|
||||||
|
return ResponseEntity.badRequest().body(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/find-job-info/{uuid}")
|
||||||
|
public ResponseEntity<?> findJobInfo(@PathVariable UUID uuid) {
|
||||||
|
return ResponseEntity.ok().body(pageService.findJobInfo(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.company.dto;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public record JobInfoDto(@Getter String title, @Getter String description) {
|
||||||
|
public JobInfoDto(String title, String description) {
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.company.dto;
|
||||||
|
|
||||||
|
import com.company.model.Page;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public record PageDto(@Getter String jobUrl) {
|
||||||
|
public PageDto(String jobUrl) {
|
||||||
|
this.jobUrl = jobUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Page toPage(PageDto pageDto) {
|
||||||
|
return Page.builder()
|
||||||
|
.jobUrl(pageDto.getJobUrl())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.company.exception;
|
||||||
|
|
||||||
|
public class PageNotFoundException extends Exception {
|
||||||
|
public PageNotFoundException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.company.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "pages")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class Page {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID id;
|
||||||
|
private String jobUrl;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.company.repo;
|
||||||
|
|
||||||
|
import com.company.model.Page;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface PageRepository extends JpaRepository<Page, UUID> {
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.company.service;
|
||||||
|
|
||||||
|
import com.company.dto.JobInfoDto;
|
||||||
|
import com.company.dto.PageDto;
|
||||||
|
import com.company.model.Page;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface PageService {
|
||||||
|
Page save(PageDto pageDto);
|
||||||
|
List<Page> findAll();
|
||||||
|
Optional<Page> findPageByUUID(UUID uuid);
|
||||||
|
JobInfoDto findJobInfo(UUID jobUUID);
|
||||||
|
Page updatePageByUUID(UUID uuid, PageDto pageDto);
|
||||||
|
UUID deletePageByUUID(UUID uuid);
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.company.service.impl;
|
||||||
|
|
||||||
|
import com.company.dto.JobInfoDto;
|
||||||
|
import com.company.dto.PageDto;
|
||||||
|
import com.company.model.Page;
|
||||||
|
import com.company.repo.PageRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PageService implements com.company.service.PageService {
|
||||||
|
private final PageRepository pageRepository;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PageService(PageRepository pageRepository, RestTemplate restTemplate) {
|
||||||
|
this.pageRepository = pageRepository;
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Page save(PageDto pageDto) {
|
||||||
|
return pageRepository.save(PageDto.toPage(pageDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public List<Page> findAll() {
|
||||||
|
return pageRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Optional<Page> findPageByUUID(UUID uuid) {
|
||||||
|
return Optional.ofNullable(pageRepository.findById(uuid).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Page updatePageByUUID(UUID uuid, PageDto pageDto) {
|
||||||
|
Page page = pageRepository.findById(uuid).get();
|
||||||
|
page.setJobUrl(pageDto.getJobUrl());
|
||||||
|
return pageRepository.save(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UUID deletePageByUUID(UUID uuid) {
|
||||||
|
pageRepository.deleteById(uuid);
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public JobInfoDto findJobInfo(UUID jobUUID) {
|
||||||
|
String jobInfoUrl = "http://nginx/aggregator-api/jobs/" + jobUUID;
|
||||||
|
return restTemplate.getForObject(jobInfoUrl, JobInfoDto.class);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: true
|
||||||
|
datasource:
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
url: jdbc:postgresql://localhost:5433/aggregator_db
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: true
|
||||||
|
datasource:
|
||||||
|
driverClassName: org.postgresql.Driver
|
||||||
|
url: jdbc:postgresql://localhost:5433/aggregator_db
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
@ -0,0 +1,3 @@
|
|||||||
|
artifactId=parser-api
|
||||||
|
groupId=com.company
|
||||||
|
version=1.0-SNAPSHOT
|
@ -0,0 +1,10 @@
|
|||||||
|
com/company/service/impl/PageService.class
|
||||||
|
com/company/dto/PageDto.class
|
||||||
|
com/company/model/Page.class
|
||||||
|
com/company/service/PageService.class
|
||||||
|
com/company/repo/PageRepository.class
|
||||||
|
com/company/model/Page$PageBuilder.class
|
||||||
|
com/company/dto/JobInfoDto.class
|
||||||
|
com/company/controller/PageController.class
|
||||||
|
com/company/ParserApiApplication.class
|
||||||
|
com/company/exception/PageNotFoundException.class
|
@ -0,0 +1,9 @@
|
|||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/ParserApiApplication.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/controller/PageController.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/model/Page.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/service/PageService.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/exception/PageNotFoundException.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/service/impl/PageService.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/dto/PageDto.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/dto/JobInfoDto.java
|
||||||
|
/Users/a-shdv/IdeaProjects/aggregator/parser-api/src/main/java/com/company/repo/PageRepository.java
|
38
shadaev_anton_lab_3/pom.xml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.7.5</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.company</groupId>
|
||||||
|
<artifactId>shadaev_anton_lab_3</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>aggregator-api</module>
|
||||||
|
<module>parser-api</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
BIN
shadaev_anton_lab_3/screenshots/1.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
shadaev_anton_lab_3/screenshots/2.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
shadaev_anton_lab_3/screenshots/3.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
shadaev_anton_lab_3/screenshots/4.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
shadaev_anton_lab_3/screenshots/5.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
shadaev_anton_lab_3/screenshots/img.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_1.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_10.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_11.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_12.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_2.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_3.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_4.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_5.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_6.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_7.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_8.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
shadaev_anton_lab_3/screenshots/img_9.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_1.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_2.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_3.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_4.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_5.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_6.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_7.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
shadaev_anton_lab_3/screenshots/test/img_8.png
Normal file
After Width: | Height: | Size: 50 KiB |