• Bootstrap: как растянуть блоки с кнопками по краям modal окна?

    steff
    @steff Автор вопроса
    Pavel-ww, да, разобрался. Уменьшил ширину селекта — увидел, всё содержимое блока выравнивается по правому краю. Спасибо.
  • Почему includes у массива сначала возвращает true, потом false?

    steff
    @steff Автор вопроса
    alexalexes, Да, дело было в типе. Первый раз функция вызывалась с конкретным номером страницы, в которой было число. При последующих вызовах значение бралось из localStorage, путем преобразования в массив строковых элементов. Там-то и было несоответствие.
  • Bootstrap: как растянуть блоки с кнопками по краям modal окна?

    steff
    @steff Автор вопроса
    И причем здесь float-end float-start?

    Не заметил, что вмето flex-start/flex-end вставилось float-start/float-end, так как flex-start и flex-end среди доступных классов я не вижу.
    Код поправил
    <div class="modal fade" id="bookReadModal" data-bs-backdrop="static" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-fullscreen">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="currentBookTitle"></h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                        </div>
                        <div class="modal-body display-3 modal-dialog-scrollable">
                            <p id="pageContent"></p>
                        </div>
    
                        <div class="modal-footer justify-content-start">
                            <div class="container">
                                <div class="row d-flex flex-row">
                                    <div id="navDiv" class="col-6 d-flex flex-row align-items-start justify-content-start">
                                        <button type="button" id="previousPageButton" name="previousPageButton" class="btn btn-outline-primary mx-1" onclick="loadPreviousPage()">Предыдущая</button>
                                        <button type="button" id="nextPageButton" name="nextPageButton" class="btn btn-outline-primary mx-1" onclick="loadNextPage()">Следующая</button>
                                        <p id="pageOfPages" class="mx-1"></p>
                                    </div>
                                    <div id="bookmarkDiv" class="col-6 d-flex flex-row align-items-end justify-content-end">
                                        <button type="button" id="bookmarkButton" name="bookmarkButton" class="btn btn-outline-primary mx-1" data-action="add" onclick="addRemoveBookmark()"><i id="bookmarkIcon" class="bi bi-bookmark"></i></button>
                                        <select class="form-select mx-1" id="bookmarkList" name="bookmarkList">
                                            <option value="1">Страница 1</option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

    но почему-то оба блока по-прежнему располагаются в левой половине нижней части окна (правый блок не притягивается с правому краю):
    61716b2f50c1b343638327.png
  • Bootstrap: как растянуть блоки с кнопками по краям modal окна?

    steff
    @steff Автор вопроса
    CSS мне не нужен, нужно сделать бутстраповскими классами.
    Добавил — не помогло.
    <div class="modal fade" id="bookReadModal" data-bs-backdrop="static" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-fullscreen">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="currentBookTitle"></h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                        </div>
                        <div class="modal-body display-3 modal-dialog-scrollable">
                            <p id="pageContent"></p>
                        </div>
    
                        <div class="modal-footer justify-content-start">
                            <div class="container">
                                <div class="row no-gutters">
                                    <div id="navDiv" class="col-6 border-1 align-items-start">
                                        <button type="button" id="previousPageButton" name="previousPageButton" class="btn btn-outline-primary" onclick="loadPreviousPage()">Предыдущая</button>
                                        <button type="button" id="nextPageButton" name="nextPageButton" class="btn btn-outline-primary" onclick="loadNextPage()">Следующая</button>
                                        <p id="pageOfPages"></p>
                                    </div>
                                    <div id="bookmarkDiv" class="col-6 border-1 float-end float-start align-items-end">
                                        <button type="button" id="bookmarkButton" name="bookmarkButton" class="btn btn-outline-primary" data-action="add" onclick="addRemoveBookmark()"><i id="bookmarkIcon" class="bi bi-bookmark"></i></button>
                                        <select class="form-select" id="bookmarkList" name="bookmarkList">
                                            <option value="1">Страница 1</option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
  • Почему includes у массива сначала возвращает true, потом false?

    steff
    @steff Автор вопроса
    alexalexes, проверю этот момент, были мысли насчёт несоответствия типов. Меня смутило, что первый раз было "true", а со второго начинается "false".
  • Как средствами JavaScript отправить форму, содержащую текстовые поля и файл, в формате JSON?

    steff
    @steff Автор вопроса
    Мне ещё вот что помогло. Возможно, кому-то окажется полезным такая возможность Poster: в нём есть раздел Code snippet, где можно увидеть готовый запрос, преобразованный для разных языков/сред/инструментов: cURL, HTTP, JavaScript - Fetch, JavaScript - jQuery, JavaScript XHR, C, NodeJS и т.д., и т.п. - все 27 вариантов:
    Для JavaScript XHR мой запрос был преобразован вот так:
    6159faf5d284e042758089.png
    Его можно брать за основу.
  • Как средствами JavaScript отправить форму, содержащую текстовые поля и файл, в формате JSON?

    steff
    @steff Автор вопроса
    Если б у меня не было проблем, вопрос бы я не задал. Сложность - как впихнуть содержимое файла в JSON-поле
  • Как средствами JavaScript отправить форму, содержащую текстовые поля и файл, в формате JSON?

    steff
    @steff Автор вопроса
    Я добавляю заголовок Authorization, в который вставляю Bearer-токен. В контроллере он принимается, и запрос обрабатывается нормально (контроллер понимает «от кого» запрос):
    @CurrentUser UserPrincipal currentUser,
    где @CurrentUser - это:
    @Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @AuthenticationPrincipal
    public @interface CurrentUser {

    По поводу e.preventDefault(); — всё нормально обрабатывается. Форма сама себя не отправляет, всё обрабатывается JS.
    С вариантом на SO ознакомлюсь, спасибо.
  • Как правильно добавить фронт к spring boot rest api?

    steff
    @steff Автор вопроса
    Орхан Гасанлы, я пока без refresh-токена. Но если этот метод потребуется, в auth-контроллере он будет работать, т.к. тот доступен без авторизации.
  • Как правильно добавить фронт к spring boot rest api?

    steff
    @steff Автор вопроса
    Пока остановлюсь на отдаче html и статики Nginx'ом. Как я понимаю, проблем с запросами к API от фронта быть не должно, если всё нормально работало через Postman.
  • Как правильно добавить фронт к spring boot rest api?

    steff
    @steff Автор вопроса
    сергей кузьмин,
    по моему разрешающий доступ к ресурсам просто выполняется в отдельный вызов , не chained

    А как разрешающий доступ комбинируется с запрещающим?
  • Как в Spring правильно создавать и инжектить сервисы?

    steff
    @steff Автор вопроса
    Орхан Гасанлы, если позволите, у меня дополнительный вопрос: как грамотно инжектить?
    Например: в контроллер можно инжектить только сервисы, а в сервисы — репозитории и другие сервисы. Или в сервисы можно инжектить только другие сервисы? Или вообще без разницы (в том смысле, что ничего не сломается, но архитектурно это неправильно)?
  • Можно ли так спроектировать API?

    steff
    @steff Автор вопроса
    Rsa97, ещё такой вопрос возник. Будет ли нормальным выделить работу со своими объектами (книгами, полками, запросами) вот сюда:
    /api/my/shelves
    /api/my/books
    /api/my/requests
    То есть при обращении к этим ресурсам будет использоваться userId пользователя, от которого идёт запрос (его получим из токена в теле запроса).

    "Общедоступные данные" будут доступны по привычным путям:
    /api/users
    /api/users/{id}/shelves
    /api/books/{id}
    При обращении к ним будут выдаваться данные, которые не связаны пользователем, от которого идёт запрос.

    Или же лучше так не мудрить?
  • Как добавить проверку на ввод в spring boot application?

    Орхан Гасанлы, подскажите, а DTO (в данном случае UserRequest) нужен в случае получения данных от пользователя? Или перед отправкой ответа Entity (User) также нужно конвертировать в DTO? То есть нужен ещё один конвертер?
  • Как в Spring правильно создавать и инжектить сервисы?

    steff
    @steff Автор вопроса
    Но ведь не будет ошибкой такой вариант:
    1. Интерфейс:
    public interface IFileService {
        void init();
    
        String save(MultipartFile file);
    
        Resource load(String filename);
    }

    2. Сервис:
    @Service
    public class FileService implements IFileService {
    
        @Override
        public void init() {
            try {
                Files.createDirectory(uploadsFolder);
            } catch (IOException e) {
                throw new RuntimeException("Не получилось создать папку " + uploadsFolder);
            }
        }
    
        @Override
        public String save(MultipartFile file) {
    ...

    3. Контроллер:
    public class AuthController {
        private static final Logger log = LoggerFactory.getLogger(AuthController.class);
    
        @Autowired
        private FileService fileService;
    ...

    То есть когда я инжекчу конкретную реализацию (FileService), а не сам интерфейс (IFileService).

    "Как я и сказал выше, если у интерфейса несколько реализаций, то необходимо уточнить название бина, который должен заинжекчен."

    Могли бы привести пример кода? Спасибо.
  • Как в Spring правильно создавать и инжектить сервисы?

    steff
    @steff Автор вопроса
    Dmitry Roo, спасибо, добрый человек, стало понятнее.
  • Как в Spring правильно создавать и инжектить сервисы?

    steff
    @steff Автор вопроса
    3. EmailServiceImpl как раз и будет использоваться, т.к. он реализует требуемый интерфейс.

    Где в приведённых мной кусках кода написано, что используется именно EmailServiceImpl?

    В сервисе SchedulerService, как я вижу, инжектится EmailService, от которого наследуется EmailServiceImpl, который нигде не используется. Так ведь? Значит, в коде ошибка?
  • Можно ли так спроектировать API?

    steff
    @steff Автор вопроса
    Rsa97, в моем случае пользователь может манипулировать только своими книгами. По-хорошему, да, лучше, чтобы книга была на какой-то полке (чтобы не было книг "не на полке").
    Я предполагаю, что книгами можно будет делиться: пользователь, посмотрев содержимое полки другого пользователя, может попросить взять книгу "почитать". В каком-то смысле "взятые почитать" это тоже "полка"... хотя лучше вынести их в отдельную сущность.
  • Можно ли так спроектировать API?

    steff
    @steff Автор вопроса
    Да, у всех объектов (пользователи, полки, книги, страницы) уникальные id + у страниц будут свои id внутри книги, чтобы можно было обращаться к конкретной странице. Например, "3", а не "27584", "4", а не "27615" (грубо).
    Изменить запись в таблице связи shelves_books. Запрос PATCH /api/books/{id} с телом { "shelf": shelfId }.

    А PUT, выходит, для другого? Принципиально ли его использование (а не PATCH)?

    Как вариант, можно ведь книгу удалять с конкретной полки:
    DELETE api/shelves/{id}/books/{id},
    после чего:
    POST api/shelves/{id}/books/{id}.
    Это нормально?