Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
684a8f1
chore(deps): add Gson 2.11.0 jar to library/ for JSON in HTTP API
Aug 18, 2025
8191c72
feat(http): HttpTaskServer + REST эндпоинты /tasks,/subtasks,/epics,/…
Aug 18, 2025
8c7b27b
fix(manager): убрать дубли в prioritized при апдейтах/удалениях; корр…
Aug 18, 2025
afa6bb0
feat(manager): фабрика getDefault()/getInMemoryTaskManager()/getFileB…
Aug 18, 2025
c96d390
feat(model): Subtask — конструкторы c Duration/LocalDateTime; Epic — …
Aug 18, 2025
16d28fb
test: BaseHttpTest — автоподнятие локального сервера на порту 0 + под…
Aug 18, 2025
8fcc5b9
test(http): покрытие /tasks,/subtasks,/epics,/history,/prioritized; п…
Aug 18, 2025
44f6f5c
chore(style): add .editorconfig
Aug 18, 2025
4b66339
add: NotFoundException
Aug 18, 2025
5d76c96
add: HttpUtil
Aug 18, 2025
5fc74e6
add: InMemoryTaskManagerTest
Aug 18, 2025
b5170dc
rename: BaseHttpTest
Aug 18, 2025
2518c65
test: add HttpTaskServerEpicsTest
Aug 18, 2025
f533336
test: add HttpTaskServerHistoryPrioritizedTest
Aug 18, 2025
ff1c46d
test: add HttpTaskServerSubtasksTest
Aug 18, 2025
f1dd37a
test: add HttpTaskServerTasksTest
Aug 18, 2025
7347ac2
test: add PrioritizedUniquenessTest
Aug 18, 2025
fd58de5
test: add TestHttpUtils
Aug 18, 2025
fcd0a42
test: add EpicStatusTest
Aug 18, 2025
47ea2f7
test: add FileBackedLegacyFormatTest
Aug 18, 2025
4b880cb
test: add FileBackedRoundTripTest
Aug 18, 2025
93e0507
test: add FileBackedTaskManagerTest
Aug 18, 2025
5582cef
test: add InMemoryHistoryManagerTest
Aug 18, 2025
22203a2
test: add PrioritizedViewTest
Aug 18, 2025
9f29198
test: add TaskManagerTest
Aug 18, 2025
6e8245e
test: rm HttpTaskServerEpicsTest(old)
Aug 18, 2025
854a7d1
test: rm HttpTaskServerHistoryPrioritizedTest(old)
Aug 18, 2025
6762b16
test: rm HttpTaskServerSubtasksTest(old)
Aug 18, 2025
82da53a
test: rm HttpTaskServerTasksTest(old)
Aug 18, 2025
0915d16
test: rm PrioritizedUniquenessTest(old)
Aug 18, 2025
6876e44
test: rm EpicStatusTest(old)
Aug 18, 2025
58b6191
test: rm FileBackedLegacyFormatTest(old)
Aug 18, 2025
6934811
test: rm FileBackedRoundTripTest(old)
Aug 18, 2025
1f1ffea
test: rm FileBackedTaskManagerTest(old)
Aug 18, 2025
16c91be
test: rm InMemoryHistoryManagerTest(old)
Aug 18, 2025
65ad3fc
test: rm InMemoryTaskManagerTest(old)
Aug 18, 2025
3b2e83f
test: rm PrioritizedViewTest(old)
Aug 18, 2025
9093612
test: rm TaskManagerTest(old)
Aug 18, 2025
83bb0e5
mod: Main
Aug 18, 2025
1340fa8
mod: TaskValidationException
Aug 18, 2025
27c577a
mod: EpicsHandler
Aug 18, 2025
5c27ee2
mod: SubtasksHandler
Aug 18, 2025
67c86e3
mod: TasksHandler
Aug 18, 2025
562db7b
mod: FileBackedTaskManager
Aug 18, 2025
1a48913
mod: HistoryManager
Aug 18, 2025
f923aa8
mod: InMemoryHistoryManager
Aug 18, 2025
9935346
mod: InMemoryTaskManager
Aug 18, 2025
a06808c
mod: ManagerSaveException
Aug 18, 2025
7663e14
mod: TaskManager
Aug 18, 2025
d9c1a78
mod: Status
Aug 18, 2025
ea7c9ec
mod: TaskType
Aug 18, 2025
bf603eb
mod: CsvUtils
Aug 18, 2025
016b52b
tests: move to src/test/java
Aug 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*.java]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
max_line_length = 100
insert_final_newline = true
Binary file added library/gson-2.11.0.jar
Binary file not shown.
109 changes: 50 additions & 59 deletions src/Main.java
Original file line number Diff line number Diff line change
@@ -1,68 +1,59 @@
import manager.Managers;
import manager.TaskManager;
import model.*;
import model.Epic;
import model.Status;
import model.Subtask;
import model.Task;

