Skip to content

Conversation

@AlexVin11
Copy link
Contributor

Реализовал логику для интервью в разделе админ

…r. In case speaker not present in update request - do nothing, if speakerId : null - update Interview and set speaker to null, if speakerId : someValue - update Interview and set speaker [iss 906].
…guments, search method now receives String except for DTO, method for update Interview now uses custom created method in InterviewMapper [iss 906].
…l field, fix naming of lastName field, move class in user package [iss 906].
…l field, fix naming of lastName field in InterviewMapper [iss 906].
…xlet. Dont understand how these files appears in my branch [iss 906].
@alexey4050
Copy link
Contributor

alexey4050 commented Dec 10, 2025

@RequestMapping("/{locale}/admin/interview")
Наставник нам предложила делать проверку чужого кода. Я попробую. {locale} решили не использовать, устанавливаться будет во фронте (язык они устанавливают). Еще admin/interview возникает вопрос где лучше размещать эндпоинт в AdminController или каждый в своем, если оставлять, так как возникает ошибка если идет дублирование пути. P.S.: я свой marketing буду переделывать (такая же ошибка)

@alexey4050
Copy link
Contributor

alexey4050 commented Dec 10, 2025

@RequestParam(required = false) String interviewSearchWord,
                                        @RequestParam(defaultValue = DEFAULT_PAGE_TO_SHOW) int page,
                                        @RequestParam(defaultValue = DEFAULT_NUMBER_OF_INTERVIEW_ON_PAGE) int size,

Мы мигрировали логику через application.yml c использованием Pageable (напрямую в параметрах метода)

@alexey4050
Copy link
Contributor

`catch (InterviewNotFoundException ex) {

        Map<String, Object> errorProps = flashPropsService.buildProps(locale, request);

        errorProps.put("status", 404);
        errorProps.put("message", ex.getMessage());
        errorProps.put("interviewId", id);
        errorProps.put("locale", locale);

        ResponseEntity<?> inertiaResponse = inertia.render("Error/InterviewNotFound", errorProps);` 

Дублирование кода в контроллере.

@alexey4050
Copy link
Contributor

alexey4050 commented Dec 10, 2025

