{"id":16653782,"url":"https://github.com/mirecl/goalmanac","last_synced_at":"2026-04-19T21:36:25.868Z","repository":{"id":117560515,"uuid":"223358809","full_name":"mirecl/goalmanac","owner":"mirecl","description":"calendar","archived":false,"fork":false,"pushed_at":"2023-02-24T23:34:51.000Z","size":1704,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-19T10:33:10.024Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","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/mirecl.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":"2019-11-22T08:33:10.000Z","updated_at":"2024-11-10T02:30:19.000Z","dependencies_parsed_at":"2023-06-06T05:15:38.861Z","dependency_job_id":null,"html_url":"https://github.com/mirecl/goalmanac","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mirecl%2Fgoalmanac","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mirecl%2Fgoalmanac/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mirecl%2Fgoalmanac/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mirecl%2Fgoalmanac/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mirecl","download_url":"https://codeload.github.com/mirecl/goalmanac/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243266837,"owners_count":20263798,"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","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":[],"created_at":"2024-10-12T09:47:37.969Z","updated_at":"2025-12-26T21:49:13.244Z","avatar_url":"https://github.com/mirecl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Микросервис - “Альманах” (Календарь)\n\n[![image](https://img.shields.io/badge/godoc-reference-blue)](http://godoc.org/github.com/mirecl/goalmanac)\n\n### Задание #1 - “Заготовка” для микросервиса “Альманах” (Календарь)\n\n**Цель:** В результате выполнения ДЗ должен получиться базовый скелет микросервиса, который будет развиваться в дальнейших ДЗ. Структура кода должна соответствовать подходу Clean Architecture. В данном задании тренируются навыки: - декомпозиции предметной области; - построения элементарной архитектуры проекта.\nЗавести в репозитории отдельную директорию для проекта \"Календарь\"\nСоздать внутри структуру директорий, соответствующую Clean Architecture.\n\nCоздать модели (структуры) календаря.\n\nCоздать методы бизнес логики (методы у структур) для работы с этими структурами:\n- добавление событий в хранилище\n- удаление событий из хранилища\n- изменение событий в хранилище\n- листинг событий\n- пр. на усмотрение студента\nСоздать объекты ошибок (error sentinels) соответсвующие бизнес ошибкам, например ErrDateBusy - данное время уже занято другим событием\n\nРеализовать хранение событий в памяти (т.е. просто складывать объекты в слайсы)\n\nРеализовать Unit тесты проверяющие работу бизнес логики (в частности ошибки)\n\nНа данном этапе не нужно:\n- Делать HTTP, GRPC и пр. интерфейсы к микросервису\n- Писать .proto-файлы (это будет позже)\n- Использовать СУБД\n\n### Задание #2 - Каркас микросервиса\n\n**Цель:** Реализовать \"каркас\" микросервиса, считывающий конфиг из файла, создающий логгер/логгеры с указанными уровнями детализации.\nНеобходимо доработать код сервиса \"Календарь\" из предыдущего задания, добавив в него:\n\n* Обработку аргументов командной строки\n* Чтение файла конфигурации (параметр --config в командной строке)\n* Создание логгеров и настройка уровня логирования\n* Создание и запуск hello-world web-сервера\n\nПараметры, передаваемые через аргументы командной строки:\n* --config - путь к конфигу\n\nПараметры, которые должны быть в конфиге:\n* http - host и port на котором должен слушать web-сервер\n* log_file - путь к файлу логов\n* log_level - уровень логирования (error / warn / info / debug)\n\n**Критерии оценки:** Web-сервер на данном этапе может быть не связан с бизнес логикой календаря и должен обрабатывать только URL /hello\nWeb-сервер должен запускаться на host:port указанном в конфиге и каждый обработанный запрос должен выводиться в log-файл.\n\n**Описание**\nФайл конфигурации состоит из 3 разделов:\n - http (настройки http-сервера: адрес сервера/порт сервера/время остановки сервера)\n - log_http (настройка logger'а для http-сервера: уровень логирования(только для stdout)/путь для сохранения файла логирования)\n - log_event (настройка logger'а для событий Календаря: уровень логирования(только для stdout)/путь для сохранения файла логирования)\n\n Пример файла конфигурации:\n ```yaml\nhttp:\n  host: 127.0.0.1\n  port: 8080\n  shutdown: 5\nlog_http:\n  level: info \n  path: http.log\nlog_event:\n  level: info\n  path: event.log\n ```\n Ротация файла логирования необходимо делать с помощью **Logrotate**.\n\n Специально созданы 2 logger'a:\n  - Для событий Календаря;\n  - Для http-запросов.\n\nСоздан один endpoint - /hello и к нему test.\n\n### Задание #3 - HTTP интерфейс\n\n**Цель:** Реализовать HTTP интерфейс для сервиса Календаря. Тех. задание: https://github.com/OtusTeam/Go/blob/master/project-calendar.md Цель данного задания - отработать навыки работы со стандартной HTTP библиотекой, поэтому технологии JSONRPC, Swagger и т.п. НЕ используются.\n\nВ директории с проектом создать отдельный пакет для Web-сервера\nРеализовать вспомогательные функции для сериализации объектов доменной области в JSON\nРеализовать вспомогательные функции для парсинга и валидации параметров методов /create_event и /update_event\nРеализовать HTTP обработчики для каждого из методов API, используя вспомогательные функции и объекты доменной области\nРеализовать middleware для логирования запросов\n\nМетоды API:\n- POST /create_event (создание)\n- POST /update_event (обновление)\n- POST /delete_event (удаление)\n- GET /events_for_day (события на сегодня)\n- GET /events_for_week (события на этой недели)\n- GET /events_for_month (события в этом месяце)\n\nВ результате каждого запроса должен возвращаться JSON документ содержащий\nлибо ***{\"result\": \"...\"}*** в случае успешного выполнения метода\nлибо ***{\"error\": \"...\"}*** в случае ошибки бизнес-логики\n\n**Критерии оценки:** Все методы должны быть реализованы\nБизнес логика (пакет internal/domain в примере) НЕ должен зависеть от кода HTTP сервера\nВ случае ошибки бизнес-логики сервер должен возвращать HTTP 200\nВ случае ошибки входных данных (невалидный int например) сервер должен возвращать HTTP 400\nВ случае остальных ошибок сервер должен возвращать HTTP 500\nWeb-сервер должен запускаться на порту указанном в конфиге и выводить в лог каждый обработанный запрос.\n\n**Описание**\n\nЗапуск сервиса\n```bash\nmake http\n```\n\nИмеется графический интерфейс для создания/изменения/удаления событий:\n- Общий вид (список событий)\n![Общий вид](https://github.com/mirecl/goalmanac/blob/master/img/1.png)\n- Создание события\n![Создание события](https://github.com/mirecl/goalmanac/blob/master/img/2.png)\n- Изменение/удаление события\n![Изменение/удаление события](https://github.com/mirecl/goalmanac/blob/master/img/3.png)\n\n\nПокрытие сервиса тестами:\n```bash\nmake test\n```\n```bash\n?       github.com/mirecl/goalmanac     [no test files]\n?       github.com/mirecl/goalmanac/cmd [no test files]\nok      github.com/mirecl/goalmanac/internal/adapters   0.004s  coverage: 92.3% of statements\nok      github.com/mirecl/goalmanac/internal/adapters/db        (cached)        coverage: 48.5% of statements\nok      github.com/mirecl/goalmanac/internal/adapters/http      0.003s\nok      github.com/mirecl/goalmanac/internal/adapters/http/validate     0.003s\nok      github.com/mirecl/goalmanac/internal/adapters/logger    (cached)        coverage: 63.0% of statements\nok      github.com/mirecl/goalmanac/internal/domain     (cached)        coverage: 100.0% of statements\n?       github.com/mirecl/goalmanac/internal/domain/entities    [no test files]\n?       github.com/mirecl/goalmanac/internal/domain/errors      [no test files]\n?       github.com/mirecl/goalmanac/internal/domain/interfaces  [no test files]\nok      github.com/mirecl/goalmanac/internal/domain/usecases    (cached)        coverage: 44.2% of statements\n```\n\nДля валидации входных параметров (POST-запросы) использовал пакет:\n```bash\ngithub.com/xeipuuv/gojsonschema\n```\nПримеры JSON-Schem в папке config:\n```json\n{\n  \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n  \"title\": \"Событие\",\n  \"description\": \"Создание события в Календаре\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"id\": {\n      \"description\": \"UID События\",\n      \"type\": \"string\",\n      \"maxLength\": 36,\n      \"minLength\": 36\n    },\n    \"user\": {\n      \"description\": \"Пользователь\",\n      \"type\": \"string\",\n      \"minLength\": 1\n    },\n    \"title\": {\n      \"description\": \"Заголовок события\",\n      \"type\": \"string\",\n      \"minLength\": 1\n    },\n    \"body\": {\n      \"description\": \"Описание события\",\n      \"type\": \"string\",\n      \"minLength\": 1\n    },\n    \"start\": {\n      \"description\": \"Время старта события\",\n      \"type\": \"string\",\n      \"format\": \"date-time\",\n      \"minLength\": 1\n    },\n    \"duration\": {\n      \"description\": \"Продолжительность события\",\n      \"type\": \"string\",\n      \"minLength\": 1,\n      \"enum\": [\n        \"10m\",\n        \"20m\",\n        \"30m\",\n        \"40m\",\n        \"50m\",\n        \"60m\"\n      ]\n    }\n  },\n  \"required\": [\n    \"user\",\n    \"title\",\n    \"body\",\n    \"start\",\n    \"duration\"\n  ]\n}\n```\n\nДля работы с дата/время использовал:\n```bash\ngithub.com/jinzhu/now\n```  \n\n### Задание #4 - Работа с базами данных\n\n**Цель:** Обеспечить сохранение событий календаря в СУБД Тех. задание: https://github.com/OtusTeam/Go/blob/master/project-calendar.md Цель данного занятия: отработка навыков работы СУБД, SQL, пакетами database/sql и github.com/jmoiron/sqlx\nУстановить базу данных (например postgres) локально (или сразу в Docker, если знаете как)\nСоздать базу данных и пользователей для проекта календарь\nСоздать схему данных (таблицы, индексы) в виде отдельного SQL файла и сохранить его в репозиторий\nВ проекте календарь создать отдельный пакет, отвечающий за сохранение моделей в СУБД\nНастройки подключения к СУБД вынести в конфиг проекта\nИзменить код приложения так, что бы обеспечить сохранение событий в СУБД\n\n**Критерии оценки:** Должны быть созданы все необходимые таблицы и индексы.\nSQL миграция должна применять с первого раза и должна быть актуальной,\nт.е. все изменения которые вы делали в своей базе должны быть отражены в миграции.\n\n**Описание**\nДля запуска БД необходимо выполнить\n```bash\nmake db\n```\nДля запуска Сервиса:\n```bash\nmake http\n```\nЛогин и пароль от БД передаются в env и считываются в сервисе:\n```golang\nviper.AutomaticEnv()\n// Зачитываем credential для БД\nviper.BindEnv(\"db.POSTGRES_PASSWORD\", \"POSTGRES_PASSWORD\")\nviper.BindEnv(\"db.POSTGRES_USER\", \"POSTGRES_USER\")\nviper.BindEnv(\"db.POSTGRES_DB\", \"POSTGRES_DB\")\n```\n\n### Задание #5 - Работа с очередями\n\n**Цель:** Реализовать \"напоминания\" о событиях с помощью RabbitMQ. Тех. задание: https://github.com/OtusTeam/Go/blob/master/project-calendar.md Цель данного занятия: отработка навыков работы с RabbitMQ и очередями вообще.\nУстановить локально очередь сообщений RabbitMQ (можно сразу в Docker если знаете как)\n\nСоздать процесс (scheduler), который периодически сканирует основную базу данных, выбирая события о которых нужно напомнить.\nПри запуске процесс должен подключаться к RabbitMQ и создавать все необходимые структуры (топики) в ней.\nПроцесс должен выбирать сообытия для которых следует отправить уведомление, сериализовать их (например в JSON) и складывать в очередь.\n\nСоздать процесс (sender), который читает сообщения из очереди и шлет уведомления.\nНепосредственно отправку делать не нужно - можно просто выводить сообщения в STDOUT.\n\n**Критерии оценки:** Код должен работать и проходить проверки go vet и golint\nНастройки подключения к очереди должны быть вынесены в конфиг проекта\nУ преподавателя должна быть возможность скомпилировать процессы scheduler и sender с помощью Makefile\nПосле запуска RabbitMQ и PostgreSQL процессы scheduler и sender должны запускаться без дополнительных действий\n\n**Описание**\n\nВ конфигурацю добавились доп. параметры (./config/config.yaml).\n```yaml\nmq:\n  host: 127.0.0.1\n  port: 5672\n  polling: 30s # Частота опроса БД\n  period: 15m  # глубина выборки при опросе\nlog_mq:\n  level: info\n  path: mq.log\n```\nЗначение default:\n```golang\nviper.SetDefault(\"mq\", map[string]interface{}{\n\t\"host\":                  \"127.0.0.1\",\n\t\"port\":                  \"5672\",\n\t\"RABBITMQ_DEFAULT_USER\": \"rabbitmq\",\n\t\"RABBITMQ_DEFAULT_PASS\": \"rabbitmq\",\n\t\"period\":                \"10m\",\n\t\"polling\":               \"1m\",\n})\n// Зачитываем credential для MQ\nviper.BindEnv(\"mq.RABBITMQ_DEFAULT_PASS\", \"RABBITMQ_DEFAULT_PASS\")\nviper.BindEnv(\"mq.RABBITMQ_DEFAULT_USER\", \"RABBITMQ_DEFAULT_USER\")\n```\nСервис MQ поднимается по команде:\n```bash\nmake service\n```\nФайл запуска: docker-compose.yml\n```yaml\nmq:\n    image: rabbitmq:3.7.5-management\n    container_name: mq\n    ports:\n        - 5672:5672\n        - 15672:15672\n    volumes:\n        - ./data/rabbitmq:/var/lib/rabbitmq/mnesia/rabbit@app-rabbitmq:cached\n    environment:\n        RABBITMQ_ERLANG_COOKIE: 6085e2412b6fa88647466c6a81c0cea0\n        RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}\n        RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}\n        RABBITMQ_DEFAULT_VHOST: /\n```\nЛогин и пароль указываются через environment.\n\nЗапуск сервисов Sender и Sheduler осуществляется через команду (при запуске http-сервера).\n```bash\nmake http\n```\nФайл запуска: сmd/http.go (отдельные goroutine)\n```golang\n// Запускаем Sender\ngo mq.ServeSender()\n// Запускаем Sheduler\ngo mq.ServeSheduler()\n```\n\nДобавилось поле в таблицу Almanac - notify (статус отправки сообщения)\n- null - новое сообщение\n- \"1\" - сообщение отправлено пользователю\n\nДобавил файл миграции:\n```sql\nALTER TABLE almanac ADD notify char(1);\ncreate index notify_idx on almanac (starttime,notify);\n```\n\nЛогика сервиса нотификации:\n1) Сервис Sender сканирует БД каждые mq.polling c глубиной выборки mq.period, где notify is null;\n2) Все попавшиеся сообщения отправляются в Rabbit MQ;\n3) Сервис Sheduler считывает сообщения и отправляет пользователю и ставит в БД flag отправки, иначе не подтверждает отправку сообщения.\n\n### Задание #6 - GRPC сервис\n\n**Цель:** Создать GRPC API для сервиса календаря Тех. задание: https://github.com/OtusTeam/Go/blob/master/project-calendar.md Цель данного занятия: отработка навыков работы с GRPC, построение современного API.\n\n- Создать отдельную директорию для Protobuf спек.\n- Создать Protobuf спеки с описанием всех методов API, их объектов запросов и ответов.\n- Создать отдельный директорию для кода GRPC сервера\n- Сгенерировать код GRPC сервера на основе Protobuf спек (скрипт генерации сохранить в репозиторий).\n\n**Критерии оценки:** Все методы должны быть реализованы\nБизнес логика (пакет internal/domain в примере) НЕ должен зависеть от кода GRPC сервера\nGRPC-сервер должен запускаться на порту указанном в конфиге и выводить в лог каждый обработанный запрос.\n\n**Описание**\n\nВ конфигурацю добавились доп. параметры (./config/config.yaml).\n```yaml\ngrpc:\n  host: 127.0.0.1\n  port: 50051\nlog_grpc:\n  level: info\n  path: info.log\n```\nЗначение default:\n```golang\nviper.SetDefault(\"log_grpc\", map[string]string{\n\t\"level\": \"info\",\n\t\"path\":  \"grpc.log\",\n})\n```\nДля работы необходимо запустить сервис БД:\n```bash\nmake service\n```\nЗапуск gRPC осуществляется через команду (при запуске http-сервера):\n```bash\nmake http\n```\n```golang\n// Запускаем GRPC API\ngo serverGRPC.Serve()\n```\nили запускается как отдельный сервис:\n```bash\nmake grpc\n```\n\nProto-файл (internal/adapters/grpc/api):\n```yaml\nsyntax = \"proto3\";\n\noption go_package = \"api\";\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/empty.proto\";\n\n// Методы Сервиса\nservice Almanac {\n    rpc Create(EventCreate) returns (ResponseOK) {};\n    rpc Update(EventUpdate) returns (ResponseOK) {};\n    rpc Delete(EventDelete) returns (ResponseOK) {};\n    rpc GetAll(google.protobuf.Empty) returns (ResponseEvents) {};\n    rpc GetDayEvent(EventUser) returns (ResponseEvents) {};\n    rpc GetWeekEvent(EventUser) returns (ResponseEvents) {};\n    rpc GetMonthEvent(EventUser) returns (ResponseEvents) {};\n}\n\n// Создание события в календаре\nmessage EventCreate {\n    string user = 1;\n    string title = 2;\n    string body = 3;\n    google.protobuf.Timestamp starttime = 4;\n    string duration = 5;\n}\n\n// Обновление события в календаре\nmessage EventUpdate {\n    string id = 1;\n    string user = 2;\n    string title = 3;\n    string body = 4;\n    google.protobuf.Timestamp starttime = 5;\n    string duration = 6;\n}\n\n// Id события - для удаления\nmessage EventDelete {\n    string id = 1;\n}\n\n// Пользователь\nmessage EventUser {\n    string user = 1;\n}\n\n// Cобытие в календаре\nmessage Event {\n    string id = 1;\n    string user = 2;\n    string title = 3;\n    string body = 4;\n    google.protobuf.Timestamp starttime = 5;\n    google.protobuf.Timestamp endtime = 6;\n}\n\n// Успешный ответ - список Cобытий\nmessage ResponseEvents {\n    repeated Event result = 1;\n}\n\n// Успешный ответ от сервиса\nmessage ResponseOK {\n    string result = 1;\n}\n```\n\nСоздал цепочку middleware из 1ого interceptor - логирование запросов (internal/adapters/grpc/interceptor.go (server.go))\n```golang\n// Создаем цепочку middleware\nmiddleware := grpc_middleware.ChainUnaryServer(g.unaryInterceptor)\n```\n\n### Documentation\n* [API Reference](http://godoc.org/github.com/mirecl/goalmanac)\n\n### Installation\n\n    go get github.com/mirecl/goalmanac","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmirecl%2Fgoalmanac","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmirecl%2Fgoalmanac","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmirecl%2Fgoalmanac/lists"}