Skip to content

Anr186/time-tracker

Repository files navigation

Time Tracker API

REST-сервис для учета времени сотрудников по задачам.

Проект выполнен на Java 17, Spring Boot 3, MyBatis, PostgreSQL, Docker.

Реализованный функционал

  • Управление задачами (Task):
    • создание задачи;
    • получение задачи по id;
    • получение списка всех задач;
    • изменение статуса задачи (NEW, IN_PROGRESS, DONE).
  • Учет времени (TimeRecord):
    • создание записи о начале работы;
    • завершение записи (установка времени окончания);
    • получение записи по id;
    • получение записей сотрудника за период;
    • получение всех записей по задаче.
  • Валидация входных DTO через Bean Validation.
  • Централизованная обработка ошибок через @RestControllerAdvice.
  • OpenAPI/Swagger-документация.
  • Unit-тесты (JUnit 5, Mockito/MyBatis).

Технологии

  • Java 17
  • Spring Boot 3.5.13
  • MyBatis
  • PostgreSQL 15
  • Maven
  • SpringDoc OpenAPI (Swagger UI)
  • JUnit 5
  • Docker, docker-compose

Запуск (Docker)

Требования

  • Установлен и запущен Docker Desktop.

Запуск

Из корня проекта выполните:

docker-compose up --build

После запуска доступны:

  • API: http://localhost:8080
  • Swagger UI: http://localhost:8080/swagger-ui.html
  • OpenAPI JSON: http://localhost:8080/api-docs
  • PostgreSQL: localhost:5432

Параметры БД (из docker-compose.yml):

  • DB: time_tracker
  • User: postgres
  • Password: postgres

Остановка

docker-compose down

Как проверить API через Swagger

  1. Запустить сервис командой docker-compose up --build.
  2. Открыть http://localhost:8080/swagger-ui.html.
  3. Выполнить запросы в порядке:
    • POST /api/tasks (создать задачу);
    • PUT /api/tasks/{id}/status (изменить статус);
    • POST /api/time-records (начать работу);
    • PUT /api/time-records/{id}/end (завершить работу);
    • GET /api/time-records/employee/{employeeId} (получить записи за период).

Как проверить API через Postman

Создайте коллекцию и добавьте запросы ниже.

Готовый файл коллекции

В проекте подготовлена коллекция Postman:

  • Файл: Time Tracker API.postman_collection.json
  • Название коллекции: Time Tracker API

Как импортировать:

  1. Откройте Postman.
  2. Нажмите Import.
  3. Выберите файл Time Tracker API.postman_collection.json.
  4. Запустите контейнеры docker-compose up --build.
  5. Выполняйте запросы коллекции по порядку (сначала создание Task, потом TimeRecord).

Что уже есть в коллекции:

  • Create Task
  • Get Task by ID
  • Get All Tasks
  • Update Task Status
  • Start Work
  • End Work
  • Get Employee Time Records
  • Get Task Time Records

Примечание: в запросе Get Employee Time Records используется employeeId=100, а в Start WorkemployeeId=23. Для корректной проверки отчета за период укажите одинаковый employeeId в обоих запросах.

1) Создать задачу

  • Method: POST
  • URL: http://localhost:8080/api/tasks
  • Body (JSON):
{
  "title": "Подготовить отчет",
  "description": "Сформировать недельный отчет по времени"
}

2) Получить задачу по ID

  • Method: GET
  • URL: http://localhost:8080/api/tasks/1

3) Получить все задачи

  • Method: GET
  • URL: http://localhost:8080/api/tasks

4) Изменить статус задачи

  • Method: PUT
  • URL: http://localhost:8080/api/tasks/1/status?status=IN_PROGRESS

Допустимые значения status: NEW, IN_PROGRESS, DONE.

5) Начать работу по задаче (создать запись времени)

  • Method: POST
  • URL: http://localhost:8080/api/time-records
  • Body (JSON):
{
  "employeeId": 1001,
  "taskId": 1,
  "startTime": "2026-04-21T10:00:00",
  "description": "Начал выполнение задачи"
}

6) Получить запись времени по ID

  • Method: GET
  • URL: http://localhost:8080/api/time-records/1

7) Завершить работу по записи времени

  • Method: PUT
  • URL: http://localhost:8080/api/time-records/1/end
  • Body (JSON):
{
  "endTime": "2026-04-21T12:30:00",
  "description": "Работа завершена"
}

8) Получить записи сотрудника за период

  • Method: GET
  • URL:
http://localhost:8080/api/time-records/employee/1001?from=2026-04-01T00:00:00&to=2026-04-30T23:59:59