public void updateEntityWithUpdateDto(InterviewUpdateDTO dto, Interview model) {
        if (dto.getTitle() != null && dto.getTitle().isPresent()) {
            String titleFromUpdateDto = dto.getTitle().get();
            if (titleFromUpdateDto == null) {
                throw new IllegalArgumentException("Title in InterviewUpdateDTO cannot be null");
            }

JsonNullable в DTO .get() никогда не вернет null если isPresent() == true (мне так кажется) не только в этом методе

@alexey4050
Copy link
Contributor

public InterviewDTO create(InterviewCreateDTO createDTO) {
        Interview model = interviewConverter.convertCreateDtoToEntity(createDTO);
        interviewRepository.save(model);
        InterviewDTO dto = interviewConverter.convertEntityToDto(model);

        return dto;
    }

interviewService.create(createDTO);
        redirectAttributes.addFlashAttribute("success", "Интервью успешно создано");

        return inertia.redirect("/" + locale + "/admin/interview");

У тебя сервис создает интервью и возвращает данные, но контроллер эти данные теряет и делает редирект( нужен редирект на создание интервью, а не на весь список) там надо + createdInterview.get..... И у тебя POST HTTP. статус другой 302 Found у тебя CREATE. Это моё мнение может быть не прав

@alexey4050
Copy link
Contributor

speakerIdFromDto != null
                            ? userRepository.findById(speakerIdFromDto)
                                    .orElseThrow(() -> new ResourceNotFoundException("User with id "
                                            + speakerIdFromDto + " not found."))

здесь у тебя одно исключение, а в сервисе UserNotFoundException и InterviewNotFoundException (посмотри в коде) несогласованность. Вроде как рекомендовано использовать единообразие в исключениях, так как разные исключения затрудняют обработку ошибок. В контроллере ловится у тебя InterviewNotFoundException

catch (InterviewNotFoundException ex) {

            Map<String, Object> errorProps = flashPropsService.buildProps(locale, request);

@AlexVin11
Copy link
Contributor Author

@RequestParam(required = false) String interviewSearchWord,
                                        @RequestParam(defaultValue = DEFAULT_PAGE_TO_SHOW) int page,
                                        @RequestParam(defaultValue = DEFAULT_NUMBER_OF_INTERVIEW_ON_PAGE) int size,

Мы мигрировали логику через application.yml c использованием Pageable (напрямую в параметрах метода)

Не понял о чем тут речь. Зашел глянуть на application.yml ы в main ветке, но ничего связанного с Pageable не вижу


@GetMapping
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<?> index(@PathVariable("locale") String locale,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

локаль в запросы не передаем, интерфейс будет локализовываться на фронте


Map<String, Object> props = flashPropsService.buildProps(locale, request);

int safeSize = Math.min(size, MAX_INTERVIEWS_ON_PAGE);
Copy link

@ann-p-1320 ann-p-1320 Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

логику принято держать в сервисах, перенеси, пжл, вот этот кусок:


        Pageable pageable = PageRequest.of(page, safeSize, Sort.by("createdAt").descending());

        Page<InterviewDTO> interviewPage = StringUtils.hasText(interviewSearchWord)
                ? interviewService.search(interviewSearchWord, pageable)
                : interviewService.getAll(pageable);

private final UserService userService;
private final InterviewService interviewService;

private static final int MAX_INTERVIEWS_ON_PAGE = 100;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вероятно, эти константы больше не нужны, так как в application.yml добавили


  data:
    web:
      pageable:
        default-page-size: 10
        max-page-size: 100

@ResponseStatus(HttpStatus.OK)
public ResponseEntity<?> index(@PathVariable("locale") String locale,
@RequestParam(required = false) String interviewSearchWord,
@RequestParam(defaultValue = DEFAULT_PAGE_TO_SHOW) int page,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно сделать передачу параметров как в LearningProgressController:

public Object getProgress(@PageableDefault(size = 10) Pageable pageable) 

Заодно потести, пжл, можно ли обойтись вообще без @PageableDefault(size = 10), весь в ямле вроде указали дефолт default-page-size: 10


return inertia.render("Admin/Interviews/Show", props);

} catch (InterviewNotFoundException ex) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Обработку ошибок лучше вынести в GlobalExceptionHandler

RedirectAttributes redirectAttributes) {
try {
interviewService.update(updateDTO, id);
redirectAttributes.addFlashAttribute("success", "Интервью успешно обновлено");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Локализованные константы убираем с бэкенда, лучше типа такого:
redirectAttributes.addFlashAttribute("success", "update.success");

return dto;
}

/*public Interview convertDtoToEntity(InterviewDTO dto) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

если код не нужен, лучше удалить

@ann-p-1320
Copy link

Пожалуйста, опиши более подробно, что именно и как реализовано, по аналогии с тем, как сделано здесь:
#935

@Getter
@Setter
public class InterviewUpdateDTO {
private JsonNullable<String> title;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

для полей входящих дто можно использовать валидацию с помощью аннотаций, например @notblank, тогда не придется валидировать в конвертере

}
}

public User speakerIdToSpeaker(Long speakerId) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

метод получения сущности по id должен быть в сервисе, а не в конвертере

public UserDTO speakerToSummary(User speaker) {
UserDTO result = new UserDTO();

if (speaker == null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

первым делом проверить на null и вернуть то, что нужно, и дальше уже писать не в ветке else, а просто

if (speaker == null) {
return null;
} else {
result.setEmail(speaker.getEmail());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше через билдер

@NotBlank(message = "Title is required.")
private String title;

private Long speakerId;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это поле будет приходить с фронта при создании сущности? По идее не должно, и тогда его лучше из дто убрать

public abstract class ReferenceMapper {

@Autowired
private EntityManager entityManager;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мапперы только перекладывают готовые поля из одних дто в другие, в них не нужно ходить в базу

@@ -0,0 +1,4 @@
package io.hexlet.cv.model;

public interface BaseEntity {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Расскажи, пожалуйста, как ты видишь дальнейшее использование этого базового класса?

return dto;
}

public InterviewDTO update(InterviewUpdateDTO updateDTO, Long id) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше спроектировать апи так, чтобы для изменения приходило одно поле или ограниченный логически связанный набор полей. Можно поговорить с фронтами и узнать, как выглядит окно редактирования, предложить вариант, когда разные поля редактируются и сохраняются отдельно.


Join<Interview, User> speakerJoin = root.join("speaker", JoinType.LEFT);

conditions.add(builder.like(builder.lower(root.get("title")), "%" + processedKeyword + "%"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вместо конкатенации можно использовать builder.like


@AfterEach
void cleanUp() {
interviewRepository.deleteAll();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Получится ли сделать так, чтобы у каждого теста были свои тестовые данные, не зависящие от другого теста, чтобы не приходилось после каждого запуска теста чистить базу?

assertThat(result.getIsPublished()).isFalse();
}

/*@Test

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если код не нужен, лучше удалить

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants