{"id":25019079,"url":"https://github.com/borisaushev/prod","last_synced_at":"2025-12-30T21:34:38.638Z","repository":{"id":228365334,"uuid":"773781995","full_name":"borisaushev/PROD","owner":"borisaushev","description":"Решение задания 2 этапа олимпиады PROD на Spring ","archived":false,"fork":false,"pushed_at":"2024-08-17T19:03:19.000Z","size":177,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T09:41:18.658Z","etag":null,"topics":["docker","hibernate","java","postgresql","spring","spring-boot","springdata-jpa"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/borisaushev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-18T11:45:34.000Z","updated_at":"2024-08-17T19:03:23.000Z","dependencies_parsed_at":"2024-06-29T21:25:03.701Z","dependency_job_id":"3615cef2-71a7-426d-bec1-a2578ad771dc","html_url":"https://github.com/borisaushev/PROD","commit_stats":null,"previous_names":["borisaushev/prod-stage-2"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/borisaushev/PROD","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borisaushev%2FPROD","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borisaushev%2FPROD/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borisaushev%2FPROD/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borisaushev%2FPROD/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/borisaushev","download_url":"https://codeload.github.com/borisaushev/PROD/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borisaushev%2FPROD/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272988919,"owners_count":25026961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","hibernate","java","postgresql","spring","spring-boot","springdata-jpa"],"created_at":"2025-02-05T11:38:47.376Z","updated_at":"2025-12-30T21:34:38.595Z","avatar_url":"https://github.com/borisaushev.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Технологии: \n**\u003c/br\u003eSpring Boot, Spring MVC, Hibernate\u003c/br\u003eSpring Data Jpa, JWT, Swagger,\u003c/br\u003e Postman, PostgreSQL, Spring JDBC,\u003c/br\u003e Maven, OpenApi 3, HttpAPI,\u003c/br\u003e JSON API**\n\n# PROD v2: Pulse Backend\n\nВаши коллеги разрабатывают социальную сеть для инвесторов. Уже совсем скоро им нужно сдавать проект, а вся команда бэкенд-разработчиков ушла в отпуск. \n\nКто-то проболтался о том, что вы знакомы с Git, HTTP, Docker, PostgreSQL и e2e-тестами. Это именно то, что нужно ребятам (а если с чем-то не знакомы, они рассчитывают на ваши навыки поиска информации)! Помогите коллегам успеть завершить проект до дедлайна и реализуйте новое HTTP API :)\n\nРезультатом выполнения данного задания является Github репозиторий с исходным кодом приложения (директория `solution`).\n\n## Про приложение\n\nПриложение должно представлять из себя HTTP сервер, реализующий необходимое [API](./tests/openapi.yml). В наследие от предыдущей команды вам достался инстанс PostgreSQL, который необходимо использовать для хранения данных.\n\nПриложение конфигурируется через переменные окружения:\n\n- `SERVER_ADDRESS` \u0026mdash; хост и порт, которые будет _слушать_ запущенный HTTP сервер. Например, `0.0.0.0:8080`.\n\n- `SERVER_PORT` \u0026mdash; содержит порт; запущенный сервер должен слушать IP `0.0.0.0` и указанный порт. Используйте эту переменную, если вам не подошел формат данных в переменной `SERVER_ADDRESS` (переданные параметры равнозначны).\n\n- `POSTGRES_CONN` \u0026mdash; URL-строка для подключения к PostgreSQL в формате `postgres://{username}:{password}@{host}:{5432}/{dbname}`.\n\n- `POSTGRES_JDBC_URL` \u0026mdash; JDBC-строка для подключения к PostgreSQL в формате `jdbc:postgresql://{host}:{port}/{dbname}`.\n\n- `POSTGRES_USERNAME` \u0026mdash; имя пользователя для подключения к PostgreSQL.\n\n- `POSTGRES_PASSWORD` \u0026mdash; пароль для подключения к PostgreSQL.\n\n- `POSTGRES_HOST` \u0026mdash; хост для подключения к PostgreSQL (например, `localhost`).\n\n- `POSTGRES_PORT` \u0026mdash; порт для подключения к PostgreSQL (например, `5432`).\n\n- `POSTGRES_DATABASE` \u0026mdash; имя базы данных PostgreSQL, с которой должно работать приложение.\n\n- `RANDOM_SECRET` \u0026mdash; псевдо-случайная последовательность из 128 символов (a-z, A-Z, 0-9), сгенерированная тестирующей системой. Можете использовать её, если вашему приложению необходим секретный ключ (например, для JWT). Если вам не требуется данное значение, можете его не использовать.\n\nАвтор приложения сам выбирает, с какими из переменных окружения ему комфортно работать.\n\nУчитывая современные реалии, приложение будет запускаться через Docker контейнер. В репозитории присутствует Dockerfile, с помощью которого будет собираться образ приложения. \nТак как приложение совсем небольшое, мы обойдемся одним Docker контейнером, docker-compose определить не получится.\n\n**Список используемых зависимостей (и фреймворков) не ограничен** (любая версия языка программирования, без ограничений на библиотеки), однако вы должны убедиться, что необходимые зависимости загружаются и подключаются в Dockerfile. Вы сами в праве выбирать стек вашего приложения, от вас зависит успех всего проекта!\n\nОписание API находится ниже, но если вы хотите ознакомиться с точными требованиями, не стесняйтесь использовать Swagger и предоставленную [Open API спецификацию](./tests/openapi.yml).\n\nТестирование решения происходит с помощью Github CI. Для отправки решения на тестирование необходимо обновить исходный код вашего репозитория на Github (git commit \u0026 git push). \n\n**Вы можете редактировать файлы в директории `solution` (и `.gitignore` в корне). Если в репозитории содержатся изменения в других файлах, решение не будет принято.**\n\n## Оценивание\n\nДля получения баллов за группу тестов решение должно пройти все тесты из данной группы.\n\nГруппы тестов могут зависеть друг от друга. Если группа B зависит от группы A, при тестировании группы B могут использоваться эндпоинты, участвовавшие в тестировании группы A. Это свойство транзитивно!\n\n| Название группы  | Описание                           | Баллы | От каких групп зависит |\n|------------------|------------------------------------|-------|------------------------|\n| 01/ping          | Успешный ответ на `/api/ping`.     | 1     |                        |\n| 02/countries     | Получение и фильтрация стран.      | 6     |                        |\n| 03/auth/register | Регистрация пользователей.         | 6     | - 02/countries         |\n| 04/auth/sign-in  | Аутентификация и получение токена. | 7     | - 03/auth/register     |\n| 05/me  | Получение и редактирование собственного профиля. | 8     | - 04/auth/sign-in     |\n| 06/profiles  | Получение профиля по логину. | 5     | - 04/auth/sign-in     |\n| 07/password  | Изменение пароля. | 7     | - 05/me     |\n| 08/friends  | Друзья! | 12     | - 04/auth/sign-in\u003cbr\u003e- 06/profiles    |\n| 09/posts/publish  | Публикация поста и получение по ID. | 12     | - 05/me\u003cbr\u003e- 08/friends     |\n| 10/posts/feed  | Получение новостной ленты. | 16     | - 09/posts/publish     |\n| 11/posts/likes  | Лайки и дизлайки. | 20     | - 10/posts/feed     |\n\nВ спорных ситуациях будет оцениваться качество кода.\n\nНа данный момент в Github CI тестирование производится на публичном наборе тестов. Данные тесты помогают провалидировать минимальную логику приложения, **но не гарантируют прохождения финальных тестов**.\n\n## Группы тестов\n\n### Общие требования\n\n**У всех эндпоинтов есть префикс `/api`.**\n\nОбратите внимание, возврат успешного ответа на `GET /api/ping` является **обязательным условием для начала тестирования приложения**.\n\nПоступающие запросы и возвращаемые ответы должны соответствовать структуре и требованиям, описанным в [Open API](./tests/openapi.yml) спецификации. Обращайте внимание на ожидаемые status code, ограничения по длине и разрешенные символы в строках.\n\nЕсли структура запроса не соответствует требованиям и описанному формату, по умолчанию возвращается код ответа 400. \nЕсли указан более специфичный код ответа, используйте его.\n\nЕсли запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.\n\n### 01/ping\n\nДостаточно реализовать возврат успешного ответа (с кодом `200`) на запрос `GET /api/ping`. Содержимое тела ответа при этом не валидируется, можно возвращать `\"ok\"`.\n\nДанная логика является блокирующей для всех остальных групп тестов. \n\n### 02/countries\n\nКак и в любом большом проекте у нас есть собственный словарь стран, который используется при регистрации пользователей и может учитываться рекомендательными системами и системой локализации контента.\n\nПро каждую страну известны следующие данные:\n```json\n{\n    \"name\": \"полное название\",\n    \"alpha2\": \"двухбуквенный код страны (в верхнем регистре)\",\n    \"alpha3\": \"трехбуквенный код страны\",\n    \"region\": \"географический регион\"\n}\n```\n\nНеобходимо реализовать следующие эндпоинты:\n\n- `GET /countries` \u0026mdash; получить список доступных стран, доступна фильтрация по регионам. \n\n- `GET /countries/{alpha2}` \u0026mdash; получить страну по её уникальному двухбуквенному коду.\n\nСамое интересное: **для получения списка стран необходимо использовать предоставленную СУБД PostgreSQL**.\n\nДанные находятся в таблице `countries`, которая имеет следующее определение:\n```sql\nCREATE TABLE countries (\n    id SERIAL PRIMARY KEY,\n    name TEXT,\n    alpha2 TEXT,\n    alpha3 TEXT,\n    region TEXT\n);\n\nINSERT INTO countries (name, alpha2, alpha3, region) VALUES\n    ('Åland Islands','AX','ALA','Europe'),\n    ('Albania','AL','ALB','Europe'),\n    ...;\n```\n\nПри тестировании в Github CI база данных уже будет содержать нужный набор данных. Обратите внимание, данные в публичном и закрытом наборе тестов могут отличаться. **Приложение должно опираться на данные в СУБД, чтобы успешно пройти закрытые тесты.**\n\nПриложение вправе менять содержимое СУБД. Если вам требуются дополнительные таблицы, создавайте их самостоятельно при старте приложения (не забудьте про `IF NOT EXISTS`).\n\nПри поиске страны по двухбуквенному коду можно реализовать регистрозависимый поиск, то есть пользователь всегда будет указывать значения в нужном регистре.\n\n### 03/auth/register\n\nЭндпоинт `/auth/register` используется для первичной регистрации пользователей. \n\nСервер должен поддерживать базу данных пользователей, валидировать запросы и не допускать наличия пользователей с эквивалентными регистрационными данными. \n\nНе храните пароль пользователей в [открытом виде](https://security.stackexchange.com/questions/36833/why-should-i-hash-passwords), используется хеширование (например, bcrypt).\n\n### 04/auth/sign-in\n\nЭндпоинт `/auth/sign-in` предназначен для аутентификации пользователя по логину и паролю и генерации сессионного токена, \nкоторый в дальнейшем будет использоваться для генерации запросов.\n\nГенерируемый токен должен уникально идентифицировать пользователя и быть сложным для подбора (можно использовать JWT).\n\nДанный токен в дальнейшем будет передаваться пользователем в заголовке `Authorization: Bearer {token}`, и приложение должно уметь понять, какой пользователь хочет сделать запрос.\n\nВременно будем считать, что время действия токена (TTL) должно составлять от 1 до 24 часов (на усмотрение разработчика).\n\n### 05/me\n\nЭндпоинт `/me/profile` используется для получения и редактирования параметров собственного профиля пользователя. Действие зависит от указанного метода (`GET` и `PATCH`).\n\nСервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. Например, `Authorization: Bearer $deddz$@pp...`.\n\nВ запросе на редактирование профиля передаются значения только тех полей, которые необходимо обновить.\n\n### 06/profiles\n\nЭндпоинт `/profiles/{login}` позволяет получить профиль другого пользователя по логину.\n\nОбратите внимание, в некоторых ситуациях профиль пользователя получить нельзя (в зависимости от значения параметра `isPublic`). Для получения дополнительных деталей ознакомьтесь со спецификацией API.\n\nВ данной группе тестов не будет проверяться логика с друзьями пользователя.\n\n### 07/password\n\nС помощью `/me/updatePassword` у пользователя появляется возможность изменить пароль от своего аккаунта.\n\nПосле изменения пароля:\n\n- Аутентификация со старым паролем становится невозможной.\n\n- Все ранее выпущенные токены должны быть отозваны. Использование старых токенов становится равнозначным использованию некорректных токенов.\n\nПосле успешной смены пароля при попытке получить свой профиль со старым токеном пользователь должен получать ошибку.\n\n### 08/friends\n\nВ приложении появляется возможность добавлять и удалять других пользователей из списка своих друзей.\nИ конечно же можно посмотреть список своих друзей.\n\nСвойство быть другом \u0026mdash; одностороннее. Если Петя добавит Машу в друзья, то профиль Пети становится доступным для Маши, даже если у Пети закрытый профиль.\n\nЧтобы не нагружать сервера и клиенты слишком сильно, в запросах на получение списка друзей используется пагинация.\nС помощью параметров `offset` и `limit` можно \"постранично\" получить весь список друзей, запрашивая данные порционно.\n\nВам потребуется запоминать дату и время последнего добавления в друзья для корректно сортировки и реализации пагинации.\n\n### 09/posts/publish\n\nВ данной группе проверяется возможность создавать публикации со стороны пользователей.\nЗатрагиваемые эндпоинты:\n- `/posts/new`\n- `/posts/{postId}`\n\nСервер должен генерировать уникальные идентификаторы и запоминать время создания публикаций.\n\nУ пользователя есть доступ к своим постам, постам пользователей с публичным профилем и постам других пользователей, которые добавили данного пользователя в друзья.\n\nВ данной группе не проверяются поля с лайками и дизлайками.\n\n### 10/posts/feed\n\nУ пользователей появилась возможность смотреть новостную ленту со своими и чужими постами. Используя пагинацию :)\n\nПоявляются запросы на `/posts/feed/my` и `/posts/feed/{login}` (значение `my` не может являться логином).\n\nВ данной группе не проверяются поля с лайками и дизлайками.\n\n### 11/posts/likes\n\nСамое интересное: пользователи могут поставить лайк и дизлайк публикации, к которой у них есть доступ.\n\nВсегда запоминается последняя реакция пользователя. Если пользователь поставил лайк два раза подряд, эффект лайка остается.\nЕсли пользователь поставил лайк, а потом дизлайк, остается реакция дизлайка.\n\nВ полях `likesCount` и `dislikesCount` необходимо отразить число лайков и дизлайков публикации, при этом от каждого пользователя учитывается только его самая последняя реакция.\n\n## Тестирование\n\nДля тестирования решения отразите ваши изменения в Github репозитории. Разрешено изменять только директорию `solution` и `.gitignore`, иначе тесты не будут запущены.\n\n### Тестирование в CI\n\nДля тестирования решений используется [Github CI](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration). При отправке новых изменений в репозиторий на Github активируется тестирующий пайплайн.\n\nПайплайн состоит из двух этапов:\n- Сборка Docker образа с вашим приложением (на основании исходного кода репозитория и Dockerfile).\n\n- Запуск тестов. Для каждой группы тестов\n    - запускаются Docker контейнеры с вашим приложением и PostgreSQL;\n\n    - тестирующая система применяет нужные миграции к запущенному PostgreSQL (создается и заполняется только таблица `countries`, остальное должно делать ваше приложение);\n\n    - тестирующая система дожидается успешного (`200`) ответа на `GET /api/ping`, на это дается не более 10 секунд;\n\n    - приложение считается запущенным и начинается запуск HTTP тестов из тестируемой группы.\n\nПроверьте, что ваше приложение готово запускать HTTP сервер на адресе, переданном в переменной окружения `SERVER_ADDRESS`. **В качестве хоста (IP) передается `0.0.0.0`, а не localhost или 127.0.0.1. Это важно!**\n\nТакже проверьте локально, что Docker образ с вашим приложением собирается (выполните `docker build .` в директории `solution`).\n\nСуществующие ограничения:\n\n- Решению выделяется 3 vCPU, 6 GB RAM и до 1 GB дискового пространства (не учитывая PostgreSQL).\n  \n- В рамках тестирования ваше приложение не должно завершать работу (помните о защите от Exception, panic и прочих причинах аварийного завершения).\n\n- Сетевое взаимодействие разрешено только с PostgreSQL и тестирующей системой. Обращаться к сторонним ресурсам по сети нельзя.\n\nВо вкладке Actions можно найти лог тестирования, в котором будут отражены результаты запуска тестов на публичном наборе тестов.\n\nПрохождение публичного набора тестов не дает гарантию прохождения финальных тестов.\n\n### Локальное тестирование\n\nДля локального тестирования вы можете пользоваться [Postman](https://www.postman.com/). В директории проекта кто-то из коллег оставил [Postman коллекцию](./tests/public-tests.json) с публичными тестами для API. Не забудьте переопределить `base_url` в переменных коллекции.\n\nДля инициализации СУБД PostgreSQL можно использовать [заранее подготовленный скрипт](./tests/init-database.sh), из которого можно выудить SQL запросы. Обратите внимание, данный файл предназначен для локального тестирования. Тестирующая система не использует данный файл.\n\nЧтобы локальное тестирование было максимально приближенным к тестированию в CI, мы рекомендуем запускать PostgreSQL и ваше приложение в Docker контейнерах (связанных одной сетью).\n\n## Changelog\n\nКак это часто бывает, заказчики проекта вносят правки в требования! \nВаших коллег ждала та же участь... Заказчики просили передать, что они будут стараться делать как можно меньше изменений.\n\nНо удача на нашей стороне! Коллеги будут фиксировать все правки в данном документе и вести ченджлог изменений.\n\nНе забывайте делать `git pull --rebase`, чтобы загрузить актуальные требования в локальную версию репозитория.\n\n### 02.03.2024\n\nКоллеги, привет! Ваш Project Manager передал все опасения касательно сроков, поэтому мы договорились, \nчто финальное тестирование будет проходить, опираясь на версию спецификации, опубликованную 3 марта 15:00 (МСК).\n\nНапоминаем! В тестах будет проверяться только то поведение, которое было описано в README либо спецификации.\n\nОбращаем внимание: при работе с публичным набором тестов в Postman обращайте внимание на содержимое вкладки Tests, именно там заключена логика тестирования.\nRequest-path в Postman изменены на `GET /api/ping`, чтобы нерелевантная информация в логах не смущала вас.\n\nИ еще немного полезных замечаний:\n\n- Если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.\n\n- Если вам нужен секретный ключ, можете (необязательно!) использовать `RANDOM_SECRET`.\n\n- Timezone при передаче времени не так важна. Важно, чтобы счетчик времени монотонно рос и был одного формата во всех ответах backend'а.\n\n- Чтобы отобразить число лайков и дизлайков поста, учитывайте только последнюю реакцию от каждого пользователя.\n\n- Если структура ответа предполагает опциональность поля, сервер не должен возвращать данное поле при его отсутствии.\n\n### 01.03.2024\n\nКоллеги, с первым днем весны!\n\nНапоминаем вам, что корректные логин, номер телефона, e-mail и другая подобная информация должны состоять минимум из одного символа!\nА длина уникального идентификатора публикации не превышает разумных значений...\n\nТакже добавим, что в эндпоинте `/countries` если хотя бы один переданный регион является некорректным, весь запрос считается некорректным. Это общее правило: если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.\n\n### 28.02.2024\n\nКоллеги передали, что связь \"друзья\" является односторонней.\n\nЕсли профиль пользователя закрыт, доступ к его профилю и его публикациям появляется у пользователей, кого данный пользователь добавил в друзья.\n\nПри этом если Маша добавила Петю в друзья, не значит, что Петя добавил Машу в друзья. Можно расценивать добавление в друзья как подписку.\n\nГруппа `08/friends` зависит от группы `06/profiles`.\n\n### 27.02.2024\n\nКоллеги, привет! Ничего критичного... Уговорили нашего Devops-инженера расширить список переменных с информацией для подключения к PostgreSQL. Смотрите секцию с описанием ENV переменных. Надеемся, теперь станет проще!\n\nТакже подсветим, что приложение должно опираться на данные в СУБД, сохранить словарь в коде приложения не получится, так как список стран может меняться! Наши QA-специалисты любят проверять работу приложения в выдуманных странах...\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborisaushev%2Fprod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborisaushev%2Fprod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborisaushev%2Fprod/lists"}