9) Получить записи по задаче

  • Method: GET
  • URL: http://localhost:8080/api/time-records/task/1

Тесты

В текущем Dockerfile используется сборка с -DskipTests, поэтому тесты не запускаются автоматически при docker-compose up --build.

Запуск тестов в Docker (без установки Java/Maven локально)

  1. Убедитесь, что контейнер БД запущен:
docker-compose up -d db
  1. Запустите тесты во временном Maven-контейнере:
docker run --rm -v "${PWD}:/app" -w /app --network time-tracker_default maven:3.9-eclipse-temurin-17 mvn test

Если тесты прошли успешно, в конце будет BUILD SUCCESS.

Просмотр данных в PostgreSQL

Вариант 1: через psql внутри контейнера

docker exec -it time-tracker-db psql -U postgres -d time_tracker

Полезные команды:

  • \dt — список таблиц
  • SELECT * FROM tasks;
  • SELECT * FROM time_records;
  • \q — выход

Вариант 2: через GUI-клиент (DBeaver/pgAdmin/DataGrip)

  • Host: localhost
  • Port: 5432
  • Database: time_tracker
  • User: postgres
  • Password: postgres

Модель данных

Task

  • id
  • title
  • description
  • status (NEW, IN_PROGRESS, DONE)
  • createdAt

TimeRecord

  • id
  • employeeId
  • taskId
  • startTime
  • endTime
  • description

Структура проекта

  • src/main/java/.../controller — REST-контроллеры
  • src/main/java/.../service — логика приложения
  • src/main/java/.../mapper — MyBatis mappers
  • src/main/resources/mapper — SQL-маппинги MyBatis
  • src/main/resources/schema.sql — схема БД
  • src/test/java — unit-тесты

Документация по файлам кода

Основной код (src/main/java)

TimeTrackerApplication.java

  • Назначение: точка входа Spring Boot приложения.
  • Основной метод:
    • main(String[] args) — запускает приложение через SpringApplication.run(...).

config/OpenApiConfig.java

  • Назначение: конфигурация OpenAPI/Swagger.
  • Основной метод:
    • openAPI() — создает бин OpenAPI с метаданными API (title, version).

controller/TaskController.java

  • Назначение: REST API для работы с задачами.
  • Основная переменная:
    • taskService — сервисный слой задач.
  • Основные методы:
    • createTask(TaskCreateRequest request) — создает задачу (POST /api/tasks).
    • getTask(Long id) — получает задачу по ID (GET /api/tasks/{id}).
    • getAllTasks() — получает список всех задач (GET /api/tasks).
    • updateStatus(Long id, Task.TaskStatus status) — меняет статус (PUT /api/tasks/{id}/status).

controller/TimeRecordController.java

  • Назначение: REST API для учета рабочего времени.
  • Основная переменная:
    • timeRecordService — сервисный слой записей времени.
  • Основные методы:
    • startWork(TimeRecordRequest request) — создает запись времени (POST /api/time-records).
    • endWork(Long id, TimeRecordUpdateRequest request) — завершает запись (PUT /api/time-records/{id}/end).
    • getTimeRecord(Long id) — получает запись по ID (GET /api/time-records/{id}).
    • getEmployeeTimeRecords(Long employeeId, LocalDateTime from, LocalDateTime to) — записи сотрудника за период (GET /api/time-records/employee/{employeeId}).
    • getTaskTimeRecords(Long taskId) — записи по задаче (GET /api/time-records/task/{taskId}).

dto/TaskCreateRequest.java

  • Назначение: DTO для создания задачи.
  • Основные поля:
    • title — название задачи; валидация @NotBlank, @Size(max = 255).
    • description — описание; валидация @Size(max = 1000).

dto/TimeRecordRequest.java

  • Назначение: DTO для создания записи времени.
  • Основные поля:
    • employeeId — ID сотрудника; @NotNull, @Positive.
    • taskId — ID задачи; @NotNull, @Positive.
    • startTime — время начала (может быть null, тогда ставится текущее время).
    • description — описание работы; @Size(max = 500).

dto/TimeRecordUpdateRequest.java

  • Назначение: DTO для завершения записи времени.
  • Основные поля:
    • endTime — время окончания (может быть null, тогда ставится текущее время).
    • description — описание работы; @Size(max = 500).

