From ad63d845dcb415f0b7415352aa578f39f8f6ab99 Mon Sep 17 00:00:00 2001 From: shipovnikaaa Date: Tue, 18 Feb 2025 18:32:31 +0300 Subject: [PATCH] first commit --- .gitignore | 32 ++++++++ README.md | 81 +++++++++++++++++++ pom.xml | 57 +++++++++++++ src/main/java/com/example/nto/App.java | 11 +++ .../nto/controller/EmployeeController.java | 32 ++++++++ .../java/com/example/nto/entity/Code.java | 24 ++++++ .../java/com/example/nto/entity/Employee.java | 28 +++++++ .../nto/repository/CodeRepository.java | 10 +++ .../nto/repository/EmployeeRepository.java | 12 +++ .../example/nto/service/EmployeeService.java | 12 +++ .../exception/CodeNotFoundException.java | 7 ++ .../exception/EmployeeNotFoundException.java | 7 ++ .../nto/service/impl/EmployeeServiceImpl.java | 50 ++++++++++++ src/main/resources/application.yml | 28 +++++++ src/main/resources/data.sql | 14 ++++ 15 files changed, 405 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/example/nto/App.java create mode 100644 src/main/java/com/example/nto/controller/EmployeeController.java create mode 100644 src/main/java/com/example/nto/entity/Code.java create mode 100644 src/main/java/com/example/nto/entity/Employee.java create mode 100644 src/main/java/com/example/nto/repository/CodeRepository.java create mode 100644 src/main/java/com/example/nto/repository/EmployeeRepository.java create mode 100644 src/main/java/com/example/nto/service/EmployeeService.java create mode 100644 src/main/java/com/example/nto/service/exception/CodeNotFoundException.java create mode 100644 src/main/java/com/example/nto/service/exception/EmployeeNotFoundException.java create mode 100644 src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/data.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98f5c59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f3d371 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# НТО 2024. II отборочный этап. Командные задания — серверная часть + +## 📖 Предыстория + +В компании S контроль доступа в офис осуществляется с помощью СКУД (системы контроля управления доступом). На данный момент у каждого сотрудника компании есть карта-пропуск с NFC меткой. А у каждой входной двери есть считыватель с обеих сторон. При поднесении карты к считывателю, дверь открывается, а информация о времени входа или выхода сотрудника фиксируется в базе данных. +Администрации компании S требуется мобильное приложение, как для рядовых сотрудников, так и для администрации с возможностью просмотра посещений и работой электронного пропуска как временной замены обычного (при помощи сканировании QR кода, который находится на считывателе). Поскольку в приложении есть возможность использовать телефон как пропуск - то к данному приложению повышенные требования к безопасности всех данных, находящихся внутри него. + + + +## 🛠️ Техническое задание + +Требуется разработать серверное приложение на Java (Java 11) с использованием Spring Boot, которое работает на основе протоколов TCP/IP и взаимодействует с клиентами благодаря RESTful API. Для хранения данных о сотрудниках и их посещениях должна использоваться реляционная база данных (H2). Сотрудникам не нужно регистрироваться, все данные в базе должны быть предзаполнены. Картинки для аватаров пользователей не должны храниться в БД. Должны быть сохранены лишь URL-адреса на ресурсы, откуда в последующем мобильное приложение загрузит соответствующий аватар. + +Сервер разрабатывается на основе предоставляемой заготовки проекта. Версии зависимостей и сами зависимости изменяться не должны. + + + +## 📂 Правила работы с проектом-заготовкой + +В предоставленном проекте необходимо изучить, но никак не модифицировать, не перемещать и не удалять следующие файлы: +- `pom.xml` +- `application.yml` +- все файлы из `db.changelog` + +Кроме описанных выше файлов, в проекте уже созданы основные классы-сущности и добавлены пустые классы всех слоев. В этих классах необходимо будет написать программный код и добавить аннотации для реализации описанного задания. Наименования классов и прочий код уже написанный в предоставляемом проекте **изменять/удалять не нужно, необходимо их доработать**. Добавлять свои дополнительные классы в проект можно. + +Создание таблиц в БД и предзаполнение их требуемыми данными уже реализовано в заготовке при помощи liquibase. + + +## 🌐 Где необходимо разместить сервер + +Серверное приложение должно быть разработано и протестировано локально (не требуется размещать сервер удаленно и осуществлять его функционирование 24/7). + + + +## 📋 Технические требования к серверу и его ответам клиенту + +Для конфигурирования Вашего сервера (его хоста/IP адреса) используйте константы из файла `Constants.kt`. + +| **Тип запроса** | **Путь** | **Параметры/Тело** | **Ответы** | +|------------------|-----------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| **GET** | `api//auth` | `` - никнейм/имя пользователя | `400` - что-то пошло не так
`401` - логина не существует или неверный
`200` - данный логин существует - можно пользоваться приложением | +| **GET** | `api//info` | `` - никнейм/имя пользователя | `400` - что-то пошло не так
`401` - логина не существует или неверный
`200` - ОК
{
"id": 1,
"login": "pivanov",
"name": "Иванов Петр Федорович",
"photo": "https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg",
"position": "Разработчик",
"lastVisit": "2024-02-12T08:30:00"
} | +| **PATCH** | `api//open` | `` - никнейм/имя пользователя
Тело: `{“value”: }`, где
`` - это код, полученный из экрана сканирования QR кода | `400` - что-то пошло не так
`401` - логина не существует или неверный
`200` - дверь открылась | + + + +## 📊 Данные, которыми необходимо наполнить базу данных: + + + +| **id** | **login** | **name** | **photo** | **position** | **lastVisit** | +|--------|------------|---------------------------------|---------------------------------------------------------------------------------------------|------------------|---------------------| +| 1 | pivanov | Иванов Петр Федорович | https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg | Разработчик | 2024-02-12T08:30:00 | +| 2 | ipetrov | Петров Иван Константинович | https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg | Аналитик | 2024-02-30T08:35:00 | +| 3 | asemenov | Семенов Анатолий Анатольевич | https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg | Разработчик | 2024-02-31T08:31:00 | +| 4 | afedorov | Федоров Александр Сергеевич | https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg | Тестировщик | 2024-02-30T08:36:00 | + + + +| **id** | **code** | +|--------|-------------------------| +| 1 | 1234567890123456789 | +| 2 | 9223372036854775807 | +| 3 | 1122334455667788990 | +| 4 | 998877665544332211 | +| 5 | 5566778899001122334 | + + + +## 📝 Решение + +Необходимо загрузить свое решение в систему [по ссылке](https://innovationcampus.ru/lms/mod/quiz/view.php?id=2078). + +Отметим, что работу необходимо осуществлять в представленных проектах-заготовках (шаблонах). + + + +## ✅ Особенности оценивания + +Оценивание происходит с помощью автоматической системы тестирования, которая в автоматическом режиме находит элементы и взаимодействует с ними (именно для этого у каждого элемента указан уникальный идентификатор, по которому будет производится поиск). Каждый тест происходит с чистой установки приложения. В случае тестирования сервера на него поочередно отправляются команды, описанные в API и ожидаются определенные корректные ответы. Сервер и приложение тестируются независимо. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..88282ee --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + org.example + NTO-2024 + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.5 + + + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter-web + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + \ No newline at end of file diff --git a/src/main/java/com/example/nto/App.java b/src/main/java/com/example/nto/App.java new file mode 100644 index 0000000..d4add94 --- /dev/null +++ b/src/main/java/com/example/nto/App.java @@ -0,0 +1,11 @@ +package com.example.nto; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/src/main/java/com/example/nto/controller/EmployeeController.java b/src/main/java/com/example/nto/controller/EmployeeController.java new file mode 100644 index 0000000..d776678 --- /dev/null +++ b/src/main/java/com/example/nto/controller/EmployeeController.java @@ -0,0 +1,32 @@ +package com.example.nto.controller; + +import com.example.nto.entity.Code; +import com.example.nto.entity.Employee; +import com.example.nto.service.EmployeeService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/{login}") +@RequiredArgsConstructor +public class EmployeeController { + + private final EmployeeService employeeService; + + @GetMapping("/auth") + public void auth(@PathVariable final String login) { + employeeService.employeeExists(login); + } + + @GetMapping("/info") + public Employee info(@PathVariable final String login) { + return employeeService.getEmployee(login); + } + + @PatchMapping("/open") + public void open(@PathVariable final String login, @RequestBody final Code code) { + employeeService.updateVisit(login, code.getValue()); + } +} diff --git a/src/main/java/com/example/nto/entity/Code.java b/src/main/java/com/example/nto/entity/Code.java new file mode 100644 index 0000000..5e3e92a --- /dev/null +++ b/src/main/java/com/example/nto/entity/Code.java @@ -0,0 +1,24 @@ +package com.example.nto.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity(name = "code") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Code { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + private long value; +} diff --git a/src/main/java/com/example/nto/entity/Employee.java b/src/main/java/com/example/nto/entity/Employee.java new file mode 100644 index 0000000..04f5c91 --- /dev/null +++ b/src/main/java/com/example/nto/entity/Employee.java @@ -0,0 +1,28 @@ +package com.example.nto.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.time.LocalDateTime; + +@Entity(name = "employee") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Employee { + + @Id + private long id; + private String login; + private String name; + private String photo; + private String position; + private LocalDateTime lastVisit; +} diff --git a/src/main/java/com/example/nto/repository/CodeRepository.java b/src/main/java/com/example/nto/repository/CodeRepository.java new file mode 100644 index 0000000..d1ed7da --- /dev/null +++ b/src/main/java/com/example/nto/repository/CodeRepository.java @@ -0,0 +1,10 @@ +package com.example.nto.repository; + +import com.example.nto.entity.Code; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CodeRepository extends JpaRepository { + boolean existsByValue(Long value); +} diff --git a/src/main/java/com/example/nto/repository/EmployeeRepository.java b/src/main/java/com/example/nto/repository/EmployeeRepository.java new file mode 100644 index 0000000..16440d3 --- /dev/null +++ b/src/main/java/com/example/nto/repository/EmployeeRepository.java @@ -0,0 +1,12 @@ +package com.example.nto.repository; + +import com.example.nto.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EmployeeRepository extends JpaRepository { + boolean existsByLogin(final String login); + + Employee findEmployeeByLogin(final String login); +} diff --git a/src/main/java/com/example/nto/service/EmployeeService.java b/src/main/java/com/example/nto/service/EmployeeService.java new file mode 100644 index 0000000..ae75d14 --- /dev/null +++ b/src/main/java/com/example/nto/service/EmployeeService.java @@ -0,0 +1,12 @@ +package com.example.nto.service; + +import com.example.nto.entity.Employee; + +public interface EmployeeService { + + void employeeExists(String login); + + Employee getEmployee(String login); + + void updateVisit(String login, long value); +} diff --git a/src/main/java/com/example/nto/service/exception/CodeNotFoundException.java b/src/main/java/com/example/nto/service/exception/CodeNotFoundException.java new file mode 100644 index 0000000..a4def9f --- /dev/null +++ b/src/main/java/com/example/nto/service/exception/CodeNotFoundException.java @@ -0,0 +1,7 @@ +package com.example.nto.service.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class CodeNotFoundException extends RuntimeException {} diff --git a/src/main/java/com/example/nto/service/exception/EmployeeNotFoundException.java b/src/main/java/com/example/nto/service/exception/EmployeeNotFoundException.java new file mode 100644 index 0000000..2f72a9f --- /dev/null +++ b/src/main/java/com/example/nto/service/exception/EmployeeNotFoundException.java @@ -0,0 +1,7 @@ +package com.example.nto.service.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public class EmployeeNotFoundException extends RuntimeException {} diff --git a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java new file mode 100644 index 0000000..8a39171 --- /dev/null +++ b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java @@ -0,0 +1,50 @@ +package com.example.nto.service.impl; + +import com.example.nto.entity.Code; +import com.example.nto.entity.Employee; +import com.example.nto.repository.CodeRepository; +import com.example.nto.repository.EmployeeRepository; +import com.example.nto.service.EmployeeService; +import com.example.nto.service.exception.CodeNotFoundException; +import com.example.nto.service.exception.EmployeeNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + private final CodeRepository codeRepository; + + @Override + public void employeeExists(final String login) { + if (!employeeRepository.existsByLogin(login)) { + throw new EmployeeNotFoundException(); + } + } + + @Override + public Employee getEmployee(final String login) { + if (!employeeRepository.existsByLogin(login)) { + throw new EmployeeNotFoundException(); + } + return employeeRepository.findEmployeeByLogin(login); + } + + @Override + public void updateVisit(final String login, final long value) { + if (!employeeRepository.existsByLogin(login)) { + throw new EmployeeNotFoundException(); + } + if (!codeRepository.existsByValue(value)) { + throw new CodeNotFoundException(); + } + final Employee employee = employeeRepository.findEmployeeByLogin(login); + employee.setLastVisit(LocalDateTime.now()); + employeeRepository.save(employee); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c6bfd72 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,28 @@ +spring: + + datasource: + url: jdbc:h2:mem:testdb + + h2: + console: + #enabled: false + enabled: true + + jpa: + #generate-ddl: false + generate-ddl: true + + hibernate: + #ddl-auto: none + ddl-auto: create-drop + + # Показываем запросы + show-sql: true + + # Своевременный запуск data.sql + defer-datasource-initialization: true + + spring-doc: + swagger-ui: + path: /swagger-ui.html + operationsSorter: method \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..03720a9 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,14 @@ +INSERT INTO employee (id, login, name, photo, position, last_visit) +VALUES +(1, 'pivanov', 'Иванов Петр Федорович', 'https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg', 'Разработчик', '2024-02-12T08:30'), +(2, 'ipetrov', 'Петров Иван Константинович', 'https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg', 'Аналитик', '2024-02-13T08:35'), +(3, 'asemenov', 'Семенов Анатолий Анатольевич', 'https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg', 'Разработчик', '2024-02-13T08:31'), +(4, 'afedorov', 'Федоров Александр Сергеевич', 'https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg', 'Тестировщик', '2024-02-12T08:36'); + +INSERT INTO code (value) +VALUES +(1234567890123456789), +(9223372036854775807), +(1122334455667788990), +(998877665544332211), +(5566778899001122334); \ No newline at end of file