// Демонстрация базовой работы с менеджером задач
public class Main {
public static void main(String[] args) {
TaskManager manager = Managers.getDefault();

// === Добавление задач ===
int id1 = manager.addNewTask(new Task("Задача 1", "Описание задачи", Status.NEW));
int epicId = manager.addNewEpic(new Epic("Эпик 1", "Описание эпика"));
int subId = manager.addNewSubtask(new Subtask("Подзадача 1", "Описание подзадачи", epicId));
manager.addNewSubtask(new Subtask("Подзадача 2", "Ещё одна", epicId));

// === Используем возвращаемые списки (убираем жёлтые лампы) TODO: это просто для себя подчеркиваю ===
int tasksCount = manager.getTasks().size();
int epicsCount = manager.getEpics().size();
int subtasksCount = manager.getSubtasks().size();
int epicSubCount = manager.getEpicSubtasks(epicId).size();
System.out.printf(
"Всего: tasks=%d, epics=%d, subtasks=%d; у эпика %d подзадач=%d%n",
tasksCount, epicsCount, subtasksCount, epicId, epicSubCount
);

// === Получение задач (для истории просмотров) — без пустых if ===
boolean viewedTask1 = manager.getTask(id1) != null;
boolean viewedEpic = manager.getEpic(epicId) != null;
boolean viewedSub = manager.getSubtask(subId) != null;
boolean viewedTask2 = manager.getTask(id1) != null;

// просто используем значения, чтобы инспекция была довольна
System.out.printf(
"Просмотры: t1=%b, epic=%b, sub=%b, t1-again=%b%n",
viewedTask1, viewedEpic, viewedSub, viewedTask2
);

// === Вывод истории просмотров ===
System.out.println("=== История просмотров ===");
for (Task task : manager.getHistory()) {
System.out.printf(
"%s (ID: %d)\nЗаголовок: %s\nСтатус: %s\n---\n",
getTypeName(task),
task.getId(),
task.getTitle(),
getStatusName(task.getStatus())
);
}
public static void main(String[] args) {
TaskManager manager = Managers.getDefault();

// Добавление задач
int id1 = manager.addNewTask(new Task("Задача 1", "Описание задачи", Status.NEW));
int epicId = manager.addNewEpic(new Epic("Эпик 1", "Описание эпика"));
int subId = manager.addNewSubtask(new Subtask("Подзадача 1", "Описание подзадачи", epicId));
manager.addNewSubtask(new Subtask("Подзадача 2", "Ещё одна", epicId));

// Краткая сводка
int tasksCount = manager.getTasks().size();
int epicsCount = manager.getEpics().size();
int subtasksCount = manager.getSubtasks().size();
int epicSubCount = manager.getEpicSubtasks(epicId).size();
System.out.printf(
"Всего: tasks=%d, epics=%d, subtasks=%d; у эпика %d подзадач=%d%n",
tasksCount, epicsCount, subtasksCount, epicId, epicSubCount);

// Получения для истории просмотров
manager.getTask(id1);
manager.getEpic(epicId);
manager.getSubtask(subId);
manager.getTask(id1); // повторно

// Вывод истории просмотров
System.out.println("=== История просмотров ===");
for (Task task : manager.getHistory()) {
System.out.printf(
"%s (ID: %d)\nЗаголовок: %s\nСтатус: %s\n---\n",
getTypeName(task), task.getId(), task.getTitle(), getStatusName(task.getStatus()));
}
}

private static String getTypeName(Task task) {
if (task instanceof Epic) {
return "Эпик";
}
if (task instanceof Subtask) {
return "Подзадача";
}
return "Задача";
private static String getTypeName(Task task) {
if (task instanceof Epic) {
return "Эпик";
}

private static String getStatusName(Status status) {
return switch (status) {
case NEW -> "Новая";
case IN_PROGRESS -> "В процессе";
case DONE -> "Выполнена";
};
if (task instanceof Subtask) {
return "Подзадача";
}
return "Задача";
}

private static String getStatusName(Status status) {
return switch (status) {
case NEW -> "Новая";
case IN_PROGRESS -> "В процессе";
case DONE -> "Выполнена";
};
}
}
8 changes: 8 additions & 0 deletions src/exceptions/NotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package exceptions;

/** sprint-9: сигнализирует об отсутствии сущности в менеджере. */
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
8 changes: 4 additions & 4 deletions src/exceptions/TaskValidationException.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package exceptions;

/**
* NEW (sprint-8): бросается при пересечении задач по времени.
*/
/** NEW (sprint-8): бросается при пересечении задач по времени. */
public class TaskValidationException extends RuntimeException {
public TaskValidationException(String message) { super(message); }
public TaskValidationException(String message) {
super(message);
}
}
49 changes: 49 additions & 0 deletions src/http/BaseHttpHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package http; // sprint 9

import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/** Общая база для HTTP-обработчиков. Содержит вспомогательные методы. Sprint 9 */
public abstract class BaseHttpHandler {

protected String readBody(HttpExchange exchange) throws IOException { // sprint 9
try (InputStream is = exchange.getRequestBody()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}

protected void sendJson(HttpExchange exchange, int status, String json)
throws IOException { // sprint 9
byte[] resp = json.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8");
exchange.sendResponseHeaders(status, resp.length);
exchange.getResponseBody().write(resp);
exchange.close();
}

protected void sendOk(HttpExchange exchange, String json) throws IOException { // 200
sendJson(exchange, 200, json);
}

protected void sendCreated(HttpExchange exchange) throws IOException { // 201
sendJson(exchange, 201, "\"created\"");
}

protected void sendNotFound(HttpExchange exchange, String message) throws IOException { // 404
sendJson(exchange, 404, "{\"error\":\"" + escape(message) + "\"}");
}

protected void sendHasOverlaps(HttpExchange exchange, String message) throws IOException { // 406
sendJson(exchange, 406, "{\"error\":\"" + escape(message) + "\"}");
}

protected void sendServerError(HttpExchange exchange, String message) throws IOException { // 500
sendJson(exchange, 500, "{\"error\":\"" + escape(message) + "\"}");
}

private String escape(String s) {
return s == null ? "" : s.replace("\"", "\\\"");
}
}
116 changes: 116 additions & 0 deletions src/http/EpicsHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package http;

import static http.HttpUtil.isNewId;
import static http.HttpUtil.parseIdOrNull;

import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import exceptions.NotFoundException;
import java.io.IOException;
import java.net.URI;
import manager.TaskManager;
import model.Epic;

/** /epics, /epics/{id}, /epics/{id}/subtasks */
public class EpicsHandler extends BaseHttpHandler implements HttpHandler {

private final TaskManager manager;
private final Gson gson;

public EpicsHandler(TaskManager manager, Gson gson) {
this.manager = manager;
this.gson = gson;
}

@Override
public void handle(HttpExchange exchange) throws IOException {
try {
String method = exchange.getRequestMethod();
URI uri = exchange.getRequestURI();
String[] parts = uri.getPath().split("/");

switch (method) {
case "GET" -> handleGet(exchange, parts);
case "POST" -> handlePost(exchange);
case "DELETE" -> handleDelete(exchange, parts);
default -> sendServerError(exchange, "Unsupported method");
}
} catch (Exception e) {
sendServerError(exchange, e.getMessage() == null ? e.toString() : e.getMessage());
}
}

private void handleGet(HttpExchange exchange, String[] parts) throws IOException {
if (parts.length == 2) { // /epics
sendOk(exchange, gson.toJson(manager.getEpics()));
return;
}
if (parts.length == 3) { // /epics/{id}
Integer id = parseIdOrNull(parts[2]);
if (id == null) {
sendNotFound(exchange, "incorrect id");
return;
}
try {
sendOk(exchange, gson.toJson(manager.getEpic(id)));
} catch (NotFoundException nf) {
sendNotFound(exchange, nf.getMessage());
}
return;
}
if (parts.length == 4 && "subtasks".equals(parts[3])) { // /epics/{id}/subtasks
Integer epicId = parseIdOrNull(parts[2]);
if (epicId == null) {
sendNotFound(exchange, "incorrect id");
return;
}
try {
// вызов getEpic для явного 404, затем выдаём список
manager.getEpic(epicId);
sendOk(exchange, gson.toJson(manager.getEpicSubtasks(epicId)));
} catch (NotFoundException nf) {
sendNotFound(exchange, nf.getMessage());
}
return;
}
sendNotFound(exchange, "incorrect path");
}

private void handlePost(HttpExchange exchange) throws IOException {
String body = readBody(exchange);
Epic epic = gson.fromJson(body, Epic.class);
if (epic == null) {
sendServerError(exchange, "empty body");
return;
}
try {
if (isNewId(epic.getId())) {
manager.addNewEpic(epic);
} else {
manager.updateEpic(epic); // NotFound → 404
}
sendCreated(exchange);
} catch (NotFoundException nf) {
sendNotFound(exchange, nf.getMessage());
}
}

private void handleDelete(HttpExchange exchange, String[] parts) throws IOException {
if (parts.length != 3) {
sendNotFound(exchange, "incorrect path");
return;
}
Integer id = parseIdOrNull(parts[2]);
if (id == null) {
sendNotFound(exchange, "incorrect id");
return;
}
try {
manager.removeEpic(id); // NotFound → 404
sendOk(exchange, "\"deleted\"");
} catch (NotFoundException nf) {
sendNotFound(exchange, nf.getMessage());
}
}
}
32 changes: 32 additions & 0 deletions src/http/HistoryHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package http; // sprint 9

import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import manager.TaskManager;

/** GET /history — вернуть историю просмотров. sprint 9 */
public class HistoryHandler extends BaseHttpHandler implements HttpHandler {

private final TaskManager manager;
private final Gson gson;

public HistoryHandler(TaskManager manager, Gson gson) {
this.manager = manager;
this.gson = gson;
}

@Override
public void handle(HttpExchange exchange) throws IOException {
if (!"GET".equals(exchange.getRequestMethod())) {
sendServerError(exchange, "Unsupported method");
return;
}
try {
sendOk(exchange, gson.toJson(manager.getHistory()));
} catch (Exception e) {
sendServerError(exchange, e.getMessage() == null ? e.toString() : e.getMessage());
}
}
}
Loading