exception/GlobalExceptionHandler.java

  • Назначение: глобальная обработка исключений REST API.
  • Основные методы:
    • handleValidationExceptions(MethodArgumentNotValidException ex) — возвращает 400 и карту ошибок валидации по полям.
    • handleNotFound(NoSuchElementException ex) — возвращает 404.
    • handleIllegalArgument(IllegalArgumentException ex) — возвращает 400.
    • handleGenericException(Exception ex) — возвращает 500.
    • handleResourceNotFound(ResourceNotFoundException ex) — возвращает 404.
  • Общий формат ответа: timestamp, status, error, message/errors.

exception/ResourceNotFoundException.java

  • Назначение: пользовательское исключение "ресурс не найден".
  • Основной элемент:
    • конструктор ResourceNotFoundException(String message).

mapper/TaskMapper.java

  • Назначение: MyBatis mapper для таблицы tasks.
  • Основные методы:
    • insert(Task task) — вставка задачи с генерацией ID.
    • findById(Long id) — поиск задачи по ID.
    • findAll() — список всех задач.
    • updateStatus(Long id, Task.TaskStatus status) — обновление статуса.
    • deleteById(Long id) — удаление задачи.

mapper/TimeRecordMapper.java

  • Назначение: MyBatis mapper для таблицы time_records.
  • Основные методы:
    • insert(TimeRecord timeRecord) — вставка записи времени.
    • findById(Long id) — поиск записи по ID.
    • findByEmployeeAndPeriod(Long employeeId, LocalDateTime from, LocalDateTime to) — записи сотрудника за период.
    • updateEndTime(Long id, LocalDateTime endTime, String description) — завершение записи.
    • findByTaskId(Long taskId) — записи по задаче.

model/Task.java

  • Назначение: модель задачи.
  • Основные поля:
    • id, title, description, status, createdAt.
  • Дополнительные элементы:
    • TaskStatus — enum статусов (NEW, IN_PROGRESS, DONE).
    • конструкторы: пустой и Task(String title, String description).
    • геттеры/сеттеры всех полей.
    • toString() — строковое представление объекта.

model/TimeRecord.java

  • Назначение: модель записи рабочего времени.
  • Основные поля:
    • id, employeeId, taskId, startTime, endTime, description.
  • Дополнительные элементы:
    • конструкторы: пустой и TimeRecord(Long employeeId, Long taskId, LocalDateTime startTime, String description).
    • геттеры/сеттеры всех полей.
    • toString() — строковое представление объекта.

service/TaskService.java

  • Назначение: логика задач.
  • Основная переменная:
    • taskMapper — доступ к данным задач.
  • Основные методы:
    • createTask(Task task) — принудительно ставит статус NEW, сохраняет задачу, затем читает ее из БД.
    • findById(Long id) — возвращает задачу по ID.
    • findAll() — возвращает все задачи.
    • updateStatus(Long id, Task.TaskStatus status) — обновляет статус задачи.

service/TimeRecordService.java

  • Назначение: логика учета рабочего времени.
  • Основная переменная:
    • timeRecordMapper — доступ к данным записей времени.
  • Основные методы:
    • startWork(TimeRecord record) — при пустом startTime ставит текущее время, сохраняет запись и читает ее из БД.
    • endWork(Long id, LocalDateTime endTime, String description) — при пустом endTime ставит текущее время, завершает запись.
    • findById(Long id) — возвращает запись по ID.
    • findByEmployeeAndPeriod(Long employeeId, LocalDateTime from, LocalDateTime to) — возвращает записи сотрудника за период.
    • findByTaskId(Long taskId) — возвращает записи по задаче.

Тестовый код (src/test/java)

TimeTrackerApplicationTests.java

  • Назначение: smoke-тест поднятия Spring-контекста.
  • Основной метод:
    • contextLoads() — проверяет, что контекст приложения стартует без ошибок.
  • Подпись в документации:
    • contextLoads() → "Контекст Spring Boot успешно поднимается".

controller/TaskControllerTest.java

  • Назначение: веб-тесты контроллера задач (MockMvc, @WebMvcTest).
  • Основные переменные:
    • mockMvc — выполнение HTTP-запросов в тесте.
    • taskService — мок сервиса.
    • objectMapper — сериализация JSON.
  • Основные тест-методы:
    • createTask_withValidRequest_EXPECT_statusCreated() → "POST /api/tasks возвращает 201 для валидного запроса".
    • createTask_withEmptyTitle_EXPECT_statusBadRequest() → "POST /api/tasks возвращает 400 при пустом title".
    • getTask_whenTaskExists_EXPECT_statusOk() → "GET /api/tasks/{id} возвращает 200 если задача найдена".
    • getTask_whenTaskNotExists_EXPECT_statusNotFound() → "GET /api/tasks/{id} возвращает 404 если задача не найдена".

