first commit
This commit is contained in:
commit
47714bb900
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -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
|
81
README.md
Normal file
81
README.md
Normal file
@ -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/<LOGIN>/auth` | `<LOGIN>` - никнейм/имя пользователя | `400` - что-то пошло не так<br>`401` - логина не существует или неверный<br>`200` - данный логин существует - можно пользоваться приложением |
|
||||
| **GET** | `api/<LOGIN>/info` | `<LOGIN>` - никнейм/имя пользователя | `400` - что-то пошло не так<br>`401` - логина не существует или неверный<br>`200` - ОК<br>{<br> "id": 1,<br> "login": "pivanov",<br> "name": "Иванов Петр Федорович",<br> "photo": "https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg",<br> "position": "Разработчик",<br> "lastVisit": "2024-02-12T08:30:00"<br>} |
|
||||
| **PATCH** | `api/<LOGIN>/open` | `<LOGIN>` - никнейм/имя пользователя<br>Тело: `{“value”: <CODE>}`, где <br> `<CODE>` - это код, полученный из экрана сканирования QR кода | `400` - что-то пошло не так<br>`401` - логина не существует или неверный<br>`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 и ожидаются определенные корректные ответы. Сервер и приложение тестируются независимо.
|
57
pom.xml
Normal file
57
pom.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?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>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>NTO-2024</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.5</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
12
src/main/java/com/example/nto/App.java
Normal file
12
src/main/java/com/example/nto/App.java
Normal file
@ -0,0 +1,12 @@
|
||||
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) {
|
||||
System.out.println("infinity stack backend: v 2024-11-20");
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api")
|
||||
public class EmployeeController {
|
||||
private final EmployeeService employeeService;
|
||||
|
||||
@GetMapping("/{login}/auth")
|
||||
private ResponseEntity<Void> auth(@PathVariable("login") String login) {
|
||||
return employeeService.checkExistence(login) ? new ResponseEntity<>(null, HttpStatus.OK) : new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@GetMapping("/{login}/info")
|
||||
private ResponseEntity<Employee> info(@PathVariable("login") String login) {
|
||||
if (!employeeService.checkExistence(login)) {
|
||||
return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(employeeService.info(login), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PatchMapping("/{login}/open")
|
||||
private ResponseEntity<Void> open(@PathVariable("login") String login, @RequestBody Code value) {
|
||||
LocalDateTime localDateTime = LocalDateTime.now();
|
||||
if (!employeeService.checkExistence(login)) {
|
||||
return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
Code code = employeeService.getCode(value.getValue());
|
||||
if (code == null) {
|
||||
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
employeeService.updateTime(login, localDateTime);
|
||||
return new ResponseEntity<>(null, HttpStatus.OK);
|
||||
}
|
||||
}
|
20
src/main/java/com/example/nto/entity/Code.java
Normal file
20
src/main/java/com/example/nto/entity/Code.java
Normal file
@ -0,0 +1,20 @@
|
||||
package com.example.nto.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Code {
|
||||
@Id
|
||||
private long id;
|
||||
private long value;
|
||||
}
|
25
src/main/java/com/example/nto/entity/Employee.java
Normal file
25
src/main/java/com/example/nto/entity/Employee.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.example.nto.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@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;
|
||||
}
|
11
src/main/java/com/example/nto/repository/CodeRepository.java
Normal file
11
src/main/java/com/example/nto/repository/CodeRepository.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.example.nto.repository;
|
||||
|
||||
import com.example.nto.entity.Code;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CodeRepository extends JpaRepository<Code, Long> {
|
||||
@Nullable Code findByValue(Long value);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.example.nto.repository;
|
||||
|
||||
import com.example.nto.entity.Employee;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
||||
@Nullable Employee findByLogin(String login);
|
||||
|
||||
@Modifying
|
||||
@Query(value = "UPDATE Employee set lastVisit = :localDateTime where login = :login")
|
||||
void updateTime(String login, LocalDateTime localDateTime);
|
||||
}
|
17
src/main/java/com/example/nto/service/EmployeeService.java
Normal file
17
src/main/java/com/example/nto/service/EmployeeService.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.example.nto.service;
|
||||
|
||||
import com.example.nto.entity.Code;
|
||||
import com.example.nto.entity.Employee;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface EmployeeService {
|
||||
boolean checkExistence(String login);
|
||||
|
||||
@Nullable Employee info(String login);
|
||||
|
||||
@Nullable Code getCode(Long value);
|
||||
|
||||
void updateTime(String login, LocalDateTime localDateTime);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EmployeeServiceImpl implements EmployeeService {
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final CodeRepository codeRepository;
|
||||
|
||||
@Override
|
||||
public boolean checkExistence(String login) {
|
||||
return employeeRepository.findByLogin(login) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Employee info(String login) {
|
||||
return employeeRepository.findByLogin(login);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Code getCode(Long value) {
|
||||
return codeRepository.findByValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateTime(String login, LocalDateTime localDateTime) {
|
||||
employeeRepository.updateTime(login, localDateTime);
|
||||
}
|
||||
}
|
28
src/main/resources/application.yml
Normal file
28
src/main/resources/application.yml
Normal file
@ -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
|
14
src/main/resources/data.sql
Normal file
14
src/main/resources/data.sql
Normal file
@ -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 (id, value)
|
||||
VALUES
|
||||
(1, 1234567890123456789),
|
||||
(2, 9223372036854775807),
|
||||
(3, 1122334455667788990),
|
||||
(4, 998877665544332211),
|
||||
(5, 5566778899001122334);
|
Loading…
x
Reference in New Issue
Block a user