Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Комментарии — API (техническое задание)

Краткое описание: набор REST-эндпоинтов для управления комментариями. Три уровня доступа:
- Admin — администраторские операции (удаление комментария по его идентификатору).
- Private — операции пользователя над собственными комментариями (создать, изменить, удалить, получить все свои комментарии).
- Public — публичный доступ (просмотр комментариев конкретного события).

---

## AdminCommentController
Цель: операции управления всеми комментариями.

- DELETE `/admin/comments/{commentId}`
- Описание: удалить комментарий (жёсткое удаление) по его идентификатору.
- Ограничения: без модерации комментариев.
- Права: только админ.
- Ответ: `204 No Content` при успехе.

---

## PrivateCommentController
Цель: пользовательские операции над собственными комментариями.

- POST `/users/{userId}/comments/{eventId}`
- Описание: создать новый комментарий от имени пользователя `userId`.
- Ограничения: длина комментария 5000 символов, проверка существования идентификаторов события и пользователя.
- Права: только аутентифицированный пользователь.
- Ответ: `201 Created` с созданным объектом.

- PATCH `/users/{userId}/comments/{eventId}/{commentId}`
- Описание: обновить собственный комментарий.
- Ограничения: длина комментария 5000 символов, проверка существования идентификаторов комментария, события и пользователя.
- Права: только автор комментария.
- Ответ: обновлённый объект комментария.

- DELETE `/users/{userId}/comments/{commentId}`
- Описание: удалить собственный комментарий (жёсткое удаление).
- Ограничения: проверка существования идентификаторов комментария и пользователя.
- Права: только автор комментария.
Comment thread
Naz1anmak marked this conversation as resolved.
- Ответ: `204 No Content`.

- GET `/users/{userId}/comments`
- Описание: получить список собственных комментариев.
- Ограничения: организовать пагинацию (по умолчанию по 10 комментариев на странице).
- Права: только аутентифицированный пользователь.
- Ответ: список комментариев.

---

## PublicCommentController
Цель: публичный просмотр комментариев.

- GET `/comments/event/{eventId}`
- Описание: получить публичный список комментариев к событию.
- Ограничения: организовать пагинацию (по умолчанию по 10 комментариев на странице).
- Доступ: публичный (без авторизации).
- Ответ: список публичных комментариев.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.category.dto.CategoryDto;
import ru.practicum.category.dto.NewCategoryDto;
import ru.practicum.category.service.CategoryService;

@Slf4j
@Validated
@RestController
@RequestMapping("/admin/categories")
@RequiredArgsConstructor
Expand All @@ -20,21 +22,21 @@ public class AdminCategoryController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CategoryDto createCategory(@RequestBody @Valid NewCategoryDto newCategoryDto) {
log.debug("Controller: createCategory data {}", newCategoryDto);
log.debug("Controller: createCategory data={}", newCategoryDto);
return categoryService.createCategory(newCategoryDto);
}

@DeleteMapping("/{categoryId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCategory(@PathVariable @Positive Long categoryId) {
log.debug("Controller: deleteCategory id={}", categoryId);
log.debug("Controller: deleteCategory categoryId={}", categoryId);
categoryService.deleteCategory(categoryId);
}

@PatchMapping("/{categoryId}")
public CategoryDto updateCategory(@PathVariable @Positive Long categoryId,
@RequestBody @Valid NewCategoryDto newCategoryDto) {
log.debug("Controller: updateCategory id={}, data={}", categoryId, newCategoryDto);
log.debug("Controller: updateCategory categoryId={}, data={}", categoryId, newCategoryDto);
return categoryService.updateCategory(categoryId, newCategoryDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.category.dto.CategoryDto;
import ru.practicum.category.service.CategoryService;

import java.util.List;

@Slf4j
@Validated
@RestController
@RequestMapping("/categories")
@RequiredArgsConstructor
Expand All @@ -23,15 +25,14 @@ public class PublicCategoryController {
public List<CategoryDto> getCategories(
@RequestParam(defaultValue = "0") @PositiveOrZero Integer from,
@RequestParam(defaultValue = "10") @Positive Integer size) {
int page = from / size;
Pageable pageable = PageRequest.of(page, size);
log.debug("Controller: getCategories with from={}, size={}", from, size);
Pageable pageable = PageRequest.of(from / size, size);
log.debug("Controller: getCategories from={}, size={}", from, size);
return categoryService.getCategories(pageable);
}

@GetMapping("/{categoryId}")
public CategoryDto getCategoryById(@PathVariable @Positive Long categoryId) {
log.debug("Controller: getCategoryById with id={}", categoryId);
log.debug("Controller: getCategoryById categoryId={}", categoryId);
return categoryService.getCategoryById(categoryId);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package ru.practicum.category.repository;

import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.practicum.category.model.Category;

import java.util.List;
import java.util.Optional;

public interface CategoryRepository extends JpaRepository<Category, Long> {

Optional<Category> findByName(@Param("name") String name);

Optional<Category> findByNameAndIdNot(@Param("name") String name, @Param("id") Long id);

@Query("SELECT c FROM Category c")
List<Category> findAllList(Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -69,7 +68,7 @@ public CategoryDto updateCategory(Long categoryId, NewCategoryDto newCategoryDto

@Override
public List<CategoryDto> getCategories(Pageable pageable) {
Page<Category> categoriesPage = categoryRepository.findAll(pageable);
List<Category> categoriesPage = categoryRepository.findAllList(pageable);

if (categoriesPage.isEmpty()) {
return List.of();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ru.practicum.comment.controller;

import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.comment.service.CommentService;

@Slf4j
@Validated
@RestController
@RequestMapping("/admin/comments")
@RequiredArgsConstructor
public class AdminCommentController {
private final CommentService commentService;

@DeleteMapping("/{commentId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCommentAdmin(@PathVariable @Positive Long commentId) {
log.debug("Controller: deleteCommentAdmin commentId={}", commentId);
commentService.deleteCommentAdmin(commentId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package ru.practicum.comment.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.comment.dto.CommentDto;
import ru.practicum.comment.dto.NewCommentDto;
import ru.practicum.comment.dto.UpdateCommentDto;
import ru.practicum.comment.service.CommentService;

import java.util.List;

@Slf4j
@Validated
@RestController
@RequestMapping("/users/{userId}/comments")
@RequiredArgsConstructor
public class PrivateCommentController {
private final CommentService commentService;

@PostMapping("/{eventId}")
@ResponseStatus(HttpStatus.CREATED)
public CommentDto createComment(@PathVariable @Positive Long userId,
Comment thread
Naz1anmak marked this conversation as resolved.
@PathVariable @Positive Long eventId,
@RequestBody @Valid NewCommentDto newCommentDto
) {
log.debug("Controller: createComment userId={}, eventId={}, data={}", userId, eventId, newCommentDto);
return commentService.createComment(userId, eventId, newCommentDto);
}

@PatchMapping("/{eventId}/{commentId}")
public CommentDto updateComment(@PathVariable @Positive Long userId,
@PathVariable @Positive Long eventId,
@PathVariable @Positive Long commentId,
@RequestBody @Valid UpdateCommentDto updateCommentDto
) {
log.debug("Controller: updateComment userId={}, eventId={}, commentId={}, data={}", userId, eventId, commentId,
updateCommentDto);
return commentService.updateComment(userId, eventId, commentId, updateCommentDto);
}

@DeleteMapping("/{commentId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteComment(@PathVariable @Positive Long userId,
@PathVariable @Positive Long commentId
) {
log.debug("Controller: deleteComment userId={}, commentId={}", userId, commentId);
commentService.deleteComment(userId, commentId);
}

@GetMapping
public List<CommentDto> getCommentsForUser(@PathVariable @Positive Long userId,
@RequestParam(defaultValue = "0") @PositiveOrZero Integer from,
@RequestParam(defaultValue = "10") @Positive Integer size
) {
log.debug("Controller: getCommentsForUser userId={}, from={}, size={}", userId, from, size);
return commentService.getCommentsForUser(userId, PageRequest.of(from / size, size));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ru.practicum.comment.controller;

import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.practicum.comment.dto.CommentDto;
import ru.practicum.comment.service.CommentService;

import java.util.List;

@Slf4j
@Validated
@RestController
@RequestMapping("/comments")
@RequiredArgsConstructor
public class PublicCommentController {
private final CommentService commentService;

@GetMapping("/event/{eventId}")
public List<CommentDto> getCommentsForEvent(@PathVariable @Positive Long eventId,
@RequestParam(defaultValue = "0") @PositiveOrZero Integer from,
@RequestParam(defaultValue = "10") @Positive Integer size
) {
log.debug("Controller: getCommentsForEvent eventId={}, from={}, size={}", eventId, from, size);
return commentService.getCommentsForEvent(eventId, PageRequest.of(from / size, size));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.practicum.comment.dto;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.time.LocalDateTime;

import static ru.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN;

public record CommentDto(
Long id,

String text,

Long userId,

Long eventId,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN)
LocalDateTime createdDate
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.practicum.comment.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record NewCommentDto(
@NotBlank(message = "Комментарий не может быть пустым")
@Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов")
String text
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.practicum.comment.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record UpdateCommentDto(
@NotBlank(message = "Комментарий не может быть пустым")
@Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов")
String text
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ru.practicum.comment.mapper;

import org.mapstruct.*;
import ru.practicum.comment.dto.CommentDto;
import ru.practicum.comment.dto.NewCommentDto;
import ru.practicum.comment.dto.UpdateCommentDto;
import ru.practicum.comment.model.Comment;
import ru.practicum.event.model.Event;
import ru.practicum.user.model.User;

@Mapper(componentModel = "spring")
public interface CommentMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdDate", ignore = true)
Comment toComment(NewCommentDto newCommentDto, User user, Event event);

@Mapping(target = "userId", source = "user.id")
@Mapping(target = "eventId", source = "event.id")
CommentDto toDto(Comment comment);

@Mapping(target = "id", ignore = true)
@Mapping(target = "user", ignore = true)
@Mapping(target = "event", ignore = true)
@Mapping(target = "createdDate", ignore = true)
void updateCommentFromDto(UpdateCommentDto dto, @MappingTarget Comment comment);
}
Loading