controller/TimeRecordControllerTest.java

  • Назначение: веб-тесты контроллера учета времени (MockMvc, @WebMvcTest).
  • Основные переменные:
    • mockMvc, timeRecordService (mock), objectMapper.
  • Основные тест-методы:
    • startWork_withValidRequest_EXPECT_statusCreated() → "POST /api/time-records возвращает 201 для валидного запроса".
    • startWork_withoutEmployeeId_EXPECT_statusBadRequest() → "POST /api/time-records возвращает 400 без employeeId".
    • startWork_withoutTaskId_EXPECT_statusBadRequest() → "POST /api/time-records возвращает 400 без taskId".
    • startWork_withNegativeEmployeeId_EXPECT_statusBadRequest() → "POST /api/time-records возвращает 400 при отрицательном employeeId".
    • endWork_withValidRequest_EXPECT_statusOk() → "PUT /api/time-records/{id}/end возвращает 200 для валидного запроса".
    • endWork_withoutDescription_EXPECT_statusOk() → "PUT /api/time-records/{id}/end возвращает 200 без description".
    • getTimeRecord_whenRecordExists_EXPECT_statusOk() → "GET /api/time-records/{id} возвращает 200 если запись найдена".
    • getTimeRecord_whenRecordNotExists_EXPECT_statusNotFound() → "GET /api/time-records/{id} возвращает 404 если запись не найдена".
    • getEmployeeTimeRecords_EXPECT_statusOk() → "GET /api/time-records/employee/{employeeId} возвращает 200".
    • getTaskTimeRecords_EXPECT_statusOk() → "GET /api/time-records/task/{taskId} возвращает 200".

service/TaskServiceTest.java

  • Назначение: unit-тесты сервиса задач (MockitoExtension).
  • Основные переменные:
    • taskMapper — mock.
    • taskService — тестируемый сервис.
  • Основные тест-методы:
    • createTask_EXPECT_statusSetToNew() → "createTask устанавливает статус NEW".
    • createTask_EXPECT_insertCalledOnce() → "createTask вызывает insert один раз".
    • findById_whenTaskExists_EXPECT_returnTask() → "findById возвращает задачу если она существует".
    • findById_whenTaskNotExists_EXPECT_returnEmpty() → "findById возвращает пустой результат если задача не найдена".
    • updateStatus_EXPECT_mapperCalledWithCorrectParams() → "updateStatus вызывает mapper с корректными параметрами".
    • findAll_EXPECT_mapperFindAllCalledOnce() → "findAll вызывает mapper.findAll один раз".

service/TimeRecordServiceTest.java

  • Назначение: unit-тесты сервиса учета времени (MockitoExtension).
  • Основные переменные:
    • timeRecordMapper — mock.
    • timeRecordService — тестируемый сервис.
  • Основные тест-методы:
    • startWork_whenStartTimeIsNull_EXPECT_startTimeSetToNow() → "startWork устанавливает текущее время, если startTime не передан".
    • startWork_whenStartTimeProvided_EXPECT_useProvidedTime() → "startWork использует переданное startTime".
    • startWork_EXPECT_insertCalledOnce() → "startWork вызывает insert один раз".
    • endWork_whenEndTimeIsNull_EXPECT_endTimeSetToNow() → "endWork устанавливает текущее время, если endTime не передан".
    • endWork_whenEndTimeProvided_EXPECT_useProvidedTime() → "endWork использует переданное endTime".
    • endWork_EXPECT_mapperCalledWithCorrectDescription() → "endWork передает корректное описание в mapper".
    • findById_whenRecordExists_EXPECT_returnRecord() → "findById возвращает запись если она существует".
    • findById_whenRecordNotExists_EXPECT_returnEmpty() → "findById возвращает пустой результат если запись не найдена".
    • findByEmployeeAndPeriod_EXPECT_mapperCalledWithCorrectParams() → "findByEmployeeAndPeriod вызывает mapper с корректными параметрами".
    • findByTaskId_EXPECT_mapperCalledWithCorrectTaskId() → "findByTaskId вызывает mapper с корректным taskId".

Создание проекта

  • При создании проекта использовался чат-бот ИИ для следующих задач:
    • Генерация нескольких тестов для приложения.
    • Исправление ошибок с несовместимостью версий Swagger.
    • Создание большей части документации для проекта.

Примечания

  • Приложение инициализирует схему через schema.sql при старте.
  • Подключение к БД в Docker настроено на хост db (имя сервиса из docker-compose.yml).
  • Если порт 8080 или 5432 занят, измените проброс портов в docker-compose.yml.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors