From 813261cf8cea42435acaafe9351829ddc021332c Mon Sep 17 00:00:00 2001 From: SShlykov Date: Sun, 3 Mar 2024 02:11:22 +0300 Subject: [PATCH 1/9] feat: book events functions --- .idea/dataSources.xml | 5 +- bookback/internal/models/bookevents.go | 12 ++--- bookback/internal/models/mapvariables.go | 12 ++--- bookback/internal/models/nullable.go | 30 +++++++++++- .../http/controllers/bookevents/controller.go | 9 +++- .../services/bookevents/repository.go | 46 +++++++++++-------- ...e_events_and_variables_uuid_generation.sql | 20 ++++++++ 7 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 03cb461..44d9707 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,14 +1,15 @@ - + postgresql true org.postgresql.Driver - jdbc:postgresql://10.102.37.150:5432/book_dev + jdbc:postgresql://77.91.126.204:5429/book_dev + $ProjectFileDir$ diff --git a/bookback/internal/models/bookevents.go b/bookback/internal/models/bookevents.go index a926c6f..0adab45 100644 --- a/bookback/internal/models/bookevents.go +++ b/bookback/internal/models/bookevents.go @@ -13,13 +13,13 @@ type BookEvent struct { ChapterID NullString `json:"chapter_id"` PageID NullString `json:"page_id"` ParagraphID NullString `json:"paragraph_id"` - EventType string `json:"event_type"` // Тип события (начало главы, начало страницы, начало параграфа...) + EventType NullString `json:"event_type"` // Тип события (начало главы, начало страницы, начало параграфа...) IsPublic bool `json:"is_public"` Key string `json:"key"` Value string `json:"value"` - Link string `json:"link"` - LinkText string `json:"link_text"` - LinkType string `json:"link_type"` - LinkImage string `json:"link_image"` - Description string `json:"description"` + Link NullString `json:"link"` + LinkText NullString `json:"link_text"` + LinkType NullString `json:"link_type"` + LinkImage NullString `json:"link_image"` + Description NullString `json:"description"` } diff --git a/bookback/internal/models/mapvariables.go b/bookback/internal/models/mapvariables.go index edbb45e..a372d5e 100644 --- a/bookback/internal/models/mapvariables.go +++ b/bookback/internal/models/mapvariables.go @@ -17,10 +17,10 @@ type MapVariable struct { Lng float64 `json:"lng"` Zoom int `json:"zoom"` Date time.Time `json:"date"` - Description string `json:"description"` - Link string `json:"link"` - LinkText string `json:"link_text"` - LinkType string `json:"link_type"` - LinkImage string `json:"link_image"` - Image string `json:"image"` + Description NullString `json:"description"` + Link NullString `json:"link"` + LinkText NullString `json:"link_text"` + LinkType NullString `json:"link_type"` + LinkImage NullString `json:"link_image"` + Image NullString `json:"image"` } diff --git a/bookback/internal/models/nullable.go b/bookback/internal/models/nullable.go index 38c550d..eb03b08 100644 --- a/bookback/internal/models/nullable.go +++ b/bookback/internal/models/nullable.go @@ -27,7 +27,7 @@ func (nt *NullTime) MarshalJSON() ([]byte, error) { // UnmarshalJSON метод для десериализации из JSON. func (nt *NullTime) UnmarshalJSON(data []byte) error { - if string(data) == "null" { + if string(data) == `null` { nt.Valid = false return nil } @@ -65,3 +65,31 @@ func (ns *NullString) UnmarshalJSON(data []byte) error { ns.Valid = err == nil return err } + +// NullInt обертка вокруг sql.NullInt64 для корректной работы с JSON +type NullInt struct { + sql.NullInt64 +} + +// NewNullInt создает новый экземпляр NullInt. +func NewNullInt(i int64, valid bool) NullInt { + return NullInt{sql.NullInt64{Int64: i, Valid: valid}} +} + +// MarshalJSON метод для сериализация в JSON. +func (ni *NullInt) MarshalJSON() ([]byte, error) { + if ni.Valid { + return json.Marshal(ni.Int64) + } + return json.Marshal(nil) +} + +func (ni *NullInt) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + ni.Valid = false + return nil + } + err := json.Unmarshal(data, &ni.Int64) + ni.Valid = err == nil + return err +} diff --git a/bookback/internal/servers/http/controllers/bookevents/controller.go b/bookback/internal/servers/http/controllers/bookevents/controller.go index e75f5b2..cb1ec5a 100644 --- a/bookback/internal/servers/http/controllers/bookevents/controller.go +++ b/bookback/internal/servers/http/controllers/bookevents/controller.go @@ -2,6 +2,7 @@ package bookevents import ( "context" + "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/models" service "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" @@ -69,6 +70,7 @@ func (bec *Controller) UpdateBookEvent(c echo.Context, ctx context.Context) erro id := c.Param("id") updatedEvent, err := bec.service.UpdateBookEvent(ctx, id, &event) if err != nil { + fmt.Println(err) return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) } @@ -86,11 +88,13 @@ func (bec *Controller) UpdateBookEvent(c echo.Context, ctx context.Context) erro // @failure 404 {object} config.HTTPError func (bec *Controller) DeleteBookEvent(c echo.Context, ctx context.Context) error { id := c.Param("id") - if _, err := bec.service.DeleteBookEvent(ctx, id); err != nil { + deletedEvent, err := bec.service.DeleteBookEvent(ctx, id) + if err != nil { + fmt.Println(err) return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) } - return c.NoContent(http.StatusNoContent) + return c.JSON(http.StatusOK, deletedEvent) } // CreateBookEvent обрабатывает запросы на создание события книги. @@ -110,6 +114,7 @@ func (bec *Controller) CreateBookEvent(c echo.Context, ctx context.Context) erro } createdEvent, err := bec.service.CreateBookEvent(ctx, &event) if err != nil { + fmt.Println(err) return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotCreated) } return c.JSON(http.StatusCreated, createdEvent) diff --git a/bookback/internal/services/bookevents/repository.go b/bookback/internal/services/bookevents/repository.go index ad13c1c..399a48a 100644 --- a/bookback/internal/services/bookevents/repository.go +++ b/bookback/internal/services/bookevents/repository.go @@ -13,7 +13,6 @@ const ( columnID = "id" columnCreatedAt = "created_at" columnUpdatedAt = "updated_at" - columnDeletedAt = "deleted_at" columnBookID = "book_id" columnChapterID = "chapter_id" columnPageID = "page_id" @@ -44,27 +43,34 @@ type repository struct { db db.Client } +func NewRepository(database db.Client) Repository { + return &repository{database} +} + func allItems() string { - cols := []string{columnID, columnBookID, columnCreatedAt, columnUpdatedAt, columnDeletedAt, - columnChapterID, columnPageID, columnParagraphID, columnEventType, columnIsPublic, - columnKey, columnValue, columnLink, columnLinkText, columnLinkType, columnLinkImage, + cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnBookID, columnChapterID, + columnPageID, columnParagraphID, columnEventType, columnIsPublic, columnKey, + columnValue, columnLink, columnLinkText, columnLinkType, columnLinkImage, columnDescription} return strings.Join(cols, ", ") } -func NewRepository(database db.Client) Repository { - return &repository{database} +func insertItems() string { + cols := []string{columnBookID, columnChapterID, columnPageID, columnParagraphID, columnEventType, + columnIsPublic, columnKey, columnValue, columnLink, columnLinkText, + columnLinkType, columnLinkImage, columnDescription} + + return strings.Join(cols, ", ") } func (r *repository) Create(ctx context.Context, event *models.BookEvent) (string, error) { - query := `INSERT INTO` + " " + tableName + ` (` + allItems() + + query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING ` + columnID - args := []interface{}{event.ID, event.BookID, event.ChapterID, event.PageID, event.ParagraphID, event.EventType, //nolint:gofmt - event.IsPublic, event.Key, event.Value, - event.Link, event.LinkText, event.LinkType, + args := []interface{}{event.BookID, event.ChapterID, event.PageID, event.ParagraphID, event.EventType, //nolint:gofmt + event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, event.LinkType, event.LinkImage, event.Description} q := db.Query{Name: "BookEventRepository.Insert", Raw: query} @@ -86,16 +92,16 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.BookEvent } func (r *repository) Update(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) { - query := `UPDATE ` + tableName + ` SET ` + services.ParamsToQuery(", ", columnBookID, columnChapterID, - columnPageID, columnParagraphID, columnEventType, columnIsPublic, columnKey, columnValue, - columnLink, columnLinkText, columnLinkType, columnLinkImage, columnDescription) + ` WHERE ` + - columnID + ` = $15` + `RETURNING ` + allItems() + query := `UPDATE ` + tableName + ` SET ` + + services.ParamsToQuery(", ", + columnBookID, columnChapterID, columnPageID, columnParagraphID, columnEventType, + columnIsPublic, columnKey, columnValue, columnLink, columnLinkText, + columnLinkType, columnLinkImage, columnDescription) + ` WHERE ` + + columnID + ` = $14` + ` RETURNING ` + allItems() - args := []interface{}{event.ID, event.BookID, event.ChapterID, //nolint:gofmt - event.PageID, event.ParagraphID, event.EventType, - event.IsPublic, event.Key, event.Value, - event.Link, event.LinkText, event.LinkType, - event.LinkImage, event.Description, id} + args := []interface{}{event.BookID, event.ChapterID, event.PageID, event.ParagraphID, event.EventType, //nolint:gofmt + event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, + event.LinkType, event.LinkImage, event.Description, id} q := db.Query{Name: "BookEventRepository.Update", Raw: query} @@ -105,7 +111,7 @@ func (r *repository) Update(ctx context.Context, id string, event *models.BookEv } func (r *repository) Delete(ctx context.Context, id string) (*models.BookEvent, error) { - query := `DELETE FROM` + " " + tableName + ` WHERE ` + services.SelectWhere(allItems, tableName, columnID) + `RETURNING ` + allItems() + query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() q := db.Query{Name: "BookEventRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) diff --git a/bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql b/bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql new file mode 100644 index 0000000..f7b1d10 --- /dev/null +++ b/bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql @@ -0,0 +1,20 @@ +-- +goose Up +-- +goose StatementBegin +BEGIN; + +ALTER TABLE book_events ALTER COLUMN id SET DEFAULT uuid_generate_v4(); +ALTER TABLE map_variables ALTER COLUMN id SET DEFAULT uuid_generate_v4(); + +ALTER TABLE book_events ALTER COLUMN created_at SET DEFAULT now(); +ALTER TABLE book_events ALTER COLUMN updated_at SET DEFAULT now(); + +ALTER TABLE map_variables ALTER COLUMN created_at SET DEFAULT now(); + +CREATE TRIGGER update_book_events_updated_at BEFORE UPDATE ON book_events FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +COMMIT; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +-- +goose StatementEnd From 4352b737c296cf12341e36d80cc27ef510330118 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Sun, 3 Mar 2024 02:14:43 +0300 Subject: [PATCH 2/9] fix: ci err --- bookback/internal/models/nullable.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bookback/internal/models/nullable.go b/bookback/internal/models/nullable.go index eb03b08..d311164 100644 --- a/bookback/internal/models/nullable.go +++ b/bookback/internal/models/nullable.go @@ -27,7 +27,7 @@ func (nt *NullTime) MarshalJSON() ([]byte, error) { // UnmarshalJSON метод для десериализации из JSON. func (nt *NullTime) UnmarshalJSON(data []byte) error { - if string(data) == `null` { + if isNull(data) { nt.Valid = false return nil } @@ -57,7 +57,7 @@ func (ns *NullString) MarshalJSON() ([]byte, error) { // UnmarshalJSON метод для десериализации из JSON. func (ns *NullString) UnmarshalJSON(data []byte) error { - if string(data) == "null" { + if isNull(data) { ns.Valid = false return nil } @@ -85,7 +85,7 @@ func (ni *NullInt) MarshalJSON() ([]byte, error) { } func (ni *NullInt) UnmarshalJSON(data []byte) error { - if string(data) == "null" { + if isNull(data) { ni.Valid = false return nil } @@ -93,3 +93,7 @@ func (ni *NullInt) UnmarshalJSON(data []byte) error { ni.Valid = err == nil return err } + +func isNull(data []byte) bool { + return string(data) == "null" +} From b5c8f7f701c51a76511417063518c1c0fd2d0e31 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Sun, 3 Mar 2024 23:51:01 +0300 Subject: [PATCH 3/9] feat: added methrics and refactored controllers --- bookback/docs/docs.go | 816 ------------------ bookback/docs/docs.md | 1 - bookback/docs/swagger.json | 795 ----------------- bookback/docs/swagger.yaml | 524 ----------- bookback/internal/metrics/interface.go | 6 + .../internal/metrics/localmetrics/metrics.go | 61 ++ bookback/internal/metrics/metrics.go | 1 - bookback/internal/models/mapvariables.go | 10 +- bookback/internal/pkg/app/app.go | 3 + bookback/internal/pkg/app/endpoint.go | 104 +-- bookback/internal/pkg/app/metrics.go | 14 + .../http}/circuitbreaker/circuitbreaker.go | 0 .../circuitbreaker/circuitbreaker_test.go | 0 .../servers/http/controllers/book/config.go | 6 + .../http/controllers/book/controller.go | 112 +-- .../servers/http/controllers/book/errors.go | 31 + .../servers/http/controllers/book/models.go | 21 + .../servers/http/controllers/book/routes.go | 31 + .../http/controllers/bookevents/config.go | 3 + .../http/controllers/bookevents/controller.go | 140 +-- .../http/controllers/bookevents/errors.go | 31 + .../http/controllers/bookevents/models.go | 20 + .../http/controllers/bookevents/routes.go | 33 + .../http/controllers/chapter/config.go | 3 + .../http/controllers/chapter/controller.go | 102 ++- .../http/controllers/chapter/errors.go | 31 + .../http/controllers/chapter/models.go | 21 + .../http/controllers/chapter/routes.go | 33 + .../servers/http/controllers/health/config.go | 3 + .../http/controllers/health/controller.go | 24 +- .../servers/http/controllers/health/routes.go | 23 + .../http/controllers/mapvariables/config.go | 3 + .../controllers/mapvariables/controller.go | 240 ++---- .../http/controllers/mapvariables/errors.go | 29 + .../http/controllers/mapvariables/models.go | 20 + .../http/controllers/mapvariables/routes.go | 32 + .../servers/http/controllers/page/config.go | 3 + .../http/controllers/page/controller.go | 107 ++- .../servers/http/controllers/page/errors.go | 31 + .../servers/http/controllers/page/models.go | 21 + .../servers/http/controllers/page/routes.go | 30 + .../http/controllers/paragraph/config.go | 3 + .../http/controllers/paragraph/controller.go | 109 ++- .../http/controllers/paragraph/errors.go | 31 + .../http/controllers/paragraph/models.go | 21 + .../http/controllers/paragraph/routes.go | 30 + .../http/controllers/swagger/controller.go | 12 + .../http/httpmiddlewares/circuitbreaker.go | 28 + .../servers/http/httpmiddlewares/cors.go | 28 + .../http/httpmiddlewares/httplogger.go | 32 + .../http/httpmiddlewares/middleware.go | 29 + .../internal/servers/http/router/router.go | 83 -- bookback/internal/servers/http/server.go | 5 - .../internal/services/book/repository_test.go | 16 +- .../services/chapter/repository_test.go | 16 +- .../services/mapvariables/repository.go | 104 +-- .../internal/services/mapvariables/service.go | 21 - .../internal/services/mapvariables/utils.go | 3 +- .../internal/services/page/repository_test.go | 16 +- .../services/paragraph/repository_test.go | 16 +- .../20240225152254_create_map_variables.sql | 4 +- ...75249_update_map_events_add_updated_at.sql | 9 + bookback/pkg/logger/handler.go | 11 +- bookback/{internal => tests}/mocks/db.go | 0 .../{internal => tests}/mocks/scanresult.go | 0 65 files changed, 1298 insertions(+), 2848 deletions(-) delete mode 100644 bookback/docs/docs.go delete mode 100644 bookback/docs/docs.md delete mode 100644 bookback/docs/swagger.json delete mode 100644 bookback/docs/swagger.yaml create mode 100644 bookback/internal/metrics/interface.go create mode 100644 bookback/internal/metrics/localmetrics/metrics.go delete mode 100644 bookback/internal/metrics/metrics.go create mode 100644 bookback/internal/pkg/app/metrics.go rename bookback/internal/{ => servers/http}/circuitbreaker/circuitbreaker.go (100%) rename bookback/internal/{ => servers/http}/circuitbreaker/circuitbreaker_test.go (100%) create mode 100644 bookback/internal/servers/http/controllers/book/config.go create mode 100644 bookback/internal/servers/http/controllers/book/errors.go create mode 100644 bookback/internal/servers/http/controllers/book/models.go create mode 100644 bookback/internal/servers/http/controllers/book/routes.go create mode 100644 bookback/internal/servers/http/controllers/bookevents/config.go create mode 100644 bookback/internal/servers/http/controllers/bookevents/errors.go create mode 100644 bookback/internal/servers/http/controllers/bookevents/models.go create mode 100644 bookback/internal/servers/http/controllers/bookevents/routes.go create mode 100644 bookback/internal/servers/http/controllers/chapter/config.go create mode 100644 bookback/internal/servers/http/controllers/chapter/errors.go create mode 100644 bookback/internal/servers/http/controllers/chapter/models.go create mode 100644 bookback/internal/servers/http/controllers/chapter/routes.go create mode 100644 bookback/internal/servers/http/controllers/health/config.go create mode 100644 bookback/internal/servers/http/controllers/health/routes.go create mode 100644 bookback/internal/servers/http/controllers/mapvariables/config.go create mode 100644 bookback/internal/servers/http/controllers/mapvariables/errors.go create mode 100644 bookback/internal/servers/http/controllers/mapvariables/models.go create mode 100644 bookback/internal/servers/http/controllers/mapvariables/routes.go create mode 100644 bookback/internal/servers/http/controllers/page/config.go create mode 100644 bookback/internal/servers/http/controllers/page/errors.go create mode 100644 bookback/internal/servers/http/controllers/page/models.go create mode 100644 bookback/internal/servers/http/controllers/page/routes.go create mode 100644 bookback/internal/servers/http/controllers/paragraph/config.go create mode 100644 bookback/internal/servers/http/controllers/paragraph/errors.go create mode 100644 bookback/internal/servers/http/controllers/paragraph/models.go create mode 100644 bookback/internal/servers/http/controllers/paragraph/routes.go create mode 100644 bookback/internal/servers/http/controllers/swagger/controller.go create mode 100644 bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go create mode 100644 bookback/internal/servers/http/httpmiddlewares/cors.go create mode 100644 bookback/internal/servers/http/httpmiddlewares/httplogger.go create mode 100644 bookback/internal/servers/http/httpmiddlewares/middleware.go delete mode 100644 bookback/internal/servers/http/router/router.go delete mode 100644 bookback/internal/servers/http/server.go create mode 100644 bookback/migrations/20240303075249_update_map_events_add_updated_at.sql rename bookback/{internal => tests}/mocks/db.go (100%) rename bookback/{internal => tests}/mocks/scanresult.go (100%) diff --git a/bookback/docs/docs.go b/bookback/docs/docs.go deleted file mode 100644 index 3d7085b..0000000 --- a/bookback/docs/docs.go +++ /dev/null @@ -1,816 +0,0 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT -package docs - -import "github.com/swaggo/swag" - -const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "contact": {}, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/books": { - "get": { - "description": "Извлекает список всех книг", - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Получить список книг", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Book" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую книгу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Создать книгу", - "parameters": [ - { - "description": "Book object", - "name": "book", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Book" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/books/{id}": { - "get": { - "description": "Извлекает книгу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Получить книгу по ID", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет книгу по ее ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Обновить книгу", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Book object", - "name": "book", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Book" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет книгу по ее ID", - "tags": [ - "Книги" - ], - "summary": "Удалить книгу", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/chapters": { - "get": { - "description": "Извлекает список всех глав", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Получить список глав", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Chapter" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую главу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Создать главу", - "parameters": [ - { - "description": "Chapter object", - "name": "chapter", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Chapter" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/chapters/{id}": { - "get": { - "description": "Извлекает главу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Получить главу по ID", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет главу по ее ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Обновить главу", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Chapter object", - "name": "chapter", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Chapter" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет главу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Удалить главу", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "406": { - "description": "Not Acceptable", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/health": { - "get": { - "description": "Возвращает статус приложения", - "produces": [ - "application/json" - ], - "tags": [ - "Статус приложения" - ], - "summary": "Получить статус приложения", - "responses": { - "200": { - "description": "healthy", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/pages": { - "get": { - "description": "Извлекает список всех страниц", - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Получить список страниц", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Page" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую страницу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Создать страницу", - "parameters": [ - { - "description": "Page object", - "name": "page", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Page" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Page" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/pages/{id}": { - "get": { - "description": "Извлекает страницу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Получить страницу по ID", - "parameters": [ - { - "type": "string", - "description": "ID страницы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Page" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/paragraphs": { - "get": { - "description": "Извлекает список всех параграфов", - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Получить список параграфов", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Paragraph" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новый параграф", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Создать параграф", - "parameters": [ - { - "description": "Paragraph object", - "name": "paragraph", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/paragraphs/{id}": { - "get": { - "description": "Извлекает параграф по его ID", - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Получить параграф по ID", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет параграф по его ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Обновить параграф", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Paragraph object", - "name": "paragraph", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет параграф по его ID", - "tags": [ - "Параграфы" - ], - "summary": "Удалить параграф", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - } - }, - "definitions": { - "config.HTTPError": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - }, - "models.Book": { - "type": "object", - "properties": { - "author": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "owner": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Chapter": { - "type": "object", - "properties": { - "book_id": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "number": { - "type": "integer" - }, - "pages": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Page" - } - }, - "text": { - "type": "string" - }, - "title": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Page": { - "type": "object", - "properties": { - "chapter_id": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "text": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Paragraph": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "page_id": { - "type": "string" - }, - "text": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - } - } -}` - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = &swag.Spec{ - Version: "0.1", - Host: "localhost:7077", - BasePath: "/api/v1", - Schemes: []string{"http"}, - Title: "Book API", - Description: "Это API для работы с книгами", - InfoInstanceName: "swagger", - SwaggerTemplate: docTemplate, - LeftDelim: "{{", - RightDelim: "}}", -} - -func init() { - swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) -} diff --git a/bookback/docs/docs.md b/bookback/docs/docs.md deleted file mode 100644 index 1b6ba71..0000000 --- a/bookback/docs/docs.md +++ /dev/null @@ -1 +0,0 @@ -## \ No newline at end of file diff --git a/bookback/docs/swagger.json b/bookback/docs/swagger.json deleted file mode 100644 index bc84a37..0000000 --- a/bookback/docs/swagger.json +++ /dev/null @@ -1,795 +0,0 @@ -{ - "schemes": [ - "http" - ], - "swagger": "2.0", - "info": { - "description": "Это API для работы с книгами", - "title": "Book API", - "contact": {}, - "version": "0.1" - }, - "host": "localhost:7077", - "basePath": "/api/v1", - "paths": { - "/books": { - "get": { - "description": "Извлекает список всех книг", - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Получить список книг", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Book" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую книгу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Создать книгу", - "parameters": [ - { - "description": "Book object", - "name": "book", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Book" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/books/{id}": { - "get": { - "description": "Извлекает книгу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Получить книгу по ID", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет книгу по ее ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Книги" - ], - "summary": "Обновить книгу", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Book object", - "name": "book", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Book" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Book" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет книгу по ее ID", - "tags": [ - "Книги" - ], - "summary": "Удалить книгу", - "parameters": [ - { - "type": "string", - "description": "Book ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/chapters": { - "get": { - "description": "Извлекает список всех глав", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Получить список глав", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Chapter" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую главу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Создать главу", - "parameters": [ - { - "description": "Chapter object", - "name": "chapter", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Chapter" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/chapters/{id}": { - "get": { - "description": "Извлекает главу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Получить главу по ID", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет главу по ее ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Обновить главу", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Chapter object", - "name": "chapter", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Chapter" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет главу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Главы" - ], - "summary": "Удалить главу", - "parameters": [ - { - "type": "string", - "description": "ID главы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Chapter" - } - }, - "406": { - "description": "Not Acceptable", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/health": { - "get": { - "description": "Возвращает статус приложения", - "produces": [ - "application/json" - ], - "tags": [ - "Статус приложения" - ], - "summary": "Получить статус приложения", - "responses": { - "200": { - "description": "healthy", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/pages": { - "get": { - "description": "Извлекает список всех страниц", - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Получить список страниц", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Page" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новую страницу", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Создать страницу", - "parameters": [ - { - "description": "Page object", - "name": "page", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Page" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Page" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/pages/{id}": { - "get": { - "description": "Извлекает страницу по ее ID", - "produces": [ - "application/json" - ], - "tags": [ - "Страницы" - ], - "summary": "Получить страницу по ID", - "parameters": [ - { - "type": "string", - "description": "ID страницы", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Page" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/paragraphs": { - "get": { - "description": "Извлекает список всех параграфов", - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Получить список параграфов", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Paragraph" - } - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "post": { - "description": "Создает новый параграф", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Создать параграф", - "parameters": [ - { - "description": "Paragraph object", - "name": "paragraph", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - }, - "/paragraphs/{id}": { - "get": { - "description": "Извлекает параграф по его ID", - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Получить параграф по ID", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "put": { - "description": "Обновляет параграф по его ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Параграфы" - ], - "summary": "Обновить параграф", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Paragraph object", - "name": "paragraph", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - }, - "delete": { - "description": "Удаляет параграф по его ID", - "tags": [ - "Параграфы" - ], - "summary": "Удалить параграф", - "parameters": [ - { - "type": "string", - "description": "ID параграфа", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.Paragraph" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/config.HTTPError" - } - } - } - } - } - }, - "definitions": { - "config.HTTPError": { - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - }, - "models.Book": { - "type": "object", - "properties": { - "author": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "owner": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Chapter": { - "type": "object", - "properties": { - "book_id": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "number": { - "type": "integer" - }, - "pages": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Page" - } - }, - "text": { - "type": "string" - }, - "title": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Page": { - "type": "object", - "properties": { - "chapter_id": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "text": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, - "models.Paragraph": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "is_public": { - "type": "boolean" - }, - "page_id": { - "type": "string" - }, - "text": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - } - } -} \ No newline at end of file diff --git a/bookback/docs/swagger.yaml b/bookback/docs/swagger.yaml deleted file mode 100644 index 7809889..0000000 --- a/bookback/docs/swagger.yaml +++ /dev/null @@ -1,524 +0,0 @@ -basePath: /api/v1 -definitions: - config.HTTPError: - properties: - message: - type: string - type: object - models.Book: - properties: - author: - type: string - created_at: - type: string - description: - type: string - id: - type: string - is_public: - type: boolean - owner: - type: integer - title: - type: string - updated_at: - type: string - type: object - models.Chapter: - properties: - book_id: - type: string - created_at: - type: string - id: - type: string - is_public: - type: boolean - number: - type: integer - pages: - items: - $ref: '#/definitions/models.Page' - type: array - text: - type: string - title: - type: string - updated_at: - type: string - type: object - models.Page: - properties: - chapter_id: - type: string - created_at: - type: string - id: - type: string - is_public: - type: boolean - text: - type: string - updated_at: - type: string - type: object - models.Paragraph: - properties: - created_at: - type: string - id: - type: string - is_public: - type: boolean - page_id: - type: string - text: - type: string - updated_at: - type: string - type: object -host: localhost:7077 -info: - contact: {} - description: Это API для работы с книгами - title: Book API - version: "0.1" -paths: - /books: - get: - description: Извлекает список всех книг - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Book' - type: array - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить список книг - tags: - - Книги - post: - consumes: - - application/json - description: Создает новую книгу - parameters: - - description: Book object - in: body - name: book - required: true - schema: - $ref: '#/definitions/models.Book' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Book' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Создать книгу - tags: - - Книги - /books/{id}: - delete: - description: Удаляет книгу по ее ID - parameters: - - description: Book ID - in: path - name: id - required: true - type: string - responses: - "204": - description: No Content - "404": - description: Not Found - schema: - $ref: '#/definitions/config.HTTPError' - summary: Удалить книгу - tags: - - Книги - get: - description: Извлекает книгу по ее ID - parameters: - - description: Book ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Book' - "404": - description: Not Found - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить книгу по ID - tags: - - Книги - put: - consumes: - - application/json - description: Обновляет книгу по ее ID - parameters: - - description: Book ID - in: path - name: id - required: true - type: string - - description: Book object - in: body - name: book - required: true - schema: - $ref: '#/definitions/models.Book' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Book' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Обновить книгу - tags: - - Книги - /chapters: - get: - description: Извлекает список всех глав - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Chapter' - type: array - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить список глав - tags: - - Главы - post: - consumes: - - application/json - description: Создает новую главу - parameters: - - description: Chapter object - in: body - name: chapter - required: true - schema: - $ref: '#/definitions/models.Chapter' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Chapter' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Создать главу - tags: - - Главы - /chapters/{id}: - delete: - description: Удаляет главу по ее ID - parameters: - - description: ID главы - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Chapter' - "406": - description: Not Acceptable - schema: - $ref: '#/definitions/config.HTTPError' - summary: Удалить главу - tags: - - Главы - get: - description: Извлекает главу по ее ID - parameters: - - description: ID главы - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Chapter' - "404": - description: Not Found - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить главу по ID - tags: - - Главы - put: - consumes: - - application/json - description: Обновляет главу по ее ID - parameters: - - description: ID главы - in: path - name: id - required: true - type: string - - description: Chapter object - in: body - name: chapter - required: true - schema: - $ref: '#/definitions/models.Chapter' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Chapter' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Обновить главу - tags: - - Главы - /health: - get: - description: Возвращает статус приложения - produces: - - application/json - responses: - "200": - description: healthy - schema: - type: string - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить статус приложения - tags: - - Статус приложения - /pages: - get: - description: Извлекает список всех страниц - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Page' - type: array - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить список страниц - tags: - - Страницы - post: - consumes: - - application/json - description: Создает новую страницу - parameters: - - description: Page object - in: body - name: page - required: true - schema: - $ref: '#/definitions/models.Page' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Page' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Создать страницу - tags: - - Страницы - /pages/{id}: - get: - description: Извлекает страницу по ее ID - parameters: - - description: ID страницы - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Page' - "404": - description: Not Found - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить страницу по ID - tags: - - Страницы - /paragraphs: - get: - description: Извлекает список всех параграфов - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Paragraph' - type: array - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить список параграфов - tags: - - Параграфы - post: - consumes: - - application/json - description: Создает новый параграф - parameters: - - description: Paragraph object - in: body - name: paragraph - required: true - schema: - $ref: '#/definitions/models.Paragraph' - produces: - - application/json - responses: - "201": - description: Created - schema: - $ref: '#/definitions/models.Paragraph' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Создать параграф - tags: - - Параграфы - /paragraphs/{id}: - delete: - description: Удаляет параграф по его ID - parameters: - - description: ID параграфа - in: path - name: id - required: true - type: string - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Paragraph' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/config.HTTPError' - summary: Удалить параграф - tags: - - Параграфы - get: - description: Извлекает параграф по его ID - parameters: - - description: ID параграфа - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Paragraph' - "404": - description: Not Found - schema: - $ref: '#/definitions/config.HTTPError' - summary: Получить параграф по ID - tags: - - Параграфы - put: - consumes: - - application/json - description: Обновляет параграф по его ID - parameters: - - description: ID параграфа - in: path - name: id - required: true - type: string - - description: Paragraph object - in: body - name: paragraph - required: true - schema: - $ref: '#/definitions/models.Paragraph' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.Paragraph' - "400": - description: Bad Request - schema: - $ref: '#/definitions/config.HTTPError' - summary: Обновить параграф - tags: - - Параграфы -schemes: -- http -swagger: "2.0" diff --git a/bookback/internal/metrics/interface.go b/bookback/internal/metrics/interface.go new file mode 100644 index 0000000..439df74 --- /dev/null +++ b/bookback/internal/metrics/interface.go @@ -0,0 +1,6 @@ +package metrics + +type Metrics interface { + IncCounter(name string, labels ...string) + ObserveHistogram(name string, value float64, labels ...string) +} diff --git a/bookback/internal/metrics/localmetrics/metrics.go b/bookback/internal/metrics/localmetrics/metrics.go new file mode 100644 index 0000000..b981468 --- /dev/null +++ b/bookback/internal/metrics/localmetrics/metrics.go @@ -0,0 +1,61 @@ +package localmetrics + +import ( + "log/slog" + "sync" +) + +type MetricValue struct { + Count int + Sum float64 +} + +// LocalMetrics структура для локального хранения метрик. +type LocalMetrics struct { + logger *slog.Logger + mu sync.RWMutex + counters map[string]int + summaries map[string]MetricValue +} + +func NewLocalMetrics(logger *slog.Logger) *LocalMetrics { + return &LocalMetrics{ + logger: logger, + counters: make(map[string]int), + summaries: make(map[string]MetricValue), + } +} + +func (l *LocalMetrics) IncCounter(name string, labels ...string) { + l.mu.Lock() + defer l.mu.Unlock() + + key := buildMetricKey(name, labels) + if _, exists := l.counters[key]; !exists { + l.counters[key]++ + } else { + l.counters[key] = 1 + } + l.logger.Info("IncCounter", slog.String("name", name)) +} + +func (l *LocalMetrics) ObserveHistogram(name string, value float64, labels ...string) { + l.mu.Lock() + defer l.mu.Unlock() + + key := buildMetricKey(name, labels) + summary, exists := l.summaries[key] + if !exists { + summary = MetricValue{} + } + summary.Count++ + summary.Sum += value + l.summaries[key] = summary + + l.logger.Info("ObserveSummary", slog.String("name", name), slog.Float64("value", value)) +} + +// buildMetricKey генерирует уникальный ключ для метрики на основе имени и меток. +func buildMetricKey(name string, _ []string) string { + return name +} diff --git a/bookback/internal/metrics/metrics.go b/bookback/internal/metrics/metrics.go deleted file mode 100644 index 1abe097..0000000 --- a/bookback/internal/metrics/metrics.go +++ /dev/null @@ -1 +0,0 @@ -package metrics diff --git a/bookback/internal/models/mapvariables.go b/bookback/internal/models/mapvariables.go index a372d5e..ff005e4 100644 --- a/bookback/internal/models/mapvariables.go +++ b/bookback/internal/models/mapvariables.go @@ -5,9 +5,9 @@ import ( ) type MapVariable struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` BookID string `json:"book_id"` ChapterID NullString `json:"chapter_id"` PageID NullString `json:"page_id"` @@ -15,8 +15,8 @@ type MapVariable struct { MapLink string `json:"map_link"` Lat float64 `json:"lat"` Lng float64 `json:"lng"` - Zoom int `json:"zoom"` - Date time.Time `json:"date"` + Zoom NullInt `json:"zoom"` + Date NullString `json:"date"` Description NullString `json:"description"` Link NullString `json:"link"` LinkText NullString `json:"link_text"` diff --git a/bookback/internal/pkg/app/app.go b/bookback/internal/pkg/app/app.go index 6251423..1370834 100644 --- a/bookback/internal/pkg/app/app.go +++ b/bookback/internal/pkg/app/app.go @@ -3,6 +3,7 @@ package app import ( "context" cfg "github.com/SShlykov/zeitment/bookback/internal/config" + "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/db" "github.com/labstack/echo/v4" "log/slog" @@ -17,6 +18,7 @@ type App struct { config *cfg.Config db db.Client Echo *echo.Echo + metrics metrics.Metrics ctx context.Context closeCtx func() @@ -29,6 +31,7 @@ func NewApp(configPath string) (*App, error) { inits := []func(ctx context.Context) error{ app.initConfig, app.initLogger, + app.initMetrics, app.initDB, app.initEndpoint, app.initRouter, diff --git a/bookback/internal/pkg/app/endpoint.go b/bookback/internal/pkg/app/endpoint.go index cc7bec1..82cf348 100644 --- a/bookback/internal/pkg/app/endpoint.go +++ b/bookback/internal/pkg/app/endpoint.go @@ -2,10 +2,18 @@ package app import ( "context" - "errors" - "fmt" - "github.com/SShlykov/zeitment/bookback/internal/circuitbreaker" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/router" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/circuitbreaker" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/book" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/chapter" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/health" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/mapvariables" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/page" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/paragraph" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/swagger" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + "github.com/SShlykov/zeitment/bookback/pkg/db" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "log/slog" @@ -14,16 +22,15 @@ import ( ) func (app *App) runWebServer(wg *sync.WaitGroup, _ context.Context) { - wg.Add(1) - go func() { + wg.Add(1) defer wg.Done() httpServer := &http.Server{ ReadHeaderTimeout: app.config.Timeout, ReadTimeout: app.config.Timeout, WriteTimeout: app.config.Timeout, IdleTimeout: app.config.IddleTimeout, - Addr: fmt.Sprintf(app.config.Address), + Addr: app.config.Address, Handler: app.Echo, } @@ -37,13 +44,22 @@ func (app *App) runWebServer(wg *sync.WaitGroup, _ context.Context) { func (app *App) initEndpoint(_ context.Context) error { e := echo.New() - cb := circuitbreaker.NewCircuitBreaker(app.config.RequestLimit, app.config.MinRequests, app.config.ErrorThresholdPercentage, - app.config.IntervalDuration, app.config.OpenStateTimeout) + cb := circuitbreaker.NewCircuitBreaker( + app.config.RequestLimit, + app.config.MinRequests, + app.config.ErrorThresholdPercentage, + app.config.IntervalDuration, + app.config.OpenStateTimeout, + ) middlewares := []echo.MiddlewareFunc{ - loggerConfiguration(app.logger), + httpmiddlewares.LoggerConfiguration(app.logger), middleware.Recover(), - createCircuitBreakerMiddleware(cb), + httpmiddlewares.CreateCircuitBreakerMiddleware(cb), + } + + if app.config.CorsEnabled { + middlewares = append(middlewares, httpmiddlewares.CORS()) } e.Use(middlewares...) @@ -53,59 +69,21 @@ func (app *App) initEndpoint(_ context.Context) error { } func (app *App) initRouter(_ context.Context) error { - router.SetCORSConfig(app.Echo, app.config.CorsEnabled) - router.SetHealthController(app.Echo, app.ctx) - router.SetBookController(app.Echo, app.db, app.ctx) - router.SetChapterController(app.Echo, app.db, app.ctx) - router.SetPageController(app.Echo, app.db, app.ctx) - router.SetParagraphController(app.Echo, app.db, app.ctx) - router.SetBookEventController(app.Echo, app.db, app.ctx) - router.SetMapVariablesController(app.Echo, app.db, app.ctx) - router.SetSwagger(app.Echo, app.config.SwaggerEnabled) - - return nil -} - -func createCircuitBreakerMiddleware(cb *circuitbreaker.CircuitBreaker) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - err := cb.Execute(func() error { - return next(c) - }) - - if err != nil { - if errors.Is(err, circuitbreaker.ErrorCb) { - return c.JSON(http.StatusUnavailableForLegalReasons, - map[string]string{"error": "Server is overloaded, please try again later.", "status": "error"}) - } - return err - } + controllers := []func(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context){ + health.SetHealthController, + book.SetBookController, + chapter.SetChapterController, + page.SetPageController, + paragraph.SetParagraphController, + bookevents.SetBookEventController, + mapvariables.SetMapVariablesController, + } - return nil - } + for _, controller := range controllers { + controller(app.Echo, app.db, app.metrics, app.logger, app.ctx) } -} -func loggerConfiguration(logger *slog.Logger) echo.MiddlewareFunc { - return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ - LogStatus: true, - LogURI: true, - LogError: true, - HandleError: true, - LogValuesFunc: func(_ echo.Context, v middleware.RequestLoggerValues) error { - if v.Error == nil { - logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", - slog.String("uri", v.URI), - slog.Int("status", v.Status), - ) - } else { - logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR", - slog.String("uri", v.URI), - slog.Int("status", v.Status), - slog.String("err", v.Error.Error()), - ) - } - return nil - }, - }) + swagger.SetSwagger(app.Echo, app.config.SwaggerEnabled) + + return nil } diff --git a/bookback/internal/pkg/app/metrics.go b/bookback/internal/pkg/app/metrics.go new file mode 100644 index 0000000..fe1b679 --- /dev/null +++ b/bookback/internal/pkg/app/metrics.go @@ -0,0 +1,14 @@ +package app + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics/localmetrics" +) + +func (app *App) initMetrics(_ context.Context) error { + logger := app.logger + logger.Info("initializing metrics as local metrics") + + app.metrics = localmetrics.NewLocalMetrics(logger) + return nil +} diff --git a/bookback/internal/circuitbreaker/circuitbreaker.go b/bookback/internal/servers/http/circuitbreaker/circuitbreaker.go similarity index 100% rename from bookback/internal/circuitbreaker/circuitbreaker.go rename to bookback/internal/servers/http/circuitbreaker/circuitbreaker.go diff --git a/bookback/internal/circuitbreaker/circuitbreaker_test.go b/bookback/internal/servers/http/circuitbreaker/circuitbreaker_test.go similarity index 100% rename from bookback/internal/circuitbreaker/circuitbreaker_test.go rename to bookback/internal/servers/http/circuitbreaker/circuitbreaker_test.go diff --git a/bookback/internal/servers/http/controllers/book/config.go b/bookback/internal/servers/http/controllers/book/config.go new file mode 100644 index 0000000..1f4fa44 --- /dev/null +++ b/bookback/internal/servers/http/controllers/book/config.go @@ -0,0 +1,6 @@ +package book + +// Тут будет конфигурация для контроллера книг. +// Права доступа, роли, константы и т.п. + +const PathPrefix = "/api/v1/books" diff --git a/bookback/internal/servers/http/controllers/book/controller.go b/bookback/internal/servers/http/controllers/book/controller.go index 9811850..9ec5232 100644 --- a/bookback/internal/servers/http/controllers/book/controller.go +++ b/bookback/internal/servers/http/controllers/book/controller.go @@ -3,31 +3,25 @@ package book import ( "context" "errors" - "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/book" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) // Controller структура для HTTP-контроллера книг. type Controller struct { Service service.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } // NewController создает новый экземпляр Controller. -func NewController(srv service.Service) *Controller { - return &Controller{Service: srv} -} - -// RegisterRoutes регистрирует маршруты для обработки запросов к книгам. -func (bc *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/books", func(c echo.Context) error { return bc.ListBooks(c, ctx) }) - e.POST("/api/v1/books", func(c echo.Context) error { return bc.CreateBook(c, ctx) }) - e.GET("/api/v1/books/:id", func(c echo.Context) error { return bc.GetBookByID(c, ctx) }) - e.PUT("/api/v1/books/:id", func(c echo.Context) error { return bc.UpdateBook(c, ctx) }) - e.DELETE("/api/v1/books/:id", func(c echo.Context) error { return bc.DeleteBook(c, ctx) }) +func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // ListBooks обрабатывает запросы на получение списка книг. @@ -37,14 +31,13 @@ func (bc *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @tags Книги // @produce application/json // @success 200 {array} models.Book -// @failure 500 {object} config.HTTPError -func (bc *Controller) ListBooks(c echo.Context, ctx context.Context) error { - books, err := bc.Service.ListBooks(ctx) +// @failure 500 {object} string +func (bc *Controller) ListBooks(c echo.Context) error { + books, err := bc.Service.ListBooks(bc.Ctx) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) + return ErrorUnknown } - return c.JSON(http.StatusOK, books) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Books: books}) } // CreateBook обрабатывает создание новой книги. @@ -56,18 +49,19 @@ func (bc *Controller) ListBooks(c echo.Context, ctx context.Context) error { // @produce application/json // @param book body models.Book true "Book object" // @success 201 {object} models.Book -// @failure 400 {object} config.HTTPError -func (bc *Controller) CreateBook(c echo.Context, ctx context.Context) error { - var book models.Book - if err := c.Bind(&book); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +// @failure 400 {object} string +// @failure 500 {object} string +func (bc *Controller) CreateBook(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - createdBook, err := bc.Service.CreateBook(ctx, &book) + + createdBook, err := bc.Service.CreateBook(bc.Ctx, request.Book) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotCreated) + return ErrorBookNotCreated } - return c.JSON(http.StatusCreated, createdBook) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Book: createdBook}) } // GetBookByID обрабатывает запросы на получение книги по ID. @@ -78,14 +72,21 @@ func (bc *Controller) CreateBook(c echo.Context, ctx context.Context) error { // @param id path string true "Book ID" // @produce application/json // @success 200 {object} models.Book -// @failure 404 {object} config.HTTPError -func (bc *Controller) GetBookByID(c echo.Context, ctx context.Context) error { +// @failure 400 {object} string +// @failure 404 {object} string +// @failure 500 {object} string +func (bc *Controller) GetBookByID(c echo.Context) error { id := c.Param("id") - book, err := bc.Service.GetBookByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + book, err := bc.Service.GetBookByID(bc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookNotFound } - return c.JSON(http.StatusOK, book) + + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Book: book}) } // UpdateBook обрабатывает обновление книги. @@ -97,22 +98,29 @@ func (bc *Controller) GetBookByID(c echo.Context, ctx context.Context) error { // @produce application/json // @param id path string true "Book ID" // @param book body models.Book true "Book object" -// @success 200 {object} models.Book -// @failure 400 {object} config.HTTPError -func (bc *Controller) UpdateBook(c echo.Context, ctx context.Context) error { - var book models.Book - if err := c.Bind(&book); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +// @success 200 {object} responseSingleModel +// @failure 400 {object} string +// @failure 404 {object} string +// @failure 500 {object} string +func (bc *Controller) UpdateBook(c echo.Context) error { + id := c.Param("id") + if id == "" { + return ErrorValidationFailed + } + + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - paramID := c.Param("id") - updatedBook, err := bc.Service.UpdateBook(ctx, paramID, &book) + + updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request.Book) if err != nil { if errors.Is(err, config.ErrorNotFound) { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookNotFound } - return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotUpdated) + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedBook) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Book: updatedBook}) } // DeleteBook обрабатывает удаление книги по ID. @@ -121,13 +129,17 @@ func (bc *Controller) UpdateBook(c echo.Context, ctx context.Context) error { // @description Удаляет книгу по ее ID // @tags Книги // @param id path string true "Book ID" -// @success 204 -// @failure 404 {object} config.HTTPError -func (bc *Controller) DeleteBook(c echo.Context, ctx context.Context) error { +// @success 204 {object} models.Book +// @failure 400 {object} string +func (bc *Controller) DeleteBook(c echo.Context) error { id := c.Param("id") - book, err := bc.Service.DeleteBook(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + book, err := bc.Service.DeleteBook(bc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorDeleteBook } - return c.JSON(http.StatusOK, book) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Book: book}) } diff --git a/bookback/internal/servers/http/controllers/book/errors.go b/bookback/internal/servers/http/controllers/book/errors.go new file mode 100644 index 0000000..e9a0a6b --- /dev/null +++ b/bookback/internal/servers/http/controllers/book/errors.go @@ -0,0 +1,31 @@ +package book + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// Возможные ошибки при работе с книгами. + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorBookNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Книга не найдена", + ) + ErrorBookNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания книги. Книга с такими параметрами уже существует.", + ) + ErrorDeleteBook = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления книги", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/book/models.go b/bookback/internal/servers/http/controllers/book/models.go new file mode 100644 index 0000000..c3eea7e --- /dev/null +++ b/bookback/internal/servers/http/controllers/book/models.go @@ -0,0 +1,21 @@ +package book + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct { +} + +type requestModel struct { + Options Options `json:"options"` + Book *models.Book `json:"book"` +} + +type responseSingleModel struct { + Book *models.Book `json:"book"` + Status string `json:"status"` +} + +type responseListModel struct { + Books []models.Book `json:"books"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/book/routes.go b/bookback/internal/servers/http/controllers/book/routes.go new file mode 100644 index 0000000..1f34d67 --- /dev/null +++ b/bookback/internal/servers/http/controllers/book/routes.go @@ -0,0 +1,31 @@ +package book + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + bookrepo "github.com/SShlykov/zeitment/bookback/internal/services/book" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +// SetBookController регистрирует контроллер книг в маршрутизаторе. +func SetBookController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := bookrepo.NewService(bookrepo.NewRepository(database)) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +// RegisterRoutes регистрирует маршруты для обработки запросов к книгам. +func (bc *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(bc.Metrics)) + + group.GET("", bc.ListBooks) + group.POST("", bc.CreateBook) + group.GET("/:id", bc.GetBookByID) + group.PUT("/:id", bc.UpdateBook) + group.DELETE("/:id", bc.DeleteBook) +} diff --git a/bookback/internal/servers/http/controllers/bookevents/config.go b/bookback/internal/servers/http/controllers/bookevents/config.go new file mode 100644 index 0000000..08ef6ae --- /dev/null +++ b/bookback/internal/servers/http/controllers/bookevents/config.go @@ -0,0 +1,3 @@ +package bookevents + +const PathPrefix = "/api/v1/bookevents" diff --git a/bookback/internal/servers/http/controllers/bookevents/controller.go b/bookback/internal/servers/http/controllers/bookevents/controller.go index cb1ec5a..6acb8e3 100644 --- a/bookback/internal/servers/http/controllers/bookevents/controller.go +++ b/bookback/internal/servers/http/controllers/bookevents/controller.go @@ -2,33 +2,26 @@ package bookevents import ( "context" + "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) type Controller struct { - service service.Service + Service service.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -func NewController(service service.Service) *Controller { - return &Controller{ - service: service, - } -} - -func (bec *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/bookevents/:id", func(c echo.Context) error { return bec.GetBookEventByID(c, ctx) }) - e.PUT("/api/v1/bookevents/:id", func(c echo.Context) error { return bec.UpdateBookEvent(c, ctx) }) - e.DELETE("/api/v1/bookevents/:id", func(c echo.Context) error { return bec.DeleteBookEvent(c, ctx) }) - e.POST("/api/v1/bookevents", func(c echo.Context) error { return bec.CreateBookEvent(c, ctx) }) - e.GET("/api/v1/bookevents/book/:id", func(c echo.Context) error { return bec.GetBookEventsByBookID(c, ctx) }) - e.GET("/api/v1/bookevents/chapter/:id", func(c echo.Context) error { return bec.GetBookEventsByChapterID(c, ctx) }) - e.GET("/api/v1/bookevents/page/:id", func(c echo.Context) error { return bec.GetBookEventsByPageID(c, ctx) }) - e.GET("/api/v1/bookevents/paragraph/:id", func(c echo.Context) error { return bec.GetBookEventsByParagraphID(c, ctx) }) +// NewController создает новый экземпляр Controller. +func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // GetBookEventByID обрабатывает запросы на получение события книги по идентификатору. @@ -40,14 +33,18 @@ func (bec *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @param id path string true "ID события книги" // @success 200 {object} models.BookEvent // @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventByID(c echo.Context, ctx context.Context) error { +func (bec *Controller) GetBookEventByID(c echo.Context) error { id := c.Param("id") - event, err := bec.service.GetBookEventByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + event, err := bec.Service.GetBookEventByID(bec.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookEventNotFound } - return c.JSON(http.StatusOK, event) + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", BookEvent: event}) } // UpdateBookEvent обрабатывает запросы на обновление события книги. @@ -61,20 +58,26 @@ func (bec *Controller) GetBookEventByID(c echo.Context, ctx context.Context) err // @param event body models.BookEvent true "BookEvent object" // @success 200 {object} models.BookEvent // @failure 400 {object} config.HTTPError -func (bec *Controller) UpdateBookEvent(c echo.Context, ctx context.Context) error { - var event models.BookEvent - if err := c.Bind(&event); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +func (bec *Controller) UpdateBookEvent(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } id := c.Param("id") - updatedEvent, err := bec.service.UpdateBookEvent(ctx, id, &event) + if id == "" { + return ErrorValidationFailed + } + + updatedEvent, err := bec.Service.UpdateBookEvent(bec.Ctx, id, request.BookEvents) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) + if errors.Is(err, config.ErrorNotFound) { + return ErrorBookEventNotFound + } + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedEvent) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", BookEvent: updatedEvent}) } // DeleteBookEvent обрабатывает запросы на удаление события книги. @@ -86,15 +89,18 @@ func (bec *Controller) UpdateBookEvent(c echo.Context, ctx context.Context) erro // @param id path string true "ID события книги" // @success 204 // @failure 404 {object} config.HTTPError -func (bec *Controller) DeleteBookEvent(c echo.Context, ctx context.Context) error { +func (bec *Controller) DeleteBookEvent(c echo.Context) error { id := c.Param("id") - deletedEvent, err := bec.service.DeleteBookEvent(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + deletedEvent, err := bec.Service.DeleteBookEvent(bec.Ctx, id) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorDeleteBookEvent } - return c.JSON(http.StatusOK, deletedEvent) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", BookEvent: deletedEvent}) } // CreateBookEvent обрабатывает запросы на создание события книги. @@ -107,17 +113,18 @@ func (bec *Controller) DeleteBookEvent(c echo.Context, ctx context.Context) erro // @param event body models.BookEvent true "BookEvent object" // @success 201 {object} models.BookEvent // @failure 400 {object} config.HTTPError -func (bec *Controller) CreateBookEvent(c echo.Context, ctx context.Context) error { - var event models.BookEvent - if err := c.Bind(&event); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +func (bec *Controller) CreateBookEvent(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - createdEvent, err := bec.service.CreateBookEvent(ctx, &event) + + createdEvent, err := bec.Service.CreateBookEvent(bec.Ctx, request.BookEvents) if err != nil { fmt.Println(err) - return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotCreated) + return ErrorBookEventNotCreated } - return c.JSON(http.StatusCreated, createdEvent) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", BookEvent: createdEvent}) } // GetBookEventsByBookID обрабатывает запросы на получение событий книги по ID книги. @@ -129,13 +136,17 @@ func (bec *Controller) CreateBookEvent(c echo.Context, ctx context.Context) erro // @param id path string true "ID книги" // @success 200 {object} []models.BookEvent // @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByBookID(c echo.Context, ctx context.Context) error { +func (bec *Controller) GetBookEventsByBookID(c echo.Context) error { id := c.Param("id") - events, err := bec.service.GetBookEventsByBookID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + events, err := bec.Service.GetBookEventsByBookID(bec.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookEventNotFound } - return c.JSON(http.StatusOK, events) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) } // GetBookEventsByChapterID обрабатывает запросы на получение событий книги по ID главы. @@ -147,13 +158,18 @@ func (bec *Controller) GetBookEventsByBookID(c echo.Context, ctx context.Context // @param id path string true "ID главы" // @success 200 {object} []models.BookEvent // @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByChapterID(c echo.Context, ctx context.Context) error { +func (bec *Controller) GetBookEventsByChapterID(c echo.Context) error { id := c.Param("id") - events, err := bec.service.GetBookEventsByChapterID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + events, err := bec.Service.GetBookEventsByChapterID(bec.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookEventNotFound } - return c.JSON(http.StatusOK, events) + + return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) } // GetBookEventsByPageID обрабатывает запросы на получение событий книги по ID страницы. @@ -165,13 +181,18 @@ func (bec *Controller) GetBookEventsByChapterID(c echo.Context, ctx context.Cont // @param id path string true "ID страницы" // @success 200 {object} []models.BookEvent // @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByPageID(c echo.Context, ctx context.Context) error { +func (bec *Controller) GetBookEventsByPageID(c echo.Context) error { id := c.Param("id") - events, err := bec.service.GetBookEventsByPageID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + events, err := bec.Service.GetBookEventsByPageID(bec.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookEventNotFound } - return c.JSON(http.StatusOK, events) + + return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) } // GetBookEventsByParagraphID обрабатывает запросы на получение событий книги по ID параграфа. @@ -183,11 +204,16 @@ func (bec *Controller) GetBookEventsByPageID(c echo.Context, ctx context.Context // @param id path string true "ID параграфа" // @success 200 {object} []models.BookEvent // @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByParagraphID(c echo.Context, ctx context.Context) error { +func (bec *Controller) GetBookEventsByParagraphID(c echo.Context) error { id := c.Param("id") - events, err := bec.service.GetBookEventsByParagraphID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + events, err := bec.Service.GetBookEventsByParagraphID(bec.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorBookEventNotFound } - return c.JSON(http.StatusOK, events) + + return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) } diff --git a/bookback/internal/servers/http/controllers/bookevents/errors.go b/bookback/internal/servers/http/controllers/bookevents/errors.go new file mode 100644 index 0000000..f5151aa --- /dev/null +++ b/bookback/internal/servers/http/controllers/bookevents/errors.go @@ -0,0 +1,31 @@ +package bookevents + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// Возможные ошибки при работе с событиями книг. + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorBookEventNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Событие книги не найдено", + ) + ErrorBookEventNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания события книги. Событие с такими параметрами уже существует.", + ) + ErrorDeleteBookEvent = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления события книги", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/bookevents/models.go b/bookback/internal/servers/http/controllers/bookevents/models.go new file mode 100644 index 0000000..4bb9ab4 --- /dev/null +++ b/bookback/internal/servers/http/controllers/bookevents/models.go @@ -0,0 +1,20 @@ +package bookevents + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct{} + +type requestModel struct { + Options Options `json:"options"` + BookEvents *models.BookEvent `json:"book_events"` +} + +type responseSingleModel struct { + BookEvent *models.BookEvent `json:"book_event"` + Status string `json:"status"` +} + +type responseListModel struct { + BookEvents []models.BookEvent `json:"book_events"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/bookevents/routes.go b/bookback/internal/servers/http/controllers/bookevents/routes.go new file mode 100644 index 0000000..c8900cf --- /dev/null +++ b/bookback/internal/servers/http/controllers/bookevents/routes.go @@ -0,0 +1,33 @@ +package bookevents + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + bookeventsrepo "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" + + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetBookEventController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := bookeventsrepo.NewService(bookeventsrepo.NewRepository(database)) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (bec *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(bec.Metrics)) + + group.POST("", bec.CreateBookEvent) + group.GET("/:id", bec.GetBookEventByID) + group.PUT("/:id", bec.UpdateBookEvent) + group.DELETE("/:id", bec.DeleteBookEvent) + group.GET("/book/:id", bec.GetBookEventsByBookID) + group.GET("/chapter/:id", bec.GetBookEventsByChapterID) + group.GET("/page/:id", bec.GetBookEventsByPageID) + group.GET("/paragraph/:id", bec.GetBookEventsByParagraphID) +} diff --git a/bookback/internal/servers/http/controllers/chapter/config.go b/bookback/internal/servers/http/controllers/chapter/config.go new file mode 100644 index 0000000..de90e41 --- /dev/null +++ b/bookback/internal/servers/http/controllers/chapter/config.go @@ -0,0 +1,3 @@ +package chapter + +const pathPrefix = "/api/v1/chapters" diff --git a/bookback/internal/servers/http/controllers/chapter/controller.go b/bookback/internal/servers/http/controllers/chapter/controller.go index d6610e0..ded89ad 100644 --- a/bookback/internal/servers/http/controllers/chapter/controller.go +++ b/bookback/internal/servers/http/controllers/chapter/controller.go @@ -2,30 +2,26 @@ package chapter import ( "context" + "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/internal/services/chapter" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) type Controller struct { Service chapter.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -func NewController(srv chapter.Service) *Controller { - return &Controller{Service: srv} -} - -func (ch *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/chapters", func(c echo.Context) error { return ch.ListChapters(c, ctx) }) - e.POST("/api/v1/chapters", func(c echo.Context) error { return ch.CreateChapter(c, ctx) }) - e.GET("/api/v1/chapters/:id", func(c echo.Context) error { return ch.GetChapterByID(c, ctx) }) - e.PUT("/api/v1/chapters/:id", func(c echo.Context) error { return ch.UpdateChapter(c, ctx) }) - e.DELETE("/api/v1/chapters/:id", func(c echo.Context) error { return ch.DeleteChapter(c, ctx) }) - - e.GET("/api/v1/chapters/book/:id", func(c echo.Context) error { return ch.GetChapterByBookID(c, ctx) }) +// NewController создает новый экземпляр Controller. +func NewController(srv chapter.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // ListChapters список глав @@ -36,13 +32,12 @@ func (ch *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @produce application/json // @success 200 {array} models.Chapter // @failure 500 {object} config.HTTPError -func (ch *Controller) ListChapters(c echo.Context, ctx context.Context) error { - chapters, err := ch.Service.ListChapters(ctx) +func (ch *Controller) ListChapters(c echo.Context) error { + chapters, err := ch.Service.ListChapters(ch.Ctx) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) + return ErrorUnknown } - return c.JSON(http.StatusOK, chapters) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Chapters: chapters}) } // CreateChapter создание новой главы @@ -55,18 +50,17 @@ func (ch *Controller) ListChapters(c echo.Context, ctx context.Context) error { // @param chapter body models.Chapter true "Chapter object" // @success 201 {object} models.Chapter // @failure 400 {object} config.HTTPError -func (ch *Controller) CreateChapter(c echo.Context, ctx context.Context) error { - var chap models.Chapter +func (ch *Controller) CreateChapter(c echo.Context) error { + var chap requestModel if err := c.Bind(&chap); err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) + return ErrorValidationFailed } - createdChapter, err := ch.Service.CreateChapter(ctx, &chap) + + createdChapter, err := ch.Service.CreateChapter(ch.Ctx, chap.Chapter) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotCreated) + return ErrorChapterNotCreated } - return c.JSON(http.StatusCreated, createdChapter) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Chapter: createdChapter}) } // GetChapterByID получение главы по ID @@ -78,14 +72,17 @@ func (ch *Controller) CreateChapter(c echo.Context, ctx context.Context) error { // @produce application/json // @success 200 {object} models.Chapter // @failure 404 {object} config.HTTPError -func (ch *Controller) GetChapterByID(c echo.Context, ctx context.Context) error { +func (ch *Controller) GetChapterByID(c echo.Context) error { id := c.Param("id") - chapt, err := ch.Service.GetChapterByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + chapt, err := ch.Service.GetChapterByID(ch.Ctx, id) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorChapterNotFound } - return c.JSON(http.StatusOK, chapt) + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Chapter: chapt}) } // UpdateChapter обновление главы @@ -99,17 +96,26 @@ func (ch *Controller) GetChapterByID(c echo.Context, ctx context.Context) error // @param chapter body models.Chapter true "Chapter object" // @success 200 {object} models.Chapter // @failure 400 {object} config.HTTPError -func (ch *Controller) UpdateChapter(c echo.Context, ctx context.Context) error { +func (ch *Controller) UpdateChapter(c echo.Context) error { id := c.Param("id") - var chap models.Chapter - if err := c.Bind(&chap); err != nil { + if id == "" { + return ErrorValidationFailed + } + + var request requestModel + if err := c.Bind(&request); err != nil { return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) } - updatedChapter, err := ch.Service.UpdateChapter(ctx, id, &chap) + + updatedChapter, err := ch.Service.UpdateChapter(ch.Ctx, id, request.Chapter) if err != nil { - return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotUpdated) + if errors.Is(err, config.ErrorNotFound) { + return ErrorChapterNotFound + } + fmt.Println(err) + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedChapter) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Chapter: updatedChapter}) } // DeleteChapter удаление главы @@ -121,13 +127,17 @@ func (ch *Controller) UpdateChapter(c echo.Context, ctx context.Context) error { // @produce application/json // @success 200 {object} models.Chapter // @failure 406 {object} config.HTTPError -func (ch *Controller) DeleteChapter(c echo.Context, ctx context.Context) error { +func (ch *Controller) DeleteChapter(c echo.Context) error { id := c.Param("id") - chapt, err := ch.Service.DeleteChapter(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + chapt, err := ch.Service.DeleteChapter(ch.Ctx, id) if err != nil { return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotDeleted) } - return c.JSON(http.StatusOK, chapt) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Chapter: chapt}) } // GetChapterByBookID получение глав по ID книги @@ -139,11 +149,15 @@ func (ch *Controller) DeleteChapter(c echo.Context, ctx context.Context) error { // @produce application/json // @success 200 {array} models.Chapter // @failure 404 {object} config.HTTPError -func (ch *Controller) GetChapterByBookID(c echo.Context, ctx context.Context) error { +func (ch *Controller) GetChapterByBookID(c echo.Context) error { id := c.Param("id") - chapters, err := ch.Service.GetChapterByBookID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + chapters, err := ch.Service.GetChapterByBookID(ch.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorDeleteChapter } - return c.JSON(http.StatusOK, chapters) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Chapters: chapters}) } diff --git a/bookback/internal/servers/http/controllers/chapter/errors.go b/bookback/internal/servers/http/controllers/chapter/errors.go new file mode 100644 index 0000000..0e98c3e --- /dev/null +++ b/bookback/internal/servers/http/controllers/chapter/errors.go @@ -0,0 +1,31 @@ +package chapter + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// Возможные ошибки при работе с главами книги. + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorChapterNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Глава не найдена", + ) + ErrorChapterNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания главы. Глава с такими параметрами уже существует.", + ) + ErrorDeleteChapter = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления главы", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/chapter/models.go b/bookback/internal/servers/http/controllers/chapter/models.go new file mode 100644 index 0000000..cebb62c --- /dev/null +++ b/bookback/internal/servers/http/controllers/chapter/models.go @@ -0,0 +1,21 @@ +package chapter + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct { +} + +type requestModel struct { + Options Options `json:"options"` + Chapter *models.Chapter `json:"chapter"` +} + +type responseSingleModel struct { + Chapter *models.Chapter `json:"chapter"` + Status string `json:"status"` +} + +type responseListModel struct { + Chapters []models.Chapter `json:"chapters"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/chapter/routes.go b/bookback/internal/servers/http/controllers/chapter/routes.go new file mode 100644 index 0000000..1701b00 --- /dev/null +++ b/bookback/internal/servers/http/controllers/chapter/routes.go @@ -0,0 +1,33 @@ +package chapter + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + bookrepo "github.com/SShlykov/zeitment/bookback/internal/services/book" + chapterrepo "github.com/SShlykov/zeitment/bookback/internal/services/chapter" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetChapterController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + chapterRepo := chapterrepo.NewRepository(database) + bookRepo := bookrepo.NewRepository(database) + service := chapterrepo.NewService(chapterRepo, bookRepo) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (ch *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(pathPrefix) + group.Use(httpmiddlewares.MetricsLogger(ch.Metrics)) + + group.GET("", ch.ListChapters) + group.POST("", ch.CreateChapter) + group.GET("/:id", ch.GetChapterByID) + group.PUT("/:id", ch.UpdateChapter) + group.DELETE("/:id", ch.DeleteChapter) + group.GET("/book/:id", ch.GetChapterByBookID) +} diff --git a/bookback/internal/servers/http/controllers/health/config.go b/bookback/internal/servers/http/controllers/health/config.go new file mode 100644 index 0000000..a3c7e5b --- /dev/null +++ b/bookback/internal/servers/http/controllers/health/config.go @@ -0,0 +1,3 @@ +package health + +const PathPrefix = "/api/v1/health" diff --git a/bookback/internal/servers/http/controllers/health/controller.go b/bookback/internal/servers/http/controllers/health/controller.go index caadcc9..d80dec2 100644 --- a/bookback/internal/servers/http/controllers/health/controller.go +++ b/bookback/internal/servers/http/controllers/health/controller.go @@ -2,25 +2,21 @@ package health import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) -type Controller interface { - GetHealthCheck(c echo.Context) error - RegisterRoutes(e *echo.Echo, _ context.Context) +type Controller struct { + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -type healthController struct { -} - -// NewController is constructor. -func NewController() Controller { - return &healthController{} -} - -func (hc *healthController) RegisterRoutes(e *echo.Echo, _ context.Context) { - e.GET("/api/v1/health", hc.GetHealthCheck) +// NewController создает новый экземпляр Controller. +func NewController(metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Metrics: metric, Logger: logger, Ctx: ctx} } // GetHealthCheck возвращает статус приложения. @@ -31,6 +27,6 @@ func (hc *healthController) RegisterRoutes(e *echo.Echo, _ context.Context) { // @produce application/json // @success 200 {string} string "healthy" // @failure 500 {object} config.HTTPError -func (hc *healthController) GetHealthCheck(c echo.Context) error { +func (hc *Controller) GetHealthCheck(c echo.Context) error { return c.JSON(http.StatusOK, "healthy") } diff --git a/bookback/internal/servers/http/controllers/health/routes.go b/bookback/internal/servers/http/controllers/health/routes.go new file mode 100644 index 0000000..7ab912e --- /dev/null +++ b/bookback/internal/servers/http/controllers/health/routes.go @@ -0,0 +1,23 @@ +package health + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetHealthController(e *echo.Echo, _ db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + controller := NewController(metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (hc *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(hc.Metrics)) + + group.GET("/", hc.GetHealthCheck) +} diff --git a/bookback/internal/servers/http/controllers/mapvariables/config.go b/bookback/internal/servers/http/controllers/mapvariables/config.go new file mode 100644 index 0000000..d4be744 --- /dev/null +++ b/bookback/internal/servers/http/controllers/mapvariables/config.go @@ -0,0 +1,3 @@ +package mapvariables + +const PathPrefix = "/api/v1/mapvariables" diff --git a/bookback/internal/servers/http/controllers/mapvariables/controller.go b/bookback/internal/servers/http/controllers/mapvariables/controller.go index fe39b0a..176fa1f 100644 --- a/bookback/internal/servers/http/controllers/mapvariables/controller.go +++ b/bookback/internal/servers/http/controllers/mapvariables/controller.go @@ -2,48 +2,27 @@ package mapvariables import ( "context" + "errors" + "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/mapvariables" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) +// Controller структура для HTTP-контроллера книг. type Controller struct { - service service.Service + Service service.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -func NewController(service service.Service) *Controller { - return &Controller{ - service: service, - } -} - -func (mvc *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/mapvariables/:id", - func(c echo.Context) error { return mvc.GetMapVariableByID(c, ctx) }) - e.PUT("/api/v1/mapvariables/:id", - func(c echo.Context) error { return mvc.UpdateMapVariable(c, ctx) }) - e.DELETE("/api/v1/mapvariables/:id", - func(c echo.Context) error { return mvc.DeleteMapVariable(c, ctx) }) - e.POST("/api/v1/mapvariables", - func(c echo.Context) error { return mvc.CreateMapVariable(c, ctx) }) - e.GET("/api/v1/mapvariables/book/:id", - func(c echo.Context) error { return mvc.GetMapVariablesByBookID(c, ctx) }) - e.GET("/api/v1/mapvariables/chapter/:id", - func(c echo.Context) error { return mvc.GetMapVariablesByChapterID(c, ctx) }) - e.GET("/api/v1/mapvariables/page/:id", - func(c echo.Context) error { return mvc.GetMapVariablesByPageID(c, ctx) }) - e.GET("/api/v1/mapvariables/paragraph/:id", - func(c echo.Context) error { return mvc.GetMapVariablesByParagraphID(c, ctx) }) - e.GET("/api/v1/bookevents/maplink/:link/book/:id/", - func(c echo.Context) error { return mvc.GetMapVariablesByMapLinkAndBookID(c, ctx) }) - e.GET("/api/v1/bookevents/maplink/:link/chapter/:id/", - func(c echo.Context) error { return mvc.GetMapVariablesByMapLinkAndChapterID(c, ctx) }) - e.GET("/api/v1/bookevents/maplink/:link/page/:id/", - func(c echo.Context) error { return mvc.GetMapVariablesByMapLinkAndPageID(c, ctx) }) - e.GET("/api/v1/bookevents/maplink/:link/paragraph/:id/", - func(c echo.Context) error { return mvc.GetMapVariablesByMapLinkAndParagraphID(c, ctx) }) +// NewController создает новый экземпляр Controller. +func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // GetMapVariableByID обрабатывает запросы на получение переменной карты по идентификатору. @@ -55,14 +34,19 @@ func (mvc *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @param id path string true "ID переменной карты" // @success 200 {object} models.MapVariable // @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariableByID(c echo.Context, ctx context.Context) error { +func (mvc *Controller) GetMapVariableByID(c echo.Context) error { id := c.Param("id") - variable, err := mvc.service.GetMapVariableByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + variable, err := mvc.Service.GetMapVariableByID(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + fmt.Println(err) + return ErrorMapVariableNotFound } - return c.JSON(http.StatusOK, variable) + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", MapVariable: variable}) } // UpdateMapVariable обрабатывает запросы на обновление переменной карты. @@ -76,19 +60,26 @@ func (mvc *Controller) GetMapVariableByID(c echo.Context, ctx context.Context) e // @param variable body models.MapVariable true "MapVariable object" // @success 200 {object} models.MapVariable // @failure 400 {object} config.HTTPError -func (mvc *Controller) UpdateMapVariable(c echo.Context, ctx context.Context) error { +func (mvc *Controller) UpdateMapVariable(c echo.Context) error { id := c.Param("id") - var variable models.MapVariable - if err := c.Bind(&variable); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) + if id == "" { + return ErrorValidationFailed + } + + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - updatedVariable, err := mvc.service.UpdateMapVariable(ctx, id, &variable) + updatedVariable, err := mvc.Service.UpdateMapVariable(mvc.Ctx, id, request.MapVariables) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorForbidden) + if errors.Is(err, config.ErrorNotFound) { + return ErrorMapVariableNotFound + } + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedVariable) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", MapVariable: updatedVariable}) } // DeleteMapVariable обрабатывает запросы на удаление переменной карты. @@ -99,13 +90,18 @@ func (mvc *Controller) UpdateMapVariable(c echo.Context, ctx context.Context) er // @param id path string true "ID переменной карты" // @success 204 // @failure 404 {object} config.HTTPError -func (mvc *Controller) DeleteMapVariable(c echo.Context, ctx context.Context) error { +func (mvc *Controller) DeleteMapVariable(c echo.Context) error { id := c.Param("id") - if _, err := mvc.service.DeleteMapVariable(ctx, id); err != nil { + if id == "" { + return ErrorValidationFailed + } + + mapVariable, err := mvc.Service.DeleteMapVariable(mvc.Ctx, id) + if err != nil { return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) } - return c.NoContent(http.StatusNoContent) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", MapVariable: mapVariable}) } // GetMapVariablesByBookID обрабатывает запросы на получение переменных карты по идентификатору книги. @@ -117,14 +113,18 @@ func (mvc *Controller) DeleteMapVariable(c echo.Context, ctx context.Context) er // @param id path string true "ID книги" // @success 200 {array} models.MapVariable // @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByBookID(c echo.Context, ctx context.Context) error { +func (mvc *Controller) GetMapVariablesByBookID(c echo.Context) error { id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByBookID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + variables, err := mvc.Service.GetMapVariablesByBookID(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorMapVariableNotFound } - return c.JSON(http.StatusOK, variables) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) } // GetMapVariablesByChapterID обрабатывает запросы на получение переменных карты по идентификатору главы. @@ -136,14 +136,18 @@ func (mvc *Controller) GetMapVariablesByBookID(c echo.Context, ctx context.Conte // @param id path string true "ID главы" // @success 200 {array} models.MapVariable // @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context, ctx context.Context) error { +func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context) error { id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByChapterID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + variables, err := mvc.Service.GetMapVariablesByChapterID(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorMapVariableNotFound } - return c.JSON(http.StatusOK, variables) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) } // GetMapVariablesByPageID обрабатывает запросы на получение переменных карты по идентификатору страницы. @@ -155,14 +159,18 @@ func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context, ctx context.Co // @param id path string true "ID страницы" // @success 200 {array} models.MapVariable // @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByPageID(c echo.Context, ctx context.Context) error { +func (mvc *Controller) GetMapVariablesByPageID(c echo.Context) error { id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByPageID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + variables, err := mvc.Service.GetMapVariablesByPageID(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorMapVariableNotFound } - return c.JSON(http.StatusOK, variables) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) } // GetMapVariablesByParagraphID обрабатывает запросы на получение переменных карты по идентификатору параграфа. @@ -174,14 +182,18 @@ func (mvc *Controller) GetMapVariablesByPageID(c echo.Context, ctx context.Conte // @param id path string true "ID параграфа" // @success 200 {array} models.MapVariable // @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context, ctx context.Context) error { +func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context) error { id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByParagraphID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + variables, err := mvc.Service.GetMapVariablesByParagraphID(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorMapVariableNotFound } - return c.JSON(http.StatusOK, variables) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) } // CreateMapVariable обрабатывает создание новой переменной карты. @@ -194,104 +206,16 @@ func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context, ctx context. // @param variable body models.MapVariable true "MapVariable object" // @success 201 {object} models.MapVariable // @failure 400 {object} config.HTTPError -func (mvc *Controller) CreateMapVariable(c echo.Context, ctx context.Context) error { - var variable models.MapVariable - if err := c.Bind(&variable); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +func (mvc *Controller) CreateMapVariable(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - createdVariable, err := mvc.service.CreateMapVariable(ctx, &variable) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorForbidden) - } - - return c.JSON(http.StatusCreated, createdVariable) -} - -// GetMapVariablesByMapLinkAndBookID обрабатывает запросы на получение переменных карты по идентификатору карты -// и идентификатору книги. -// @router /bookevents/maplink/{link}/book/{id} [get] -// @summary Получить переменные карты по идентификатору карты и идентификатору книги -// @description Извлекает переменные карты по идентификатору карты и идентификатору книги -// @tags Переменные карты -// @produce application/json -// @param link path string true "ID карты" -// @param id path string true "ID книги" -// @success 200 {array} models.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByMapLinkAndBookID(c echo.Context, ctx context.Context) error { - link := c.Param("link") - id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByMapLinkAndBookID(ctx, link, id) + createdVariable, err := mvc.Service.CreateMapVariable(mvc.Ctx, request.MapVariables) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) - } - - return c.JSON(http.StatusOK, variables) -} - -// GetMapVariablesByMapLinkAndChapterID обрабатывает запросы на получение переменных карты по идентификатору карты -// и идентификатору главы. -// @router /bookevents/maplink/{link}/chapter/{id} [get] -// @summary Получить переменные карты по идентификатору карты и идентификатору главы -// @description Извлекает переменные карты по идентификатору карты и идентификатору главы -// @tags Переменные карты -// @produce application/json -// @param link path string true "ID карты" -// @param id path string true "ID главы" -// @success 200 {array} models.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByMapLinkAndChapterID(c echo.Context, ctx context.Context) error { - link := c.Param("link") - id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByMapLinkAndChapterID(ctx, link, id) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) - } - - return c.JSON(http.StatusOK, variables) -} - -// GetMapVariablesByMapLinkAndPageID обрабатывает запросы на получение переменных карты по идентификатору карты -// и идентификатору страницы. -// @router /bookevents/maplink/{link}/page/{id} [get] -// @summary Получить переменные карты по идентификатору карты и идентификатору страницы -// @description Извлекает переменные карты по идентификатору карты и идентификатору страницы -// @tags Переменные карты -// @produce application/json -// @param link path string true "ID карты" -// @param id path string true "ID страницы" -// @success 200 {array} models.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByMapLinkAndPageID(c echo.Context, ctx context.Context) error { - link := c.Param("link") - id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByMapLinkAndPageID(ctx, link, id) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) - } - - return c.JSON(http.StatusOK, variables) -} - -// GetMapVariablesByMapLinkAndParagraphID обрабатывает запросы на получение переменных карты по идентификатору карты -// и идентификатору параграфа. -// @router /bookevents/maplink/{link}/paragraph/{id} [get] -// @summary Получить переменные карты по идентификатору карты и идентификатору параграфа -// @description Извлекает переменные карты по идентификатору карты и идентификатору параграфа -// @tags Переменные карты -// @produce application/json -// @param link path string true "ID карты" -// @param id path string true "ID параграфа" -// @success 200 {array} models.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByMapLinkAndParagraphID(c echo.Context, ctx context.Context) error { - link := c.Param("link") - id := c.Param("id") - variables, err := mvc.service.GetMapVariablesByMapLinkAndParagraphID(ctx, link, id) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorMapVariableNotCreated } - return c.JSON(http.StatusOK, variables) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", MapVariable: createdVariable}) } diff --git a/bookback/internal/servers/http/controllers/mapvariables/errors.go b/bookback/internal/servers/http/controllers/mapvariables/errors.go new file mode 100644 index 0000000..672992d --- /dev/null +++ b/bookback/internal/servers/http/controllers/mapvariables/errors.go @@ -0,0 +1,29 @@ +package mapvariables + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorMapVariableNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Переменная не найдена", + ) + ErrorMapVariableNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания переменной. Переменная с такими параметрами уже существует.", + ) + ErrorDeleteMapVariable = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления переменной", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/mapvariables/models.go b/bookback/internal/servers/http/controllers/mapvariables/models.go new file mode 100644 index 0000000..07d0511 --- /dev/null +++ b/bookback/internal/servers/http/controllers/mapvariables/models.go @@ -0,0 +1,20 @@ +package mapvariables + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct{} + +type requestModel struct { + Options Options `json:"options"` + MapVariables *models.MapVariable `json:"map_variables"` +} + +type responseSingleModel struct { + MapVariable *models.MapVariable `json:"map_variable"` + Status string `json:"status"` +} + +type responseListModel struct { + MapVariables []models.MapVariable `json:"map_variables"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/mapvariables/routes.go b/bookback/internal/servers/http/controllers/mapvariables/routes.go new file mode 100644 index 0000000..7adcc95 --- /dev/null +++ b/bookback/internal/servers/http/controllers/mapvariables/routes.go @@ -0,0 +1,32 @@ +package mapvariables + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + mapvariablesrepo "github.com/SShlykov/zeitment/bookback/internal/services/mapvariables" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetMapVariablesController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := mapvariablesrepo.NewService(mapvariablesrepo.NewRepository(database)) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (mvc *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(mvc.Metrics)) + + group.GET("/:id", mvc.GetMapVariableByID) + group.PUT("/:id", mvc.UpdateMapVariable) + group.DELETE("/:id", mvc.DeleteMapVariable) + group.POST("", mvc.CreateMapVariable) + group.GET("/book/:id", mvc.GetMapVariablesByBookID) + group.GET("/chapter/:id", mvc.GetMapVariablesByChapterID) + group.GET("/page/:id", mvc.GetMapVariablesByPageID) + group.GET("/paragraph/:id", mvc.GetMapVariablesByParagraphID) +} diff --git a/bookback/internal/servers/http/controllers/page/config.go b/bookback/internal/servers/http/controllers/page/config.go new file mode 100644 index 0000000..940710e --- /dev/null +++ b/bookback/internal/servers/http/controllers/page/config.go @@ -0,0 +1,3 @@ +package page + +const PathPrefix = "/api/v1/pages" diff --git a/bookback/internal/servers/http/controllers/page/controller.go b/bookback/internal/servers/http/controllers/page/controller.go index 452ce32..034ef87 100644 --- a/bookback/internal/servers/http/controllers/page/controller.go +++ b/bookback/internal/servers/http/controllers/page/controller.go @@ -2,30 +2,24 @@ package page import ( "context" - "fmt" + "errors" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/page" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) type Controller struct { Service service.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -func NewController(srv service.Service) *Controller { - return &Controller{Service: srv} -} - -func (p *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/pages", func(c echo.Context) error { return p.ListPages(c, ctx) }) - e.POST("/api/v1/pages", func(c echo.Context) error { return p.CreatePage(c, ctx) }) - e.GET("/api/v1/pages/:id", func(c echo.Context) error { return p.GetPageByID(c, ctx) }) - e.PUT("/api/v1/pages/:id", func(c echo.Context) error { return p.UpdatePage(c, ctx) }) - e.DELETE("/api/v1/pages/:id", func(c echo.Context) error { return p.DeletePage(c, ctx) }) - - e.GET("/api/v1/chapters/:id/pages", func(c echo.Context) error { return p.GetPagesByChapterID(c, ctx) }) +func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // ListPages список страниц @@ -36,13 +30,12 @@ func (p *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @produce application/json // @success 200 {array} models.Page // @failure 500 {object} config.HTTPError -func (p *Controller) ListPages(c echo.Context, ctx context.Context) error { - pages, err := p.Service.ListPages(ctx) +func (p *Controller) ListPages(c echo.Context) error { + pages, err := p.Service.ListPages(p.Ctx) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) + return ErrorUnknown } - return c.JSON(http.StatusOK, pages) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Pages: pages}) } // CreatePage создание новой страницы @@ -55,16 +48,17 @@ func (p *Controller) ListPages(c echo.Context, ctx context.Context) error { // @param page body models.Page true "Page object" // @success 201 {object} models.Page // @failure 400 {object} config.HTTPError -func (p *Controller) CreatePage(c echo.Context, ctx context.Context) error { - var page models.Page - if err := c.Bind(&page); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +func (p *Controller) CreatePage(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - createdPage, err := p.Service.CreatePage(ctx, &page) + + createdPage, err := p.Service.CreatePage(p.Ctx, request.Page) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotCreated) + return ErrorPageNotCreated } - return c.JSON(http.StatusCreated, createdPage) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Page: createdPage}) } // GetPageByID получение страницы по ID @@ -76,13 +70,18 @@ func (p *Controller) CreatePage(c echo.Context, ctx context.Context) error { // @param id path string true "ID страницы" // @success 200 {object} models.Page // @failure 404 {object} config.HTTPError -func (p *Controller) GetPageByID(c echo.Context, ctx context.Context) error { +func (p *Controller) GetPageByID(c echo.Context) error { id := c.Param("id") - page, err := p.Service.GetPageByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + page, err := p.Service.GetPageByID(p.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorPageNotFound } - return c.JSON(http.StatusOK, page) + + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Page: page}) } // UpdatePage обновление страницы @@ -96,18 +95,25 @@ func (p *Controller) GetPageByID(c echo.Context, ctx context.Context) error { // @param page body models.Page true "Page object" // @success 200 {object} models.Page // @failure 400 {object} config.HTTPError -func (p *Controller) UpdatePage(c echo.Context, ctx context.Context) error { +func (p *Controller) UpdatePage(c echo.Context) error { id := c.Param("id") - var page models.Page - if err := c.Bind(&page); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) + if id == "" { + return ErrorValidationFailed } - updatedPage, err := p.Service.UpdatePage(ctx, id, &page) + + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + + updatedPage, err := p.Service.UpdatePage(p.Ctx, id, request.Page) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotUpdated) + if errors.Is(err, config.ErrorNotFound) { + return ErrorPageNotFound + } + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedPage) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Page: updatedPage}) } // DeletePage удаление страницы @@ -119,14 +125,17 @@ func (p *Controller) UpdatePage(c echo.Context, ctx context.Context) error { // @produce application/json // @success 200 {object} models.Page // @failure 500 {object} config.HTTPError -func (p *Controller) DeletePage(c echo.Context, ctx context.Context) error { +func (p *Controller) DeletePage(c echo.Context) error { id := c.Param("id") - deletedPage, err := p.Service.DeletePage(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + deletedPage, err := p.Service.DeletePage(p.Ctx, id) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotDeleted) + return ErrorDeletePage } - return c.JSON(http.StatusOK, deletedPage) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Page: deletedPage}) } // GetPagesByChapterID получение страниц по ID главы @@ -138,11 +147,15 @@ func (p *Controller) DeletePage(c echo.Context, ctx context.Context) error { // @param id path string true "ID главы" // @success 200 {array} models.Page // @failure 404 {object} config.HTTPError -func (p *Controller) GetPagesByChapterID(c echo.Context, ctx context.Context) error { +func (p *Controller) GetPagesByChapterID(c echo.Context) error { id := c.Param("id") - pages, err := p.Service.GetPagesByChapterID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + pages, err := p.Service.GetPagesByChapterID(p.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorPageNotFound } - return c.JSON(http.StatusOK, pages) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Pages: pages}) } diff --git a/bookback/internal/servers/http/controllers/page/errors.go b/bookback/internal/servers/http/controllers/page/errors.go new file mode 100644 index 0000000..38e1e3b --- /dev/null +++ b/bookback/internal/servers/http/controllers/page/errors.go @@ -0,0 +1,31 @@ +package page + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// Возможные ошибки, которые могут возникнуть при работе с контроллером страниц. + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorPageNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Страница не найдена", + ) + ErrorPageNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания страницы. Страница с такими параметрами уже существует.", + ) + ErrorDeletePage = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления страницы", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/page/models.go b/bookback/internal/servers/http/controllers/page/models.go new file mode 100644 index 0000000..75b06d4 --- /dev/null +++ b/bookback/internal/servers/http/controllers/page/models.go @@ -0,0 +1,21 @@ +package page + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct { +} + +type requestModel struct { + Options Options `json:"options"` + Page *models.Page `json:"page"` +} + +type responseSingleModel struct { + Page *models.Page `json:"page"` + Status string `json:"status"` +} + +type responseListModel struct { + Pages []models.Page `json:"pages"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/page/routes.go b/bookback/internal/servers/http/controllers/page/routes.go new file mode 100644 index 0000000..c1e0c53 --- /dev/null +++ b/bookback/internal/servers/http/controllers/page/routes.go @@ -0,0 +1,30 @@ +package page + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + pagerepo "github.com/SShlykov/zeitment/bookback/internal/services/page" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetPageController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := pagerepo.NewService(pagerepo.NewRepository(database)) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (p *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(p.Metrics)) + + group.GET("", p.ListPages) + group.POST("", p.CreatePage) + group.GET("/:id", p.GetPageByID) + group.PUT("/:id", p.UpdatePage) + group.DELETE("/:id", p.DeletePage) + group.GET("/chapters/:id", p.GetPagesByChapterID) +} diff --git a/bookback/internal/servers/http/controllers/paragraph/config.go b/bookback/internal/servers/http/controllers/paragraph/config.go new file mode 100644 index 0000000..fdea98e --- /dev/null +++ b/bookback/internal/servers/http/controllers/paragraph/config.go @@ -0,0 +1,3 @@ +package paragraph + +const PathPrefix = "/api/v1/paragraphs" diff --git a/bookback/internal/servers/http/controllers/paragraph/controller.go b/bookback/internal/servers/http/controllers/paragraph/controller.go index ebdd52c..e66bff4 100644 --- a/bookback/internal/servers/http/controllers/paragraph/controller.go +++ b/bookback/internal/servers/http/controllers/paragraph/controller.go @@ -2,30 +2,25 @@ package paragraph import ( "context" - "fmt" + "errors" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/paragraph" "github.com/labstack/echo/v4" + "log/slog" "net/http" ) type Controller struct { Service service.Service + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context } -func NewController(srv service.Service) *Controller { - return &Controller{Service: srv} -} - -func (p *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { - e.GET("/api/v1/paragraphs", func(c echo.Context) error { return p.ListParagraphs(c, ctx) }) - e.POST("/api/v1/paragraphs", func(c echo.Context) error { return p.CreateParagraph(c, ctx) }) - e.GET("/api/v1/paragraphs/:id", func(c echo.Context) error { return p.GetParagraphByID(c, ctx) }) - e.PUT("/api/v1/paragraphs/:id", func(c echo.Context) error { return p.UpdateParagraph(c, ctx) }) - e.DELETE("/api/v1/paragraphs/:id", func(c echo.Context) error { return p.DeleteParagraph(c, ctx) }) - - e.GET("/api/v1/pages/:id/paragraphs", func(c echo.Context) error { return p.GetParagraphsByPageID(c, ctx) }) +// NewController создает новый экземпляр Controller. +func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { + return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } // ListParagraphs список параграфов @@ -36,13 +31,12 @@ func (p *Controller) RegisterRoutes(e *echo.Echo, ctx context.Context) { // @produce application/json // @success 200 {array} models.Paragraph // @failure 500 {object} config.HTTPError -func (p *Controller) ListParagraphs(c echo.Context, ctx context.Context) error { - paragraphs, err := p.Service.ListParagraphs(ctx) +func (p *Controller) ListParagraphs(c echo.Context) error { + paragraphs, err := p.Service.ListParagraphs(p.Ctx) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusBadGateway, config.ErrorForbidden) + return ErrorUnknown } - return c.JSON(http.StatusOK, paragraphs) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Paragraphs: paragraphs}) } // CreateParagraph создание нового параграфа @@ -55,17 +49,17 @@ func (p *Controller) ListParagraphs(c echo.Context, ctx context.Context) error { // @param paragraph body models.Paragraph true "Paragraph object" // @success 201 {object} models.Paragraph // @failure 400 {object} config.HTTPError -func (p *Controller) CreateParagraph(c echo.Context, ctx context.Context) error { - var paragraph models.Paragraph - if err := c.Bind(¶graph); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) +func (p *Controller) CreateParagraph(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed } - createdParagraph, err := p.Service.CreateParagraph(ctx, ¶graph) + + createdParagraph, err := p.Service.CreateParagraph(p.Ctx, request.Paragraph) if err != nil { - fmt.Println(err) - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotCreated) + return ErrorParagraphNotFound } - return c.JSON(http.StatusCreated, createdParagraph) + return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Paragraph: createdParagraph}) } // GetParagraphByID получение параграфа по идентификатору @@ -77,13 +71,18 @@ func (p *Controller) CreateParagraph(c echo.Context, ctx context.Context) error // @produce application/json // @success 200 {object} models.Paragraph // @failure 404 {object} config.HTTPError -func (p *Controller) GetParagraphByID(c echo.Context, ctx context.Context) error { +func (p *Controller) GetParagraphByID(c echo.Context) error { id := c.Param("id") - paragraph, err := p.Service.GetParagraphByID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + paragraph, err := p.Service.GetParagraphByID(p.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorParagraphNotFound } - return c.JSON(http.StatusOK, paragraph) + + return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Paragraph: paragraph}) } // UpdateParagraph обновление параграфа @@ -97,17 +96,25 @@ func (p *Controller) GetParagraphByID(c echo.Context, ctx context.Context) error // @param paragraph body models.Paragraph true "Paragraph object" // @success 200 {object} models.Paragraph // @failure 400 {object} config.HTTPError -func (p *Controller) UpdateParagraph(c echo.Context, ctx context.Context) error { +func (p *Controller) UpdateParagraph(c echo.Context) error { id := c.Param("id") - var paragraph models.Paragraph - if err := c.Bind(¶graph); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) + if id == "" { + return ErrorValidationFailed } - updatedParagraph, err := p.Service.UpdateParagraph(ctx, id, ¶graph) + + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + + updatedParagraph, err := p.Service.UpdateParagraph(p.Ctx, id, request.Paragraph) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotUpdated) + if errors.Is(err, config.ErrorNotFound) { + return ErrorParagraphNotFound + } + return ErrorUnknown } - return c.JSON(http.StatusOK, updatedParagraph) + return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Paragraph: updatedParagraph}) } // DeleteParagraph удаление параграфа @@ -118,17 +125,21 @@ func (p *Controller) UpdateParagraph(c echo.Context, ctx context.Context) error // @param id path string true "ID параграфа" // @success 200 {object} models.Paragraph // @failure 500 {object} config.HTTPError -func (p *Controller) DeleteParagraph(c echo.Context, ctx context.Context) error { +func (p *Controller) DeleteParagraph(c echo.Context) error { id := c.Param("id") - deletedParagraph, err := p.Service.DeleteParagraph(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + deletedParagraph, err := p.Service.DeleteParagraph(p.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, config.ErrorNotDeleted) + return ErrorDeleteParagraph } - return c.JSON(http.StatusOK, deletedParagraph) + return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Paragraph: deletedParagraph}) } // GetParagraphsByPageID получение параграфов по ID страницы -// @router /pages/{id}/paragraphs [get] +// @router /paragraphs/pages/{id} [get] // @summary Получить параграфы по ID страницы // @description Извлекает параграфы по ID страницы // @tags Параграфы @@ -136,11 +147,15 @@ func (p *Controller) DeleteParagraph(c echo.Context, ctx context.Context) error // @produce application/json // @success 200 {array} models.Paragraph // @failure 404 {object} config.HTTPError -func (p *Controller) GetParagraphsByPageID(c echo.Context, ctx context.Context) error { +func (p *Controller) GetParagraphsByPageID(c echo.Context) error { id := c.Param("id") - paragraphs, err := p.Service.GetParagraphsByPageID(ctx, id) + if id == "" { + return ErrorValidationFailed + } + + paragraphs, err := p.Service.GetParagraphsByPageID(p.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return ErrorParagraphNotFound } - return c.JSON(http.StatusOK, paragraphs) + return c.JSON(http.StatusOK, responseListModel{Status: "ok", Paragraphs: paragraphs}) } diff --git a/bookback/internal/servers/http/controllers/paragraph/errors.go b/bookback/internal/servers/http/controllers/paragraph/errors.go new file mode 100644 index 0000000..28dc31f --- /dev/null +++ b/bookback/internal/servers/http/controllers/paragraph/errors.go @@ -0,0 +1,31 @@ +package paragraph + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// Возможные ошибки при работе с книгами. + +var ( + ErrorValidationFailed = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", + ) + ErrorParagraphNotFound = echo.NewHTTPError( + http.StatusNotFound, + "Параграф не найдена", + ) + ErrorParagraphNotCreated = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка создания параграфа. Параграф с такими параметрами уже существует.", + ) + ErrorDeleteParagraph = echo.NewHTTPError( + http.StatusBadRequest, + "Ошибка удаления параграфа", + ) + ErrorUnknown = echo.NewHTTPError( + http.StatusInternalServerError, + "Неизвестная ошибка", + ) +) diff --git a/bookback/internal/servers/http/controllers/paragraph/models.go b/bookback/internal/servers/http/controllers/paragraph/models.go new file mode 100644 index 0000000..6c57dd2 --- /dev/null +++ b/bookback/internal/servers/http/controllers/paragraph/models.go @@ -0,0 +1,21 @@ +package paragraph + +import "github.com/SShlykov/zeitment/bookback/internal/models" + +type Options struct { +} + +type requestModel struct { + Options Options `json:"options"` + Paragraph *models.Paragraph `json:"paragraph"` +} + +type responseSingleModel struct { + Paragraph *models.Paragraph `json:"paragraph"` + Status string `json:"status"` +} + +type responseListModel struct { + Paragraphs []models.Paragraph `json:"paragraphs"` + Status string `json:"status"` +} diff --git a/bookback/internal/servers/http/controllers/paragraph/routes.go b/bookback/internal/servers/http/controllers/paragraph/routes.go new file mode 100644 index 0000000..e644b98 --- /dev/null +++ b/bookback/internal/servers/http/controllers/paragraph/routes.go @@ -0,0 +1,30 @@ +package paragraph + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" + paragraphrepo "github.com/SShlykov/zeitment/bookback/internal/services/paragraph" + "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetParagraphController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := paragraphrepo.NewService(paragraphrepo.NewRepository(database)) + controller := NewController(service, metrics, logger, ctx) + + controller.RegisterRoutes(e) +} + +func (p *Controller) RegisterRoutes(e *echo.Echo) { + group := e.Group(PathPrefix) + group.Use(httpmiddlewares.MetricsLogger(p.Metrics)) + + group.GET("", p.ListParagraphs) + group.POST("", p.CreateParagraph) + group.GET("/:id", p.GetParagraphByID) + group.PUT("/:id", p.UpdateParagraph) + group.DELETE("/:id", p.DeleteParagraph) + group.GET("/pages/:id", p.GetParagraphsByPageID) +} diff --git a/bookback/internal/servers/http/controllers/swagger/controller.go b/bookback/internal/servers/http/controllers/swagger/controller.go new file mode 100644 index 0000000..74f598b --- /dev/null +++ b/bookback/internal/servers/http/controllers/swagger/controller.go @@ -0,0 +1,12 @@ +package swagger + +import ( + "github.com/labstack/echo/v4" + echoSwagger "github.com/swaggo/echo-swagger" +) + +func SetSwagger(e *echo.Echo, swaggerEnabled bool) { + if swaggerEnabled { + e.GET("/swagger/*", echoSwagger.WrapHandler) + } +} diff --git a/bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go b/bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go new file mode 100644 index 0000000..b105dd9 --- /dev/null +++ b/bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go @@ -0,0 +1,28 @@ +package httpmiddlewares + +import ( + "errors" + "github.com/SShlykov/zeitment/bookback/internal/servers/http/circuitbreaker" + "github.com/labstack/echo/v4" + "net/http" +) + +func CreateCircuitBreakerMiddleware(cb *circuitbreaker.CircuitBreaker) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + err := cb.Execute(func() error { + return next(c) + }) + + if err != nil { + if errors.Is(err, circuitbreaker.ErrorCb) { + return c.JSON(http.StatusUnavailableForLegalReasons, + map[string]string{"error": "Server is overloaded, please try again later.", "status": "error"}) + } + return err + } + + return nil + } + } +} diff --git a/bookback/internal/servers/http/httpmiddlewares/cors.go b/bookback/internal/servers/http/httpmiddlewares/cors.go new file mode 100644 index 0000000..c1d6017 --- /dev/null +++ b/bookback/internal/servers/http/httpmiddlewares/cors.go @@ -0,0 +1,28 @@ +package httpmiddlewares + +import ( + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "net/http" +) + +func CORS() echo.MiddlewareFunc { + return middleware.CORSWithConfig(middleware.CORSConfig{ + AllowCredentials: true, + UnsafeWildcardOriginWithAllowCredentials: true, + AllowOrigins: []string{"*"}, + AllowHeaders: []string{ + echo.HeaderAccessControlAllowHeaders, + echo.HeaderContentType, + echo.HeaderContentLength, + echo.HeaderAcceptEncoding, + }, + AllowMethods: []string{ + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodDelete, + }, + MaxAge: 86400, + }) +} diff --git a/bookback/internal/servers/http/httpmiddlewares/httplogger.go b/bookback/internal/servers/http/httpmiddlewares/httplogger.go new file mode 100644 index 0000000..6ddb637 --- /dev/null +++ b/bookback/internal/servers/http/httpmiddlewares/httplogger.go @@ -0,0 +1,32 @@ +package httpmiddlewares + +import ( + "context" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log/slog" +) + +func LoggerConfiguration(logger *slog.Logger) echo.MiddlewareFunc { + return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + LogError: true, + HandleError: true, + LogValuesFunc: func(_ echo.Context, v middleware.RequestLoggerValues) error { + if v.Error == nil { + logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", + slog.String("uri", v.URI), + slog.Int("status", v.Status), + ) + } else { + logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR", + slog.String("uri", v.URI), + slog.Int("status", v.Status), + slog.String("err", v.Error.Error()), + ) + } + return nil + }, + }) +} diff --git a/bookback/internal/servers/http/httpmiddlewares/middleware.go b/bookback/internal/servers/http/httpmiddlewares/middleware.go new file mode 100644 index 0000000..f5fad9f --- /dev/null +++ b/bookback/internal/servers/http/httpmiddlewares/middleware.go @@ -0,0 +1,29 @@ +package httpmiddlewares + +import ( + "fmt" + "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/labstack/echo/v4" + "time" +) + +func MetricsLogger(metrics metrics.Metrics) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + start := time.Now() + + err := next(c) + + duration := time.Since(start) + + req, res := c.Request(), c.Response() + + // Метрики + status := fmt.Sprintf("%d", res.Status) + metrics.IncCounter("http_requests_total", "method", req.Method, "path", req.URL.Path, "status", status) + metrics.ObserveHistogram("http_request_duration_seconds", duration.Seconds(), "method", req.Method, "path", req.URL.Path) + + return err + } + } +} diff --git a/bookback/internal/servers/http/router/router.go b/bookback/internal/servers/http/router/router.go deleted file mode 100644 index 5553f33..0000000 --- a/bookback/internal/servers/http/router/router.go +++ /dev/null @@ -1,83 +0,0 @@ -package router - -import ( - "context" - _ "github.com/SShlykov/zeitment/bookback/docs" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/book" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/chapter" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/health" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/page" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/paragraph" - bookrepo "github.com/SShlykov/zeitment/bookback/internal/services/book" - bookeventsrepo "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" - chapterrepo "github.com/SShlykov/zeitment/bookback/internal/services/chapter" - mapvariablesrepo "github.com/SShlykov/zeitment/bookback/internal/services/mapvariables" - pagerepo "github.com/SShlykov/zeitment/bookback/internal/services/page" - paragraphrepo "github.com/SShlykov/zeitment/bookback/internal/services/paragraph" - "github.com/SShlykov/zeitment/bookback/pkg/db" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - echoSwagger "github.com/swaggo/echo-swagger" - "net/http" -) - -func SetCORSConfig(e *echo.Echo, corsEnabled bool) { - if corsEnabled { - e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ - AllowCredentials: true, - UnsafeWildcardOriginWithAllowCredentials: true, - AllowOrigins: []string{"*"}, - AllowHeaders: []string{ - echo.HeaderAccessControlAllowHeaders, - echo.HeaderContentType, - echo.HeaderContentLength, - echo.HeaderAcceptEncoding, - }, - AllowMethods: []string{ - http.MethodGet, - http.MethodPost, - http.MethodPut, - http.MethodDelete, - }, - MaxAge: 86400, - })) - } -} - -func SetHealthController(e *echo.Echo, ctx context.Context) { - health.NewController().RegisterRoutes(e, ctx) -} - -func SetBookController(e *echo.Echo, database db.Client, ctx context.Context) { - book.NewController(bookrepo.NewService(bookrepo.NewRepository(database))).RegisterRoutes(e, ctx) -} - -func SetPageController(e *echo.Echo, database db.Client, ctx context.Context) { - page.NewController(pagerepo.NewService(pagerepo.NewRepository(database))).RegisterRoutes(e, ctx) -} - -func SetChapterController(e *echo.Echo, database db.Client, ctx context.Context) { - chapterRepo := chapterrepo.NewRepository(database) - bookRepo := bookrepo.NewRepository(database) - chapter.NewController(chapterrepo.NewService(chapterRepo, bookRepo)).RegisterRoutes(e, ctx) -} - -func SetParagraphController(e *echo.Echo, database db.Client, ctx context.Context) { - paragraph.NewController(paragraphrepo.NewService(paragraphrepo.NewRepository(database))).RegisterRoutes(e, ctx) -} - -func SetMapVariablesController(e *echo.Echo, database db.Client, ctx context.Context) { - mapvariables.NewController(mapvariablesrepo.NewService(mapvariablesrepo.NewRepository(database))).RegisterRoutes(e, ctx) -} - -func SetBookEventController(e *echo.Echo, database db.Client, ctx context.Context) { - bookevents.NewController(bookeventsrepo.NewService(bookeventsrepo.NewRepository(database))).RegisterRoutes(e, ctx) -} - -func SetSwagger(e *echo.Echo, swaggerEnabled bool) { - if swaggerEnabled { - e.GET("/swagger/*", echoSwagger.WrapHandler) - } -} diff --git a/bookback/internal/servers/http/server.go b/bookback/internal/servers/http/server.go deleted file mode 100644 index c441750..0000000 --- a/bookback/internal/servers/http/server.go +++ /dev/null @@ -1,5 +0,0 @@ -package http - -func Start() { - -} diff --git a/bookback/internal/services/book/repository_test.go b/bookback/internal/services/book/repository_test.go index 1226bca..e7c925a 100644 --- a/bookback/internal/services/book/repository_test.go +++ b/bookback/internal/services/book/repository_test.go @@ -1,8 +1,8 @@ package book import ( - "github.com/SShlykov/zeitment/bookback/internal/mocks" "github.com/SShlykov/zeitment/bookback/internal/models" + mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -26,16 +26,16 @@ func newTestBook() *models.Book { } } -func rowFromBook(book *models.Book) *mocks.ScanResult { - return mocks.NewScanResult([]interface{}{book.ID, book.CreatedAt, book.UpdatedAt, book.DeletedAt, book.Owner, //nolint:gofmt +func rowFromBook(book *models.Book) *mocks2.ScanResult { + return mocks2.NewScanResult([]interface{}{book.ID, book.CreatedAt, book.UpdatedAt, book.DeletedAt, book.Owner, //nolint:gofmt book.Title, book.Author, book.Description, book.IsPublic, book.Publication, book.ImageLink, book.MapLink, book.MapParamsID, book.Variables, }) } func inits(ctrl *gomock.Controller) (Repository, *models.Book) { - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testBook := newTestBook() row := rowFromBook(testBook) @@ -63,12 +63,12 @@ func TestRepository_Create(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testBook := &models.Book{} - row := mocks.NewScanResult([]interface{}{faker.UUIDHyphenated()}) + row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) diff --git a/bookback/internal/services/chapter/repository_test.go b/bookback/internal/services/chapter/repository_test.go index 7a19213..e9b7320 100644 --- a/bookback/internal/services/chapter/repository_test.go +++ b/bookback/internal/services/chapter/repository_test.go @@ -1,8 +1,8 @@ package chapter import ( - "github.com/SShlykov/zeitment/bookback/internal/mocks" "github.com/SShlykov/zeitment/bookback/internal/models" + mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -22,16 +22,16 @@ func newTestChapter() *models.Chapter { } } -func rowFromChapter(chapter *models.Chapter) *mocks.ScanResult { - return mocks.NewScanResult([]interface{}{chapter.ID, chapter.CreatedAt, chapter.UpdatedAt, chapter.DeletedAt, +func rowFromChapter(chapter *models.Chapter) *mocks2.ScanResult { + return mocks2.NewScanResult([]interface{}{chapter.ID, chapter.CreatedAt, chapter.UpdatedAt, chapter.DeletedAt, chapter.Title, chapter.Number, chapter.Text, chapter.BookID, chapter.IsPublic, chapter.MapLink, chapter.MapParamsID, }) } func initChapters(ctrl *gomock.Controller) (Repository, *models.Chapter) { - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testChapter := newTestChapter() row := rowFromChapter(testChapter) @@ -59,12 +59,12 @@ func TestRepository_Create(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testChapter := &models.Chapter{} - row := mocks.NewScanResult([]interface{}{faker.UUIDHyphenated()}) + row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) diff --git a/bookback/internal/services/mapvariables/repository.go b/bookback/internal/services/mapvariables/repository.go index 2b6bb74..6cc5dcc 100644 --- a/bookback/internal/services/mapvariables/repository.go +++ b/bookback/internal/services/mapvariables/repository.go @@ -11,7 +11,8 @@ import ( const ( tableName = "map_variables" columnID = "id" - columnInsertedAt = "inserted_at" + columnCreatedAt = "created_at" + columnUpdatedAt = "updated_at" columnBookID = "book_id" columnChapterID = "chapter_id" columnPageID = "page_id" @@ -38,37 +39,39 @@ type Repository interface { GetByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) GetByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) GetByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) - - GetByMapLinkAndBookID(ctx context.Context, mapLink, bookID string) ([]models.MapVariable, error) - GetByMapLinkAndChapterID(ctx context.Context, mapLink, chapterID string) ([]models.MapVariable, error) - GetByMapLinkAndPageID(ctx context.Context, mapLink, pageID string) ([]models.MapVariable, error) - GetByMapLinkAndParagraphID(ctx context.Context, mapLink, paragraphID string) ([]models.MapVariable, error) } type repository struct { db db.Client } +func NewRepository(database db.Client) Repository { + return &repository{database} +} + func allItems() string { - cols := []string{columnID, columnInsertedAt, columnBookID, columnChapterID, columnPageID, + cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, columnLinkType, columnLinkImage, columnImage} return strings.Join(cols, ", ") } -func NewRepository(database db.Client) Repository { - return &repository{database} +func insertItems() string { + cols := []string{columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, + columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, + columnLinkType, columnLinkImage, columnImage} + + return strings.Join(cols, ", ") } func (r *repository) Create(ctx context.Context, variable *models.MapVariable) (string, error) { - query := `INSERT INTO` + " " + tableName + ` (` + allItems() + - `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id` + query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + + `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id` - args := []interface{}{variable.ID, variable.CreatedAt, variable.BookID, variable.ChapterID, //nolint:gofmt - variable.PageID, variable.ParagraphID, variable.MapLink, variable.Lat, variable.Lng, - variable.Zoom, variable.Date, variable.Description, variable.Link, variable.LinkText, - variable.LinkType, variable.LinkImage, variable.Image} + args := []interface{}{variable.BookID, variable.ChapterID, variable.PageID, variable.ParagraphID, variable.MapLink, //nolint:gofmt + variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, + variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image} q := db.Query{Name: "MapVariableRepository.Insert", Raw: query} @@ -91,18 +94,15 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.MapVariab } func (r *repository) Update(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) { - query := `UPDATE ` + tableName + ` SET ` + - columnInsertedAt + ` = $1, ` + columnBookID + ` = $2, ` + columnChapterID + ` = $3, ` + - columnPageID + ` = $4, ` + columnParagraphID + ` = $5, ` + columnMapLink + ` = $6, ` + - columnLat + ` = $7, ` + columnLng + ` = $8, ` + columnZoom + ` = $9, ` + - columnDate + ` = $10, ` + columnDescription + ` = $11, ` + columnLink + ` = $12, ` + - columnLinkText + ` = $13, ` + columnLinkType + ` = $14, ` + columnLinkImage + ` = $15, ` + - columnImage + ` = $16 WHERE ` + columnID + ` = $17 RETURNING ` + allItems() - - args := []interface{}{variable.CreatedAt, variable.BookID, variable.ChapterID, variable.PageID, //nolint:gofmt - variable.ParagraphID, variable.MapLink, variable.Lat, variable.Lng, variable.Zoom, - variable.Date, variable.Description, variable.Link, variable.LinkText, variable.LinkType, - variable.LinkImage, variable.Image, id} + query := `UPDATE ` + tableName + ` SET ` + services.ParamsToQuery(", ", + columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, + columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, + columnLinkType, columnLinkImage, columnImage) + + ` WHERE ` + columnID + ` = $16 RETURNING ` + allItems() + + args := []interface{}{variable.BookID, variable.ChapterID, variable.PageID, variable.ParagraphID, variable.MapLink, //nolint:gofmt + variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, + variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image, id} q := db.Query{Name: "MapVariableRepository.Update", Raw: query} @@ -112,7 +112,7 @@ func (r *repository) Update(ctx context.Context, id string, variable *models.Map } func (r *repository) Delete(ctx context.Context, id string) (*models.MapVariable, error) { - query := `DELETE FROM` + " " + tableName + ` WHERE ` + columnID + ` = $1 RETURNING ` + allItems() + query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() q := db.Query{Name: "MapVariableRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) @@ -166,51 +166,3 @@ func (r *repository) GetByParagraphID(ctx context.Context, paragraphID string) ( return readList(rows) } - -func (r *repository) GetByMapLinkAndBookID(ctx context.Context, mapLink, bookID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnMapLink, columnBookID) - - q := db.Query{Name: "MapVariableRepository.GetByMapLinkAndBookID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, mapLink, bookID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByMapLinkAndChapterID(ctx context.Context, mapLink, chapterID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnMapLink, columnChapterID) - - q := db.Query{Name: "MapVariableRepository.GetByMapLinkAndChapterID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, mapLink, chapterID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByMapLinkAndPageID(ctx context.Context, mapLink, pageID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnMapLink, columnPageID) - - q := db.Query{Name: "MapVariableRepository.GetByMapLinkAndPageID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, mapLink, pageID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByMapLinkAndParagraphID(ctx context.Context, mapLink, paragraphID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnMapLink, columnParagraphID) - - q := db.Query{Name: "MapVariableRepository.GetByMapLinkAndParagraphID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, mapLink, paragraphID) - if err != nil { - return nil, err - } - - return readList(rows) -} diff --git a/bookback/internal/services/mapvariables/service.go b/bookback/internal/services/mapvariables/service.go index 9791581..cedbe76 100644 --- a/bookback/internal/services/mapvariables/service.go +++ b/bookback/internal/services/mapvariables/service.go @@ -14,11 +14,6 @@ type Service interface { GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) - - GetMapVariablesByMapLinkAndBookID(ctx context.Context, mapLink, bookID string) ([]models.MapVariable, error) - GetMapVariablesByMapLinkAndChapterID(ctx context.Context, mapLink, chapterID string) ([]models.MapVariable, error) - GetMapVariablesByMapLinkAndPageID(ctx context.Context, mapLink, pageID string) ([]models.MapVariable, error) - GetMapVariablesByMapLinkAndParagraphID(ctx context.Context, mapLink, paragraphID string) ([]models.MapVariable, error) } type service struct { @@ -66,19 +61,3 @@ func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([ func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) { return s.repo.GetByParagraphID(ctx, paragraphID) } - -func (s *service) GetMapVariablesByMapLinkAndBookID(ctx context.Context, mapLink, bookID string) ([]models.MapVariable, error) { - return s.repo.GetByMapLinkAndBookID(ctx, mapLink, bookID) -} - -func (s *service) GetMapVariablesByMapLinkAndChapterID(ctx context.Context, mapLink, chapterID string) ([]models.MapVariable, error) { - return s.repo.GetByMapLinkAndChapterID(ctx, mapLink, chapterID) -} - -func (s *service) GetMapVariablesByMapLinkAndPageID(ctx context.Context, mapLink, pageID string) ([]models.MapVariable, error) { - return s.repo.GetByMapLinkAndPageID(ctx, mapLink, pageID) -} - -func (s *service) GetMapVariablesByMapLinkAndParagraphID(ctx context.Context, mapLink, paragraphID string) ([]models.MapVariable, error) { - return s.repo.GetByMapLinkAndParagraphID(ctx, mapLink, paragraphID) -} diff --git a/bookback/internal/services/mapvariables/utils.go b/bookback/internal/services/mapvariables/utils.go index 34ab2f6..8dac089 100644 --- a/bookback/internal/services/mapvariables/utils.go +++ b/bookback/internal/services/mapvariables/utils.go @@ -24,7 +24,8 @@ func readList(rows pgx.Rows) ([]models.MapVariable, error) { func readItem(row pgx.Row) (*models.MapVariable, error) { var variable models.MapVariable - if err := row.Scan(&variable.ID, &variable.CreatedAt, &variable.BookID, + + if err := row.Scan(&variable.ID, &variable.CreatedAt, &variable.UpdatedAt, &variable.BookID, &variable.ChapterID, &variable.PageID, &variable.ParagraphID, &variable.MapLink, &variable.Lat, &variable.Lng, &variable.Zoom, &variable.Date, &variable.Description, &variable.Link, &variable.LinkText, &variable.LinkType, &variable.LinkImage, &variable.Image); err != nil { diff --git a/bookback/internal/services/page/repository_test.go b/bookback/internal/services/page/repository_test.go index 3c0059c..a939b75 100644 --- a/bookback/internal/services/page/repository_test.go +++ b/bookback/internal/services/page/repository_test.go @@ -1,8 +1,8 @@ package page import ( - "github.com/SShlykov/zeitment/bookback/internal/mocks" "github.com/SShlykov/zeitment/bookback/internal/models" + mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -23,14 +23,14 @@ func newTestPage() *models.Page { } } -func rowFromPage(page *models.Page) *mocks.ScanResult { - return mocks.NewScanResult([]interface{}{page.ID, page.CreatedAt, page.UpdatedAt, page.DeletedAt, +func rowFromPage(page *models.Page) *mocks2.ScanResult { + return mocks2.NewScanResult([]interface{}{page.ID, page.CreatedAt, page.UpdatedAt, page.DeletedAt, page.Title, page.Text, page.ChapterID, page.IsPublic, page.MapParamsID}) } func initPages(ctrl *gomock.Controller) (Repository, *models.Page) { - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testPage := newTestPage() row := rowFromPage(testPage) @@ -59,12 +59,12 @@ func TestRepository_Create(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testPage := &models.Page{} - row := mocks.NewScanResult([]interface{}{faker.UUIDHyphenated()}) + row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) diff --git a/bookback/internal/services/paragraph/repository_test.go b/bookback/internal/services/paragraph/repository_test.go index 4d34199..8ea0824 100644 --- a/bookback/internal/services/paragraph/repository_test.go +++ b/bookback/internal/services/paragraph/repository_test.go @@ -1,8 +1,8 @@ package paragraph import ( - "github.com/SShlykov/zeitment/bookback/internal/mocks" "github.com/SShlykov/zeitment/bookback/internal/models" + mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -23,14 +23,14 @@ func newTestParagraph() *models.Paragraph { } } -func rowFromParagraph(paragraph *models.Paragraph) *mocks.ScanResult { - return mocks.NewScanResult([]interface{}{paragraph.ID, paragraph.CreatedAt, paragraph.UpdatedAt, paragraph.DeletedAt, +func rowFromParagraph(paragraph *models.Paragraph) *mocks2.ScanResult { + return mocks2.NewScanResult([]interface{}{paragraph.ID, paragraph.CreatedAt, paragraph.UpdatedAt, paragraph.DeletedAt, paragraph.Title, paragraph.Text, paragraph.Type, paragraph.IsPublic, paragraph.PageID}) } func initParagraphs(ctrl *gomock.Controller) (Repository, *models.Paragraph) { - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testParagraph := newTestParagraph() row := rowFromParagraph(testParagraph) @@ -59,12 +59,12 @@ func TestRepository_Create(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - db := mocks.NewMockDB(ctrl) + client := mocks2.NewMockClient(ctrl) + db := mocks2.NewMockDB(ctrl) testParagraph := &models.Paragraph{} - row := mocks.NewScanResult([]interface{}{faker.UUIDHyphenated()}) + row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) diff --git a/bookback/migrations/20240225152254_create_map_variables.sql b/bookback/migrations/20240225152254_create_map_variables.sql index e7137b1..a72c1e3 100644 --- a/bookback/migrations/20240225152254_create_map_variables.sql +++ b/bookback/migrations/20240225152254_create_map_variables.sql @@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS map_variables ( map_link TEXT NOT NULL, lat DOUBLE PRECISION NOT NULL, lng DOUBLE PRECISION NOT NULL, - zoom INTEGER NOT NULL, - date TIMESTAMP WITH TIME ZONE NOT NULL, + zoom INTEGER, + date varchar(255), description TEXT, link TEXT, link_text TEXT, diff --git a/bookback/migrations/20240303075249_update_map_events_add_updated_at.sql b/bookback/migrations/20240303075249_update_map_events_add_updated_at.sql new file mode 100644 index 0000000..e3d79b6 --- /dev/null +++ b/bookback/migrations/20240303075249_update_map_events_add_updated_at.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE map_variables ADD COLUMN updated_at TIMESTAMP; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE map_variables DROP COLUMN updated_at; +-- +goose StatementEnd diff --git a/bookback/pkg/logger/handler.go b/bookback/pkg/logger/handler.go index 36f9c6f..dd01a76 100644 --- a/bookback/pkg/logger/handler.go +++ b/bookback/pkg/logger/handler.go @@ -7,6 +7,7 @@ import ( "io" "log" "log/slog" + "strings" ) type PrettyHandlerOptions struct { @@ -39,23 +40,21 @@ func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error { return true }) - b, err := json.MarshalIndent(fields, "", " ") + b, err := json.MarshalIndent(fields, "", "") if err != nil { return err } + opts := strings.Replace(string(b), "\n", " ", -1) timeStr := r.Time.Format("[15:05:05.000]") msg := color.CyanString(r.Message) - h.l.Println(timeStr, level, msg, color.WhiteString(string(b))) + h.l.Println(timeStr, level, msg, color.WhiteString(opts)) return nil } -func NewPrettyHandler( - out io.Writer, - opts PrettyHandlerOptions, -) *PrettyHandler { +func NewPrettyHandler(out io.Writer, opts PrettyHandlerOptions) *PrettyHandler { h := &PrettyHandler{ Handler: slog.NewJSONHandler(out, &opts.SlogOpts), l: log.New(out, "", 0), diff --git a/bookback/internal/mocks/db.go b/bookback/tests/mocks/db.go similarity index 100% rename from bookback/internal/mocks/db.go rename to bookback/tests/mocks/db.go diff --git a/bookback/internal/mocks/scanresult.go b/bookback/tests/mocks/scanresult.go similarity index 100% rename from bookback/internal/mocks/scanresult.go rename to bookback/tests/mocks/scanresult.go From 4339f46061d118591df0925b304088f7771760ab Mon Sep 17 00:00:00 2001 From: SShlykov Date: Sun, 3 Mar 2024 23:55:43 +0300 Subject: [PATCH 4/9] fix: merge conflict err --- .../internal/servers/http/controllers/bookevents/controller.go | 1 - 1 file changed, 1 deletion(-) diff --git a/bookback/internal/servers/http/controllers/bookevents/controller.go b/bookback/internal/servers/http/controllers/bookevents/controller.go index cbd40d7..5348c02 100644 --- a/bookback/internal/servers/http/controllers/bookevents/controller.go +++ b/bookback/internal/servers/http/controllers/bookevents/controller.go @@ -3,7 +3,6 @@ package bookevents import ( "context" "errors" - "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/metrics" service "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" From 400fc0fa7e8f296d615cd33c7dd163ebc8effd2d Mon Sep 17 00:00:00 2001 From: SShlykov Date: Wed, 6 Mar 2024 00:45:38 +0300 Subject: [PATCH 5/9] started to clean --- .../db/postgres/bookevtrepo/bookevents.go} | 40 +++--- .../postgres/bookevtrepo/bookevents_test.go | 1 + .../db/postgres/bookevtrepo}/utils.go | 2 +- .../db/postgres/bookrepo/book.go} | 47 ++++--- .../db/postgres/bookrepo/book_test.go} | 2 +- .../db/postgres/bookrepo}/utils.go | 2 +- .../db/postgres/chapterrepo/chapter.go} | 30 ++--- .../db/postgres/chapterrepo/chapter_test.go} | 9 +- .../db/postgres/chapterrepo}/utils.go | 2 +- .../db/postgres/mapvarrepo/mapvariables.go} | 40 +++--- .../db/postgres/mapvarrepo}/utils.go | 2 +- .../db/postgres/pagerepo/page.go} | 34 ++--- .../db/postgres/pagerepo/page_test.go} | 9 +- .../db/postgres/pagerepo}/utils.go | 2 +- .../db/postgres/paragraphrepo/paragraph.go} | 34 ++--- .../postgres/paragraphrepo/paragraph_test.go} | 9 +- .../db/postgres/paragraphrepo}/utils.go | 2 +- .../http/circuitbreaker/circuitbreaker.go | 0 .../circuitbreaker/circuitbreaker_test.go | 0 .../http/httpmiddlewares/circuitbreaker.go | 2 +- .../http/httpmiddlewares/cors.go | 0 .../http/httpmiddlewares/httplogger.go | 0 .../http/httpmiddlewares/middleware.go | 0 .../http/v1}/book/config.go | 0 .../http/v1}/book/controller.go | 14 +-- .../http/v1}/book/errors.go | 0 .../http/v1}/book/models.go | 10 +- .../http/v1}/book/routes.go | 11 +- .../http/v1}/bookevents/config.go | 0 .../http/v1}/bookevents/controller.go | 20 +-- .../http/v1}/bookevents/errors.go | 0 .../http/v1}/bookevents/models.go | 0 .../http/v1}/bookevents/routes.go | 12 +- .../http/v1}/chapter/config.go | 0 .../http/v1}/chapter/controller.go | 18 +-- .../http/v1}/chapter/errors.go | 0 .../http/v1}/chapter/models.go | 0 .../http/v1}/chapter/routes.go | 12 +- .../http/v1}/health/config.go | 0 .../http/v1}/health/controller.go | 0 .../http/v1}/health/routes.go | 6 +- .../http/v1}/mapvariables/config.go | 0 .../http/v1}/mapvariables/controller.go | 20 +-- .../http/v1}/mapvariables/errors.go | 0 .../http/v1}/mapvariables/models.go | 0 .../http/v1}/mapvariables/routes.go | 11 +- .../http/v1}/page/config.go | 0 .../http/v1}/page/controller.go | 18 +-- .../http/v1}/page/errors.go | 0 .../http/v1}/page/models.go | 0 .../http/v1}/page/routes.go | 11 +- .../http/v1}/paragraph/config.go | 0 .../http/v1}/paragraph/controller.go | 18 +-- .../http/v1}/paragraph/errors.go | 0 .../http/v1}/paragraph/models.go | 0 .../http/v1}/paragraph/routes.go | 11 +- .../http/v1}/swagger/controller.go | 0 .../{models => domain/entity}/book.go | 2 +- .../{models => domain/entity}/bookevents.go | 2 +- .../{models => domain/entity}/chapter.go | 2 +- .../{models => domain/entity}/mapvariables.go | 2 +- .../{models => domain/entity}/nullable.go | 2 +- .../{models => domain/entity}/page.go | 2 +- .../{models => domain/entity}/paragraph.go | 2 +- .../domain/services/book/interface.go | 14 +++ .../{ => domain}/services/book/service.go | 28 ++--- .../domain/services/bookevents/interface.go | 17 +++ .../services/bookevents/service.go | 34 ++--- .../domain/services/chapter/interface.go | 15 +++ .../domain/services/chapter/service.go | 60 +++++++++ .../domain/services/mapvariables/interface.go | 10 ++ .../services/mapvariables/service.go | 39 +++--- .../domain/services/page/interface.go | 15 +++ .../internal/domain/services/page/service.go | 54 ++++++++ .../domain/services/paragraph/interface.go | 17 +++ .../services/paragraph/service.go | 26 ++-- bookback/internal/pkg/app/app.go | 4 +- bookback/internal/pkg/app/closer.go | 4 +- bookback/internal/pkg/app/db.go | 8 +- bookback/internal/pkg/app/endpoint.go | 26 ++-- .../services/bookevents/repository_test.go | 1 - bookback/internal/services/chapter/service.go | 81 ------------ bookback/internal/services/helpers.go | 33 ----- bookback/internal/services/helpers_test.go | 99 --------------- bookback/internal/services/page/service.go | 54 -------- bookback/pkg/db/pg/client.go | 37 ------ bookback/pkg/db/pg/pg.go | 114 ----------------- bookback/pkg/db/sq/sq.go | 25 ---- bookback/pkg/postgres/client.go | 63 ++++++++++ .../pkg/{db/db.go => postgres/interface.go} | 3 +- bookback/pkg/postgres/pg/pg.go | 118 ++++++++++++++++++ .../transaction/transaction.go | 22 ++-- bookback/tests/integration-test/test.go | 1 + bookback/tests/mocks/db.go | 6 +- 94 files changed, 725 insertions(+), 778 deletions(-) rename bookback/internal/{services/bookevents/repository.go => adapters/db/postgres/bookevtrepo/bookevents.go} (78%) create mode 100644 bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go rename bookback/internal/{services/bookevents => adapters/db/postgres/bookevtrepo}/utils.go (97%) rename bookback/internal/{services/book/repository.go => adapters/db/postgres/bookrepo/book.go} (67%) rename bookback/internal/{services/book/repository_test.go => adapters/db/postgres/bookrepo/book_test.go} (99%) rename bookback/internal/{services/book => adapters/db/postgres/bookrepo}/utils.go (98%) rename bookback/internal/{services/chapter/repository.go => adapters/db/postgres/chapterrepo/chapter.go} (79%) rename bookback/internal/{services/chapter/repository_test.go => adapters/db/postgres/chapterrepo/chapter_test.go} (90%) rename bookback/internal/{services/chapter => adapters/db/postgres/chapterrepo}/utils.go (97%) rename bookback/internal/{services/mapvariables/repository.go => adapters/db/postgres/mapvarrepo/mapvariables.go} (78%) rename bookback/internal/{services/mapvariables => adapters/db/postgres/mapvarrepo}/utils.go (97%) rename bookback/internal/{services/page/repository.go => adapters/db/postgres/pagerepo/page.go} (74%) rename bookback/internal/{services/page/repository_test.go => adapters/db/postgres/pagerepo/page_test.go} (90%) rename bookback/internal/{services/page => adapters/db/postgres/pagerepo}/utils.go (97%) rename bookback/internal/{services/paragraph/repository.go => adapters/db/postgres/paragraphrepo/paragraph.go} (74%) rename bookback/internal/{services/paragraph/repository_test.go => adapters/db/postgres/paragraphrepo/paragraph_test.go} (90%) rename bookback/internal/{services/paragraph => adapters/db/postgres/paragraphrepo}/utils.go (97%) rename bookback/internal/{servers => controller}/http/circuitbreaker/circuitbreaker.go (100%) rename bookback/internal/{servers => controller}/http/circuitbreaker/circuitbreaker_test.go (100%) rename bookback/internal/{servers => controller}/http/httpmiddlewares/circuitbreaker.go (88%) rename bookback/internal/{servers => controller}/http/httpmiddlewares/cors.go (100%) rename bookback/internal/{servers => controller}/http/httpmiddlewares/httplogger.go (100%) rename bookback/internal/{servers => controller}/http/httpmiddlewares/middleware.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/book/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/book/controller.go (92%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/book/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/book/models.go (58%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/book/routes.go (60%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/bookevents/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/bookevents/controller.go (93%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/bookevents/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/bookevents/models.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/bookevents/routes.go (64%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/chapter/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/chapter/controller.go (91%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/chapter/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/chapter/models.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/chapter/routes.go (59%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/health/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/health/controller.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/health/routes.go (60%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/mapvariables/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/mapvariables/controller.go (94%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/mapvariables/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/mapvariables/models.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/mapvariables/routes.go (64%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/page/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/page/controller.go (91%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/page/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/page/models.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/page/routes.go (54%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/paragraph/config.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/paragraph/controller.go (91%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/paragraph/errors.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/paragraph/models.go (100%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/paragraph/routes.go (54%) rename bookback/internal/{servers/http/controllers => controller/http/v1}/swagger/controller.go (100%) rename bookback/internal/{models => domain/entity}/book.go (98%) rename bookback/internal/{models => domain/entity}/bookevents.go (98%) rename bookback/internal/{models => domain/entity}/chapter.go (98%) rename bookback/internal/{models => domain/entity}/mapvariables.go (98%) rename bookback/internal/{models => domain/entity}/nullable.go (99%) rename bookback/internal/{models => domain/entity}/page.go (97%) rename bookback/internal/{models => domain/entity}/paragraph.go (96%) create mode 100644 bookback/internal/domain/services/book/interface.go rename bookback/internal/{ => domain}/services/book/service.go (57%) create mode 100644 bookback/internal/domain/services/bookevents/interface.go rename bookback/internal/{ => domain}/services/bookevents/service.go (53%) create mode 100644 bookback/internal/domain/services/chapter/interface.go create mode 100644 bookback/internal/domain/services/chapter/service.go create mode 100644 bookback/internal/domain/services/mapvariables/interface.go rename bookback/internal/{ => domain}/services/mapvariables/service.go (52%) create mode 100644 bookback/internal/domain/services/page/interface.go create mode 100644 bookback/internal/domain/services/page/service.go create mode 100644 bookback/internal/domain/services/paragraph/interface.go rename bookback/internal/{ => domain}/services/paragraph/service.go (51%) delete mode 100644 bookback/internal/services/bookevents/repository_test.go delete mode 100644 bookback/internal/services/chapter/service.go delete mode 100644 bookback/internal/services/helpers.go delete mode 100644 bookback/internal/services/helpers_test.go delete mode 100644 bookback/internal/services/page/service.go delete mode 100644 bookback/pkg/db/pg/client.go delete mode 100644 bookback/pkg/db/pg/pg.go delete mode 100644 bookback/pkg/db/sq/sq.go create mode 100644 bookback/pkg/postgres/client.go rename bookback/pkg/{db/db.go => postgres/interface.go} (92%) create mode 100644 bookback/pkg/postgres/pg/pg.go rename bookback/pkg/{db => postgres}/transaction/transaction.go (67%) create mode 100644 bookback/tests/integration-test/test.go diff --git a/bookback/internal/services/bookevents/repository.go b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go similarity index 78% rename from bookback/internal/services/bookevents/repository.go rename to bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go index 399a48a..3684dfc 100644 --- a/bookback/internal/services/bookevents/repository.go +++ b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go @@ -1,10 +1,10 @@ -package bookevents +package bookevtrepo import ( "context" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" "strings" ) @@ -40,10 +40,10 @@ type Repository interface { } type repository struct { - db db.Client + db postgres.Client } -func NewRepository(database db.Client) Repository { +func NewRepository(database postgres.Client) Repository { return &repository{database} } @@ -73,7 +73,7 @@ func (r *repository) Create(ctx context.Context, event *models.BookEvent) (strin event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, event.LinkType, event.LinkImage, event.Description} - q := db.Query{Name: "BookEventRepository.Insert", Raw: query} + q := postgres.Query{Name: "BookEventRepository.Insert", Raw: query} var id string if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { @@ -84,16 +84,16 @@ func (r *repository) Create(ctx context.Context, event *models.BookEvent) (strin } func (r *repository) FindByID(ctx context.Context, id string) (*models.BookEvent, error) { - query := services.SelectWhere(allItems, tableName, columnID) + query := querybuilder.SelectWhere(allItems, tableName, columnID) - q := db.Query{Name: "BookEventRepository.FindByID", Raw: query} + q := postgres.Query{Name: "BookEventRepository.FindByID", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) } func (r *repository) Update(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) { query := `UPDATE ` + tableName + ` SET ` + - services.ParamsToQuery(", ", + querybuilder.ParamsToQuery(", ", columnBookID, columnChapterID, columnPageID, columnParagraphID, columnEventType, columnIsPublic, columnKey, columnValue, columnLink, columnLinkText, columnLinkType, columnLinkImage, columnDescription) + ` WHERE ` + @@ -103,7 +103,7 @@ func (r *repository) Update(ctx context.Context, id string, event *models.BookEv event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, event.LinkType, event.LinkImage, event.Description, id} - q := db.Query{Name: "BookEventRepository.Update", Raw: query} + q := postgres.Query{Name: "BookEventRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) @@ -111,17 +111,17 @@ func (r *repository) Update(ctx context.Context, id string, event *models.BookEv } func (r *repository) Delete(ctx context.Context, id string) (*models.BookEvent, error) { - query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() + query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - q := db.Query{Name: "BookEventRepository.Delete", Raw: query} + q := postgres.Query{Name: "BookEventRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) } func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.BookEvent, error) { - query := services.SelectWhere(allItems, tableName, columnBookID) + query := querybuilder.SelectWhere(allItems, tableName, columnBookID) - q := db.Query{Name: "BookEventRepository.GetByBookID", Raw: query} + q := postgres.Query{Name: "BookEventRepository.GetByBookID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, bookID) if err != nil { return nil, err @@ -130,9 +130,9 @@ func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.B } func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]models.BookEvent, error) { - query := services.SelectWhere(allItems, tableName, columnChapterID) + query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) - q := db.Query{Name: "BookEventRepository.GetByChapterID", Raw: query} + q := postgres.Query{Name: "BookEventRepository.GetByChapterID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, chapterID) if err != nil { return nil, err @@ -141,9 +141,9 @@ func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]mo } func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.BookEvent, error) { - query := services.SelectWhere(allItems, tableName, columnPageID) + query := querybuilder.SelectWhere(allItems, tableName, columnPageID) - q := db.Query{Name: "BookEventRepository.GetByPageID", Raw: query} + q := postgres.Query{Name: "BookEventRepository.GetByPageID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, pageID) if err != nil { return nil, err @@ -152,9 +152,9 @@ func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.B } func (r *repository) GetByParagraphID(ctx context.Context, paragraphID string) ([]models.BookEvent, error) { - query := services.SelectWhere(allItems, tableName, columnParagraphID) + query := querybuilder.SelectWhere(allItems, tableName, columnParagraphID) - q := db.Query{Name: "BookEventRepository.GetByParagraphID", Raw: query} + q := postgres.Query{Name: "BookEventRepository.GetByParagraphID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, paragraphID) if err != nil { return nil, err diff --git a/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go new file mode 100644 index 0000000..f44dc05 --- /dev/null +++ b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go @@ -0,0 +1 @@ +package bookevtrepo diff --git a/bookback/internal/services/bookevents/utils.go b/bookback/internal/adapters/db/postgres/bookevtrepo/utils.go similarity index 97% rename from bookback/internal/services/bookevents/utils.go rename to bookback/internal/adapters/db/postgres/bookevtrepo/utils.go index 781f29d..ed8b411 100644 --- a/bookback/internal/services/bookevents/utils.go +++ b/bookback/internal/adapters/db/postgres/bookevtrepo/utils.go @@ -1,4 +1,4 @@ -package bookevents +package bookevtrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/book/repository.go b/bookback/internal/adapters/db/postgres/bookrepo/book.go similarity index 67% rename from bookback/internal/services/book/repository.go rename to bookback/internal/adapters/db/postgres/bookrepo/book.go index 26be6ae..31bdcfe 100644 --- a/bookback/internal/services/book/repository.go +++ b/bookback/internal/adapters/db/postgres/bookrepo/book.go @@ -1,11 +1,10 @@ -package book +package bookrepo import ( "context" "errors" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "strings" ) @@ -31,15 +30,15 @@ const ( // Repository определяет интерфейс для взаимодействия с хранилищем книг. type Repository interface { - Create(ctx context.Context, book *models.Book) (string, error) - FindByID(ctx context.Context, id string) (*models.Book, error) - Update(ctx context.Context, id string, book *models.Book) (*models.Book, error) - Delete(ctx context.Context, id string) (*models.Book, error) - List(ctx context.Context) ([]models.Book, error) + Create(ctx context.Context, book *entity.Book) (string, error) + FindByID(ctx context.Context, id string) (*entity.Book, error) + Update(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) + Delete(ctx context.Context, id string) (*entity.Book, error) + List(ctx context.Context) ([]entity.Book, error) } type repository struct { - db db.Client + db postgres.Client } func allItems() string { @@ -58,11 +57,11 @@ func insertItems() string { } // NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(client db.Client) Repository { +func NewRepository(client postgres.Client) Repository { return &repository{db: client} } -func (r *repository) Create(ctx context.Context, book *models.Book) (string, error) { +func (r *repository) Create(ctx context.Context, book *entity.Book) (string, error) { query := "INSERT INTO" + " " + tableName + ` (` + insertItems() + `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ` + Returning + columnID @@ -72,7 +71,7 @@ func (r *repository) Create(ctx context.Context, book *models.Book) (string, err args := []interface{}{book.Title, book.Author, book.Owner, book.Description, book.IsPublic, //nolint:gofmt book.Publication, book.ImageLink, book.MapLink, book.MapParamsID, book.Variables} - q := db.Query{Name: "BookRepository.Insert", Raw: query} + q := postgres.Query{Name: "BookRepository.Insert", Raw: query} var id string if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { @@ -82,19 +81,19 @@ func (r *repository) Create(ctx context.Context, book *models.Book) (string, err return id, nil } -func (r *repository) FindByID(ctx context.Context, id string) (*models.Book, error) { - query := services.SelectWhere(allItems, tableName, columnID) + " AND " + columnDeletedAt + ` IS NULL` + " LIMIT 1" +func (r *repository) FindByID(ctx context.Context, id string) (*entity.Book, error) { + query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND " + columnDeletedAt + ` IS NULL` + " LIMIT 1" - q := db.Query{Name: "BookRepository.FindById", Raw: query} + q := postgres.Query{Name: "BookRepository.FindById", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) } -func (r *repository) Update(ctx context.Context, id string, updBook *models.Book) (*models.Book, error) { +func (r *repository) Update(ctx context.Context, id string, updBook *entity.Book) (*entity.Book, error) { query := "Update " + tableName + " SET " + - services.ParamsToQuery(", ", columnTitle, columnAuthor, columnOwner, columnDescription, columnIsPublic, + querybuilder.ParamsToQuery(", ", columnTitle, columnAuthor, columnOwner, columnDescription, columnIsPublic, columnPublication, columnImageLink, columnMapLink, columnMapParamsID, columnVariables) + " WHERE " + columnID + " = $11" + Returning + allItems() @@ -102,26 +101,26 @@ func (r *repository) Update(ctx context.Context, id string, updBook *models.Book updBook.IsPublic, updBook.Publication, updBook.ImageLink, updBook.MapLink, updBook.MapParamsID, updBook.Variables, id} - q := db.Query{Name: "BookRepository.Update", Raw: query} + q := postgres.Query{Name: "BookRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) return readItem(row) } -func (r *repository) Delete(ctx context.Context, id string) (*models.Book, error) { - query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() +func (r *repository) Delete(ctx context.Context, id string) (*entity.Book, error) { + query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - q := db.Query{Name: "BookRepository.Delete", Raw: query} + q := postgres.Query{Name: "BookRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) } -func (r *repository) List(ctx context.Context) ([]models.Book, error) { +func (r *repository) List(ctx context.Context) ([]entity.Book, error) { query := `SELECT ` + allItems() + ` FROM ` + tableName + Where + columnDeletedAt + ` IS NULL` - q := db.Query{Name: "BookRepository.List", Raw: query} + q := postgres.Query{Name: "BookRepository.List", Raw: query} rows, err := r.db.DB().QueryRawContextMulti(ctx, q) if err != nil { diff --git a/bookback/internal/services/book/repository_test.go b/bookback/internal/adapters/db/postgres/bookrepo/book_test.go similarity index 99% rename from bookback/internal/services/book/repository_test.go rename to bookback/internal/adapters/db/postgres/bookrepo/book_test.go index e7c925a..5a72f4a 100644 --- a/bookback/internal/services/book/repository_test.go +++ b/bookback/internal/adapters/db/postgres/bookrepo/book_test.go @@ -1,4 +1,4 @@ -package book +package bookrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/book/utils.go b/bookback/internal/adapters/db/postgres/bookrepo/utils.go similarity index 98% rename from bookback/internal/services/book/utils.go rename to bookback/internal/adapters/db/postgres/bookrepo/utils.go index 85be18c..35746c0 100644 --- a/bookback/internal/services/book/utils.go +++ b/bookback/internal/adapters/db/postgres/bookrepo/utils.go @@ -1,4 +1,4 @@ -package book +package bookrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/chapter/repository.go b/bookback/internal/adapters/db/postgres/chapterrepo/chapter.go similarity index 79% rename from bookback/internal/services/chapter/repository.go rename to bookback/internal/adapters/db/postgres/chapterrepo/chapter.go index d7894e4..c9917f1 100644 --- a/bookback/internal/services/chapter/repository.go +++ b/bookback/internal/adapters/db/postgres/chapterrepo/chapter.go @@ -1,12 +1,12 @@ -package chapter +package chapterrepo import ( "context" "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" "strings" ) @@ -41,11 +41,11 @@ type Repository interface { } type repository struct { - db db.Client + db postgres.Client } // NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database db.Client) Repository { +func NewRepository(database postgres.Client) Repository { return &repository{database} } @@ -67,7 +67,7 @@ func (r *repository) Create(ctx context.Context, chapter *models.Chapter) (strin query := "INSERT INTO" + " " + tableName + ` (` + insertItems() + `) VALUES ($1, $2, $3, $4, $5, $6, $7) ` + Returning + columnID - q := db.Query{Name: "ChapterRepository.Create", Raw: query} + q := postgres.Query{Name: "ChapterRepository.Create", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, chapter.Title, chapter.Number, chapter.Text, chapter.BookID, chapter.IsPublic, chapter.MapLink, chapter.MapParamsID) @@ -80,11 +80,11 @@ func (r *repository) Create(ctx context.Context, chapter *models.Chapter) (strin } func (r *repository) FindByID(ctx context.Context, id string) (*models.Chapter, error) { - query := services.SelectWhere(allItems, tableName, columnID) + " AND" + NotDeleted + query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND" + NotDeleted fmt.Println(query) - q := db.Query{Name: "ChapterRepository.FindByID", Raw: query} + q := postgres.Query{Name: "ChapterRepository.FindByID", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) @@ -92,22 +92,22 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.Chapter, func (r *repository) Update(ctx context.Context, id string, updChapter *models.Chapter) (*models.Chapter, error) { query := "Update " + tableName + " SET " + - services.ParamsToQuery(", ", columnTitle, columnNumber, columnText, columnBookID, + querybuilder.ParamsToQuery(", ", columnTitle, columnNumber, columnText, columnBookID, columnIsPublic, columnMapLink, columnMapParamsID) + " WHERE " + columnID + " = $8" + Returning + allItems() args := []interface{}{updChapter.Title, updChapter.Number, updChapter.Text, updChapter.BookID, updChapter.IsPublic, updChapter.MapLink, updChapter.MapParamsID, id} - q := db.Query{Name: "BookRepository.Update", Raw: query} + q := postgres.Query{Name: "BookRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) return readItem(row) } func (r *repository) Delete(ctx context.Context, id string) (*models.Chapter, error) { - query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - q := db.Query{Name: "BookRepository.Delete", Raw: query} + query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() + q := postgres.Query{Name: "BookRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) @@ -117,7 +117,7 @@ func (r *repository) Delete(ctx context.Context, id string) (*models.Chapter, er func (r *repository) List(ctx context.Context) ([]models.Chapter, error) { query := `SELECT ` + allItems() + FromTable + WHERE + NotDeleted - q := db.Query{Name: "ChapterRepository.List", Raw: query} + q := postgres.Query{Name: "ChapterRepository.List", Raw: query} rows, err := r.db.DB().QueryRawContextMulti(ctx, q) if err != nil { @@ -128,9 +128,9 @@ func (r *repository) List(ctx context.Context) ([]models.Chapter, error) { } func (r *repository) GetChapterByBookID(ctx context.Context, bookID string) ([]models.Chapter, error) { - query := services.SelectWhere(allItems, tableName, columnBookID) + " AND" + NotDeleted + query := querybuilder.SelectWhere(allItems, tableName, columnBookID) + " AND" + NotDeleted - q := db.Query{Name: "ChapterRepository.GetChapterByBookID", Raw: query} + q := postgres.Query{Name: "ChapterRepository.GetChapterByBookID", Raw: query} rows, err := r.db.DB().QueryRawContextMulti(ctx, q, bookID) if err != nil { diff --git a/bookback/internal/services/chapter/repository_test.go b/bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go similarity index 90% rename from bookback/internal/services/chapter/repository_test.go rename to bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go index e9b7320..3bb01b3 100644 --- a/bookback/internal/services/chapter/repository_test.go +++ b/bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go @@ -1,6 +1,7 @@ -package chapter +package chapterrepo import ( + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" "github.com/SShlykov/zeitment/bookback/internal/models" mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" @@ -29,7 +30,7 @@ func rowFromChapter(chapter *models.Chapter) *mocks2.ScanResult { }) } -func initChapters(ctrl *gomock.Controller) (Repository, *models.Chapter) { +func initChapters(ctrl *gomock.Controller) (bookrepo.Repository, *models.Chapter) { client := mocks2.NewMockClient(ctrl) db := mocks2.NewMockDB(ctrl) @@ -39,7 +40,7 @@ func initChapters(ctrl *gomock.Controller) (Repository, *models.Chapter) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) return repo, testChapter } @@ -69,7 +70,7 @@ func TestRepository_Create(t *testing.T) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) id, err := repo.Create(nil, testChapter) assert.Empty(t, err) assert.NotEmpty(t, id) diff --git a/bookback/internal/services/chapter/utils.go b/bookback/internal/adapters/db/postgres/chapterrepo/utils.go similarity index 97% rename from bookback/internal/services/chapter/utils.go rename to bookback/internal/adapters/db/postgres/chapterrepo/utils.go index 77d2631..2bb962b 100644 --- a/bookback/internal/services/chapter/utils.go +++ b/bookback/internal/adapters/db/postgres/chapterrepo/utils.go @@ -1,4 +1,4 @@ -package chapter +package chapterrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/mapvariables/repository.go b/bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go similarity index 78% rename from bookback/internal/services/mapvariables/repository.go rename to bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go index 6cc5dcc..0752f20 100644 --- a/bookback/internal/services/mapvariables/repository.go +++ b/bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go @@ -1,10 +1,10 @@ -package mapvariables +package mapvarrepo import ( "context" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" "strings" ) @@ -42,10 +42,10 @@ type Repository interface { } type repository struct { - db db.Client + db postgres.Client } -func NewRepository(database db.Client) Repository { +func NewRepository(database postgres.Client) Repository { return &repository{database} } @@ -73,7 +73,7 @@ func (r *repository) Create(ctx context.Context, variable *models.MapVariable) ( variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image} - q := db.Query{Name: "MapVariableRepository.Insert", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.Insert", Raw: query} var id string if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { @@ -84,9 +84,9 @@ func (r *repository) Create(ctx context.Context, variable *models.MapVariable) ( } func (r *repository) FindByID(ctx context.Context, id string) (*models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnID) + query := querybuilder.SelectWhere(allItems, tableName, columnID) - q := db.Query{Name: "MapVariableRepository.FindByID", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.FindByID", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) @@ -94,7 +94,7 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.MapVariab } func (r *repository) Update(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) { - query := `UPDATE ` + tableName + ` SET ` + services.ParamsToQuery(", ", + query := `UPDATE ` + tableName + ` SET ` + querybuilder.ParamsToQuery(", ", columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, columnLinkType, columnLinkImage, columnImage) + @@ -104,7 +104,7 @@ func (r *repository) Update(ctx context.Context, id string, variable *models.Map variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image, id} - q := db.Query{Name: "MapVariableRepository.Update", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) @@ -112,17 +112,17 @@ func (r *repository) Update(ctx context.Context, id string, variable *models.Map } func (r *repository) Delete(ctx context.Context, id string) (*models.MapVariable, error) { - query := services.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() + query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - q := db.Query{Name: "MapVariableRepository.Delete", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) } func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnBookID) + query := querybuilder.SelectWhere(allItems, tableName, columnBookID) - q := db.Query{Name: "MapVariableRepository.GetByBookID", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.GetByBookID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, bookID) if err != nil { return nil, err @@ -132,9 +132,9 @@ func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.M } func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnChapterID) + query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) - q := db.Query{Name: "MapVariableRepository.GetByChapterID", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.GetByChapterID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, chapterID) if err != nil { return nil, err @@ -144,9 +144,9 @@ func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]mo } func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnPageID) + query := querybuilder.SelectWhere(allItems, tableName, columnPageID) - q := db.Query{Name: "MapVariableRepository.GetByPageID", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.GetByPageID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, pageID) if err != nil { return nil, err @@ -156,9 +156,9 @@ func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.M } func (r *repository) GetByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) { - query := services.SelectWhere(allItems, tableName, columnParagraphID) + query := querybuilder.SelectWhere(allItems, tableName, columnParagraphID) - q := db.Query{Name: "MapVariableRepository.GetByParagraphID", Raw: query} + q := postgres.Query{Name: "MapVariableRepository.GetByParagraphID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, paragraphID) if err != nil { return nil, err diff --git a/bookback/internal/services/mapvariables/utils.go b/bookback/internal/adapters/db/postgres/mapvarrepo/utils.go similarity index 97% rename from bookback/internal/services/mapvariables/utils.go rename to bookback/internal/adapters/db/postgres/mapvarrepo/utils.go index 8dac089..d51653c 100644 --- a/bookback/internal/services/mapvariables/utils.go +++ b/bookback/internal/adapters/db/postgres/mapvarrepo/utils.go @@ -1,4 +1,4 @@ -package mapvariables +package mapvarrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/page/repository.go b/bookback/internal/adapters/db/postgres/pagerepo/page.go similarity index 74% rename from bookback/internal/services/page/repository.go rename to bookback/internal/adapters/db/postgres/pagerepo/page.go index 6d43983..186c3e0 100644 --- a/bookback/internal/services/page/repository.go +++ b/bookback/internal/adapters/db/postgres/pagerepo/page.go @@ -1,10 +1,10 @@ -package page +package pagerepo import ( "context" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" "strings" ) @@ -34,11 +34,11 @@ type Repository interface { } type repository struct { - db db.Client + db postgres.Client } // NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database db.Client) Repository { +func NewRepository(database postgres.Client) Repository { return &repository{database} } @@ -56,12 +56,12 @@ func insertItems() string { } // Create inserts a new page into the database and returns its ID. -func (r *repository) Create(ctx context.Context, page *models.Page) (string, error) { +func (r *repository) Create(ctx context.Context, pageDto *models.Page) (string, error) { query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + `) VALUES ($1, $2, $3, $4, $5) ` + Returning + columnID - args := []interface{}{page.Title, page.Text, page.ChapterID, page.IsPublic, page.MapParamsID} + args := []interface{}{pageDto.Title, pageDto.Text, pageDto.ChapterID, pageDto.IsPublic, pageDto.MapParamsID} - q := db.Query{Name: "PageRepository.Create", Raw: query} + q := postgres.Query{Name: "PageRepository.Create", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) var id string @@ -74,8 +74,8 @@ func (r *repository) Create(ctx context.Context, page *models.Page) (string, err // FindByID retrieves a page by its ID. func (r *repository) FindByID(ctx context.Context, id string) (*models.Page, error) { - query := services.SelectWhere(allItems, tableName, columnID) - q := db.Query{Name: "PageRepository.FindByID", Raw: query} + query := querybuilder.SelectWhere(allItems, tableName, columnID) + q := postgres.Query{Name: "PageRepository.FindByID", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) @@ -85,12 +85,12 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.Page, err // Update modifies an existing page's data. func (r *repository) Update(ctx context.Context, id string, updPage *models.Page) (*models.Page, error) { query := "Update " + tableName + " SET " + - services.ParamsToQuery(", ", columnTitle, columnText, columnChapterID, columnIsPublic, columnMapParamsID) + + querybuilder.ParamsToQuery(", ", columnTitle, columnText, columnChapterID, columnIsPublic, columnMapParamsID) + " WHERE " + columnID + " = $6" + Returning + allItems() args := []interface{}{updPage.Title, updPage.Text, updPage.ChapterID, updPage.IsPublic, updPage.MapParamsID, id} - q := db.Query{Name: "PageRepository.Update", Raw: query} + q := postgres.Query{Name: "PageRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) @@ -99,9 +99,9 @@ func (r *repository) Update(ctx context.Context, id string, updPage *models.Page // Delete removes a page from the database. func (r *repository) Delete(ctx context.Context, id string) (*models.Page, error) { - query := services.DeleteQuery(tableName, columnID) + Returning + allItems() + query := querybuilder.DeleteQuery(tableName, columnID) + Returning + allItems() - q := db.Query{Name: "PageRepository.Delete", Raw: query} + q := postgres.Query{Name: "PageRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) @@ -111,7 +111,7 @@ func (r *repository) Delete(ctx context.Context, id string) (*models.Page, error func (r *repository) List(ctx context.Context) ([]models.Page, error) { query := `Select ` + allItems() + ` FROM ` + tableName + ` WHERE ` + columnDeletedAt + ` IS NULL` - q := db.Query{Name: "PageRepository.List", Raw: query} + q := postgres.Query{Name: "PageRepository.List", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q) if err != nil { return nil, err @@ -122,9 +122,9 @@ func (r *repository) List(ctx context.Context) ([]models.Page, error) { // GetPagesByChapterID retrieves all pages for a given chapter ID. func (r *repository) GetPagesByChapterID(ctx context.Context, chapterID string) ([]models.Page, error) { - query := services.SelectWhere(allItems, tableName, columnChapterID) + ` AND ` + columnDeletedAt + ` IS NULL` + query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) + ` AND ` + columnDeletedAt + ` IS NULL` - q := db.Query{Name: "PageRepository.GetPagesByChapterID", Raw: query} + q := postgres.Query{Name: "PageRepository.GetPagesByChapterID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, chapterID) if err != nil { diff --git a/bookback/internal/services/page/repository_test.go b/bookback/internal/adapters/db/postgres/pagerepo/page_test.go similarity index 90% rename from bookback/internal/services/page/repository_test.go rename to bookback/internal/adapters/db/postgres/pagerepo/page_test.go index a939b75..088e39d 100644 --- a/bookback/internal/services/page/repository_test.go +++ b/bookback/internal/adapters/db/postgres/pagerepo/page_test.go @@ -1,6 +1,7 @@ -package page +package pagerepo import ( + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" "github.com/SShlykov/zeitment/bookback/internal/models" mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" @@ -28,7 +29,7 @@ func rowFromPage(page *models.Page) *mocks2.ScanResult { page.Title, page.Text, page.ChapterID, page.IsPublic, page.MapParamsID}) } -func initPages(ctrl *gomock.Controller) (Repository, *models.Page) { +func initPages(ctrl *gomock.Controller) (bookrepo.Repository, *models.Page) { client := mocks2.NewMockClient(ctrl) db := mocks2.NewMockDB(ctrl) @@ -38,7 +39,7 @@ func initPages(ctrl *gomock.Controller) (Repository, *models.Page) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) return repo, testPage } @@ -69,7 +70,7 @@ func TestRepository_Create(t *testing.T) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) id, err := repo.Create(nil, testPage) assert.Empty(t, err) assert.NotEmpty(t, id) diff --git a/bookback/internal/services/page/utils.go b/bookback/internal/adapters/db/postgres/pagerepo/utils.go similarity index 97% rename from bookback/internal/services/page/utils.go rename to bookback/internal/adapters/db/postgres/pagerepo/utils.go index 696e60c..e08c931 100644 --- a/bookback/internal/services/page/utils.go +++ b/bookback/internal/adapters/db/postgres/pagerepo/utils.go @@ -1,4 +1,4 @@ -package page +package pagerepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/services/paragraph/repository.go b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go similarity index 74% rename from bookback/internal/services/paragraph/repository.go rename to bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go index 0aa4643..a74f5ef 100644 --- a/bookback/internal/services/paragraph/repository.go +++ b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go @@ -1,11 +1,11 @@ -package paragraph +package paragraphrepo import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" "strings" ) @@ -35,11 +35,11 @@ type Repository interface { } type repository struct { - db db.Client + db postgres.Client } // NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database db.Client) Repository { +func NewRepository(database postgres.Client) Repository { return &repository{database} } @@ -57,12 +57,12 @@ func insertItems() string { } // Create inserts a new Paragraph into the database -func (r *repository) Create(ctx context.Context, paragraph *models.Paragraph) (string, error) { +func (r *repository) Create(ctx context.Context, paragraphDto *models.Paragraph) (string, error) { query := "INSERT INTO" + " " + tableName + " (" + insertItems() + ") VALUES ($1, $2, $3, $4, $5) " + Returning + columnID - q := db.Query{Name: "ParagraphRepository.Create", Raw: query} + q := postgres.Query{Name: "ParagraphRepository.Create", Raw: query} - args := []interface{}{paragraph.Title, paragraph.Text, paragraph.Type, paragraph.IsPublic, paragraph.PageID} + args := []interface{}{paragraphDto.Title, paragraphDto.Text, paragraphDto.Type, paragraphDto.IsPublic, paragraphDto.PageID} row := r.db.DB().QueryRowContext(ctx, q, args...) var id string @@ -75,9 +75,9 @@ func (r *repository) Create(ctx context.Context, paragraph *models.Paragraph) (s // FindByID retrieves a paragraph by its ID func (r *repository) FindByID(ctx context.Context, id string) (*models.Paragraph, error) { - query := services.SelectWhere(allItems, tableName, columnID) + " AND deleted_at IS NULL" + query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND deleted_at IS NULL" - q := db.Query{Name: "ParagraphRepository.FindByID", Raw: query} + q := postgres.Query{Name: "ParagraphRepository.FindByID", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) @@ -87,13 +87,13 @@ func (r *repository) FindByID(ctx context.Context, id string) (*models.Paragraph // Update modifies an existing paragraph's data func (r *repository) Update(ctx context.Context, id string, updParagraph *models.Paragraph) (*models.Paragraph, error) { query := "UPDATE" + " " + tableName + " SET " + - services.ParamsToQuery(", ", columnTitle, columnText, columnType, columnIsPublic, columnPageID) + + querybuilder.ParamsToQuery(", ", columnTitle, columnText, columnType, columnIsPublic, columnPageID) + " WHERE id = $6" + Returning + allItems() args := []interface{}{updParagraph.Title, updParagraph.Text, updParagraph.Type, updParagraph.IsPublic, updParagraph.PageID, id} - q := db.Query{Name: "ParagraphRepository.Update", Raw: query} + q := postgres.Query{Name: "ParagraphRepository.Update", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, args...) @@ -102,8 +102,8 @@ func (r *repository) Update(ctx context.Context, id string, updParagraph *models // Delete removes a paragraph from the database func (r *repository) Delete(ctx context.Context, id string) (*models.Paragraph, error) { - query := services.DeleteQuery(tableName, columnID) + Returning + allItems() - q := db.Query{Name: "ParagraphRepository.Delete", Raw: query} + query := querybuilder.DeleteQuery(tableName, columnID) + Returning + allItems() + q := postgres.Query{Name: "ParagraphRepository.Delete", Raw: query} row := r.db.DB().QueryRowContext(ctx, q, id) return readItem(row) @@ -113,7 +113,7 @@ func (r *repository) Delete(ctx context.Context, id string) (*models.Paragraph, func (r *repository) List(ctx context.Context) ([]models.Paragraph, error) { query := "SELECT " + allItems() + " FROM " + tableName + " WHERE deleted_at IS NULL" - q := db.Query{Name: "ParagraphRepository.List", Raw: query} + q := postgres.Query{Name: "ParagraphRepository.List", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q) if err != nil { @@ -124,9 +124,9 @@ func (r *repository) List(ctx context.Context) ([]models.Paragraph, error) { } func (r *repository) GetParagraphsByPageID(ctx context.Context, pageID string) ([]models.Paragraph, error) { - query := services.SelectWhere(allItems, tableName, columnPageID) + " AND deleted_at IS NULL" + query := querybuilder.SelectWhere(allItems, tableName, columnPageID) + " AND deleted_at IS NULL" - q := db.Query{Name: "ParagraphRepository.GetParagraphsByPageID", Raw: query} + q := postgres.Query{Name: "ParagraphRepository.GetParagraphsByPageID", Raw: query} rows, err := r.db.DB().QueryContext(ctx, q, pageID) if err != nil { diff --git a/bookback/internal/services/paragraph/repository_test.go b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go similarity index 90% rename from bookback/internal/services/paragraph/repository_test.go rename to bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go index 8ea0824..7121788 100644 --- a/bookback/internal/services/paragraph/repository_test.go +++ b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go @@ -1,6 +1,7 @@ -package paragraph +package paragraphrepo import ( + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" "github.com/SShlykov/zeitment/bookback/internal/models" mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" "github.com/go-faker/faker/v4" @@ -28,7 +29,7 @@ func rowFromParagraph(paragraph *models.Paragraph) *mocks2.ScanResult { paragraph.Title, paragraph.Text, paragraph.Type, paragraph.IsPublic, paragraph.PageID}) } -func initParagraphs(ctrl *gomock.Controller) (Repository, *models.Paragraph) { +func initParagraphs(ctrl *gomock.Controller) (bookrepo.Repository, *models.Paragraph) { client := mocks2.NewMockClient(ctrl) db := mocks2.NewMockDB(ctrl) @@ -38,7 +39,7 @@ func initParagraphs(ctrl *gomock.Controller) (Repository, *models.Paragraph) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) return repo, testParagraph } @@ -69,7 +70,7 @@ func TestRepository_Create(t *testing.T) { client.EXPECT().DB().Return(db) db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - repo := NewRepository(client) + repo := bookrepo.NewRepository(client) id, err := repo.Create(nil, testParagraph) assert.Empty(t, err) assert.NotEmpty(t, id) diff --git a/bookback/internal/services/paragraph/utils.go b/bookback/internal/adapters/db/postgres/paragraphrepo/utils.go similarity index 97% rename from bookback/internal/services/paragraph/utils.go rename to bookback/internal/adapters/db/postgres/paragraphrepo/utils.go index becf881..2dff533 100644 --- a/bookback/internal/services/paragraph/utils.go +++ b/bookback/internal/adapters/db/postgres/paragraphrepo/utils.go @@ -1,4 +1,4 @@ -package paragraph +package paragraphrepo import ( "github.com/SShlykov/zeitment/bookback/internal/models" diff --git a/bookback/internal/servers/http/circuitbreaker/circuitbreaker.go b/bookback/internal/controller/http/circuitbreaker/circuitbreaker.go similarity index 100% rename from bookback/internal/servers/http/circuitbreaker/circuitbreaker.go rename to bookback/internal/controller/http/circuitbreaker/circuitbreaker.go diff --git a/bookback/internal/servers/http/circuitbreaker/circuitbreaker_test.go b/bookback/internal/controller/http/circuitbreaker/circuitbreaker_test.go similarity index 100% rename from bookback/internal/servers/http/circuitbreaker/circuitbreaker_test.go rename to bookback/internal/controller/http/circuitbreaker/circuitbreaker_test.go diff --git a/bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go b/bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go similarity index 88% rename from bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go rename to bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go index b105dd9..6cf8c50 100644 --- a/bookback/internal/servers/http/httpmiddlewares/circuitbreaker.go +++ b/bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go @@ -2,7 +2,7 @@ package httpmiddlewares import ( "errors" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/circuitbreaker" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/circuitbreaker" "github.com/labstack/echo/v4" "net/http" ) diff --git a/bookback/internal/servers/http/httpmiddlewares/cors.go b/bookback/internal/controller/http/httpmiddlewares/cors.go similarity index 100% rename from bookback/internal/servers/http/httpmiddlewares/cors.go rename to bookback/internal/controller/http/httpmiddlewares/cors.go diff --git a/bookback/internal/servers/http/httpmiddlewares/httplogger.go b/bookback/internal/controller/http/httpmiddlewares/httplogger.go similarity index 100% rename from bookback/internal/servers/http/httpmiddlewares/httplogger.go rename to bookback/internal/controller/http/httpmiddlewares/httplogger.go diff --git a/bookback/internal/servers/http/httpmiddlewares/middleware.go b/bookback/internal/controller/http/httpmiddlewares/middleware.go similarity index 100% rename from bookback/internal/servers/http/httpmiddlewares/middleware.go rename to bookback/internal/controller/http/httpmiddlewares/middleware.go diff --git a/bookback/internal/servers/http/controllers/book/config.go b/bookback/internal/controller/http/v1/book/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/book/config.go rename to bookback/internal/controller/http/v1/book/config.go diff --git a/bookback/internal/servers/http/controllers/book/controller.go b/bookback/internal/controller/http/v1/book/controller.go similarity index 92% rename from bookback/internal/servers/http/controllers/book/controller.go rename to bookback/internal/controller/http/v1/book/controller.go index 9ec5232..c50a73d 100644 --- a/bookback/internal/servers/http/controllers/book/controller.go +++ b/bookback/internal/controller/http/v1/book/controller.go @@ -4,8 +4,8 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/config" + service "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" "github.com/SShlykov/zeitment/bookback/internal/metrics" - service "github.com/SShlykov/zeitment/bookback/internal/services/book" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -30,7 +30,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @description Извлекает список всех книг // @tags Книги // @produce application/json -// @success 200 {array} models.Book +// @success 200 {array} entity.Book // @failure 500 {object} string func (bc *Controller) ListBooks(c echo.Context) error { books, err := bc.Service.ListBooks(bc.Ctx) @@ -47,8 +47,8 @@ func (bc *Controller) ListBooks(c echo.Context) error { // @tags Книги // @accept application/json // @produce application/json -// @param book body models.Book true "Book object" -// @success 201 {object} models.Book +// @param book body entity.Book true "Book object" +// @success 201 {object} entity.Book // @failure 400 {object} string // @failure 500 {object} string func (bc *Controller) CreateBook(c echo.Context) error { @@ -71,7 +71,7 @@ func (bc *Controller) CreateBook(c echo.Context) error { // @tags Книги // @param id path string true "Book ID" // @produce application/json -// @success 200 {object} models.Book +// @success 200 {object} entity.Book // @failure 400 {object} string // @failure 404 {object} string // @failure 500 {object} string @@ -97,7 +97,7 @@ func (bc *Controller) GetBookByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "Book ID" -// @param book body models.Book true "Book object" +// @param book body entity.Book true "Book object" // @success 200 {object} responseSingleModel // @failure 400 {object} string // @failure 404 {object} string @@ -129,7 +129,7 @@ func (bc *Controller) UpdateBook(c echo.Context) error { // @description Удаляет книгу по ее ID // @tags Книги // @param id path string true "Book ID" -// @success 204 {object} models.Book +// @success 204 {object} entity.Book // @failure 400 {object} string func (bc *Controller) DeleteBook(c echo.Context) error { id := c.Param("id") diff --git a/bookback/internal/servers/http/controllers/book/errors.go b/bookback/internal/controller/http/v1/book/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/book/errors.go rename to bookback/internal/controller/http/v1/book/errors.go diff --git a/bookback/internal/servers/http/controllers/book/models.go b/bookback/internal/controller/http/v1/book/models.go similarity index 58% rename from bookback/internal/servers/http/controllers/book/models.go rename to bookback/internal/controller/http/v1/book/models.go index c3eea7e..f99b0b6 100644 --- a/bookback/internal/servers/http/controllers/book/models.go +++ b/bookback/internal/controller/http/v1/book/models.go @@ -1,21 +1,23 @@ package book -import "github.com/SShlykov/zeitment/bookback/internal/models" +import ( + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) type Options struct { } type requestModel struct { Options Options `json:"options"` - Book *models.Book `json:"book"` + Book *entity.Book `json:"book"` } type responseSingleModel struct { - Book *models.Book `json:"book"` + Book *entity.Book `json:"book"` Status string `json:"status"` } type responseListModel struct { - Books []models.Book `json:"books"` + Books []entity.Book `json:"books"` Status string `json:"status"` } diff --git a/bookback/internal/servers/http/controllers/book/routes.go b/bookback/internal/controller/http/v1/book/routes.go similarity index 60% rename from bookback/internal/servers/http/controllers/book/routes.go rename to bookback/internal/controller/http/v1/book/routes.go index 1f34d67..1999bd1 100644 --- a/bookback/internal/servers/http/controllers/book/routes.go +++ b/bookback/internal/controller/http/v1/book/routes.go @@ -2,17 +2,18 @@ package book import ( "context" + bookrepo2 "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + bookrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - bookrepo "github.com/SShlykov/zeitment/bookback/internal/services/book" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) // SetBookController регистрирует контроллер книг в маршрутизаторе. -func SetBookController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := bookrepo.NewService(bookrepo.NewRepository(database)) +func SetBookController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := bookrepo.NewService(bookrepo2.NewRepository(database)) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/bookevents/config.go b/bookback/internal/controller/http/v1/bookevents/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/bookevents/config.go rename to bookback/internal/controller/http/v1/bookevents/config.go diff --git a/bookback/internal/servers/http/controllers/bookevents/controller.go b/bookback/internal/controller/http/v1/bookevents/controller.go similarity index 93% rename from bookback/internal/servers/http/controllers/bookevents/controller.go rename to bookback/internal/controller/http/v1/bookevents/controller.go index 5348c02..244cc40 100644 --- a/bookback/internal/servers/http/controllers/bookevents/controller.go +++ b/bookback/internal/controller/http/v1/bookevents/controller.go @@ -4,8 +4,8 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/config" + service "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" "github.com/SShlykov/zeitment/bookback/internal/metrics" - service "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -30,7 +30,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @tags События книги // @produce application/json // @param id path string true "ID события книги" -// @success 200 {object} models.BookEvent +// @success 200 {object} entity.BookEvent // @failure 404 {object} config.HTTPError func (bec *Controller) GetBookEventByID(c echo.Context) error { id := c.Param("id") @@ -54,8 +54,8 @@ func (bec *Controller) GetBookEventByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "ID события книги" -// @param event body models.BookEvent true "BookEvent object" -// @success 200 {object} models.BookEvent +// @param event body entity.BookEvent true "BookEvent object" +// @success 200 {object} entity.BookEvent // @failure 400 {object} config.HTTPError func (bec *Controller) UpdateBookEvent(c echo.Context) error { var request requestModel @@ -109,8 +109,8 @@ func (bec *Controller) DeleteBookEvent(c echo.Context) error { // @tags События книги // @accept application/json // @produce application/json -// @param event body models.BookEvent true "BookEvent object" -// @success 201 {object} models.BookEvent +// @param event body entity.BookEvent true "BookEvent object" +// @success 201 {object} entity.BookEvent // @failure 400 {object} config.HTTPError func (bec *Controller) CreateBookEvent(c echo.Context) error { var request requestModel @@ -132,7 +132,7 @@ func (bec *Controller) CreateBookEvent(c echo.Context) error { // @tags События книги // @produce application/json // @param id path string true "ID книги" -// @success 200 {object} []models.BookEvent +// @success 200 {object} []entity.BookEvent // @failure 404 {object} config.HTTPError func (bec *Controller) GetBookEventsByBookID(c echo.Context) error { id := c.Param("id") @@ -154,7 +154,7 @@ func (bec *Controller) GetBookEventsByBookID(c echo.Context) error { // @tags События книги // @produce application/json // @param id path string true "ID главы" -// @success 200 {object} []models.BookEvent +// @success 200 {object} []entity.BookEvent // @failure 404 {object} config.HTTPError func (bec *Controller) GetBookEventsByChapterID(c echo.Context) error { id := c.Param("id") @@ -177,7 +177,7 @@ func (bec *Controller) GetBookEventsByChapterID(c echo.Context) error { // @tags События книги // @produce application/json // @param id path string true "ID страницы" -// @success 200 {object} []models.BookEvent +// @success 200 {object} []entity.BookEvent // @failure 404 {object} config.HTTPError func (bec *Controller) GetBookEventsByPageID(c echo.Context) error { id := c.Param("id") @@ -200,7 +200,7 @@ func (bec *Controller) GetBookEventsByPageID(c echo.Context) error { // @tags События книги // @produce application/json // @param id path string true "ID параграфа" -// @success 200 {object} []models.BookEvent +// @success 200 {object} []entity.BookEvent // @failure 404 {object} config.HTTPError func (bec *Controller) GetBookEventsByParagraphID(c echo.Context) error { id := c.Param("id") diff --git a/bookback/internal/servers/http/controllers/bookevents/errors.go b/bookback/internal/controller/http/v1/bookevents/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/bookevents/errors.go rename to bookback/internal/controller/http/v1/bookevents/errors.go diff --git a/bookback/internal/servers/http/controllers/bookevents/models.go b/bookback/internal/controller/http/v1/bookevents/models.go similarity index 100% rename from bookback/internal/servers/http/controllers/bookevents/models.go rename to bookback/internal/controller/http/v1/bookevents/models.go diff --git a/bookback/internal/servers/http/controllers/bookevents/routes.go b/bookback/internal/controller/http/v1/bookevents/routes.go similarity index 64% rename from bookback/internal/servers/http/controllers/bookevents/routes.go rename to bookback/internal/controller/http/v1/bookevents/routes.go index c8900cf..042a188 100644 --- a/bookback/internal/servers/http/controllers/bookevents/routes.go +++ b/bookback/internal/controller/http/v1/bookevents/routes.go @@ -2,17 +2,17 @@ package bookevents import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + bookeventsrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - bookeventsrepo "github.com/SShlykov/zeitment/bookback/internal/services/bookevents" - - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetBookEventController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := bookeventsrepo.NewService(bookeventsrepo.NewRepository(database)) +func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := bookeventsrepo.NewService(bookrepo.NewRepository(database)) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/chapter/config.go b/bookback/internal/controller/http/v1/chapter/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/chapter/config.go rename to bookback/internal/controller/http/v1/chapter/config.go diff --git a/bookback/internal/servers/http/controllers/chapter/controller.go b/bookback/internal/controller/http/v1/chapter/controller.go similarity index 91% rename from bookback/internal/servers/http/controllers/chapter/controller.go rename to bookback/internal/controller/http/v1/chapter/controller.go index ded89ad..302dc6b 100644 --- a/bookback/internal/servers/http/controllers/chapter/controller.go +++ b/bookback/internal/controller/http/v1/chapter/controller.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/services/chapter" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -30,7 +30,7 @@ func NewController(srv chapter.Service, metric metrics.Metrics, logger *slog.Log // @description Извлекает список всех глав // @tags Главы // @produce application/json -// @success 200 {array} models.Chapter +// @success 200 {array} entity.Chapter // @failure 500 {object} config.HTTPError func (ch *Controller) ListChapters(c echo.Context) error { chapters, err := ch.Service.ListChapters(ch.Ctx) @@ -47,8 +47,8 @@ func (ch *Controller) ListChapters(c echo.Context) error { // @tags Главы // @accept application/json // @produce application/json -// @param chapter body models.Chapter true "Chapter object" -// @success 201 {object} models.Chapter +// @param chapter body entity.Chapter true "Chapter object" +// @success 201 {object} entity.Chapter // @failure 400 {object} config.HTTPError func (ch *Controller) CreateChapter(c echo.Context) error { var chap requestModel @@ -70,7 +70,7 @@ func (ch *Controller) CreateChapter(c echo.Context) error { // @tags Главы // @param id path string true "ID главы" // @produce application/json -// @success 200 {object} models.Chapter +// @success 200 {object} entity.Chapter // @failure 404 {object} config.HTTPError func (ch *Controller) GetChapterByID(c echo.Context) error { id := c.Param("id") @@ -93,8 +93,8 @@ func (ch *Controller) GetChapterByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "ID главы" -// @param chapter body models.Chapter true "Chapter object" -// @success 200 {object} models.Chapter +// @param chapter body entity.Chapter true "Chapter object" +// @success 200 {object} entity.Chapter // @failure 400 {object} config.HTTPError func (ch *Controller) UpdateChapter(c echo.Context) error { id := c.Param("id") @@ -125,7 +125,7 @@ func (ch *Controller) UpdateChapter(c echo.Context) error { // @tags Главы // @param id path string true "ID главы" // @produce application/json -// @success 200 {object} models.Chapter +// @success 200 {object} entity.Chapter // @failure 406 {object} config.HTTPError func (ch *Controller) DeleteChapter(c echo.Context) error { id := c.Param("id") @@ -147,7 +147,7 @@ func (ch *Controller) DeleteChapter(c echo.Context) error { // @tags Главы // @param id path string true "ID книги" // @produce application/json -// @success 200 {array} models.Chapter +// @success 200 {array} entity.Chapter // @failure 404 {object} config.HTTPError func (ch *Controller) GetChapterByBookID(c echo.Context) error { id := c.Param("id") diff --git a/bookback/internal/servers/http/controllers/chapter/errors.go b/bookback/internal/controller/http/v1/chapter/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/chapter/errors.go rename to bookback/internal/controller/http/v1/chapter/errors.go diff --git a/bookback/internal/servers/http/controllers/chapter/models.go b/bookback/internal/controller/http/v1/chapter/models.go similarity index 100% rename from bookback/internal/servers/http/controllers/chapter/models.go rename to bookback/internal/controller/http/v1/chapter/models.go diff --git a/bookback/internal/servers/http/controllers/chapter/routes.go b/bookback/internal/controller/http/v1/chapter/routes.go similarity index 59% rename from bookback/internal/servers/http/controllers/chapter/routes.go rename to bookback/internal/controller/http/v1/chapter/routes.go index 1701b00..83e4c49 100644 --- a/bookback/internal/servers/http/controllers/chapter/routes.go +++ b/bookback/internal/controller/http/v1/chapter/routes.go @@ -2,17 +2,17 @@ package chapter import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + chapterrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - bookrepo "github.com/SShlykov/zeitment/bookback/internal/services/book" - chapterrepo "github.com/SShlykov/zeitment/bookback/internal/services/chapter" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetChapterController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - chapterRepo := chapterrepo.NewRepository(database) +func SetChapterController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + chapterRepo := bookrepo.NewRepository(database) bookRepo := bookrepo.NewRepository(database) service := chapterrepo.NewService(chapterRepo, bookRepo) controller := NewController(service, metrics, logger, ctx) diff --git a/bookback/internal/servers/http/controllers/health/config.go b/bookback/internal/controller/http/v1/health/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/health/config.go rename to bookback/internal/controller/http/v1/health/config.go diff --git a/bookback/internal/servers/http/controllers/health/controller.go b/bookback/internal/controller/http/v1/health/controller.go similarity index 100% rename from bookback/internal/servers/http/controllers/health/controller.go rename to bookback/internal/controller/http/v1/health/controller.go diff --git a/bookback/internal/servers/http/controllers/health/routes.go b/bookback/internal/controller/http/v1/health/routes.go similarity index 60% rename from bookback/internal/servers/http/controllers/health/routes.go rename to bookback/internal/controller/http/v1/health/routes.go index 7ab912e..ce50bc9 100644 --- a/bookback/internal/servers/http/controllers/health/routes.go +++ b/bookback/internal/controller/http/v1/health/routes.go @@ -2,14 +2,14 @@ package health import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetHealthController(e *echo.Echo, _ db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func SetHealthController(e *echo.Echo, _ postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { controller := NewController(metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/mapvariables/config.go b/bookback/internal/controller/http/v1/mapvariables/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/mapvariables/config.go rename to bookback/internal/controller/http/v1/mapvariables/config.go diff --git a/bookback/internal/servers/http/controllers/mapvariables/controller.go b/bookback/internal/controller/http/v1/mapvariables/controller.go similarity index 94% rename from bookback/internal/servers/http/controllers/mapvariables/controller.go rename to bookback/internal/controller/http/v1/mapvariables/controller.go index 176fa1f..344c66a 100644 --- a/bookback/internal/servers/http/controllers/mapvariables/controller.go +++ b/bookback/internal/controller/http/v1/mapvariables/controller.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/config" + service "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" "github.com/SShlykov/zeitment/bookback/internal/metrics" - service "github.com/SShlykov/zeitment/bookback/internal/services/mapvariables" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -32,7 +32,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @tags Переменные карты // @produce application/json // @param id path string true "ID переменной карты" -// @success 200 {object} models.MapVariable +// @success 200 {object} entity.MapVariable // @failure 404 {object} config.HTTPError func (mvc *Controller) GetMapVariableByID(c echo.Context) error { id := c.Param("id") @@ -57,8 +57,8 @@ func (mvc *Controller) GetMapVariableByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "ID переменной карты" -// @param variable body models.MapVariable true "MapVariable object" -// @success 200 {object} models.MapVariable +// @param variable body entity.MapVariable true "MapVariable object" +// @success 200 {object} entity.MapVariable // @failure 400 {object} config.HTTPError func (mvc *Controller) UpdateMapVariable(c echo.Context) error { id := c.Param("id") @@ -111,7 +111,7 @@ func (mvc *Controller) DeleteMapVariable(c echo.Context) error { // @tags Переменные карты // @produce application/json // @param id path string true "ID книги" -// @success 200 {array} models.MapVariable +// @success 200 {array} entity.MapVariable // @failure 404 {object} config.HTTPError func (mvc *Controller) GetMapVariablesByBookID(c echo.Context) error { id := c.Param("id") @@ -134,7 +134,7 @@ func (mvc *Controller) GetMapVariablesByBookID(c echo.Context) error { // @tags Переменные карты // @produce application/json // @param id path string true "ID главы" -// @success 200 {array} models.MapVariable +// @success 200 {array} entity.MapVariable // @failure 404 {object} config.HTTPError func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context) error { id := c.Param("id") @@ -157,7 +157,7 @@ func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context) error { // @tags Переменные карты // @produce application/json // @param id path string true "ID страницы" -// @success 200 {array} models.MapVariable +// @success 200 {array} entity.MapVariable // @failure 404 {object} config.HTTPError func (mvc *Controller) GetMapVariablesByPageID(c echo.Context) error { id := c.Param("id") @@ -180,7 +180,7 @@ func (mvc *Controller) GetMapVariablesByPageID(c echo.Context) error { // @tags Переменные карты // @produce application/json // @param id path string true "ID параграфа" -// @success 200 {array} models.MapVariable +// @success 200 {array} entity.MapVariable // @failure 404 {object} config.HTTPError func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context) error { id := c.Param("id") @@ -203,8 +203,8 @@ func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context) error { // @tags Переменные карты // @accept application/json // @produce application/json -// @param variable body models.MapVariable true "MapVariable object" -// @success 201 {object} models.MapVariable +// @param variable body entity.MapVariable true "MapVariable object" +// @success 201 {object} entity.MapVariable // @failure 400 {object} config.HTTPError func (mvc *Controller) CreateMapVariable(c echo.Context) error { var request requestModel diff --git a/bookback/internal/servers/http/controllers/mapvariables/errors.go b/bookback/internal/controller/http/v1/mapvariables/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/mapvariables/errors.go rename to bookback/internal/controller/http/v1/mapvariables/errors.go diff --git a/bookback/internal/servers/http/controllers/mapvariables/models.go b/bookback/internal/controller/http/v1/mapvariables/models.go similarity index 100% rename from bookback/internal/servers/http/controllers/mapvariables/models.go rename to bookback/internal/controller/http/v1/mapvariables/models.go diff --git a/bookback/internal/servers/http/controllers/mapvariables/routes.go b/bookback/internal/controller/http/v1/mapvariables/routes.go similarity index 64% rename from bookback/internal/servers/http/controllers/mapvariables/routes.go rename to bookback/internal/controller/http/v1/mapvariables/routes.go index 7adcc95..ff33e55 100644 --- a/bookback/internal/servers/http/controllers/mapvariables/routes.go +++ b/bookback/internal/controller/http/v1/mapvariables/routes.go @@ -2,16 +2,17 @@ package mapvariables import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + mapvariablesrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - mapvariablesrepo "github.com/SShlykov/zeitment/bookback/internal/services/mapvariables" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetMapVariablesController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := mapvariablesrepo.NewService(mapvariablesrepo.NewRepository(database)) +func SetMapVariablesController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := mapvariablesrepo.NewService(bookrepo.NewRepository(database)) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/page/config.go b/bookback/internal/controller/http/v1/page/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/page/config.go rename to bookback/internal/controller/http/v1/page/config.go diff --git a/bookback/internal/servers/http/controllers/page/controller.go b/bookback/internal/controller/http/v1/page/controller.go similarity index 91% rename from bookback/internal/servers/http/controllers/page/controller.go rename to bookback/internal/controller/http/v1/page/controller.go index 034ef87..018f206 100644 --- a/bookback/internal/servers/http/controllers/page/controller.go +++ b/bookback/internal/controller/http/v1/page/controller.go @@ -4,8 +4,8 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/config" + service "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" "github.com/SShlykov/zeitment/bookback/internal/metrics" - service "github.com/SShlykov/zeitment/bookback/internal/services/page" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -28,7 +28,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @description Извлекает список всех страниц // @tags Страницы // @produce application/json -// @success 200 {array} models.Page +// @success 200 {array} entity.Page // @failure 500 {object} config.HTTPError func (p *Controller) ListPages(c echo.Context) error { pages, err := p.Service.ListPages(p.Ctx) @@ -45,8 +45,8 @@ func (p *Controller) ListPages(c echo.Context) error { // @tags Страницы // @accept application/json // @produce application/json -// @param page body models.Page true "Page object" -// @success 201 {object} models.Page +// @param page body entity.Page true "Page object" +// @success 201 {object} entity.Page // @failure 400 {object} config.HTTPError func (p *Controller) CreatePage(c echo.Context) error { var request requestModel @@ -68,7 +68,7 @@ func (p *Controller) CreatePage(c echo.Context) error { // @tags Страницы // @produce application/json // @param id path string true "ID страницы" -// @success 200 {object} models.Page +// @success 200 {object} entity.Page // @failure 404 {object} config.HTTPError func (p *Controller) GetPageByID(c echo.Context) error { id := c.Param("id") @@ -92,8 +92,8 @@ func (p *Controller) GetPageByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "ID страницы" -// @param page body models.Page true "Page object" -// @success 200 {object} models.Page +// @param page body entity.Page true "Page object" +// @success 200 {object} entity.Page // @failure 400 {object} config.HTTPError func (p *Controller) UpdatePage(c echo.Context) error { id := c.Param("id") @@ -123,7 +123,7 @@ func (p *Controller) UpdatePage(c echo.Context) error { // @tags Страницы // @param id path string true "ID страницы" // @produce application/json -// @success 200 {object} models.Page +// @success 200 {object} entity.Page // @failure 500 {object} config.HTTPError func (p *Controller) DeletePage(c echo.Context) error { id := c.Param("id") @@ -145,7 +145,7 @@ func (p *Controller) DeletePage(c echo.Context) error { // @tags Страницы // @produce application/json // @param id path string true "ID главы" -// @success 200 {array} models.Page +// @success 200 {array} entity.Page // @failure 404 {object} config.HTTPError func (p *Controller) GetPagesByChapterID(c echo.Context) error { id := c.Param("id") diff --git a/bookback/internal/servers/http/controllers/page/errors.go b/bookback/internal/controller/http/v1/page/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/page/errors.go rename to bookback/internal/controller/http/v1/page/errors.go diff --git a/bookback/internal/servers/http/controllers/page/models.go b/bookback/internal/controller/http/v1/page/models.go similarity index 100% rename from bookback/internal/servers/http/controllers/page/models.go rename to bookback/internal/controller/http/v1/page/models.go diff --git a/bookback/internal/servers/http/controllers/page/routes.go b/bookback/internal/controller/http/v1/page/routes.go similarity index 54% rename from bookback/internal/servers/http/controllers/page/routes.go rename to bookback/internal/controller/http/v1/page/routes.go index c1e0c53..d449f08 100644 --- a/bookback/internal/servers/http/controllers/page/routes.go +++ b/bookback/internal/controller/http/v1/page/routes.go @@ -2,16 +2,17 @@ package page import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + pagerepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - pagerepo "github.com/SShlykov/zeitment/bookback/internal/services/page" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetPageController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := pagerepo.NewService(pagerepo.NewRepository(database)) +func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := pagerepo.NewService(bookrepo.NewRepository(database)) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/paragraph/config.go b/bookback/internal/controller/http/v1/paragraph/config.go similarity index 100% rename from bookback/internal/servers/http/controllers/paragraph/config.go rename to bookback/internal/controller/http/v1/paragraph/config.go diff --git a/bookback/internal/servers/http/controllers/paragraph/controller.go b/bookback/internal/controller/http/v1/paragraph/controller.go similarity index 91% rename from bookback/internal/servers/http/controllers/paragraph/controller.go rename to bookback/internal/controller/http/v1/paragraph/controller.go index e66bff4..68c6fd0 100644 --- a/bookback/internal/servers/http/controllers/paragraph/controller.go +++ b/bookback/internal/controller/http/v1/paragraph/controller.go @@ -4,8 +4,8 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/config" + service "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" "github.com/SShlykov/zeitment/bookback/internal/metrics" - service "github.com/SShlykov/zeitment/bookback/internal/services/paragraph" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -29,7 +29,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @description Извлекает список всех параграфов // @tags Параграфы // @produce application/json -// @success 200 {array} models.Paragraph +// @success 200 {array} entity.Paragraph // @failure 500 {object} config.HTTPError func (p *Controller) ListParagraphs(c echo.Context) error { paragraphs, err := p.Service.ListParagraphs(p.Ctx) @@ -46,8 +46,8 @@ func (p *Controller) ListParagraphs(c echo.Context) error { // @tags Параграфы // @accept application/json // @produce application/json -// @param paragraph body models.Paragraph true "Paragraph object" -// @success 201 {object} models.Paragraph +// @param paragraph body entity.Paragraph true "Paragraph object" +// @success 201 {object} entity.Paragraph // @failure 400 {object} config.HTTPError func (p *Controller) CreateParagraph(c echo.Context) error { var request requestModel @@ -69,7 +69,7 @@ func (p *Controller) CreateParagraph(c echo.Context) error { // @tags Параграфы // @param id path string true "ID параграфа" // @produce application/json -// @success 200 {object} models.Paragraph +// @success 200 {object} entity.Paragraph // @failure 404 {object} config.HTTPError func (p *Controller) GetParagraphByID(c echo.Context) error { id := c.Param("id") @@ -93,8 +93,8 @@ func (p *Controller) GetParagraphByID(c echo.Context) error { // @accept application/json // @produce application/json // @param id path string true "ID параграфа" -// @param paragraph body models.Paragraph true "Paragraph object" -// @success 200 {object} models.Paragraph +// @param paragraph body entity.Paragraph true "Paragraph object" +// @success 200 {object} entity.Paragraph // @failure 400 {object} config.HTTPError func (p *Controller) UpdateParagraph(c echo.Context) error { id := c.Param("id") @@ -123,7 +123,7 @@ func (p *Controller) UpdateParagraph(c echo.Context) error { // @description Удаляет параграф по его ID // @tags Параграфы // @param id path string true "ID параграфа" -// @success 200 {object} models.Paragraph +// @success 200 {object} entity.Paragraph // @failure 500 {object} config.HTTPError func (p *Controller) DeleteParagraph(c echo.Context) error { id := c.Param("id") @@ -145,7 +145,7 @@ func (p *Controller) DeleteParagraph(c echo.Context) error { // @tags Параграфы // @param id path string true "ID страницы" // @produce application/json -// @success 200 {array} models.Paragraph +// @success 200 {array} entity.Paragraph // @failure 404 {object} config.HTTPError func (p *Controller) GetParagraphsByPageID(c echo.Context) error { id := c.Param("id") diff --git a/bookback/internal/servers/http/controllers/paragraph/errors.go b/bookback/internal/controller/http/v1/paragraph/errors.go similarity index 100% rename from bookback/internal/servers/http/controllers/paragraph/errors.go rename to bookback/internal/controller/http/v1/paragraph/errors.go diff --git a/bookback/internal/servers/http/controllers/paragraph/models.go b/bookback/internal/controller/http/v1/paragraph/models.go similarity index 100% rename from bookback/internal/servers/http/controllers/paragraph/models.go rename to bookback/internal/controller/http/v1/paragraph/models.go diff --git a/bookback/internal/servers/http/controllers/paragraph/routes.go b/bookback/internal/controller/http/v1/paragraph/routes.go similarity index 54% rename from bookback/internal/servers/http/controllers/paragraph/routes.go rename to bookback/internal/controller/http/v1/paragraph/routes.go index e644b98..8bd7a95 100644 --- a/bookback/internal/servers/http/controllers/paragraph/routes.go +++ b/bookback/internal/controller/http/v1/paragraph/routes.go @@ -2,16 +2,17 @@ package paragraph import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + paragraphrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - paragraphrepo "github.com/SShlykov/zeitment/bookback/internal/services/paragraph" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" ) -func SetParagraphController(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := paragraphrepo.NewService(paragraphrepo.NewRepository(database)) +func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + service := paragraphrepo.NewService(bookrepo.NewRepository(database)) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) diff --git a/bookback/internal/servers/http/controllers/swagger/controller.go b/bookback/internal/controller/http/v1/swagger/controller.go similarity index 100% rename from bookback/internal/servers/http/controllers/swagger/controller.go rename to bookback/internal/controller/http/v1/swagger/controller.go diff --git a/bookback/internal/models/book.go b/bookback/internal/domain/entity/book.go similarity index 98% rename from bookback/internal/models/book.go rename to bookback/internal/domain/entity/book.go index 0030447..519d768 100644 --- a/bookback/internal/models/book.go +++ b/bookback/internal/domain/entity/book.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/models/bookevents.go b/bookback/internal/domain/entity/bookevents.go similarity index 98% rename from bookback/internal/models/bookevents.go rename to bookback/internal/domain/entity/bookevents.go index 0adab45..e436053 100644 --- a/bookback/internal/models/bookevents.go +++ b/bookback/internal/domain/entity/bookevents.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/models/chapter.go b/bookback/internal/domain/entity/chapter.go similarity index 98% rename from bookback/internal/models/chapter.go rename to bookback/internal/domain/entity/chapter.go index d633861..9edf962 100644 --- a/bookback/internal/models/chapter.go +++ b/bookback/internal/domain/entity/chapter.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/models/mapvariables.go b/bookback/internal/domain/entity/mapvariables.go similarity index 98% rename from bookback/internal/models/mapvariables.go rename to bookback/internal/domain/entity/mapvariables.go index ff005e4..ed29ee4 100644 --- a/bookback/internal/models/mapvariables.go +++ b/bookback/internal/domain/entity/mapvariables.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/models/nullable.go b/bookback/internal/domain/entity/nullable.go similarity index 99% rename from bookback/internal/models/nullable.go rename to bookback/internal/domain/entity/nullable.go index d311164..284f481 100644 --- a/bookback/internal/models/nullable.go +++ b/bookback/internal/domain/entity/nullable.go @@ -1,4 +1,4 @@ -package models +package entity import ( "database/sql" diff --git a/bookback/internal/models/page.go b/bookback/internal/domain/entity/page.go similarity index 97% rename from bookback/internal/models/page.go rename to bookback/internal/domain/entity/page.go index eb80a56..59c8bcc 100644 --- a/bookback/internal/models/page.go +++ b/bookback/internal/domain/entity/page.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/models/paragraph.go b/bookback/internal/domain/entity/paragraph.go similarity index 96% rename from bookback/internal/models/paragraph.go rename to bookback/internal/domain/entity/paragraph.go index 3e77ca8..3794fc7 100644 --- a/bookback/internal/models/paragraph.go +++ b/bookback/internal/domain/entity/paragraph.go @@ -1,4 +1,4 @@ -package models +package entity import ( "time" diff --git a/bookback/internal/domain/services/book/interface.go b/bookback/internal/domain/services/book/interface.go new file mode 100644 index 0000000..59a4f53 --- /dev/null +++ b/bookback/internal/domain/services/book/interface.go @@ -0,0 +1,14 @@ +package book + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repo interface { + Create(ctx context.Context, book *entity.Book) (string, error) + FindByID(ctx context.Context, id string) (*entity.Book, error) + Update(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) + Delete(ctx context.Context, id string) (*entity.Book, error) + List(ctx context.Context) ([]entity.Book, error) +} diff --git a/bookback/internal/services/book/service.go b/bookback/internal/domain/services/book/service.go similarity index 57% rename from bookback/internal/services/book/service.go rename to bookback/internal/domain/services/book/service.go index acd6a54..5127a91 100644 --- a/bookback/internal/services/book/service.go +++ b/bookback/internal/domain/services/book/service.go @@ -3,29 +3,29 @@ package book import ( "context" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) // Service описывает сервис для работы с книгами. type Service interface { - CreateBook(ctx context.Context, book *models.Book) (*models.Book, error) - GetBookByID(ctx context.Context, id string) (*models.Book, error) - UpdateBook(ctx context.Context, id string, book *models.Book) (*models.Book, error) - DeleteBook(ctx context.Context, id string) (*models.Book, error) - ListBooks(ctx context.Context) ([]models.Book, error) + CreateBook(ctx context.Context, book *entity.Book) (*entity.Book, error) + GetBookByID(ctx context.Context, id string) (*entity.Book, error) + UpdateBook(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) + DeleteBook(ctx context.Context, id string) (*entity.Book, error) + ListBooks(ctx context.Context) ([]entity.Book, error) } type service struct { - repo Repository + repo Repo } // NewService создает новый экземпляр Service. -func NewService(repo Repository) Service { +func NewService(repo Repo) Service { return &service{repo} } -func (s *service) CreateBook(ctx context.Context, book *models.Book) (*models.Book, error) { - var newBook *models.Book +func (s *service) CreateBook(ctx context.Context, book *entity.Book) (*entity.Book, error) { + var newBook *entity.Book id, err := s.repo.Create(ctx, book) if err != nil { return nil, err @@ -39,25 +39,25 @@ func (s *service) CreateBook(ctx context.Context, book *models.Book) (*models.Bo return newBook, err } -func (s *service) GetBookByID(ctx context.Context, id string) (*models.Book, error) { +func (s *service) GetBookByID(ctx context.Context, id string) (*entity.Book, error) { return s.repo.FindByID(ctx, id) } -func (s *service) UpdateBook(ctx context.Context, id string, book *models.Book) (*models.Book, error) { +func (s *service) UpdateBook(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) { if !s.isBookExisted(ctx, id) { return nil, config.ErrorNotFound } return s.repo.Update(ctx, id, book) } -func (s *service) DeleteBook(ctx context.Context, id string) (*models.Book, error) { +func (s *service) DeleteBook(ctx context.Context, id string) (*entity.Book, error) { if !s.isBookExisted(ctx, id) { return nil, config.ErrorNotFound } return s.repo.Delete(ctx, id) } -func (s *service) ListBooks(ctx context.Context) ([]models.Book, error) { +func (s *service) ListBooks(ctx context.Context) ([]entity.Book, error) { return s.repo.List(ctx) } diff --git a/bookback/internal/domain/services/bookevents/interface.go b/bookback/internal/domain/services/bookevents/interface.go new file mode 100644 index 0000000..3b11390 --- /dev/null +++ b/bookback/internal/domain/services/bookevents/interface.go @@ -0,0 +1,17 @@ +package bookevents + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repository interface { + Create(ctx context.Context, event *entity.BookEvent) (string, error) + FindByID(ctx context.Context, id string) (*entity.BookEvent, error) + Update(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) + Delete(ctx context.Context, id string) (*entity.BookEvent, error) + GetByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) + GetByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) + GetByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) + GetByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) +} diff --git a/bookback/internal/services/bookevents/service.go b/bookback/internal/domain/services/bookevents/service.go similarity index 53% rename from bookback/internal/services/bookevents/service.go rename to bookback/internal/domain/services/bookevents/service.go index af1b154..83bfbaf 100644 --- a/bookback/internal/services/bookevents/service.go +++ b/bookback/internal/domain/services/bookevents/service.go @@ -2,18 +2,18 @@ package bookevents import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) type Service interface { - CreateBookEvent(ctx context.Context, event *models.BookEvent) (*models.BookEvent, error) - GetBookEventByID(ctx context.Context, id string) (*models.BookEvent, error) - UpdateBookEvent(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) - DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) - GetBookEventsByBookID(ctx context.Context, bookID string) ([]models.BookEvent, error) - GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]models.BookEvent, error) - GetBookEventsByPageID(ctx context.Context, pageID string) ([]models.BookEvent, error) - GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]models.BookEvent, error) + CreateBookEvent(ctx context.Context, event *entity.BookEvent) (*entity.BookEvent, error) + GetBookEventByID(ctx context.Context, id string) (*entity.BookEvent, error) + UpdateBookEvent(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) + DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) + GetBookEventsByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) } type service struct { @@ -24,7 +24,7 @@ func NewService(repo Repository) Service { return &service{repo} } -func (s *service) CreateBookEvent(ctx context.Context, event *models.BookEvent) (*models.BookEvent, error) { +func (s *service) CreateBookEvent(ctx context.Context, event *entity.BookEvent) (*entity.BookEvent, error) { id, err := s.repo.Create(ctx, event) if err != nil { return nil, err @@ -34,30 +34,30 @@ func (s *service) CreateBookEvent(ctx context.Context, event *models.BookEvent) return event, err } -func (s *service) GetBookEventByID(ctx context.Context, id string) (*models.BookEvent, error) { +func (s *service) GetBookEventByID(ctx context.Context, id string) (*entity.BookEvent, error) { return s.repo.FindByID(ctx, id) } -func (s *service) UpdateBookEvent(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) { +func (s *service) UpdateBookEvent(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) { return s.repo.Update(ctx, id, event) } -func (s *service) DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) { +func (s *service) DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) { return s.repo.Delete(ctx, id) } -func (s *service) GetBookEventsByBookID(ctx context.Context, bookID string) ([]models.BookEvent, error) { +func (s *service) GetBookEventsByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) { return s.repo.GetByBookID(ctx, bookID) } -func (s *service) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]models.BookEvent, error) { +func (s *service) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) { return s.repo.GetByChapterID(ctx, chapterID) } -func (s *service) GetBookEventsByPageID(ctx context.Context, pageID string) ([]models.BookEvent, error) { +func (s *service) GetBookEventsByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) { return s.repo.GetByPageID(ctx, pageID) } -func (s *service) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]models.BookEvent, error) { +func (s *service) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) { return s.repo.GetByParagraphID(ctx, paragraphID) } diff --git a/bookback/internal/domain/services/chapter/interface.go b/bookback/internal/domain/services/chapter/interface.go new file mode 100644 index 0000000..7631617 --- /dev/null +++ b/bookback/internal/domain/services/chapter/interface.go @@ -0,0 +1,15 @@ +package chapter + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repository interface { + Create(ctx context.Context, chapter *entity.Chapter) (string, error) + FindByID(ctx context.Context, id string) (*entity.Chapter, error) + Update(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) + Delete(ctx context.Context, id string) (*entity.Chapter, error) + List(ctx context.Context) ([]entity.Chapter, error) + GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) +} diff --git a/bookback/internal/domain/services/chapter/service.go b/bookback/internal/domain/services/chapter/service.go new file mode 100644 index 0000000..752096d --- /dev/null +++ b/bookback/internal/domain/services/chapter/service.go @@ -0,0 +1,60 @@ +package chapter + +import ( + "context" + "fmt" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Service interface { + CreateChapter(ctx context.Context, chapter *entity.Chapter) (*entity.Chapter, error) + GetChapterByID(ctx context.Context, id string) (*entity.Chapter, error) + UpdateChapter(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) + DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) + ListChapters(ctx context.Context) ([]entity.Chapter, error) + + GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) +} + +type service struct { + chapterRepo Repository +} + +func NewService(chapterRepo Repository) Service { + return &service{chapterRepo: chapterRepo} +} + +func (ch *service) CreateChapter(ctx context.Context, chapter *entity.Chapter) (*entity.Chapter, error) { + id, err := ch.chapterRepo.Create(ctx, chapter) + if err != nil { + return nil, err + } + + chapter, err = ch.GetChapterByID(ctx, id) + if err != nil { + fmt.Println(err) + return nil, err + } + + return chapter, nil +} + +func (ch *service) GetChapterByID(ctx context.Context, id string) (*entity.Chapter, error) { + return ch.chapterRepo.FindByID(ctx, id) +} + +func (ch *service) UpdateChapter(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) { + return ch.chapterRepo.Update(ctx, id, chapter) +} + +func (ch *service) DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) { + return ch.chapterRepo.Delete(ctx, id) +} + +func (ch *service) ListChapters(ctx context.Context) ([]entity.Chapter, error) { + return ch.chapterRepo.List(ctx) +} + +func (ch *service) GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) { + return ch.chapterRepo.GetChapterByBookID(ctx, bookID) +} diff --git a/bookback/internal/domain/services/mapvariables/interface.go b/bookback/internal/domain/services/mapvariables/interface.go new file mode 100644 index 0000000..c11d1ed --- /dev/null +++ b/bookback/internal/domain/services/mapvariables/interface.go @@ -0,0 +1,10 @@ +package mapvariables + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repository interface { + Create(ctx context.Context, event *entity.BookEvent) (string, error) +} diff --git a/bookback/internal/services/mapvariables/service.go b/bookback/internal/domain/services/mapvariables/service.go similarity index 52% rename from bookback/internal/services/mapvariables/service.go rename to bookback/internal/domain/services/mapvariables/service.go index cedbe76..d8207b5 100644 --- a/bookback/internal/services/mapvariables/service.go +++ b/bookback/internal/domain/services/mapvariables/service.go @@ -2,29 +2,30 @@ package mapvariables import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/mapvarrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) type Service interface { - CreateMapVariable(ctx context.Context, variable *models.MapVariable) (*models.MapVariable, error) - GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) - UpdateMapVariable(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) - DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) - GetMapVariablesByBookID(ctx context.Context, mapID string) ([]models.MapVariable, error) - GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) - GetMapVariablesByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) - GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) + CreateMapVariable(ctx context.Context, variable *entity.MapVariable) (*entity.MapVariable, error) + GetMapVariableByID(ctx context.Context, id string) (*entity.MapVariable, error) + UpdateMapVariable(ctx context.Context, id string, variable *entity.MapVariable) (*entity.MapVariable, error) + DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) + GetMapVariablesByBookID(ctx context.Context, mapID string) ([]entity.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]entity.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string) ([]entity.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]entity.MapVariable, error) } type service struct { - repo Repository + repo mapvarrepo.Repository } -func NewService(repo Repository) Service { +func NewService(repo mapvarrepo.Repository) Service { return &service{repo} } -func (s *service) CreateMapVariable(ctx context.Context, variable *models.MapVariable) (*models.MapVariable, error) { +func (s *service) CreateMapVariable(ctx context.Context, variable *entity.MapVariable) (*entity.MapVariable, error) { id, err := s.repo.Create(ctx, variable) if err != nil { return nil, err @@ -34,30 +35,30 @@ func (s *service) CreateMapVariable(ctx context.Context, variable *models.MapVar return variable, err } -func (s *service) GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) { +func (s *service) GetMapVariableByID(ctx context.Context, id string) (*entity.MapVariable, error) { return s.repo.FindByID(ctx, id) } -func (s *service) UpdateMapVariable(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) { +func (s *service) UpdateMapVariable(ctx context.Context, id string, variable *entity.MapVariable) (*entity.MapVariable, error) { return s.repo.Update(ctx, id, variable) } -func (s *service) DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) { +func (s *service) DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) { return s.repo.Delete(ctx, id) } -func (s *service) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]models.MapVariable, error) { +func (s *service) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]entity.MapVariable, error) { return s.repo.GetByBookID(ctx, mapID) } -func (s *service) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) { +func (s *service) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]entity.MapVariable, error) { return s.repo.GetByChapterID(ctx, chapterID) } -func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) { +func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]entity.MapVariable, error) { return s.repo.GetByPageID(ctx, pageID) } -func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) { +func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]entity.MapVariable, error) { return s.repo.GetByParagraphID(ctx, paragraphID) } diff --git a/bookback/internal/domain/services/page/interface.go b/bookback/internal/domain/services/page/interface.go new file mode 100644 index 0000000..e1e50c6 --- /dev/null +++ b/bookback/internal/domain/services/page/interface.go @@ -0,0 +1,15 @@ +package page + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repository interface { + Create(ctx context.Context, page *entity.Page) (string, error) + FindByID(ctx context.Context, id string) (*entity.Page, error) + Update(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) + Delete(ctx context.Context, id string) (*entity.Page, error) + List(ctx context.Context) ([]entity.Page, error) + GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) +} diff --git a/bookback/internal/domain/services/page/service.go b/bookback/internal/domain/services/page/service.go new file mode 100644 index 0000000..133e188 --- /dev/null +++ b/bookback/internal/domain/services/page/service.go @@ -0,0 +1,54 @@ +package page + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Service interface { + CreatePage(ctx context.Context, page *entity.Page) (*entity.Page, error) + GetPageByID(ctx context.Context, id string) (*entity.Page, error) + UpdatePage(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) + DeletePage(ctx context.Context, id string) (*entity.Page, error) + ListPages(ctx context.Context) ([]entity.Page, error) + + GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) +} + +type service struct { + repo Repository +} + +func NewService(repo Repository) Service { + return &service{repo} +} + +func (s *service) CreatePage(ctx context.Context, page *entity.Page) (*entity.Page, error) { + id, err := s.repo.Create(ctx, page) + if err != nil { + return nil, err + } + page.ID = id + + return page, err +} + +func (s *service) GetPageByID(ctx context.Context, id string) (*entity.Page, error) { + return s.repo.FindByID(ctx, id) +} + +func (s *service) UpdatePage(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) { + return s.repo.Update(ctx, id, page) +} + +func (s *service) DeletePage(ctx context.Context, id string) (*entity.Page, error) { + return s.repo.Delete(ctx, id) +} + +func (s *service) ListPages(ctx context.Context) ([]entity.Page, error) { + return s.repo.List(ctx) +} + +func (s *service) GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) { + return s.repo.GetPagesByChapterID(ctx, chapterID) +} diff --git a/bookback/internal/domain/services/paragraph/interface.go b/bookback/internal/domain/services/paragraph/interface.go new file mode 100644 index 0000000..683a7b4 --- /dev/null +++ b/bookback/internal/domain/services/paragraph/interface.go @@ -0,0 +1,17 @@ +package paragraph + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Repository interface { + List(ctx context.Context) ([]entity.Paragraph, error) + Create(ctx context.Context, paragraph *entity.Paragraph) (string, error) + FindByID(ctx context.Context, id string) (*entity.Paragraph, error) + Update(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) + Delete(ctx context.Context, id string) (*entity.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) + GetParagraphsByChapterID(ctx context.Context, chapterID string) ([]entity.Paragraph, error) + GetParagraphsByBookID(ctx context.Context, bookID string) ([]entity.Paragraph, error) +} diff --git a/bookback/internal/services/paragraph/service.go b/bookback/internal/domain/services/paragraph/service.go similarity index 51% rename from bookback/internal/services/paragraph/service.go rename to bookback/internal/domain/services/paragraph/service.go index d42c8ed..0e9bbf5 100644 --- a/bookback/internal/services/paragraph/service.go +++ b/bookback/internal/domain/services/paragraph/service.go @@ -2,17 +2,17 @@ package paragraph import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) type Service interface { - CreateParagraph(ctx context.Context, paragraph *models.Paragraph) (*models.Paragraph, error) - GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) - UpdateParagraph(ctx context.Context, id string, paragraph *models.Paragraph) (*models.Paragraph, error) - DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) - ListParagraphs(ctx context.Context) ([]models.Paragraph, error) + CreateParagraph(ctx context.Context, paragraph *entity.Paragraph) (*entity.Paragraph, error) + GetParagraphByID(ctx context.Context, id string) (*entity.Paragraph, error) + UpdateParagraph(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) + DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) + ListParagraphs(ctx context.Context) ([]entity.Paragraph, error) - GetParagraphsByPageID(ctx context.Context, pageID string) ([]models.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) } type service struct { @@ -23,7 +23,7 @@ func NewService(repo Repository) Service { return &service{repo} } -func (s *service) CreateParagraph(ctx context.Context, paragraph *models.Paragraph) (*models.Paragraph, error) { +func (s *service) CreateParagraph(ctx context.Context, paragraph *entity.Paragraph) (*entity.Paragraph, error) { id, err := s.repo.Create(ctx, paragraph) if err != nil { return nil, err @@ -33,22 +33,22 @@ func (s *service) CreateParagraph(ctx context.Context, paragraph *models.Paragra return paragraph, err } -func (s *service) GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) { +func (s *service) GetParagraphByID(ctx context.Context, id string) (*entity.Paragraph, error) { return s.repo.FindByID(ctx, id) } -func (s *service) UpdateParagraph(ctx context.Context, id string, paragraph *models.Paragraph) (*models.Paragraph, error) { +func (s *service) UpdateParagraph(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) { return s.repo.Update(ctx, id, paragraph) } -func (s *service) DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) { +func (s *service) DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) { return s.repo.Delete(ctx, id) } -func (s *service) ListParagraphs(ctx context.Context) ([]models.Paragraph, error) { +func (s *service) ListParagraphs(ctx context.Context) ([]entity.Paragraph, error) { return s.repo.List(ctx) } -func (s *service) GetParagraphsByPageID(ctx context.Context, pageID string) ([]models.Paragraph, error) { +func (s *service) GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) { return s.repo.GetParagraphsByPageID(ctx, pageID) } diff --git a/bookback/internal/pkg/app/app.go b/bookback/internal/pkg/app/app.go index 1370834..b93ad37 100644 --- a/bookback/internal/pkg/app/app.go +++ b/bookback/internal/pkg/app/app.go @@ -4,7 +4,7 @@ import ( "context" cfg "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" "os" @@ -16,7 +16,7 @@ type App struct { configPath string logger *slog.Logger config *cfg.Config - db db.Client + db postgres.Client Echo *echo.Echo metrics metrics.Metrics diff --git a/bookback/internal/pkg/app/closer.go b/bookback/internal/pkg/app/closer.go index f758fbb..b1b5de9 100644 --- a/bookback/internal/pkg/app/closer.go +++ b/bookback/internal/pkg/app/closer.go @@ -9,10 +9,10 @@ import ( func (app *App) closer(ctx context.Context) error { <-ctx.Done() ctx, cancel := context.WithTimeout(context.Background(), app.config.ShutdownTimeout) - app.logger.Log(context.Background(), slog.LevelInfo, "Shutting down servers") + app.logger.Log(context.Background(), slog.LevelInfo, "Shutting down controller") defer cancel() if err := app.Echo.Shutdown(ctx); err != nil { - return errors.New("failed to shutdown servers: " + err.Error()) + return errors.New("failed to shutdown controller: " + err.Error()) } return nil diff --git a/bookback/internal/pkg/app/db.go b/bookback/internal/pkg/app/db.go index a061d7e..fe686b3 100644 --- a/bookback/internal/pkg/app/db.go +++ b/bookback/internal/pkg/app/db.go @@ -4,7 +4,7 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/internal/config" - "github.com/SShlykov/zeitment/bookback/pkg/db/pg" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "time" ) @@ -13,7 +13,7 @@ func (app *App) initDB(ctx context.Context) error { if err != nil { return errors.New("failed to init pg config: " + err.Error()) } - db, err := pg.NewClient(ctx, pgConf.DSN()) + db, err := postgres.NewClient(ctx, app.logger, pgConf.DSN()) if err != nil { return errors.New("failed to init pg client: " + err.Error()) } @@ -27,11 +27,11 @@ func (app *App) initDB(ctx context.Context) error { if err != nil { broken++ if broken > pgConf.MaxPingAttempts() { - app.logger.Error("db is down") + app.logger.Error("postgres is down") app.closeCtx() break } - app.logger.Error("failed to ping db") + app.logger.Error("failed to ping postgres") } else { broken = 0 } diff --git a/bookback/internal/pkg/app/endpoint.go b/bookback/internal/pkg/app/endpoint.go index 82cf348..a227e2f 100644 --- a/bookback/internal/pkg/app/endpoint.go +++ b/bookback/internal/pkg/app/endpoint.go @@ -2,18 +2,18 @@ package app import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/circuitbreaker" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/book" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/chapter" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/health" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/mapvariables" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/page" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/paragraph" + "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/swagger" "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/circuitbreaker" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/book" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/chapter" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/health" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/page" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/paragraph" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/controllers/swagger" - "github.com/SShlykov/zeitment/bookback/internal/servers/http/httpmiddlewares" - "github.com/SShlykov/zeitment/bookback/pkg/db" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "log/slog" @@ -22,8 +22,8 @@ import ( ) func (app *App) runWebServer(wg *sync.WaitGroup, _ context.Context) { + wg.Add(1) go func() { - wg.Add(1) defer wg.Done() httpServer := &http.Server{ ReadHeaderTimeout: app.config.Timeout, @@ -69,7 +69,7 @@ func (app *App) initEndpoint(_ context.Context) error { } func (app *App) initRouter(_ context.Context) error { - controllers := []func(e *echo.Echo, database db.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context){ + controllers := []func(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context){ health.SetHealthController, book.SetBookController, chapter.SetChapterController, diff --git a/bookback/internal/services/bookevents/repository_test.go b/bookback/internal/services/bookevents/repository_test.go deleted file mode 100644 index 2c112ce..0000000 --- a/bookback/internal/services/bookevents/repository_test.go +++ /dev/null @@ -1 +0,0 @@ -package bookevents diff --git a/bookback/internal/services/chapter/service.go b/bookback/internal/services/chapter/service.go deleted file mode 100644 index 2344fa3..0000000 --- a/bookback/internal/services/chapter/service.go +++ /dev/null @@ -1,81 +0,0 @@ -package chapter - -import ( - "context" - "errors" - "fmt" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/services/book" -) - -type Service interface { - CreateChapter(ctx context.Context, chapter *models.Chapter) (*models.Chapter, error) - GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) - UpdateChapter(ctx context.Context, id string, chapter *models.Chapter) (*models.Chapter, error) - DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) - ListChapters(ctx context.Context) ([]models.Chapter, error) - - GetChapterByBookID(ctx context.Context, bookID string) ([]models.Chapter, error) -} - -type service struct { - chapterRepo Repository - bookRepo book.Repository -} - -func NewService(chapterRepo Repository, bookRepo book.Repository) Service { - return &service{chapterRepo: chapterRepo, bookRepo: bookRepo} -} - -func (ch *service) CreateChapter(ctx context.Context, chapter *models.Chapter) (*models.Chapter, error) { - if !ch.isBookExisted(ctx, chapter.BookID) { - return nil, errors.New("book not found") - } - - id, err := ch.chapterRepo.Create(ctx, chapter) - if err != nil { - return nil, err - } - - chapter, err = ch.GetChapterByID(ctx, id) - if err != nil { - fmt.Println(err) - return nil, err - } - - return chapter, nil -} - -func (ch *service) GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) { - return ch.chapterRepo.FindByID(ctx, id) -} - -func (ch *service) UpdateChapter(ctx context.Context, id string, chapter *models.Chapter) (*models.Chapter, error) { - if !ch.isBookExisted(ctx, chapter.BookID) { - return nil, errors.New("book not found") - } - return ch.chapterRepo.Update(ctx, id, chapter) -} - -func (ch *service) DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) { - return ch.chapterRepo.Delete(ctx, id) -} - -func (ch *service) ListChapters(ctx context.Context) ([]models.Chapter, error) { - return ch.chapterRepo.List(ctx) -} - -func (ch *service) GetChapterByBookID(ctx context.Context, bookID string) ([]models.Chapter, error) { - if !ch.isBookExisted(ctx, bookID) { - return nil, errors.New("book not found") - } - return ch.chapterRepo.GetChapterByBookID(ctx, bookID) -} - -func (ch *service) isBookExisted(ctx context.Context, id string) bool { - depBook, err := ch.bookRepo.FindByID(ctx, id) - if err != nil || depBook == nil { - return false - } - return true -} diff --git a/bookback/internal/services/helpers.go b/bookback/internal/services/helpers.go deleted file mode 100644 index b369edd..0000000 --- a/bookback/internal/services/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package services - -import "strconv" - -// DeleteQuery returns a SQL query to delete a row from a table by its ID. -// e.g. DeleteQuery(table_name, id_name) -> DELETE FROM table_name WHERE id_name = $1 -func DeleteQuery(tableName, idName string) string { - sql := `DELETE FROM` + " " + tableName + ` WHERE ` + idName + ` = $1` - - return sql -} - -// SelectWhere returns a SQL query to select all items from a table with a WHERE clause. -// e.g. SelectWhere(allItems, table_name, column1, column2) -> SELECT allItems... FROM table_name WHERE column1 = $1 AND column2 = $2 -func SelectWhere(allItems func() string, tableName string, args ...string) string { - sql := `SELECT ` + allItems() + ` FROM ` + tableName - if len(args) > 0 { - sql += ` WHERE ` + ParamsToQuery(" AND ", args...) - } - return sql -} - -// ParamsToQuery returns a string of SQL query parameters. -// e.g. ParamsToQuery(", ", "column1", "column2") -> column1 = $1, column2 = $2 -func ParamsToQuery(joiner string, args ...string) (sql string) { - for i, arg := range args { - sql += arg + ` = $` + strconv.Itoa(i+1) - if i < len(args)-1 { - sql += joiner - } - } - return -} diff --git a/bookback/internal/services/helpers_test.go b/bookback/internal/services/helpers_test.go deleted file mode 100644 index 217f7a7..0000000 --- a/bookback/internal/services/helpers_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package services - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func mockAllItems() string { - return "*" -} - -func TestDeleteQuery(t *testing.T) { - tests := []struct { - name string - tableName string - idName string - want string - }{ - { - name: "Single WHERE condition", - tableName: "books", - idName: "id", - want: "DELETE FROM books WHERE id = $1", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := DeleteQuery(tt.tableName, tt.idName) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestSelectWhere(t *testing.T) { - tests := []struct { - name string - allItems func() string - tableName string - args []string - want string - }{ - { - name: "No WHERE conditions", - allItems: mockAllItems, - tableName: "books", - args: nil, - want: "SELECT * FROM books", - }, - { - name: "Single WHERE condition", - allItems: mockAllItems, - tableName: "books", - args: []string{"id"}, - want: "SELECT * FROM books WHERE id = $1", - }, - { - name: "Multiple WHERE conditions", - allItems: mockAllItems, - tableName: "books", - args: []string{"id", "title"}, - want: "SELECT * FROM books WHERE id = $1 AND title = $2", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := SelectWhere(tt.allItems, tt.tableName, tt.args...) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestParamsToQuery(t *testing.T) { - tests := []struct { - name string - args []string - want string - }{ - { - name: "Single parameter", - args: []string{"id"}, - want: "id = $1", - }, - { - name: "Multiple parameters", - args: []string{"id", "name"}, - want: "id = $1, name = $2", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ParamsToQuery(", ", tt.args...) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/bookback/internal/services/page/service.go b/bookback/internal/services/page/service.go deleted file mode 100644 index 3d28365..0000000 --- a/bookback/internal/services/page/service.go +++ /dev/null @@ -1,54 +0,0 @@ -package page - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/models" -) - -type Service interface { - CreatePage(ctx context.Context, page *models.Page) (*models.Page, error) - GetPageByID(ctx context.Context, id string) (*models.Page, error) - UpdatePage(ctx context.Context, id string, page *models.Page) (*models.Page, error) - DeletePage(ctx context.Context, id string) (*models.Page, error) - ListPages(ctx context.Context) ([]models.Page, error) - - GetPagesByChapterID(ctx context.Context, chapterID string) ([]models.Page, error) -} - -type service struct { - repo Repository -} - -func NewService(repo Repository) Service { - return &service{repo} -} - -func (s *service) CreatePage(ctx context.Context, page *models.Page) (*models.Page, error) { - id, err := s.repo.Create(ctx, page) - if err != nil { - return nil, err - } - page.ID = id - - return page, err -} - -func (s *service) GetPageByID(ctx context.Context, id string) (*models.Page, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *service) UpdatePage(ctx context.Context, id string, page *models.Page) (*models.Page, error) { - return s.repo.Update(ctx, id, page) -} - -func (s *service) DeletePage(ctx context.Context, id string) (*models.Page, error) { - return s.repo.Delete(ctx, id) -} - -func (s *service) ListPages(ctx context.Context) ([]models.Page, error) { - return s.repo.List(ctx) -} - -func (s *service) GetPagesByChapterID(ctx context.Context, chapterID string) ([]models.Page, error) { - return s.repo.GetPagesByChapterID(ctx, chapterID) -} diff --git a/bookback/pkg/db/pg/client.go b/bookback/pkg/db/pg/client.go deleted file mode 100644 index d7f0663..0000000 --- a/bookback/pkg/db/pg/client.go +++ /dev/null @@ -1,37 +0,0 @@ -package pg - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/pkg/db" - "github.com/jackc/pgx/v5/pgxpool" -) - -type pgClient struct { - db db.DB -} - -func (c *pgClient) QueryRowContext(ctx context.Context, query string, args ...interface{}) { //nolint:gofmt - c.db.QueryRowContext(ctx, db.Query{Name: "Query", Raw: query}, args...) -} - -func NewClient(ctx context.Context, dsn string) (db.Client, error) { - op := "pg.NewClient" - dbc, err := pgxpool.New(ctx, dsn) - if err != nil { - return nil, errors.New(op + ": " + err.Error()) - } - - return &pgClient{db: &pg{dbc}}, nil -} - -func (c *pgClient) DB() db.DB { - return c.db -} -func (c *pgClient) Close() error { - if c.db != nil { - c.db.Close() - } - - return nil -} diff --git a/bookback/pkg/db/pg/pg.go b/bookback/pkg/db/pg/pg.go deleted file mode 100644 index 104dc81..0000000 --- a/bookback/pkg/db/pg/pg.go +++ /dev/null @@ -1,114 +0,0 @@ -package pg - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/pkg/db" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgxpool" -) - -type key string - -const ( - TxKey key = "tx" -) - -type pg struct { - db *pgxpool.Pool -} - -func NewDB(dbc *pgxpool.Pool) db.DB { - return &pg{db: dbc} -} - -func (p *pg) ScanSingleContext(ctx context.Context, q db.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt - // TODO: logQuery(ctx, q, args...) - - row, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - - return pgxscan.ScanOne(dest, row) -} - -func (p *pg) ScanAllContext(ctx context.Context, q db.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt - // TODO: logQuery(ctx, q, args...) - - rows, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - - return pgxscan.ScanAll(dest, rows) -} - -func (p *pg) ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) { //nolint:gofmt - // TODO: logQuery(ctx, q, args...) - - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Exec(ctx, q.Raw, args...) - } - - return p.db.Exec(ctx, q.Raw, args...) -} - -func (p *pg) QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) { - // TODO: logQuery(ctx, q, args...) - - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Query(ctx, q.Raw, args...) - } - - return p.db.Query(ctx, q.Raw, args...) -} - -func (p *pg) QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row { - // TODO: logQuery(ctx, q, args...) - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.QueryRow(ctx, q.Raw, args...) - } - - res := p.db.QueryRow(ctx, q.Raw, args...) - - return res -} - -func (p *pg) QueryRawContextMulti(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Query(ctx, q.Raw, args...) - } - - return p.db.Query(ctx, q.Raw, args...) -} - -func (p *pg) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { - return p.db.BeginTx(ctx, txOptions) -} - -func (p *pg) Ping(ctx context.Context) error { - return p.db.Ping(ctx) -} - -func (p *pg) Close() { - p.db.Close() -} - -func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { - return context.WithValue(ctx, TxKey, tx) -} - -// func logQuery(ctx context.Context, q db.Query, args ...interface{}) { -// prettyQuery := prettier.Pretty(q.QueryRaw, prettier.PlaceholderDollar, args...) -// log.Println( -// ctx, -// fmt.Sprintf("sql: %s", q.Name), -// fmt.Sprintf("query: %s", prettyQuery), -// ) -//} diff --git a/bookback/pkg/db/sq/sq.go b/bookback/pkg/db/sq/sq.go deleted file mode 100644 index ef35030..0000000 --- a/bookback/pkg/db/sq/sq.go +++ /dev/null @@ -1,25 +0,0 @@ -package sq - -import ( - "github.com/Masterminds/squirrel" -) - -var SQ = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) - -type Eq = squirrel.Eq -type NotEq = squirrel.NotEq -type Or = squirrel.Or -type And = squirrel.And -type SelectBuilder = squirrel.SelectBuilder - -func Insert(table string) squirrel.InsertBuilder { - return SQ.Insert(table) -} - -func Select(columns ...string) squirrel.SelectBuilder { - return SQ.Select(columns...) -} - -func Update(table string) squirrel.UpdateBuilder { - return SQ.Update(table) -} diff --git a/bookback/pkg/postgres/client.go b/bookback/pkg/postgres/client.go new file mode 100644 index 0000000..3767c74 --- /dev/null +++ b/bookback/pkg/postgres/client.go @@ -0,0 +1,63 @@ +package postgres + +import ( + "context" + "github.com/Masterminds/squirrel" + "github.com/SShlykov/zeitment/bookback/pkg/postgres/pg" + "github.com/jackc/pgx/v5/pgxpool" + "log/slog" + "time" +) + +const ( + _defaultMaxPoolSize = 1 + _defaultConnAttempts = 10 + _defaultConnTimeout = time.Second +) + +type pgClient struct { + maxPoolSize int + connAttempts int + connTimeout time.Duration + + db DB + Builder squirrel.StatementBuilderType +} + +func NewClient(ctx context.Context, logger *slog.Logger, dsn string) (Client, error) { + client := &pgClient{} + + poolConfig, err := pgxpool.ParseConfig(dsn) + if err != nil { + slog.Error("Cant parse dsn", slog.String("dsn", dsn)) + return nil, err + } + + poolConfig.MaxConns = int32(_defaultMaxPoolSize) + + for client.connAttempts > 0 { + var pool *pgxpool.Pool + pool, err = pgxpool.NewWithConfig(ctx, poolConfig) + if err == nil { + client.db = pg.NewDB(pool, logger) + return client, nil + } + + time.Sleep(client.connTimeout) + + client.connAttempts-- + } + + return nil, err +} + +func (c *pgClient) DB() DB { + return c.db +} +func (c *pgClient) Close() error { + if c.db != nil { + c.db.Close() + } + + return nil +} diff --git a/bookback/pkg/db/db.go b/bookback/pkg/postgres/interface.go similarity index 92% rename from bookback/pkg/db/db.go rename to bookback/pkg/postgres/interface.go index 52f434d..968e844 100644 --- a/bookback/pkg/db/db.go +++ b/bookback/pkg/postgres/interface.go @@ -1,4 +1,4 @@ -package db +package postgres import ( "context" @@ -11,7 +11,6 @@ type Handler func(ctx context.Context) error type Client interface { DB() DB Close() error - QueryRowContext(ctx context.Context, query string, args ...interface{}) //nolint:gofmt } type DB interface { diff --git a/bookback/pkg/postgres/pg/pg.go b/bookback/pkg/postgres/pg/pg.go new file mode 100644 index 0000000..8e6b23f --- /dev/null +++ b/bookback/pkg/postgres/pg/pg.go @@ -0,0 +1,118 @@ +package pg + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" + "log/slog" +) + +type key string + +const ( + TxKey key = "tx" +) + +type Postgres struct { + Pool *pgxpool.Pool + logger *slog.Logger +} + +func NewDB(dbc *pgxpool.Pool, logger *slog.Logger) postgres.DB { + return &Postgres{Pool: dbc, logger: logger} +} + +func (p *Postgres) ScanSingleContext(ctx context.Context, q postgres.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt + logQuery(ctx, p.logger, q, args...) + + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanOne(dest, row) +} + +func (p *Postgres) ScanAllContext(ctx context.Context, q postgres.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt + logQuery(ctx, p.logger, q, args...) + + rows, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanAll(dest, rows) +} + +func (p *Postgres) ExecContext(ctx context.Context, q postgres.Query, args ...interface{}) (pgconn.CommandTag, error) { //nolint:gofmt + logQuery(ctx, p.logger, q, args...) + + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Exec(ctx, q.Raw, args...) + } + + return p.Pool.Exec(ctx, q.Raw, args...) +} + +func (p *Postgres) QueryContext(ctx context.Context, q postgres.Query, args ...interface{}) (pgx.Rows, error) { + logQuery(ctx, p.logger, q, args...) + + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.Raw, args...) + } + + return p.Pool.Query(ctx, q.Raw, args...) +} + +func (p *Postgres) QueryRowContext(ctx context.Context, q postgres.Query, args ...interface{}) pgx.Row { + logQuery(ctx, p.logger, q, args...) + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.QueryRow(ctx, q.Raw, args...) + } + + res := p.Pool.QueryRow(ctx, q.Raw, args...) + + return res +} + +func (p *Postgres) QueryRawContextMulti(ctx context.Context, q postgres.Query, args ...interface{}) (pgx.Rows, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.Raw, args...) + } + + return p.Pool.Query(ctx, q.Raw, args...) +} + +func (p *Postgres) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { + return p.Pool.BeginTx(ctx, txOptions) +} + +func (p *Postgres) Ping(ctx context.Context) error { + return p.Pool.Ping(ctx) +} + +func (p *Postgres) Close() { + if p.Pool != nil { + p.Pool.Close() + } +} + +func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { + return context.WithValue(ctx, TxKey, tx) +} + +func logQuery(_ context.Context, logger *slog.Logger, q postgres.Query, args ...interface{}) { + logger.Debug( + "executing query", + slog.String("sql", q.Name), + slog.String("query", q.Raw), + slog.Group("args", args), + ) +} diff --git a/bookback/pkg/db/transaction/transaction.go b/bookback/pkg/postgres/transaction/transaction.go similarity index 67% rename from bookback/pkg/db/transaction/transaction.go rename to bookback/pkg/postgres/transaction/transaction.go index 1e5bf36..371c79f 100644 --- a/bookback/pkg/db/transaction/transaction.go +++ b/bookback/pkg/postgres/transaction/transaction.go @@ -3,24 +3,22 @@ package transaction import ( "context" "errors" - "github.com/SShlykov/zeitment/bookback/pkg/db" - "github.com/SShlykov/zeitment/bookback/pkg/db/pg" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/SShlykov/zeitment/bookback/pkg/postgres/pg" "github.com/jackc/pgx/v5" ) type manager struct { - db db.Transactor + db postgres.Transactor } -// NewTransactionManager создает новый менеджер транзакций, который удовлетворяет интерфейсу db.TxManager -func NewTransactionManager(db db.Transactor) db.TxManager { - return &manager{ - db: db, - } +// NewTransactionManager создает новый менеджер транзакций, который удовлетворяет интерфейсу postgres.TxManager +func NewTransactionManager(db postgres.Transactor) postgres.TxManager { + return &manager{db: db} } -// transaction основная функция, которая выполняет указанный пользователем обработчик в транзакции -func (m *manager) transaction(ctx context.Context, opts pgx.TxOptions, fn db.Handler) (err error) { +// Transaction основная функция, которая выполняет указанный пользователем обработчик в транзакции +func (m *manager) Transaction(ctx context.Context, opts pgx.TxOptions, fn postgres.Handler) (err error) { tx, ok := ctx.Value(pg.TxKey).(pgx.Tx) if ok { return fn(ctx) @@ -61,7 +59,7 @@ func (m *manager) transaction(ctx context.Context, opts pgx.TxOptions, fn db.Han return err } -func (m *manager) ReadCommitted(ctx context.Context, f db.Handler) error { +func (m *manager) ReadCommitted(ctx context.Context, f postgres.Handler) error { txOpts := pgx.TxOptions{IsoLevel: pgx.ReadCommitted} - return m.transaction(ctx, txOpts, f) + return m.Transaction(ctx, txOpts, f) } diff --git a/bookback/tests/integration-test/test.go b/bookback/tests/integration-test/test.go new file mode 100644 index 0000000..d009d48 --- /dev/null +++ b/bookback/tests/integration-test/test.go @@ -0,0 +1 @@ +package integration_test diff --git a/bookback/tests/mocks/db.go b/bookback/tests/mocks/db.go index 5a08336..23d3585 100644 --- a/bookback/tests/mocks/db.go +++ b/bookback/tests/mocks/db.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: pkg/db/db.go +// Source: pkg/postgres/interface.go // // Generated by this command: // -// mockgen -source=pkg/db/db.go -destination=internal/mocks/db.go -package=mocks +// mockgen -source=pkg/postgres/interface.go -destination=internal/mocks/interface.go -package=mocks // // Package mocks is a generated GoMock package. @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - db "github.com/SShlykov/zeitment/bookback/pkg/db" + db "github.com/SShlykov/zeitment/bookback/pkg/postgres" v5 "github.com/jackc/pgx/v5" pgconn "github.com/jackc/pgx/v5/pgconn" gomock "go.uber.org/mock/gomock" From 93333eb4224847631e060e520e2ed6deaa9ffdf2 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Wed, 6 Mar 2024 18:17:23 +0300 Subject: [PATCH 6/9] feat: cleaning --- bookback/cmd/bookback/main.go | 4 +- .../20240218174249_create_books_table.sql | 0 .../20240218174500_create_chapters_table.sql | 0 .../20240218174611_create_pages_table.sql | 0 .../20240218174705_create_paragraph_table.sql | 0 .../20240225152127_create_book_event.sql | 0 .../20240225152254_create_map_variables.sql | 0 ...226210848_update_tables_autogeneration.sql | 0 ...27210215_update_book_default_variables.sql | 0 ...02202835_update_page_table_added_title.sql | 0 ...639_update_paragraph_table_added_title.sql | 0 ...paragraph_table_set_updated_at_default.sql | 0 ...e_events_and_variables_uuid_generation.sql | 0 ...75249_update_map_events_add_updated_at.sql | 0 .../db/postgres/bookevtrepo/bookevents.go | 163 --------------- .../postgres/bookevtrepo/bookevents_test.go | 1 - .../adapters/db/postgres/bookevtrepo/utils.go | 34 --- .../adapters/db/postgres/bookrepo/book.go | 131 ------------ .../db/postgres/bookrepo/book_test.go | 103 ---------- .../adapters/db/postgres/bookrepo/utils.go | 36 ---- .../db/postgres/chapterrepo/chapter.go | 141 ------------- .../db/postgres/chapterrepo/chapter_test.go | 100 --------- .../adapters/db/postgres/chapterrepo/utils.go | 34 --- .../db/postgres/mapvarrepo/mapvariables.go | 168 --------------- .../adapters/db/postgres/mapvarrepo/utils.go | 36 ---- .../adapters/db/postgres/pagerepo/page.go | 135 ------------ .../db/postgres/pagerepo/page_test.go | 100 --------- .../adapters/db/postgres/pagerepo/utils.go | 30 --- .../db/postgres/paragraphrepo/paragraph.go | 137 ------------- .../postgres/paragraphrepo/paragraph_test.go | 101 --------- .../db/postgres/paragraphrepo/utils.go | 30 --- .../internal/{pkg => bootstrap}/app/app.go | 2 +- .../internal/{pkg => bootstrap}/app/closer.go | 0 .../internal/{pkg => bootstrap}/app/config.go | 2 +- .../internal/{pkg => bootstrap}/app/db.go | 2 +- .../{pkg => bootstrap}/app/endpoint.go | 0 .../internal/{pkg => bootstrap}/app/logger.go | 0 .../{pkg => bootstrap}/app/metrics.go | 0 bookback/internal/config/const.go | 22 -- .../controller/http/v1/book/controller.go | 12 +- .../controller/http/v1/book/models.go | 2 + .../controller/http/v1/book/routes.go | 2 +- .../http/v1/bookevents/controller.go | 5 - .../controller/http/v1/chapter/controller.go | 11 +- .../http/v1/mapvariables/controller.go | 7 +- .../controller/http/v1/page/controller.go | 5 - .../http/v1/paragraph/controller.go | 5 - bookback/internal/domain/entity/book.go | 79 +++++-- bookback/internal/domain/entity/bookevents.go | 79 +++++-- bookback/internal/domain/entity/chapter.go | 68 ++++-- .../internal/domain/entity/mapvariables.go | 93 +++++++-- bookback/internal/domain/entity/nullable.go | 99 --------- bookback/internal/domain/entity/page.go | 72 ++++++- bookback/internal/domain/entity/paragraph.go | 70 ++++++- .../repository/pgrepo/0-genericrepository.go | 194 ++++++++++++++++++ .../repository/pgrepo/bookeventsrepo.go | 26 +++ .../domain/repository/pgrepo/bookrepo.go | 26 +++ .../domain/repository/pgrepo/chapterrepo.go | 26 +++ .../repository/pgrepo/mapvariablesrepo.go | 26 +++ .../domain/repository/pgrepo/pagerepo.go | 26 +++ .../domain/repository/pgrepo/paragraphrepo.go | 26 +++ .../internal/domain/services/book/service.go | 12 +- bookback/{internal => pkg}/config/config.go | 0 bookback/{internal => pkg}/config/pg.go | 0 bookback/pkg/postgres/client.go | 9 +- bookback/pkg/postgres/interface.go | 2 + 66 files changed, 756 insertions(+), 1738 deletions(-) rename bookback/{ => database}/migrations/20240218174249_create_books_table.sql (100%) rename bookback/{ => database}/migrations/20240218174500_create_chapters_table.sql (100%) rename bookback/{ => database}/migrations/20240218174611_create_pages_table.sql (100%) rename bookback/{ => database}/migrations/20240218174705_create_paragraph_table.sql (100%) rename bookback/{ => database}/migrations/20240225152127_create_book_event.sql (100%) rename bookback/{ => database}/migrations/20240225152254_create_map_variables.sql (100%) rename bookback/{ => database}/migrations/20240226210848_update_tables_autogeneration.sql (100%) rename bookback/{ => database}/migrations/20240227210215_update_book_default_variables.sql (100%) rename bookback/{ => database}/migrations/20240302202835_update_page_table_added_title.sql (100%) rename bookback/{ => database}/migrations/20240302205639_update_paragraph_table_added_title.sql (100%) rename bookback/{ => database}/migrations/20240302211749_update_paragraph_table_set_updated_at_default.sql (100%) rename bookback/{ => database}/migrations/20240302221129_update_events_and_variables_uuid_generation.sql (100%) rename bookback/{ => database}/migrations/20240303075249_update_map_events_add_updated_at.sql (100%) delete mode 100644 bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go delete mode 100644 bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go delete mode 100644 bookback/internal/adapters/db/postgres/bookevtrepo/utils.go delete mode 100644 bookback/internal/adapters/db/postgres/bookrepo/book.go delete mode 100644 bookback/internal/adapters/db/postgres/bookrepo/book_test.go delete mode 100644 bookback/internal/adapters/db/postgres/bookrepo/utils.go delete mode 100644 bookback/internal/adapters/db/postgres/chapterrepo/chapter.go delete mode 100644 bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go delete mode 100644 bookback/internal/adapters/db/postgres/chapterrepo/utils.go delete mode 100644 bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go delete mode 100644 bookback/internal/adapters/db/postgres/mapvarrepo/utils.go delete mode 100644 bookback/internal/adapters/db/postgres/pagerepo/page.go delete mode 100644 bookback/internal/adapters/db/postgres/pagerepo/page_test.go delete mode 100644 bookback/internal/adapters/db/postgres/pagerepo/utils.go delete mode 100644 bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go delete mode 100644 bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go delete mode 100644 bookback/internal/adapters/db/postgres/paragraphrepo/utils.go rename bookback/internal/{pkg => bootstrap}/app/app.go (95%) rename bookback/internal/{pkg => bootstrap}/app/closer.go (100%) rename bookback/internal/{pkg => bootstrap}/app/config.go (82%) rename bookback/internal/{pkg => bootstrap}/app/db.go (93%) rename bookback/internal/{pkg => bootstrap}/app/endpoint.go (100%) rename bookback/internal/{pkg => bootstrap}/app/logger.go (100%) rename bookback/internal/{pkg => bootstrap}/app/metrics.go (100%) delete mode 100644 bookback/internal/config/const.go delete mode 100644 bookback/internal/domain/entity/nullable.go create mode 100644 bookback/internal/domain/repository/pgrepo/0-genericrepository.go create mode 100644 bookback/internal/domain/repository/pgrepo/bookeventsrepo.go create mode 100644 bookback/internal/domain/repository/pgrepo/bookrepo.go create mode 100644 bookback/internal/domain/repository/pgrepo/chapterrepo.go create mode 100644 bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go create mode 100644 bookback/internal/domain/repository/pgrepo/pagerepo.go create mode 100644 bookback/internal/domain/repository/pgrepo/paragraphrepo.go rename bookback/{internal => pkg}/config/config.go (100%) rename bookback/{internal => pkg}/config/pg.go (100%) diff --git a/bookback/cmd/bookback/main.go b/bookback/cmd/bookback/main.go index 0113676..1395901 100644 --- a/bookback/cmd/bookback/main.go +++ b/bookback/cmd/bookback/main.go @@ -3,7 +3,7 @@ package main import ( "flag" "fmt" - appPkg "github.com/SShlykov/zeitment/bookback/internal/pkg/app" + pkg "github.com/SShlykov/zeitment/bookback/internal/bootstrap/app" "os" ) @@ -19,7 +19,7 @@ var configPath string // @consumes application/json func main() { flag.StringVar(&configPath, "config", "./config/default.yml", "path to the configuration file") - app, err := appPkg.NewApp(configPath) + app, err := pkg.NewApp(configPath) if err != nil { fmt.Printf("failed to create app: %+v\n", err) os.Exit(2) diff --git a/bookback/migrations/20240218174249_create_books_table.sql b/bookback/database/migrations/20240218174249_create_books_table.sql similarity index 100% rename from bookback/migrations/20240218174249_create_books_table.sql rename to bookback/database/migrations/20240218174249_create_books_table.sql diff --git a/bookback/migrations/20240218174500_create_chapters_table.sql b/bookback/database/migrations/20240218174500_create_chapters_table.sql similarity index 100% rename from bookback/migrations/20240218174500_create_chapters_table.sql rename to bookback/database/migrations/20240218174500_create_chapters_table.sql diff --git a/bookback/migrations/20240218174611_create_pages_table.sql b/bookback/database/migrations/20240218174611_create_pages_table.sql similarity index 100% rename from bookback/migrations/20240218174611_create_pages_table.sql rename to bookback/database/migrations/20240218174611_create_pages_table.sql diff --git a/bookback/migrations/20240218174705_create_paragraph_table.sql b/bookback/database/migrations/20240218174705_create_paragraph_table.sql similarity index 100% rename from bookback/migrations/20240218174705_create_paragraph_table.sql rename to bookback/database/migrations/20240218174705_create_paragraph_table.sql diff --git a/bookback/migrations/20240225152127_create_book_event.sql b/bookback/database/migrations/20240225152127_create_book_event.sql similarity index 100% rename from bookback/migrations/20240225152127_create_book_event.sql rename to bookback/database/migrations/20240225152127_create_book_event.sql diff --git a/bookback/migrations/20240225152254_create_map_variables.sql b/bookback/database/migrations/20240225152254_create_map_variables.sql similarity index 100% rename from bookback/migrations/20240225152254_create_map_variables.sql rename to bookback/database/migrations/20240225152254_create_map_variables.sql diff --git a/bookback/migrations/20240226210848_update_tables_autogeneration.sql b/bookback/database/migrations/20240226210848_update_tables_autogeneration.sql similarity index 100% rename from bookback/migrations/20240226210848_update_tables_autogeneration.sql rename to bookback/database/migrations/20240226210848_update_tables_autogeneration.sql diff --git a/bookback/migrations/20240227210215_update_book_default_variables.sql b/bookback/database/migrations/20240227210215_update_book_default_variables.sql similarity index 100% rename from bookback/migrations/20240227210215_update_book_default_variables.sql rename to bookback/database/migrations/20240227210215_update_book_default_variables.sql diff --git a/bookback/migrations/20240302202835_update_page_table_added_title.sql b/bookback/database/migrations/20240302202835_update_page_table_added_title.sql similarity index 100% rename from bookback/migrations/20240302202835_update_page_table_added_title.sql rename to bookback/database/migrations/20240302202835_update_page_table_added_title.sql diff --git a/bookback/migrations/20240302205639_update_paragraph_table_added_title.sql b/bookback/database/migrations/20240302205639_update_paragraph_table_added_title.sql similarity index 100% rename from bookback/migrations/20240302205639_update_paragraph_table_added_title.sql rename to bookback/database/migrations/20240302205639_update_paragraph_table_added_title.sql diff --git a/bookback/migrations/20240302211749_update_paragraph_table_set_updated_at_default.sql b/bookback/database/migrations/20240302211749_update_paragraph_table_set_updated_at_default.sql similarity index 100% rename from bookback/migrations/20240302211749_update_paragraph_table_set_updated_at_default.sql rename to bookback/database/migrations/20240302211749_update_paragraph_table_set_updated_at_default.sql diff --git a/bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql b/bookback/database/migrations/20240302221129_update_events_and_variables_uuid_generation.sql similarity index 100% rename from bookback/migrations/20240302221129_update_events_and_variables_uuid_generation.sql rename to bookback/database/migrations/20240302221129_update_events_and_variables_uuid_generation.sql diff --git a/bookback/migrations/20240303075249_update_map_events_add_updated_at.sql b/bookback/database/migrations/20240303075249_update_map_events_add_updated_at.sql similarity index 100% rename from bookback/migrations/20240303075249_update_map_events_add_updated_at.sql rename to bookback/database/migrations/20240303075249_update_map_events_add_updated_at.sql diff --git a/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go deleted file mode 100644 index 3684dfc..0000000 --- a/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents.go +++ /dev/null @@ -1,163 +0,0 @@ -package bookevtrepo - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" - "strings" -) - -const ( - tableName = "book_events" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnBookID = "book_id" - columnChapterID = "chapter_id" - columnPageID = "page_id" - columnParagraphID = "paragraph_id" - columnEventType = "event_type" - columnIsPublic = "is_public" - columnKey = "key" - columnValue = "value" - columnLink = "link" - columnLinkText = "link_text" - columnLinkType = "link_type" - columnLinkImage = "link_image" - columnDescription = "description" -) - -type Repository interface { - Create(ctx context.Context, event *models.BookEvent) (string, error) - FindByID(ctx context.Context, id string) (*models.BookEvent, error) - Update(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) - Delete(ctx context.Context, id string) (*models.BookEvent, error) - GetByBookID(ctx context.Context, bookID string) ([]models.BookEvent, error) - GetByChapterID(ctx context.Context, chapterID string) ([]models.BookEvent, error) - GetByPageID(ctx context.Context, pageID string) ([]models.BookEvent, error) - GetByParagraphID(ctx context.Context, paragraphID string) ([]models.BookEvent, error) -} - -type repository struct { - db postgres.Client -} - -func NewRepository(database postgres.Client) Repository { - return &repository{database} -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnBookID, columnChapterID, - columnPageID, columnParagraphID, columnEventType, columnIsPublic, columnKey, - columnValue, columnLink, columnLinkText, columnLinkType, columnLinkImage, - columnDescription} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnBookID, columnChapterID, columnPageID, columnParagraphID, columnEventType, - columnIsPublic, columnKey, columnValue, columnLink, columnLinkText, - columnLinkType, columnLinkImage, columnDescription} - - return strings.Join(cols, ", ") -} - -func (r *repository) Create(ctx context.Context, event *models.BookEvent) (string, error) { - query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + - `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING ` + - columnID - - args := []interface{}{event.BookID, event.ChapterID, event.PageID, event.ParagraphID, event.EventType, //nolint:gofmt - event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, event.LinkType, - event.LinkImage, event.Description} - - q := postgres.Query{Name: "BookEventRepository.Insert", Raw: query} - - var id string - if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -func (r *repository) FindByID(ctx context.Context, id string) (*models.BookEvent, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) - - q := postgres.Query{Name: "BookEventRepository.FindByID", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - return readItem(row) -} - -func (r *repository) Update(ctx context.Context, id string, event *models.BookEvent) (*models.BookEvent, error) { - query := `UPDATE ` + tableName + ` SET ` + - querybuilder.ParamsToQuery(", ", - columnBookID, columnChapterID, columnPageID, columnParagraphID, columnEventType, - columnIsPublic, columnKey, columnValue, columnLink, columnLinkText, - columnLinkType, columnLinkImage, columnDescription) + ` WHERE ` + - columnID + ` = $14` + ` RETURNING ` + allItems() - - args := []interface{}{event.BookID, event.ChapterID, event.PageID, event.ParagraphID, event.EventType, //nolint:gofmt - event.IsPublic, event.Key, event.Value, event.Link, event.LinkText, - event.LinkType, event.LinkImage, event.Description, id} - - q := postgres.Query{Name: "BookEventRepository.Update", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -func (r *repository) Delete(ctx context.Context, id string) (*models.BookEvent, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - - q := postgres.Query{Name: "BookEventRepository.Delete", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - return readItem(row) -} - -func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.BookEvent, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnBookID) - - q := postgres.Query{Name: "BookEventRepository.GetByBookID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, bookID) - if err != nil { - return nil, err - } - return readList(rows) -} - -func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]models.BookEvent, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) - - q := postgres.Query{Name: "BookEventRepository.GetByChapterID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, chapterID) - if err != nil { - return nil, err - } - return readList(rows) -} - -func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.BookEvent, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnPageID) - - q := postgres.Query{Name: "BookEventRepository.GetByPageID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, pageID) - if err != nil { - return nil, err - } - return readList(rows) -} - -func (r *repository) GetByParagraphID(ctx context.Context, paragraphID string) ([]models.BookEvent, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnParagraphID) - - q := postgres.Query{Name: "BookEventRepository.GetByParagraphID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, paragraphID) - if err != nil { - return nil, err - } - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go b/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go deleted file mode 100644 index f44dc05..0000000 --- a/bookback/internal/adapters/db/postgres/bookevtrepo/bookevents_test.go +++ /dev/null @@ -1 +0,0 @@ -package bookevtrepo diff --git a/bookback/internal/adapters/db/postgres/bookevtrepo/utils.go b/bookback/internal/adapters/db/postgres/bookevtrepo/utils.go deleted file mode 100644 index ed8b411..0000000 --- a/bookback/internal/adapters/db/postgres/bookevtrepo/utils.go +++ /dev/null @@ -1,34 +0,0 @@ -package bookevtrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -func readList(rows pgx.Rows) ([]models.BookEvent, error) { - defer rows.Close() - events := make([]models.BookEvent, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - events = append(events, *item) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return events, rows.Err() -} - -func readItem(row pgx.Row) (*models.BookEvent, error) { - var event models.BookEvent - if err := row.Scan(&event.ID, &event.CreatedAt, &event.UpdatedAt, &event.BookID, &event.ChapterID, - &event.PageID, &event.ParagraphID, &event.EventType, &event.IsPublic, &event.Key, &event.Value, - &event.Link, &event.LinkText, &event.LinkType, &event.LinkImage, &event.Description); err != nil { - return nil, err - } - - return &event, nil -} diff --git a/bookback/internal/adapters/db/postgres/bookrepo/book.go b/bookback/internal/adapters/db/postgres/bookrepo/book.go deleted file mode 100644 index 31bdcfe..0000000 --- a/bookback/internal/adapters/db/postgres/bookrepo/book.go +++ /dev/null @@ -1,131 +0,0 @@ -package bookrepo - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "strings" -) - -const ( - tableName = "books" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnDeletedAt = "deleted_at" - columnOwner = "owner" - columnTitle = "title" - columnAuthor = "author" - columnDescription = "description" - columnIsPublic = "is_public" - columnPublication = "publication" - columnImageLink = "image_link" - columnMapLink = "map_link" - columnMapParamsID = "map_params_id" - columnVariables = "variables" - Returning = " RETURNING " - Where = " WHERE " -) - -// Repository определяет интерфейс для взаимодействия с хранилищем книг. -type Repository interface { - Create(ctx context.Context, book *entity.Book) (string, error) - FindByID(ctx context.Context, id string) (*entity.Book, error) - Update(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) - Delete(ctx context.Context, id string) (*entity.Book, error) - List(ctx context.Context) ([]entity.Book, error) -} - -type repository struct { - db postgres.Client -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnDeletedAt, columnOwner, columnTitle, - columnAuthor, columnDescription, columnIsPublic, columnPublication, columnImageLink, columnMapLink, - columnMapParamsID, columnVariables} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnTitle, columnAuthor, columnOwner, columnDescription, columnIsPublic, - columnPublication, columnImageLink, columnMapLink, columnMapParamsID, columnVariables} - - return strings.Join(cols, ", ") -} - -// NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(client postgres.Client) Repository { - return &repository{db: client} -} - -func (r *repository) Create(ctx context.Context, book *entity.Book) (string, error) { - query := "INSERT INTO" + " " + tableName + ` (` + insertItems() + - `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ` + Returning + columnID - - if book.Variables == nil { - book.Variables = []string{} - } - - args := []interface{}{book.Title, book.Author, book.Owner, book.Description, book.IsPublic, //nolint:gofmt - book.Publication, book.ImageLink, book.MapLink, book.MapParamsID, book.Variables} - q := postgres.Query{Name: "BookRepository.Insert", Raw: query} - - var id string - if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -func (r *repository) FindByID(ctx context.Context, id string) (*entity.Book, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND " + columnDeletedAt + ` IS NULL` + " LIMIT 1" - - q := postgres.Query{Name: "BookRepository.FindById", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -func (r *repository) Update(ctx context.Context, id string, updBook *entity.Book) (*entity.Book, error) { - query := "Update " + tableName + " SET " + - querybuilder.ParamsToQuery(", ", columnTitle, columnAuthor, columnOwner, columnDescription, columnIsPublic, - columnPublication, columnImageLink, columnMapLink, columnMapParamsID, columnVariables) + - " WHERE " + columnID + " = $11" + Returning + allItems() - - args := []interface{}{updBook.Title, updBook.Author, updBook.Owner, updBook.Description, //nolint:gofmt - updBook.IsPublic, updBook.Publication, updBook.ImageLink, updBook.MapLink, updBook.MapParamsID, - updBook.Variables, id} - - q := postgres.Query{Name: "BookRepository.Update", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -func (r *repository) Delete(ctx context.Context, id string) (*entity.Book, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - - q := postgres.Query{Name: "BookRepository.Delete", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -func (r *repository) List(ctx context.Context) ([]entity.Book, error) { - query := `SELECT ` + allItems() + ` FROM ` + tableName + Where + columnDeletedAt + ` IS NULL` - - q := postgres.Query{Name: "BookRepository.List", Raw: query} - - rows, err := r.db.DB().QueryRawContextMulti(ctx, q) - if err != nil { - return nil, errors.New("params error") - } - - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/bookrepo/book_test.go b/bookback/internal/adapters/db/postgres/bookrepo/book_test.go deleted file mode 100644 index 5a72f4a..0000000 --- a/bookback/internal/adapters/db/postgres/bookrepo/book_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package bookrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" - "github.com/go-faker/faker/v4" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" - "time" -) - -func newTestBook() *models.Book { - return &models.Book{ - ID: faker.UUIDHyphenated(), - Title: faker.Username(), - Author: faker.Name(), - Owner: faker.UUIDHyphenated(), - Description: faker.Sentence(), - IsPublic: false, - Publication: models.NewNullTime(time.Now(), true), - ImageLink: models.NewNullString(faker.Word(), true), - MapLink: models.NewNullString(faker.Word(), true), - MapParamsID: models.NewNullString(faker.Word(), true), - Variables: []string{faker.Word(), faker.Word(), faker.Word()}, - } -} - -func rowFromBook(book *models.Book) *mocks2.ScanResult { - return mocks2.NewScanResult([]interface{}{book.ID, book.CreatedAt, book.UpdatedAt, book.DeletedAt, book.Owner, //nolint:gofmt - book.Title, book.Author, book.Description, book.IsPublic, book.Publication, book.ImageLink, book.MapLink, - book.MapParamsID, book.Variables, - }) -} - -func inits(ctrl *gomock.Controller) (Repository, *models.Book) { - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testBook := newTestBook() - row := rowFromBook(testBook) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := NewRepository(client) - - return repo, testBook -} - -func TestRepository_FindByID(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testBook := inits(ctrl) - - book, err := repo.FindByID(nil, testBook.ID) - assert.Empty(t, err) - assert.Equal(t, testBook, book) -} - -func TestRepository_Create(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testBook := &models.Book{} - - row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := NewRepository(client) - id, err := repo.Create(nil, testBook) - assert.Empty(t, err) - assert.NotEmpty(t, id) -} - -func TestRepository_Update(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testBook := inits(ctrl) - - book, err := repo.Update(nil, testBook.ID, testBook) - assert.Empty(t, err) - assert.Equal(t, testBook, book) -} - -func TestRepository_Delete(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - repo, testBook := inits(ctrl) - - book, err := repo.Delete(nil, testBook.ID) - assert.Empty(t, err) - assert.Equal(t, testBook, book) -} diff --git a/bookback/internal/adapters/db/postgres/bookrepo/utils.go b/bookback/internal/adapters/db/postgres/bookrepo/utils.go deleted file mode 100644 index 35746c0..0000000 --- a/bookback/internal/adapters/db/postgres/bookrepo/utils.go +++ /dev/null @@ -1,36 +0,0 @@ -package bookrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -// readList reads a list of books from the database. -// It returns a slice of books and an error if any. -func readList(rows pgx.Rows) ([]models.Book, error) { - defer rows.Close() - books := make([]models.Book, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - books = append(books, *item) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return books, rows.Err() -} - -func readItem(row pgx.Row) (*models.Book, error) { - var book models.Book - if err := row.Scan(&book.ID, &book.CreatedAt, &book.UpdatedAt, &book.DeletedAt, &book.Owner, - &book.Title, &book.Author, &book.Description, &book.IsPublic, &book.Publication, - &book.ImageLink, &book.MapLink, &book.MapParamsID, &book.Variables); err != nil { - return nil, err - } - - return &book, nil -} diff --git a/bookback/internal/adapters/db/postgres/chapterrepo/chapter.go b/bookback/internal/adapters/db/postgres/chapterrepo/chapter.go deleted file mode 100644 index c9917f1..0000000 --- a/bookback/internal/adapters/db/postgres/chapterrepo/chapter.go +++ /dev/null @@ -1,141 +0,0 @@ -package chapterrepo - -import ( - "context" - "errors" - "fmt" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" - "strings" -) - -const ( - tableName = "chapters" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnDeletedAt = "deleted_at" - columnTitle = "title" - columnNumber = "number" - columnText = "text" - columnBookID = "book_id" - columnIsPublic = "is_public" - columnMapLink = "map_link" - columnMapParamsID = "map_params_id" - Returning = " RETURNING " - WHERE = " WHERE " - FromTable = " FROM " + tableName + " " - NotDeleted = " " + columnDeletedAt + " IS NULL " -) - -// Repository определяет интерфейс для взаимодействия с хранилищем глав. -type Repository interface { - Create(ctx context.Context, book *models.Chapter) (string, error) - FindByID(ctx context.Context, id string) (*models.Chapter, error) - Update(ctx context.Context, id string, book *models.Chapter) (*models.Chapter, error) - Delete(ctx context.Context, id string) (*models.Chapter, error) - List(ctx context.Context) ([]models.Chapter, error) - - GetChapterByBookID(ctx context.Context, bookID string) ([]models.Chapter, error) -} - -type repository struct { - db postgres.Client -} - -// NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database postgres.Client) Repository { - return &repository{database} -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnDeletedAt, columnTitle, columnNumber, - columnText, columnBookID, columnIsPublic, columnMapLink, columnMapParamsID} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnTitle, columnNumber, columnText, columnBookID, columnIsPublic, columnMapLink, - columnMapParamsID} - - return strings.Join(cols, ", ") -} - -func (r *repository) Create(ctx context.Context, chapter *models.Chapter) (string, error) { - query := "INSERT INTO" + " " + tableName + ` (` + insertItems() + - `) VALUES ($1, $2, $3, $4, $5, $6, $7) ` + Returning + columnID - - q := postgres.Query{Name: "ChapterRepository.Create", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, chapter.Title, chapter.Number, chapter.Text, chapter.BookID, - chapter.IsPublic, chapter.MapLink, chapter.MapParamsID) - - var id string - if err := row.Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -func (r *repository) FindByID(ctx context.Context, id string) (*models.Chapter, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND" + NotDeleted - - fmt.Println(query) - - q := postgres.Query{Name: "ChapterRepository.FindByID", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -func (r *repository) Update(ctx context.Context, id string, updChapter *models.Chapter) (*models.Chapter, error) { - query := "Update " + tableName + " SET " + - querybuilder.ParamsToQuery(", ", columnTitle, columnNumber, columnText, columnBookID, - columnIsPublic, columnMapLink, columnMapParamsID) + - " WHERE " + columnID + " = $8" + Returning + allItems() - - args := []interface{}{updChapter.Title, updChapter.Number, updChapter.Text, updChapter.BookID, - updChapter.IsPublic, updChapter.MapLink, updChapter.MapParamsID, id} - - q := postgres.Query{Name: "BookRepository.Update", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -func (r *repository) Delete(ctx context.Context, id string) (*models.Chapter, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - q := postgres.Query{Name: "BookRepository.Delete", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -func (r *repository) List(ctx context.Context) ([]models.Chapter, error) { - query := `SELECT ` + allItems() + FromTable + WHERE + NotDeleted - - q := postgres.Query{Name: "ChapterRepository.List", Raw: query} - - rows, err := r.db.DB().QueryRawContextMulti(ctx, q) - if err != nil { - return nil, errors.New("params error") - } - - return readList(rows) -} - -func (r *repository) GetChapterByBookID(ctx context.Context, bookID string) ([]models.Chapter, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnBookID) + " AND" + NotDeleted - - q := postgres.Query{Name: "ChapterRepository.GetChapterByBookID", Raw: query} - - rows, err := r.db.DB().QueryRawContextMulti(ctx, q, bookID) - if err != nil { - return nil, errors.New("params error") - } - - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go b/bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go deleted file mode 100644 index 3bb01b3..0000000 --- a/bookback/internal/adapters/db/postgres/chapterrepo/chapter_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package chapterrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/models" - mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" - "github.com/go-faker/faker/v4" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" -) - -func newTestChapter() *models.Chapter { - return &models.Chapter{ - ID: faker.UUIDHyphenated(), - Title: faker.Username(), - IsPublic: false, - BookID: faker.UUIDHyphenated(), - Number: 7, - Text: faker.Sentence(), - MapLink: models.NewNullString(faker.Word(), true), - MapParamsID: models.NewNullString(faker.Word(), true), - } -} - -func rowFromChapter(chapter *models.Chapter) *mocks2.ScanResult { - return mocks2.NewScanResult([]interface{}{chapter.ID, chapter.CreatedAt, chapter.UpdatedAt, chapter.DeletedAt, - chapter.Title, chapter.Number, chapter.Text, chapter.BookID, chapter.IsPublic, chapter.MapLink, - chapter.MapParamsID, - }) -} - -func initChapters(ctrl *gomock.Controller) (bookrepo.Repository, *models.Chapter) { - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testChapter := newTestChapter() - row := rowFromChapter(testChapter) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - - return repo, testChapter -} - -func TestRepository_FindByID(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testChapter := initChapters(ctrl) - - book, err := repo.FindByID(nil, testChapter.ID) - assert.Empty(t, err) - assert.Equal(t, testChapter, book) -} - -func TestRepository_Create(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testChapter := &models.Chapter{} - - row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - id, err := repo.Create(nil, testChapter) - assert.Empty(t, err) - assert.NotEmpty(t, id) -} - -func TestRepository_Update(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testChapter := initChapters(ctrl) - - book, err := repo.Update(nil, testChapter.ID, testChapter) - assert.Empty(t, err) - assert.Equal(t, testChapter, book) -} - -func TestRepository_Delete(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - repo, testChapter := initChapters(ctrl) - - book, err := repo.Delete(nil, testChapter.ID) - assert.Empty(t, err) - assert.Equal(t, testChapter, book) -} diff --git a/bookback/internal/adapters/db/postgres/chapterrepo/utils.go b/bookback/internal/adapters/db/postgres/chapterrepo/utils.go deleted file mode 100644 index 2bb962b..0000000 --- a/bookback/internal/adapters/db/postgres/chapterrepo/utils.go +++ /dev/null @@ -1,34 +0,0 @@ -package chapterrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -func readList(rows pgx.Rows) ([]models.Chapter, error) { - defer rows.Close() - chapters := make([]models.Chapter, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - chapters = append(chapters, *item) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return chapters, rows.Err() -} - -func readItem(row pgx.Row) (*models.Chapter, error) { - var chapter models.Chapter - if err := row.Scan(&chapter.ID, &chapter.CreatedAt, &chapter.UpdatedAt, &chapter.DeletedAt, &chapter.Title, - &chapter.Number, &chapter.Text, &chapter.BookID, &chapter.IsPublic, - &chapter.MapLink, &chapter.MapParamsID); err != nil { - return nil, err - } - - return &chapter, nil -} diff --git a/bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go b/bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go deleted file mode 100644 index 0752f20..0000000 --- a/bookback/internal/adapters/db/postgres/mapvarrepo/mapvariables.go +++ /dev/null @@ -1,168 +0,0 @@ -package mapvarrepo - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" - "strings" -) - -const ( - tableName = "map_variables" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnBookID = "book_id" - columnChapterID = "chapter_id" - columnPageID = "page_id" - columnParagraphID = "paragraph_id" - columnMapLink = "map_link" - columnLat = "lat" - columnLng = "lng" - columnZoom = "zoom" - columnDate = "date" - columnDescription = "description" - columnLink = "link" - columnLinkText = "link_text" - columnLinkType = "link_type" - columnLinkImage = "link_image" - columnImage = "image" -) - -type Repository interface { - Create(ctx context.Context, variable *models.MapVariable) (string, error) - FindByID(ctx context.Context, id string) (*models.MapVariable, error) - Update(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) - Delete(ctx context.Context, id string) (*models.MapVariable, error) - GetByBookID(ctx context.Context, bookID string) ([]models.MapVariable, error) - GetByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) - GetByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) - GetByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) -} - -type repository struct { - db postgres.Client -} - -func NewRepository(database postgres.Client) Repository { - return &repository{database} -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnBookID, columnChapterID, columnPageID, - columnParagraphID, columnMapLink, columnLat, columnLng, columnZoom, columnDate, - columnDescription, columnLink, columnLinkText, columnLinkType, columnLinkImage, columnImage} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, - columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, - columnLinkType, columnLinkImage, columnImage} - - return strings.Join(cols, ", ") -} - -func (r *repository) Create(ctx context.Context, variable *models.MapVariable) (string, error) { - query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + - `) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id` - - args := []interface{}{variable.BookID, variable.ChapterID, variable.PageID, variable.ParagraphID, variable.MapLink, //nolint:gofmt - variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, - variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image} - - q := postgres.Query{Name: "MapVariableRepository.Insert", Raw: query} - - var id string - if err := r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -func (r *repository) FindByID(ctx context.Context, id string) (*models.MapVariable, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) - - q := postgres.Query{Name: "MapVariableRepository.FindByID", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -func (r *repository) Update(ctx context.Context, id string, variable *models.MapVariable) (*models.MapVariable, error) { - query := `UPDATE ` + tableName + ` SET ` + querybuilder.ParamsToQuery(", ", - columnBookID, columnChapterID, columnPageID, columnParagraphID, columnMapLink, - columnLat, columnLng, columnZoom, columnDate, columnDescription, columnLink, columnLinkText, - columnLinkType, columnLinkImage, columnImage) + - ` WHERE ` + columnID + ` = $16 RETURNING ` + allItems() - - args := []interface{}{variable.BookID, variable.ChapterID, variable.PageID, variable.ParagraphID, variable.MapLink, //nolint:gofmt - variable.Lat, variable.Lng, variable.Zoom, variable.Date, variable.Description, - variable.Link, variable.LinkText, variable.LinkType, variable.LinkImage, variable.Image, id} - - q := postgres.Query{Name: "MapVariableRepository.Update", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -func (r *repository) Delete(ctx context.Context, id string) (*models.MapVariable, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + ` RETURNING ` + allItems() - - q := postgres.Query{Name: "MapVariableRepository.Delete", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - return readItem(row) -} - -func (r *repository) GetByBookID(ctx context.Context, bookID string) ([]models.MapVariable, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnBookID) - - q := postgres.Query{Name: "MapVariableRepository.GetByBookID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, bookID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByChapterID(ctx context.Context, chapterID string) ([]models.MapVariable, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) - - q := postgres.Query{Name: "MapVariableRepository.GetByChapterID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, chapterID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByPageID(ctx context.Context, pageID string) ([]models.MapVariable, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnPageID) - - q := postgres.Query{Name: "MapVariableRepository.GetByPageID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, pageID) - if err != nil { - return nil, err - } - - return readList(rows) -} - -func (r *repository) GetByParagraphID(ctx context.Context, paragraphID string) ([]models.MapVariable, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnParagraphID) - - q := postgres.Query{Name: "MapVariableRepository.GetByParagraphID", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, paragraphID) - if err != nil { - return nil, err - } - - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/mapvarrepo/utils.go b/bookback/internal/adapters/db/postgres/mapvarrepo/utils.go deleted file mode 100644 index d51653c..0000000 --- a/bookback/internal/adapters/db/postgres/mapvarrepo/utils.go +++ /dev/null @@ -1,36 +0,0 @@ -package mapvarrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -func readList(rows pgx.Rows) ([]models.MapVariable, error) { - defer rows.Close() - variables := make([]models.MapVariable, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - variables = append(variables, *item) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return variables, rows.Err() -} - -func readItem(row pgx.Row) (*models.MapVariable, error) { - var variable models.MapVariable - - if err := row.Scan(&variable.ID, &variable.CreatedAt, &variable.UpdatedAt, &variable.BookID, - &variable.ChapterID, &variable.PageID, &variable.ParagraphID, &variable.MapLink, &variable.Lat, - &variable.Lng, &variable.Zoom, &variable.Date, &variable.Description, &variable.Link, &variable.LinkText, - &variable.LinkType, &variable.LinkImage, &variable.Image); err != nil { - return nil, err - } - - return &variable, nil -} diff --git a/bookback/internal/adapters/db/postgres/pagerepo/page.go b/bookback/internal/adapters/db/postgres/pagerepo/page.go deleted file mode 100644 index 186c3e0..0000000 --- a/bookback/internal/adapters/db/postgres/pagerepo/page.go +++ /dev/null @@ -1,135 +0,0 @@ -package pagerepo - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" - "strings" -) - -const ( - tableName = "pages" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnDeletedAt = "deleted_at" - columnTitle = "title" - columnText = "text" - columnChapterID = "chapter_id" - columnIsPublic = "is_public" - columnMapParamsID = "map_params_id" - Returning = " RETURNING " -) - -// Repository определяет интерфейс для взаимодействия с хранилищем книг. -type Repository interface { - Create(ctx context.Context, book *models.Page) (string, error) - FindByID(ctx context.Context, id string) (*models.Page, error) - Update(ctx context.Context, id string, book *models.Page) (*models.Page, error) - Delete(ctx context.Context, id string) (*models.Page, error) - List(ctx context.Context) ([]models.Page, error) - - GetPagesByChapterID(ctx context.Context, chapterID string) ([]models.Page, error) -} - -type repository struct { - db postgres.Client -} - -// NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database postgres.Client) Repository { - return &repository{database} -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnDeletedAt, columnTitle, columnText, columnChapterID, - columnIsPublic, columnMapParamsID} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnTitle, columnText, columnChapterID, columnIsPublic, columnMapParamsID} - - return strings.Join(cols, ", ") -} - -// Create inserts a new page into the database and returns its ID. -func (r *repository) Create(ctx context.Context, pageDto *models.Page) (string, error) { - query := `INSERT INTO` + " " + tableName + ` (` + insertItems() + `) VALUES ($1, $2, $3, $4, $5) ` + - Returning + columnID - args := []interface{}{pageDto.Title, pageDto.Text, pageDto.ChapterID, pageDto.IsPublic, pageDto.MapParamsID} - - q := postgres.Query{Name: "PageRepository.Create", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - var id string - if err := row.Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -// FindByID retrieves a page by its ID. -func (r *repository) FindByID(ctx context.Context, id string) (*models.Page, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) - q := postgres.Query{Name: "PageRepository.FindByID", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -// Update modifies an existing page's data. -func (r *repository) Update(ctx context.Context, id string, updPage *models.Page) (*models.Page, error) { - query := "Update " + tableName + " SET " + - querybuilder.ParamsToQuery(", ", columnTitle, columnText, columnChapterID, columnIsPublic, columnMapParamsID) + - " WHERE " + columnID + " = $6" + Returning + allItems() - - args := []interface{}{updPage.Title, updPage.Text, updPage.ChapterID, updPage.IsPublic, updPage.MapParamsID, id} - - q := postgres.Query{Name: "PageRepository.Update", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -// Delete removes a page from the database. -func (r *repository) Delete(ctx context.Context, id string) (*models.Page, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + Returning + allItems() - - q := postgres.Query{Name: "PageRepository.Delete", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -// List retrieves all pages for a given chapter ID. -func (r *repository) List(ctx context.Context) ([]models.Page, error) { - query := `Select ` + allItems() + ` FROM ` + tableName + ` WHERE ` + columnDeletedAt + ` IS NULL` - - q := postgres.Query{Name: "PageRepository.List", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q) - if err != nil { - return nil, err - } - - return readList(rows) -} - -// GetPagesByChapterID retrieves all pages for a given chapter ID. -func (r *repository) GetPagesByChapterID(ctx context.Context, chapterID string) ([]models.Page, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnChapterID) + ` AND ` + columnDeletedAt + ` IS NULL` - - q := postgres.Query{Name: "PageRepository.GetPagesByChapterID", Raw: query} - - rows, err := r.db.DB().QueryContext(ctx, q, chapterID) - if err != nil { - return nil, err - } - - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/pagerepo/page_test.go b/bookback/internal/adapters/db/postgres/pagerepo/page_test.go deleted file mode 100644 index 088e39d..0000000 --- a/bookback/internal/adapters/db/postgres/pagerepo/page_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package pagerepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/models" - mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" - "github.com/go-faker/faker/v4" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" - "time" -) - -func newTestPage() *models.Page { - return &models.Page{ - ID: "1", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Title: faker.Word(), - Text: "Test text", - ChapterID: faker.UUIDHyphenated(), - IsPublic: true, - MapParamsID: models.NewNullString(faker.UUIDHyphenated(), true), - } -} - -func rowFromPage(page *models.Page) *mocks2.ScanResult { - return mocks2.NewScanResult([]interface{}{page.ID, page.CreatedAt, page.UpdatedAt, page.DeletedAt, - page.Title, page.Text, page.ChapterID, page.IsPublic, page.MapParamsID}) -} - -func initPages(ctrl *gomock.Controller) (bookrepo.Repository, *models.Page) { - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testPage := newTestPage() - row := rowFromPage(testPage) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - - return repo, testPage -} - -func TestRepository_FindByID(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testPage := initPages(ctrl) - - page, err := repo.FindByID(nil, testPage.ID) - - assert.Empty(t, err) - assert.Equal(t, testPage, page) -} - -func TestRepository_Create(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testPage := &models.Page{} - - row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - id, err := repo.Create(nil, testPage) - assert.Empty(t, err) - assert.NotEmpty(t, id) -} - -func TestRepository_Update(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testPage := initPages(ctrl) - - page, err := repo.Update(nil, testPage.ID, testPage) - assert.Empty(t, err) - assert.Equal(t, testPage, page) -} - -func TestRepository_Delete(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - repo, testPage := initPages(ctrl) - - page, err := repo.Delete(nil, testPage.ID) - assert.Empty(t, err) - assert.Equal(t, testPage, page) -} diff --git a/bookback/internal/adapters/db/postgres/pagerepo/utils.go b/bookback/internal/adapters/db/postgres/pagerepo/utils.go deleted file mode 100644 index e08c931..0000000 --- a/bookback/internal/adapters/db/postgres/pagerepo/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package pagerepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -func readList(rows pgx.Rows) ([]models.Page, error) { - defer rows.Close() - pages := make([]models.Page, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - pages = append(pages, *item) - } - - return pages, rows.Err() -} - -func readItem(row pgx.Row) (*models.Page, error) { - var page models.Page - if err := row.Scan(&page.ID, &page.CreatedAt, &page.UpdatedAt, &page.DeletedAt, &page.Title, &page.Text, &page.ChapterID, - &page.IsPublic, &page.MapParamsID); err != nil { - return nil, err - } - - return &page, nil -} diff --git a/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go deleted file mode 100644 index a74f5ef..0000000 --- a/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph.go +++ /dev/null @@ -1,137 +0,0 @@ -package paragraphrepo - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/querybuilder" - "strings" -) - -const ( - tableName = "paragraphs" - columnID = "id" - columnCreatedAt = "created_at" - columnUpdatedAt = "updated_at" - columnDeletedAt = "deleted_at" - columnTitle = "title" - columnText = "text" - columnType = "type" - columnIsPublic = "is_public" - columnPageID = "page_id" - Returning = " RETURNING " -) - -// Repository определяет интерфейс для взаимодействия с хранилищем книг. -type Repository interface { - Create(ctx context.Context, paragraph *models.Paragraph) (string, error) - FindByID(ctx context.Context, id string) (*models.Paragraph, error) - Update(ctx context.Context, id string, updParagraph *models.Paragraph) (*models.Paragraph, error) - Delete(ctx context.Context, id string) (*models.Paragraph, error) - List(ctx context.Context) ([]models.Paragraph, error) - - GetParagraphsByPageID(ctx context.Context, pageID string) ([]models.Paragraph, error) -} - -type repository struct { - db postgres.Client -} - -// NewRepository создает новый экземпляр репозитория для книг. -func NewRepository(database postgres.Client) Repository { - return &repository{database} -} - -func allItems() string { - cols := []string{columnID, columnCreatedAt, columnUpdatedAt, columnDeletedAt, columnTitle, - columnText, columnType, columnIsPublic, columnPageID} - - return strings.Join(cols, ", ") -} - -func insertItems() string { - cols := []string{columnTitle, columnText, columnType, columnIsPublic, columnPageID} - - return strings.Join(cols, ", ") -} - -// Create inserts a new Paragraph into the database -func (r *repository) Create(ctx context.Context, paragraphDto *models.Paragraph) (string, error) { - query := "INSERT INTO" + " " + tableName + " (" + insertItems() + ") VALUES ($1, $2, $3, $4, $5) " + - Returning + columnID - q := postgres.Query{Name: "ParagraphRepository.Create", Raw: query} - - args := []interface{}{paragraphDto.Title, paragraphDto.Text, paragraphDto.Type, paragraphDto.IsPublic, paragraphDto.PageID} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - var id string - if err := row.Scan(&id); err != nil { - return "", err - } - - return id, nil -} - -// FindByID retrieves a paragraph by its ID -func (r *repository) FindByID(ctx context.Context, id string) (*models.Paragraph, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnID) + " AND deleted_at IS NULL" - - q := postgres.Query{Name: "ParagraphRepository.FindByID", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -// Update modifies an existing paragraph's data -func (r *repository) Update(ctx context.Context, id string, updParagraph *models.Paragraph) (*models.Paragraph, error) { - query := "UPDATE" + " " + tableName + " SET " + - querybuilder.ParamsToQuery(", ", columnTitle, columnText, columnType, columnIsPublic, columnPageID) + - " WHERE id = $6" + Returning + allItems() - - args := []interface{}{updParagraph.Title, updParagraph.Text, updParagraph.Type, updParagraph.IsPublic, - updParagraph.PageID, id} - - q := postgres.Query{Name: "ParagraphRepository.Update", Raw: query} - - row := r.db.DB().QueryRowContext(ctx, q, args...) - - return readItem(row) -} - -// Delete removes a paragraph from the database -func (r *repository) Delete(ctx context.Context, id string) (*models.Paragraph, error) { - query := querybuilder.DeleteQuery(tableName, columnID) + Returning + allItems() - q := postgres.Query{Name: "ParagraphRepository.Delete", Raw: query} - row := r.db.DB().QueryRowContext(ctx, q, id) - - return readItem(row) -} - -// List retrieves all paragraphs (adjust based on your needs, e.g., by parent Page or Chapter ID) -func (r *repository) List(ctx context.Context) ([]models.Paragraph, error) { - query := "SELECT " + allItems() + " FROM " + tableName + " WHERE deleted_at IS NULL" - - q := postgres.Query{Name: "ParagraphRepository.List", Raw: query} - - rows, err := r.db.DB().QueryContext(ctx, q) - if err != nil { - return nil, errors.New("params error") - } - - return readList(rows) -} - -func (r *repository) GetParagraphsByPageID(ctx context.Context, pageID string) ([]models.Paragraph, error) { - query := querybuilder.SelectWhere(allItems, tableName, columnPageID) + " AND deleted_at IS NULL" - - q := postgres.Query{Name: "ParagraphRepository.GetParagraphsByPageID", Raw: query} - - rows, err := r.db.DB().QueryContext(ctx, q, pageID) - if err != nil { - return nil, errors.New("params error") - } - - return readList(rows) -} diff --git a/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go b/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go deleted file mode 100644 index 7121788..0000000 --- a/bookback/internal/adapters/db/postgres/paragraphrepo/paragraph_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package paragraphrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/models" - mocks2 "github.com/SShlykov/zeitment/bookback/tests/mocks" - "github.com/go-faker/faker/v4" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" - "time" -) - -func newTestParagraph() *models.Paragraph { - return &models.Paragraph{ - ID: "1", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Title: faker.Word(), - Text: faker.Word(), - Type: faker.Word(), - PageID: faker.UUIDHyphenated(), - IsPublic: true, - } -} - -func rowFromParagraph(paragraph *models.Paragraph) *mocks2.ScanResult { - return mocks2.NewScanResult([]interface{}{paragraph.ID, paragraph.CreatedAt, paragraph.UpdatedAt, paragraph.DeletedAt, - paragraph.Title, paragraph.Text, paragraph.Type, paragraph.IsPublic, paragraph.PageID}) -} - -func initParagraphs(ctrl *gomock.Controller) (bookrepo.Repository, *models.Paragraph) { - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testParagraph := newTestParagraph() - row := rowFromParagraph(testParagraph) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - - return repo, testParagraph -} - -func TestRepository_FindByID(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testParagraph := initParagraphs(ctrl) - - paragraph, err := repo.FindByID(nil, testParagraph.ID) - - assert.Empty(t, err) - assert.Equal(t, testParagraph, paragraph) -} - -func TestRepository_Create(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks2.NewMockClient(ctrl) - db := mocks2.NewMockDB(ctrl) - - testParagraph := &models.Paragraph{} - - row := mocks2.NewScanResult([]interface{}{faker.UUIDHyphenated()}) - - client.EXPECT().DB().Return(db) - db.EXPECT().QueryRowContext(gomock.Any(), gomock.Any(), gomock.Any()).Return(row) - - repo := bookrepo.NewRepository(client) - id, err := repo.Create(nil, testParagraph) - assert.Empty(t, err) - assert.NotEmpty(t, id) -} - -func TestRepository_Update(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testParagraph := initParagraphs(ctrl) - - paragraph, err := repo.Update(nil, testParagraph.ID, testParagraph) - - assert.Empty(t, err) - assert.Equal(t, testParagraph, paragraph) -} - -func TestRepository_Delete(t *testing.T) { - t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - repo, testParagraph := initParagraphs(ctrl) - - paragraph, err := repo.Delete(nil, testParagraph.ID) - - assert.Empty(t, err) - assert.Equal(t, testParagraph, paragraph) -} diff --git a/bookback/internal/adapters/db/postgres/paragraphrepo/utils.go b/bookback/internal/adapters/db/postgres/paragraphrepo/utils.go deleted file mode 100644 index 2dff533..0000000 --- a/bookback/internal/adapters/db/postgres/paragraphrepo/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package paragraphrepo - -import ( - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/jackc/pgx/v5" -) - -func readList(rows pgx.Rows) ([]models.Paragraph, error) { - defer rows.Close() - paragraphs := make([]models.Paragraph, 0) - for rows.Next() { - item, err := readItem(rows) - if err != nil { - return nil, err - } - paragraphs = append(paragraphs, *item) - } - - return paragraphs, rows.Err() -} - -func readItem(row pgx.Row) (*models.Paragraph, error) { - var paragraph models.Paragraph - if err := row.Scan(¶graph.ID, ¶graph.CreatedAt, ¶graph.UpdatedAt, ¶graph.DeletedAt, - ¶graph.Title, ¶graph.Text, ¶graph.Type, ¶graph.IsPublic, ¶graph.PageID); err != nil { - return nil, err - } - - return ¶graph, nil -} diff --git a/bookback/internal/pkg/app/app.go b/bookback/internal/bootstrap/app/app.go similarity index 95% rename from bookback/internal/pkg/app/app.go rename to bookback/internal/bootstrap/app/app.go index b93ad37..56f10ec 100644 --- a/bookback/internal/pkg/app/app.go +++ b/bookback/internal/bootstrap/app/app.go @@ -2,8 +2,8 @@ package app import ( "context" - cfg "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/metrics" + cfg "github.com/SShlykov/zeitment/bookback/pkg/config" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" "log/slog" diff --git a/bookback/internal/pkg/app/closer.go b/bookback/internal/bootstrap/app/closer.go similarity index 100% rename from bookback/internal/pkg/app/closer.go rename to bookback/internal/bootstrap/app/closer.go diff --git a/bookback/internal/pkg/app/config.go b/bookback/internal/bootstrap/app/config.go similarity index 82% rename from bookback/internal/pkg/app/config.go rename to bookback/internal/bootstrap/app/config.go index acd8d02..eeb1fe2 100644 --- a/bookback/internal/pkg/app/config.go +++ b/bookback/internal/bootstrap/app/config.go @@ -3,7 +3,7 @@ package app import ( "context" "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" + "github.com/SShlykov/zeitment/bookback/pkg/config" ) func (app *App) initConfig(_ context.Context) error { diff --git a/bookback/internal/pkg/app/db.go b/bookback/internal/bootstrap/app/db.go similarity index 93% rename from bookback/internal/pkg/app/db.go rename to bookback/internal/bootstrap/app/db.go index fe686b3..fb8fc7b 100644 --- a/bookback/internal/pkg/app/db.go +++ b/bookback/internal/bootstrap/app/db.go @@ -3,7 +3,7 @@ package app import ( "context" "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" + "github.com/SShlykov/zeitment/bookback/pkg/config" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "time" ) diff --git a/bookback/internal/pkg/app/endpoint.go b/bookback/internal/bootstrap/app/endpoint.go similarity index 100% rename from bookback/internal/pkg/app/endpoint.go rename to bookback/internal/bootstrap/app/endpoint.go diff --git a/bookback/internal/pkg/app/logger.go b/bookback/internal/bootstrap/app/logger.go similarity index 100% rename from bookback/internal/pkg/app/logger.go rename to bookback/internal/bootstrap/app/logger.go diff --git a/bookback/internal/pkg/app/metrics.go b/bookback/internal/bootstrap/app/metrics.go similarity index 100% rename from bookback/internal/pkg/app/metrics.go rename to bookback/internal/bootstrap/app/metrics.go diff --git a/bookback/internal/config/const.go b/bookback/internal/config/const.go deleted file mode 100644 index abd957b..0000000 --- a/bookback/internal/config/const.go +++ /dev/null @@ -1,22 +0,0 @@ -package config - -type HTTPError struct { - Message string `json:"message"` -} - -func (e HTTPError) Error() string { - return e.Message -} - -type repoError struct { - Error string `json:"error"` -} - -var ( - ErrorNotFound = HTTPError{"record not found"} - ErrorBadInput = HTTPError{"bad input"} - ErrorForbidden = HTTPError{"forbidden"} - ErrorNotCreated = HTTPError{"not created"} - ErrorNotUpdated = HTTPError{"not updated"} - ErrorNotDeleted = HTTPError{"not deleted"} -) diff --git a/bookback/internal/controller/http/v1/book/controller.go b/bookback/internal/controller/http/v1/book/controller.go index c50a73d..7760359 100644 --- a/bookback/internal/controller/http/v1/book/controller.go +++ b/bookback/internal/controller/http/v1/book/controller.go @@ -2,8 +2,6 @@ package book import ( "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -25,7 +23,7 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log } // ListBooks обрабатывает запросы на получение списка книг. -// @router /books [get] +// @router /books [post] // @summary Получить список книг // @description Извлекает список всех книг // @tags Книги @@ -33,6 +31,11 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @success 200 {array} entity.Book // @failure 500 {object} string func (bc *Controller) ListBooks(c echo.Context) error { + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + books, err := bc.Service.ListBooks(bc.Ctx) if err != nil { return ErrorUnknown @@ -115,9 +118,6 @@ func (bc *Controller) UpdateBook(c echo.Context) error { updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request.Book) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorBookNotFound - } return ErrorUnknown } return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Book: updatedBook}) diff --git a/bookback/internal/controller/http/v1/book/models.go b/bookback/internal/controller/http/v1/book/models.go index f99b0b6..30f7c4e 100644 --- a/bookback/internal/controller/http/v1/book/models.go +++ b/bookback/internal/controller/http/v1/book/models.go @@ -5,6 +5,8 @@ import ( ) type Options struct { + Limit int `json:"limit"` + Offset int `json:"offset"` } type requestModel struct { diff --git a/bookback/internal/controller/http/v1/book/routes.go b/bookback/internal/controller/http/v1/book/routes.go index 1999bd1..fcb7c61 100644 --- a/bookback/internal/controller/http/v1/book/routes.go +++ b/bookback/internal/controller/http/v1/book/routes.go @@ -24,7 +24,7 @@ func (bc *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) group.Use(httpmiddlewares.MetricsLogger(bc.Metrics)) - group.GET("", bc.ListBooks) + group.POST("", bc.ListBooks) group.POST("", bc.CreateBook) group.GET("/:id", bc.GetBookByID) group.PUT("/:id", bc.UpdateBook) diff --git a/bookback/internal/controller/http/v1/bookevents/controller.go b/bookback/internal/controller/http/v1/bookevents/controller.go index 244cc40..95febf8 100644 --- a/bookback/internal/controller/http/v1/bookevents/controller.go +++ b/bookback/internal/controller/http/v1/bookevents/controller.go @@ -2,8 +2,6 @@ package bookevents import ( "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -70,9 +68,6 @@ func (bec *Controller) UpdateBookEvent(c echo.Context) error { updatedEvent, err := bec.Service.UpdateBookEvent(bec.Ctx, id, request.BookEvents) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorBookEventNotFound - } return ErrorUnknown } diff --git a/bookback/internal/controller/http/v1/chapter/controller.go b/bookback/internal/controller/http/v1/chapter/controller.go index 302dc6b..65ea9d3 100644 --- a/bookback/internal/controller/http/v1/chapter/controller.go +++ b/bookback/internal/controller/http/v1/chapter/controller.go @@ -2,9 +2,6 @@ package chapter import ( "context" - "errors" - "fmt" - "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -104,15 +101,11 @@ func (ch *Controller) UpdateChapter(c echo.Context) error { var request requestModel if err := c.Bind(&request); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, config.ErrorBadInput) + return echo.NewHTTPError(http.StatusBadRequest, ErrorValidationFailed) } updatedChapter, err := ch.Service.UpdateChapter(ch.Ctx, id, request.Chapter) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorChapterNotFound - } - fmt.Println(err) return ErrorUnknown } return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Chapter: updatedChapter}) @@ -135,7 +128,7 @@ func (ch *Controller) DeleteChapter(c echo.Context) error { chapt, err := ch.Service.DeleteChapter(ch.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotAcceptable, config.ErrorNotDeleted) + return echo.NewHTTPError(http.StatusNotAcceptable, ErrorDeleteChapter) } return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Chapter: chapt}) } diff --git a/bookback/internal/controller/http/v1/mapvariables/controller.go b/bookback/internal/controller/http/v1/mapvariables/controller.go index 344c66a..9c3c195 100644 --- a/bookback/internal/controller/http/v1/mapvariables/controller.go +++ b/bookback/internal/controller/http/v1/mapvariables/controller.go @@ -2,9 +2,7 @@ package mapvariables import ( "context" - "errors" "fmt" - "github.com/SShlykov/zeitment/bookback/internal/config" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -73,9 +71,6 @@ func (mvc *Controller) UpdateMapVariable(c echo.Context) error { updatedVariable, err := mvc.Service.UpdateMapVariable(mvc.Ctx, id, request.MapVariables) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorMapVariableNotFound - } return ErrorUnknown } @@ -98,7 +93,7 @@ func (mvc *Controller) DeleteMapVariable(c echo.Context) error { mapVariable, err := mvc.Service.DeleteMapVariable(mvc.Ctx, id) if err != nil { - return echo.NewHTTPError(http.StatusNotFound, config.ErrorNotFound) + return echo.NewHTTPError(http.StatusNotFound, ErrorDeleteMapVariable) } return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", MapVariable: mapVariable}) diff --git a/bookback/internal/controller/http/v1/page/controller.go b/bookback/internal/controller/http/v1/page/controller.go index 018f206..038e1ea 100644 --- a/bookback/internal/controller/http/v1/page/controller.go +++ b/bookback/internal/controller/http/v1/page/controller.go @@ -2,8 +2,6 @@ package page import ( "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -108,9 +106,6 @@ func (p *Controller) UpdatePage(c echo.Context) error { updatedPage, err := p.Service.UpdatePage(p.Ctx, id, request.Page) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorPageNotFound - } return ErrorUnknown } return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Page: updatedPage}) diff --git a/bookback/internal/controller/http/v1/paragraph/controller.go b/bookback/internal/controller/http/v1/paragraph/controller.go index 68c6fd0..29f75be 100644 --- a/bookback/internal/controller/http/v1/paragraph/controller.go +++ b/bookback/internal/controller/http/v1/paragraph/controller.go @@ -2,8 +2,6 @@ package paragraph import ( "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/config" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/labstack/echo/v4" @@ -109,9 +107,6 @@ func (p *Controller) UpdateParagraph(c echo.Context) error { updatedParagraph, err := p.Service.UpdateParagraph(p.Ctx, id, request.Paragraph) if err != nil { - if errors.Is(err, config.ErrorNotFound) { - return ErrorParagraphNotFound - } return ErrorUnknown } return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Paragraph: updatedParagraph}) diff --git a/bookback/internal/domain/entity/book.go b/bookback/internal/domain/entity/book.go index 519d768..950b49d 100644 --- a/bookback/internal/domain/entity/book.go +++ b/bookback/internal/domain/entity/book.go @@ -1,25 +1,70 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) type Book struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt NullTime `json:"deleted_at"` - Owner string `json:"owner"` - Title string `json:"title"` - Author string `json:"author"` - Description string `json:"description"` - IsPublic bool `json:"is_public"` - Publication NullTime `json:"publication"` - ImageLink NullString `json:"image_link"` // обложка - MapLink NullString `json:"map_link"` // карта - MapParamsID NullString `json:"map_params_id"` // Параметры карты (координаты и тп.) - // Переменные мира, список ключей ? Ограничение на длину ключа - // и значения + ограничение на количество переменных - Variables []string `json:"variables"` - //Chapters []Chapter + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt sql.NullTime // Обратите внимание на правильное использование типа NullTime + Owner string + Title string + Author string + Description string + IsPublic bool + Publication sql.NullTime + ImageLink sql.NullString + MapLink sql.NullString + MapParamsID sql.NullString + Variables []string +} + +func (b Book) TableName() string { + return "books" +} + +func (b Book) AllFields() []string { + return []string{"id", "created_at", "updated_at", "deleted_at", "owner", "title", "author", "description", + "is_public", "publication", "image_link", "map_link", "map_params_id", "variables"} +} + +func (b Book) InsertFields() []string { + return []string{"owner", "title", "author", "description", "is_public", "publication", "image_link", "map_link", + "map_params_id", "variables"} +} + +func (b Book) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(Book); ok { + return []interface{}{ + e.Owner, e.Title, e.Author, e.Description, e.IsPublic, e.Publication, + e.ImageLink, e.MapLink, e.MapParamsID, e.Variables, + } + } + return nil +} + +func (b Book) ReadItem(row pgx.Row) (any, error) { + var book Book + err := row.Scan(&book.ID, &book.CreatedAt, &book.UpdatedAt, &book.DeletedAt, &book.Owner, &book.Title, &book.Author, + &book.Description, &book.IsPublic, &book.Publication, &book.ImageLink, &book.MapLink, &book.MapParamsID) + if err != nil { + return nil, err + } + return book, nil +} + +func (b Book) ReadList(rows pgx.Rows) ([]any, error) { + var books []any + for rows.Next() { + book, err := b.ReadItem(rows) + if err != nil { + return nil, err + } + books = append(books, book) + } + return books, nil } diff --git a/bookback/internal/domain/entity/bookevents.go b/bookback/internal/domain/entity/bookevents.go index e436053..eba98bd 100644 --- a/bookback/internal/domain/entity/bookevents.go +++ b/bookback/internal/domain/entity/bookevents.go @@ -1,25 +1,70 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) type BookEvent struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - - BookID string `json:"book_id"` - ChapterID NullString `json:"chapter_id"` - PageID NullString `json:"page_id"` - ParagraphID NullString `json:"paragraph_id"` - EventType NullString `json:"event_type"` // Тип события (начало главы, начало страницы, начало параграфа...) - IsPublic bool `json:"is_public"` - Key string `json:"key"` - Value string `json:"value"` - Link NullString `json:"link"` - LinkText NullString `json:"link_text"` - LinkType NullString `json:"link_type"` - LinkImage NullString `json:"link_image"` - Description NullString `json:"description"` + ID string + CreatedAt time.Time + UpdatedAt time.Time + BookID string + ChapterID sql.NullString + PageID sql.NullString + ParagraphID sql.NullString + EventType sql.NullString // Тип события (начало главы, начало страницы, начало параграфа...) + IsPublic bool + Key string + Value string + Link sql.NullString + LinkText sql.NullString + LinkType sql.NullString + LinkImage sql.NullString + Description sql.NullString +} + +func (be BookEvent) TableName() string { + return "chapters" +} + +func (be BookEvent) AllFields() []string { + return []string{"id", "created_at", "updated_at", "book_id", "chapter_id", "page_id", "paragraph_id", "event_type", + "is_public", "key", "value", "link", "link_text", "link_type", "link_image", "description"} +} + +func (be BookEvent) InsertFields() []string { + return []string{"book_id", "chapter_id", "page_id", "paragraph_id", "event_type", + "is_public", "key", "value", "link", "link_text", "link_type", "link_image", "description"} +} + +func (be BookEvent) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(BookEvent); ok { + return []interface{}{e.BookID, e.ChapterID, e.PageID, e.ParagraphID, e.EventType, + e.IsPublic, e.Key, e.Value, e.Link, e.LinkText, e.LinkType, e.LinkImage, e.Description} + } + return nil +} + +func (be BookEvent) ReadItem(row pgx.Row) (any, error) { + var e BookEvent + err := row.Scan(&e.ID, &e.CreatedAt, &e.UpdatedAt, &e.BookID, &e.ChapterID, &e.PageID, &e.ParagraphID, &e.EventType, + &e.IsPublic, &e.Key, &e.Value, &e.Link, &e.LinkText, &e.LinkType, &e.LinkImage, &e.Description) + if err != nil { + return nil, err + } + return e, nil +} + +func (be BookEvent) ReadList(rows pgx.Rows) ([]any, error) { + var entities []any + for rows.Next() { + chapter, err := be.ReadItem(rows) + if err != nil { + return nil, err + } + entities = append(entities, chapter) + } + return entities, nil } diff --git a/bookback/internal/domain/entity/chapter.go b/bookback/internal/domain/entity/chapter.go index 9edf962..6821823 100644 --- a/bookback/internal/domain/entity/chapter.go +++ b/bookback/internal/domain/entity/chapter.go @@ -1,21 +1,63 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) type Chapter struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt NullTime `json:"deleted_at"` - Title string `json:"title"` - Number int `json:"number"` // Порядковый номер главы (не уникальный т.к. главы могут быть скрыты) - Text string `json:"text"` // Превью текста главы - BookID string `json:"book_id"` - IsPublic bool `json:"is_public"` - MapLink NullString `json:"map_link"` // карта - MapParamsID NullString `json:"map_params_id"` // параметры карты (координаты и тп.) - //Variables []string `json:"variables"` // переменные мира key: key, value: value - //Pages []Page `json:"pages"` + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt sql.NullTime + Title string + Number int // Порядковый номер главы (не уникальный т.к. главы могут быть скрыты) + Text string // Превью текста главы + BookID string + IsPublic bool + MapLink sql.NullString // карта + MapParamsID sql.NullString // параметры карты (координаты и тп.) +} + +func (c Chapter) TableName() string { + return "chapters" +} + +func (c Chapter) AllFields() []string { + return []string{"id", "created_at", "updated_at", "deleted_at", "title", "number", "text", "book_id", + "is_public", "map_link", "map_params_id"} +} + +func (c Chapter) InsertFields() []string { + return []string{"title", "number", "text", "book_id", "is_public", "map_link", "map_params_id"} +} + +func (c Chapter) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(Chapter); ok { + return []interface{}{e.Title, e.Number, e.Text, e.BookID, e.IsPublic, e.MapLink, e.MapParamsID} + } + return nil +} + +func (c Chapter) ReadItem(row pgx.Row) (any, error) { + var e Chapter + err := row.Scan(&e.ID, &e.CreatedAt, &e.UpdatedAt, &e.Title, &e.Number, &e.Text, &e.BookID, &e.IsPublic, + &e.MapLink, &e.MapParamsID) + if err != nil { + return nil, err + } + return e, nil +} + +func (c Chapter) ReadList(rows pgx.Rows) ([]any, error) { + var entities []any + for rows.Next() { + chapter, err := c.ReadItem(rows) + if err != nil { + return nil, err + } + entities = append(entities, chapter) + } + return entities, nil } diff --git a/bookback/internal/domain/entity/mapvariables.go b/bookback/internal/domain/entity/mapvariables.go index ed29ee4..5ed14cb 100644 --- a/bookback/internal/domain/entity/mapvariables.go +++ b/bookback/internal/domain/entity/mapvariables.go @@ -1,26 +1,83 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) +// MapVariable структура для хранения информации о переменных карты type MapVariable struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - BookID string `json:"book_id"` - ChapterID NullString `json:"chapter_id"` - PageID NullString `json:"page_id"` - ParagraphID NullString `json:"paragraph_id"` - MapLink string `json:"map_link"` - Lat float64 `json:"lat"` - Lng float64 `json:"lng"` - Zoom NullInt `json:"zoom"` - Date NullString `json:"date"` - Description NullString `json:"description"` - Link NullString `json:"link"` - LinkText NullString `json:"link_text"` - LinkType NullString `json:"link_type"` - LinkImage NullString `json:"link_image"` - Image NullString `json:"image"` + ID string + CreatedAt time.Time + UpdatedAt time.Time + BookID string + ChapterID sql.NullString + PageID sql.NullString + ParagraphID sql.NullString + MapLink string + Lat float64 + Lng float64 + Zoom sql.NullInt64 + Date sql.NullString + Description sql.NullString + Link sql.NullString + LinkText sql.NullString + LinkType sql.NullString + LinkImage sql.NullString + Image sql.NullString +} + +// TableName возвращает имя таблицы для структуры MapVariable +func (mv MapVariable) TableName() string { + return "map_variables" +} + +// AllFields возвращает список всех полей структуры MapVariable +func (mv MapVariable) AllFields() []string { + return []string{"id", "created_at", "updated_at", "book_id", "chapter_id", "page_id", "paragraph_id", "map_link", + "lat", "lng", "zoom", "date", "description", "link", "link_text", "link_type", "link_image", "image"} +} + +// InsertFields возвращает список полей, используемых при вставке новой записи +func (mv MapVariable) InsertFields() []string { + return []string{"book_id", "chapter_id", "page_id", "paragraph_id", "map_link", "lat", "lng", "zoom", "date", + "description", "link", "link_text", "link_type", "link_image", "image"} +} + +// EntityToInsertValues преобразует сущность в список значений для вставки +func (mv MapVariable) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(MapVariable); ok { + return []interface{}{ + e.BookID, e.ChapterID, e.PageID, e.ParagraphID, e.MapLink, e.Lat, e.Lng, e.Zoom, e.Date, + e.Description, e.Link, e.LinkText, e.LinkType, e.LinkImage, e.Image, + } + } + return nil +} + +// ReadItem читает одну запись из строки запроса +func (mv MapVariable) ReadItem(row pgx.Row) (any, error) { + var mapVariable MapVariable + err := row.Scan(&mapVariable.ID, &mapVariable.CreatedAt, &mapVariable.UpdatedAt, &mapVariable.BookID, &mapVariable.ChapterID, + &mapVariable.PageID, &mapVariable.ParagraphID, &mapVariable.MapLink, &mapVariable.Lat, &mapVariable.Lng, &mapVariable.Zoom, + &mapVariable.Date, &mapVariable.Description, &mapVariable.Link, &mapVariable.LinkText, &mapVariable.LinkType, + &mapVariable.LinkImage, &mapVariable.Image) + if err != nil { + return nil, err + } + return mapVariable, nil +} + +// ReadList читает список записей из результатов запроса +func (mv MapVariable) ReadList(rows pgx.Rows) ([]any, error) { + var mapVariables []any + for rows.Next() { + mapVariable, err := mv.ReadItem(rows) + if err != nil { + return nil, err + } + mapVariables = append(mapVariables, mapVariable) + } + return mapVariables, nil } diff --git a/bookback/internal/domain/entity/nullable.go b/bookback/internal/domain/entity/nullable.go deleted file mode 100644 index 284f481..0000000 --- a/bookback/internal/domain/entity/nullable.go +++ /dev/null @@ -1,99 +0,0 @@ -package entity - -import ( - "database/sql" - "encoding/json" - "time" -) - -// NullTime обертка вокруг sql.NullTime для корректной работы с JSON -type NullTime struct { - sql.NullTime -} - -// NewNullTime создает новый экземпляр NullTime. -func NewNullTime(t time.Time, valid bool) NullTime { - return NullTime{sql.NullTime{Time: t, Valid: valid}} -} - -// MarshalJSON метод для сериализация в JSON. -// Возвращает значение в формате JSON или null. -func (nt *NullTime) MarshalJSON() ([]byte, error) { - if nt.Valid { - return json.Marshal(nt.Time) - } - return json.Marshal(nil) -} - -// UnmarshalJSON метод для десериализации из JSON. -func (nt *NullTime) UnmarshalJSON(data []byte) error { - if isNull(data) { - nt.Valid = false - return nil - } - err := json.Unmarshal(data, &nt.Time) - nt.Valid = err == nil - return err -} - -// NullString обертка вокруг sql.NullString для корректной работы с JSON -type NullString struct { - sql.NullString -} - -// NewNullString создает новый экземпляр NullString. -func NewNullString(str string, valid bool) NullString { - return NullString{sql.NullString{String: str, Valid: valid}} -} - -// MarshalJSON метод для сериализация в JSON. -// Возвращает значение в формате JSON или null. -func (ns *NullString) MarshalJSON() ([]byte, error) { - if ns.Valid { - return json.Marshal(ns.String) - } - return json.Marshal(nil) -} - -// UnmarshalJSON метод для десериализации из JSON. -func (ns *NullString) UnmarshalJSON(data []byte) error { - if isNull(data) { - ns.Valid = false - return nil - } - err := json.Unmarshal(data, &ns.String) - ns.Valid = err == nil - return err -} - -// NullInt обертка вокруг sql.NullInt64 для корректной работы с JSON -type NullInt struct { - sql.NullInt64 -} - -// NewNullInt создает новый экземпляр NullInt. -func NewNullInt(i int64, valid bool) NullInt { - return NullInt{sql.NullInt64{Int64: i, Valid: valid}} -} - -// MarshalJSON метод для сериализация в JSON. -func (ni *NullInt) MarshalJSON() ([]byte, error) { - if ni.Valid { - return json.Marshal(ni.Int64) - } - return json.Marshal(nil) -} - -func (ni *NullInt) UnmarshalJSON(data []byte) error { - if isNull(data) { - ni.Valid = false - return nil - } - err := json.Unmarshal(data, &ni.Int64) - ni.Valid = err == nil - return err -} - -func isNull(data []byte) bool { - return string(data) == "null" -} diff --git a/bookback/internal/domain/entity/page.go b/bookback/internal/domain/entity/page.go index 59c8bcc..8e19cb1 100644 --- a/bookback/internal/domain/entity/page.go +++ b/bookback/internal/domain/entity/page.go @@ -1,19 +1,69 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) +// Page структура для хранения информации о странице type Page struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt NullTime `json:"deleted_at"` - Title string `json:"title"` - Text string `json:"text"` - ChapterID string `json:"chapter_id"` - IsPublic bool `json:"is_public"` - MapParamsID NullString `json:"map_params"` // параметры карты (координаты и тп.) - //Paragraphs []Paragraph - //Variables []Variable `json:"variables"` // переменные мира key: key, value: value + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt sql.NullTime + Title string + Text string + ChapterID string + IsPublic bool + MapParamsID sql.NullString +} + +// TableName возвращает имя таблицы для структуры Page +func (p Page) TableName() string { + return "pages" +} + +// AllFields возвращает список всех полей структуры Page +func (p Page) AllFields() []string { + return []string{"id", "created_at", "updated_at", "deleted_at", "title", "text", "chapter_id", "is_public", "map_params_id"} +} + +// InsertFields возвращает список полей, используемых при вставке новой записи +func (p Page) InsertFields() []string { + return []string{"title", "text", "chapter_id", "is_public", "map_params_id"} +} + +// EntityToInsertValues преобразует сущность Page в список значений для вставки +func (p Page) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(Page); ok { + return []interface{}{ + e.Title, e.Text, e.ChapterID, e.IsPublic, e.MapParamsID, + } + } + return nil +} + +// ReadItem читает одну запись из строки запроса +func (p Page) ReadItem(row pgx.Row) (any, error) { + var page Page + err := row.Scan(&page.ID, &page.CreatedAt, &page.UpdatedAt, &page.DeletedAt, &page.Title, &page.Text, + &page.ChapterID, &page.IsPublic, &page.MapParamsID) + if err != nil { + return nil, err + } + return page, nil +} + +// ReadList читает список записей из результатов запроса +func (p Page) ReadList(rows pgx.Rows) ([]any, error) { + var pages []any + for rows.Next() { + page, err := p.ReadItem(rows) + if err != nil { + return nil, err + } + pages = append(pages, page) + } + return pages, nil } diff --git a/bookback/internal/domain/entity/paragraph.go b/bookback/internal/domain/entity/paragraph.go index 3794fc7..ddcf249 100644 --- a/bookback/internal/domain/entity/paragraph.go +++ b/bookback/internal/domain/entity/paragraph.go @@ -1,17 +1,69 @@ package entity import ( + "database/sql" + "github.com/jackc/pgx/v5" "time" ) +// Paragraph структура для хранения информации о параграфе type Paragraph struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt NullTime `json:"deleted_at"` - Title string `json:"title"` - Text string `json:"text"` - Type string `json:"type"` - IsPublic bool `json:"is_public"` - PageID string `json:"page_id"` + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt sql.NullTime + Title string + Text string + Type string + IsPublic bool + PageID string +} + +// TableName возвращает имя таблицы для структуры Paragraph +func (p Paragraph) TableName() string { + return "paragraphs" +} + +// AllFields возвращает список всех полей структуры Paragraph +func (p Paragraph) AllFields() []string { + return []string{"id", "created_at", "updated_at", "deleted_at", "title", "text", "type", "is_public", "page_id"} +} + +// InsertFields возвращает список полей, используемых при вставке новой записи +func (p Paragraph) InsertFields() []string { + return []string{"title", "text", "type", "is_public", "page_id"} +} + +// EntityToInsertValues преобразует сущность Paragraph в список значений для вставки +func (p Paragraph) EntityToInsertValues(entity any) []interface{} { + if e, ok := entity.(Paragraph); ok { + return []interface{}{ + e.Title, e.Text, e.Type, e.IsPublic, e.PageID, + } + } + return nil +} + +// ReadItem читает одну запись из строки запроса +func (p Paragraph) ReadItem(row pgx.Row) (any, error) { + var paragraph Paragraph + err := row.Scan(¶graph.ID, ¶graph.CreatedAt, ¶graph.UpdatedAt, ¶graph.DeletedAt, ¶graph.Title, + ¶graph.Text, ¶graph.Type, ¶graph.IsPublic, ¶graph.PageID) + if err != nil { + return nil, err + } + return paragraph, nil +} + +// ReadList читает список записей из результатов запроса +func (p Paragraph) ReadList(rows pgx.Rows) ([]any, error) { + var paragraphs []any + for rows.Next() { + paragraph, err := p.ReadItem(rows) + if err != nil { + return nil, err + } + paragraphs = append(paragraphs, paragraph) + } + return paragraphs, nil } diff --git a/bookback/internal/domain/repository/pgrepo/0-genericrepository.go b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go new file mode 100644 index 0000000..e559e07 --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go @@ -0,0 +1,194 @@ +package pgrepo + +import ( + "context" + "fmt" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/jackc/pgx/v5" + "strings" +) + +type Simulated interface { + TableName() string + AllFields() []string + InsertFields() []string + EntityToInsertValues(entity any) []interface{} + ReadItem(row pgx.Row) (any, error) + ReadList(rows pgx.Rows) ([]any, error) +} + +type Repository[T Simulated] interface { + Create(item T) (string, error) + Update(id string, item T) (T, error) + HardDelete(id string) (T, error) + + FindByID(id string) (T, error) + FindByKV(key string, value interface{}) ([]T, error) + + List(ctx context.Context, limit uint64, offset uint64) ([]T, error) +} + +type repository[T Simulated] struct { + Name string + entity T + db postgres.Client + ctx context.Context +} + +func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ([]T, error) { + query, args, err := r.db.Builder(). + Select(r.entity.AllFields()...). + From(r.entity.TableName()). + Offset(offset). + Limit(limit). + Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + ToSql() + + if err != nil { + return nil, err + } + + q := postgres.Query{Name: r.Name + ".List", Raw: query} + rows, err := r.db.DB().QueryContext(ctx, q, args...) + + return r.readList(rows) +} + +func (r *repository[T]) Create(entity T) (string, error) { + query, args, err := r.db.Builder(). + Insert(entity.TableName()). + Columns(entity.InsertFields()...). + Values(entity.EntityToInsertValues(entity)...). + Prefix("RETURNING id"). + ToSql() + + if err != nil { + return "", err + } + + q := postgres.Query{Name: r.Name + ".Insert", Raw: query} + + var id string + if err = r.db.DB().QueryRowContext(r.ctx, q, args...).Scan(&id); err != nil { + return "", err + } + + return id, nil +} + +func (r *repository[T]) Update(id string, entity T) (T, error) { + var zero T + + updateQuery := r.db.Builder().Update(entity.TableName()) + insertList := entity.EntityToInsertValues(entity) + for i, f := range entity.InsertFields() { + updateQuery = updateQuery.Set(f, insertList[i]) + } + + query, args, err := updateQuery. + Where("? = ?", r.entity.TableName()+`.id`, id). + Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + ToSql() + + if err != nil { + return zero, err + } + + q := postgres.Query{Name: r.Name + ".Update", Raw: query} + row := r.db.DB().QueryRowContext(r.ctx, q, args...) + + return r.readItem(row) +} + +// HardDelete is a hard delete; But it expects that all children are deleted before +func (r *repository[T]) HardDelete(id string) (T, error) { + var zero T + + query, args, err := r.db.Builder(). + Delete(r.entity.TableName()). + Where("? = ?", r.entity.TableName()+`.id`, id). + Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + ToSql() + + if err != nil { + return zero, err + } + + q := postgres.Query{Name: r.Name + ".Delete", Raw: query} + row := r.db.DB().QueryRowContext(r.ctx, q, args...) + return r.readItem(row) +} + +// FindByKV ONLY FOR QUERY COMPOSITION; KEY WILL NOT BE ESCAPED/CHECKED SO IT'S UNSAFE +// e.g. you need a function FindByTitle(title string) ([]Book, error) -> FindByKV("title", title) +func (r *repository[T]) FindByKV(key string, value interface{}) ([]T, error) { + zero := make([]T, 0) + + query, args, err := r.db.Builder(). + Select(r.entity.AllFields()...). + From(r.entity.TableName()). + Where("? = ?", key, value). + Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + ToSql() + + if err != nil { + return zero, err + } + + q := postgres.Query{Name: r.Name + ".FindByKV", Raw: query} + rows, err := r.db.DB().QueryContext(r.ctx, q, args...) + if err != nil { + return zero, err + } + + return r.readList(rows) +} + +func (r *repository[T]) FindByID(id string) (T, error) { + var zero T + + query, args, err := r.db.Builder(). + Select(r.entity.AllFields()...). + From(r.entity.TableName()). + Where(r.entity.TableName()+".id = ?", id). + Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + ToSql() + + if err != nil { + return zero, err + } + + q := postgres.Query{Name: r.Name + ".FindByID", Raw: query} + row := r.db.DB().QueryRowContext(r.ctx, q, args...) + return r.readItem(row) +} + +func (r *repository[T]) readItem(row pgx.Row) (T, error) { + var zero T + res, err := r.entity.ReadItem(row) + if err != nil { + return zero, err + } + + result, ok := res.(T) + if !ok { + return zero, fmt.Errorf("ошибка приведения типа в %s.Update", r.Name) + } + + return result, nil +} + +func (r *repository[T]) readList(rows pgx.Rows) ([]T, error) { + zero := make([]T, 0) + res, err := r.entity.ReadList(rows) + if err != nil { + return zero, err + } + + result := make([]T, len(res)) + for i, r := range res { + result[i] = r.(T) + } + + return result, nil +} diff --git a/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go b/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go new file mode 100644 index 0000000..7a7b40c --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type BookEventsRepo interface { + Repository[entity.BookEvent] +} + +type bookEventsRepo struct { + repository[entity.BookEvent] +} + +func NewBookEventsRepository(db postgres.Client, ctx context.Context) BookEventsRepo { + return &bookEventsRepo{ + repository: repository[entity.BookEvent]{ + Name: "BookEventsRepository", + entity: entity.BookEvent{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/repository/pgrepo/bookrepo.go b/bookback/internal/domain/repository/pgrepo/bookrepo.go new file mode 100644 index 0000000..618d860 --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/bookrepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type BookRepo interface { + Repository[entity.Book] +} + +type bookRepo struct { + repository[entity.Book] +} + +func NewBookRepository(db postgres.Client, ctx context.Context) BookRepo { + return &bookRepo{ + repository: repository[entity.Book]{ + Name: "BookRepository", + entity: entity.Book{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/repository/pgrepo/chapterrepo.go b/bookback/internal/domain/repository/pgrepo/chapterrepo.go new file mode 100644 index 0000000..71a0315 --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/chapterrepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type ChapterRepo interface { + Repository[entity.Chapter] +} + +type chapterRepo struct { + repository[entity.Chapter] +} + +func NewChapterRepository(db postgres.Client, ctx context.Context) ChapterRepo { + return &chapterRepo{ + repository: repository[entity.Chapter]{ + Name: "ChapterRepository", + entity: entity.Chapter{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go b/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go new file mode 100644 index 0000000..76302c8 --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type MapVariablesRepo interface { + Repository[entity.MapVariable] +} + +type mapVariablesRepo struct { + repository[entity.MapVariable] +} + +func NewMapVariablesRepository(db postgres.Client, ctx context.Context) MapVariablesRepo { + return &mapVariablesRepo{ + repository: repository[entity.MapVariable]{ + Name: "MapVariablesRepository", + entity: entity.MapVariable{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/repository/pgrepo/pagerepo.go b/bookback/internal/domain/repository/pgrepo/pagerepo.go new file mode 100644 index 0000000..377d773 --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/pagerepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type PageRepo interface { + Repository[entity.Page] +} + +type pageRepo struct { + repository[entity.Page] +} + +func NewPageRepository(db postgres.Client, ctx context.Context) PageRepo { + return &pageRepo{ + repository: repository[entity.Page]{ + Name: "PageRepository", + entity: entity.Page{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/repository/pgrepo/paragraphrepo.go b/bookback/internal/domain/repository/pgrepo/paragraphrepo.go new file mode 100644 index 0000000..a3ce31e --- /dev/null +++ b/bookback/internal/domain/repository/pgrepo/paragraphrepo.go @@ -0,0 +1,26 @@ +package pgrepo + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" +) + +type ParagraphRepo interface { + Repository[entity.Paragraph] +} + +type paragraphRepo struct { + repository[entity.Paragraph] +} + +func NewParagraphRepository(db postgres.Client, ctx context.Context) ParagraphRepo { + return ¶graphRepo{ + repository: repository[entity.Paragraph]{ + Name: "ParagraphRepository", + entity: entity.Paragraph{}, + ctx: ctx, + db: db, + }, + } +} diff --git a/bookback/internal/domain/services/book/service.go b/bookback/internal/domain/services/book/service.go index 5127a91..f1ea2cc 100644 --- a/bookback/internal/domain/services/book/service.go +++ b/bookback/internal/domain/services/book/service.go @@ -2,7 +2,6 @@ package book import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/config" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) @@ -25,12 +24,15 @@ func NewService(repo Repo) Service { } func (s *service) CreateBook(ctx context.Context, book *entity.Book) (*entity.Book, error) { - var newBook *entity.Book + if book.Variables == nil { + book.Variables = []string{} + } id, err := s.repo.Create(ctx, book) if err != nil { return nil, err } + var newBook *entity.Book newBook, err = s.GetBookByID(ctx, id) if err != nil { return nil, err @@ -44,16 +46,10 @@ func (s *service) GetBookByID(ctx context.Context, id string) (*entity.Book, err } func (s *service) UpdateBook(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) { - if !s.isBookExisted(ctx, id) { - return nil, config.ErrorNotFound - } return s.repo.Update(ctx, id, book) } func (s *service) DeleteBook(ctx context.Context, id string) (*entity.Book, error) { - if !s.isBookExisted(ctx, id) { - return nil, config.ErrorNotFound - } return s.repo.Delete(ctx, id) } diff --git a/bookback/internal/config/config.go b/bookback/pkg/config/config.go similarity index 100% rename from bookback/internal/config/config.go rename to bookback/pkg/config/config.go diff --git a/bookback/internal/config/pg.go b/bookback/pkg/config/pg.go similarity index 100% rename from bookback/internal/config/pg.go rename to bookback/pkg/config/pg.go diff --git a/bookback/pkg/postgres/client.go b/bookback/pkg/postgres/client.go index 3767c74..df25a81 100644 --- a/bookback/pkg/postgres/client.go +++ b/bookback/pkg/postgres/client.go @@ -21,11 +21,11 @@ type pgClient struct { connTimeout time.Duration db DB - Builder squirrel.StatementBuilderType + builder squirrel.StatementBuilderType } func NewClient(ctx context.Context, logger *slog.Logger, dsn string) (Client, error) { - client := &pgClient{} + client := &pgClient{builder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} poolConfig, err := pgxpool.ParseConfig(dsn) if err != nil { @@ -54,6 +54,11 @@ func NewClient(ctx context.Context, logger *slog.Logger, dsn string) (Client, er func (c *pgClient) DB() DB { return c.db } + +func (c *pgClient) Builder() squirrel.StatementBuilderType { + return c.builder +} + func (c *pgClient) Close() error { if c.db != nil { c.db.Close() diff --git a/bookback/pkg/postgres/interface.go b/bookback/pkg/postgres/interface.go index 968e844..2024fdb 100644 --- a/bookback/pkg/postgres/interface.go +++ b/bookback/pkg/postgres/interface.go @@ -2,6 +2,7 @@ package postgres import ( "context" + "github.com/Masterminds/squirrel" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" ) @@ -10,6 +11,7 @@ type Handler func(ctx context.Context) error type Client interface { DB() DB + Builder() squirrel.StatementBuilderType Close() error } From 25e3f5986b789d06f919c6f380daf37d25747107 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Wed, 6 Mar 2024 23:56:40 +0300 Subject: [PATCH 7/9] fix: cleaning --- .../internal/application/usecase/.gitignore | 0 bookback/internal/bootstrap/app/closer.go | 5 +- bookback/internal/bootstrap/app/endpoint.go | 30 ++-- .../controller/http/v1/book/models.go | 25 ---- .../controller/http/v1/bookevents/models.go | 20 --- .../controller/http/v1/chapter/models.go | 21 --- .../controller/http/v1/mapvariables/models.go | 20 --- .../controller/http/v1/page/models.go | 21 --- .../controller/http/v1/paragraph/models.go | 21 --- bookback/internal/domain/entity/book.go | 2 +- .../repository/pgrepo/0-genericrepository.go | 129 ++++++++++-------- .../repository/pgrepo/bookeventsrepo.go | 4 +- .../domain/repository/pgrepo/bookrepo.go | 4 +- .../domain/repository/pgrepo/chapterrepo.go | 4 +- .../repository/pgrepo/mapvariablesrepo.go | 4 +- .../domain/repository/pgrepo/pagerepo.go | 4 +- .../domain/repository/pgrepo/paragraphrepo.go | 4 +- .../domain/services/book/interface.go | 4 +- .../internal/domain/services/book/service.go | 64 ++++++--- .../domain/services/bookevents/interface.go | 12 +- .../domain/services/bookevents/service.go | 38 ++++-- .../domain/services/chapter/interface.go | 6 +- .../domain/services/chapter/service.go | 23 +++- .../domain/services/mapvariables/interface.go | 7 +- .../domain/services/mapvariables/service.go | 43 +++--- .../domain/services/page/interface.go | 6 +- .../internal/domain/services/page/service.go | 23 +++- .../domain/services/paragraph/interface.go | 8 +- .../domain/services/paragraph/service.go | 22 ++- .../http/middleware}/circuitbreaker.go | 4 +- .../http/middleware}/cors.go | 2 +- .../http/middleware}/httplogger.go | 2 +- .../http/middleware}/middleware.go | 2 +- .../http/v1/book/config.go | 0 .../http/v1/book/controller.go | 23 ++-- .../http/v1/book/errors.go | 0 .../http/v1/book/routes.go | 11 +- .../http/v1/bookevents/config.go | 0 .../http/v1/bookevents/controller.go | 0 .../http/v1/bookevents/errors.go | 0 .../http/v1/bookevents/models.go | 22 +++ .../http/v1/bookevents/routes.go | 11 +- .../http/v1/chapter/config.go | 0 .../http/v1/chapter/controller.go | 7 +- .../http/v1/chapter/errors.go | 0 .../infrastructure/http/v1/chapter/models.go | 23 ++++ .../http/v1/chapter/routes.go | 13 +- .../http/v1/health/config.go | 0 .../http/v1/health/controller.go | 0 .../http/v1/health/routes.go | 4 +- .../http/v1/mapvariables/config.go | 0 .../http/v1/mapvariables/controller.go | 0 .../http/v1/mapvariables/errors.go | 0 .../http/v1/mapvariables/models.go | 20 +++ .../http/v1/mapvariables/routes.go | 11 +- .../http/v1/page/config.go | 0 .../http/v1/page/controller.go | 6 +- .../http/v1/page/errors.go | 0 .../infrastructure/http/v1/page/models.go | 23 ++++ .../http/v1/page/routes.go | 11 +- .../http/v1/paragraph/config.go | 0 .../http/v1/paragraph/controller.go | 7 +- .../http/v1/paragraph/errors.go | 0 .../http/v1/paragraph/models.go | 23 ++++ .../http/v1/paragraph/routes.go | 11 +- .../http/v1/swagger/controller.go | 0 bookback/internal/models/book_model.go | 34 +++++ bookback/internal/models/converter/book.go | 52 +++++++ bookback/internal/models/model.go | 25 ++++ bookback/internal/models/types/null.go | 31 +++++ .../circuitbreaker/circuitbreaker.go | 0 .../circuitbreaker/circuitbreaker_test.go | 0 bookback/pkg/postgres/interface.go | 2 + 73 files changed, 592 insertions(+), 362 deletions(-) create mode 100644 bookback/internal/application/usecase/.gitignore delete mode 100644 bookback/internal/controller/http/v1/book/models.go delete mode 100644 bookback/internal/controller/http/v1/bookevents/models.go delete mode 100644 bookback/internal/controller/http/v1/chapter/models.go delete mode 100644 bookback/internal/controller/http/v1/mapvariables/models.go delete mode 100644 bookback/internal/controller/http/v1/page/models.go delete mode 100644 bookback/internal/controller/http/v1/paragraph/models.go rename bookback/internal/{controller/http/httpmiddlewares => infrastructure/http/middleware}/circuitbreaker.go (84%) rename bookback/internal/{controller/http/httpmiddlewares => infrastructure/http/middleware}/cors.go (96%) rename bookback/internal/{controller/http/httpmiddlewares => infrastructure/http/middleware}/httplogger.go (97%) rename bookback/internal/{controller/http/httpmiddlewares => infrastructure/http/middleware}/middleware.go (96%) rename bookback/internal/{controller => infrastructure}/http/v1/book/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/book/controller.go (81%) rename bookback/internal/{controller => infrastructure}/http/v1/book/errors.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/book/routes.go (70%) rename bookback/internal/{controller => infrastructure}/http/v1/bookevents/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/bookevents/controller.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/bookevents/errors.go (100%) create mode 100644 bookback/internal/infrastructure/http/v1/bookevents/models.go rename bookback/internal/{controller => infrastructure}/http/v1/bookevents/routes.go (70%) rename bookback/internal/{controller => infrastructure}/http/v1/chapter/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/chapter/controller.go (96%) rename bookback/internal/{controller => infrastructure}/http/v1/chapter/errors.go (100%) create mode 100644 bookback/internal/infrastructure/http/v1/chapter/models.go rename bookback/internal/{controller => infrastructure}/http/v1/chapter/routes.go (61%) rename bookback/internal/{controller => infrastructure}/http/v1/health/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/health/controller.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/health/routes.go (79%) rename bookback/internal/{controller => infrastructure}/http/v1/mapvariables/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/mapvariables/controller.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/mapvariables/errors.go (100%) create mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/models.go rename bookback/internal/{controller => infrastructure}/http/v1/mapvariables/routes.go (70%) rename bookback/internal/{controller => infrastructure}/http/v1/page/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/page/controller.go (96%) rename bookback/internal/{controller => infrastructure}/http/v1/page/errors.go (100%) create mode 100644 bookback/internal/infrastructure/http/v1/page/models.go rename bookback/internal/{controller => infrastructure}/http/v1/page/routes.go (66%) rename bookback/internal/{controller => infrastructure}/http/v1/paragraph/config.go (100%) rename bookback/internal/{controller => infrastructure}/http/v1/paragraph/controller.go (96%) rename bookback/internal/{controller => infrastructure}/http/v1/paragraph/errors.go (100%) create mode 100644 bookback/internal/infrastructure/http/v1/paragraph/models.go rename bookback/internal/{controller => infrastructure}/http/v1/paragraph/routes.go (66%) rename bookback/internal/{controller => infrastructure}/http/v1/swagger/controller.go (100%) create mode 100644 bookback/internal/models/book_model.go create mode 100644 bookback/internal/models/converter/book.go create mode 100644 bookback/internal/models/model.go create mode 100644 bookback/internal/models/types/null.go rename bookback/{internal/controller/http => pkg}/circuitbreaker/circuitbreaker.go (100%) rename bookback/{internal/controller/http => pkg}/circuitbreaker/circuitbreaker_test.go (100%) diff --git a/bookback/internal/application/usecase/.gitignore b/bookback/internal/application/usecase/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/bookback/internal/bootstrap/app/closer.go b/bookback/internal/bootstrap/app/closer.go index b1b5de9..371455b 100644 --- a/bookback/internal/bootstrap/app/closer.go +++ b/bookback/internal/bootstrap/app/closer.go @@ -8,10 +8,11 @@ import ( func (app *App) closer(ctx context.Context) error { <-ctx.Done() - ctx, cancel := context.WithTimeout(context.Background(), app.config.ShutdownTimeout) + + shutdownCtx, cancel := context.WithTimeout(context.Background(), app.config.ShutdownTimeout) app.logger.Log(context.Background(), slog.LevelInfo, "Shutting down controller") defer cancel() - if err := app.Echo.Shutdown(ctx); err != nil { + if err := app.Echo.Shutdown(shutdownCtx); err != nil { return errors.New("failed to shutdown controller: " + err.Error()) } diff --git a/bookback/internal/bootstrap/app/endpoint.go b/bookback/internal/bootstrap/app/endpoint.go index a227e2f..d790541 100644 --- a/bookback/internal/bootstrap/app/endpoint.go +++ b/bookback/internal/bootstrap/app/endpoint.go @@ -2,20 +2,20 @@ package app import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/circuitbreaker" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/book" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/chapter" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/health" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/page" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/paragraph" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/v1/swagger" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/book" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/chapter" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/health" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/mapvariables" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/page" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/paragraph" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/swagger" "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/circuitbreaker" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" + echomv "github.com/labstack/echo/v4/middleware" "log/slog" "net/http" "sync" @@ -53,13 +53,13 @@ func (app *App) initEndpoint(_ context.Context) error { ) middlewares := []echo.MiddlewareFunc{ - httpmiddlewares.LoggerConfiguration(app.logger), - middleware.Recover(), - httpmiddlewares.CreateCircuitBreakerMiddleware(cb), + middleware.LoggerConfiguration(app.logger), + echomv.Recover(), + middleware.CreateCircuitBreakerMiddleware(cb), } if app.config.CorsEnabled { - middlewares = append(middlewares, httpmiddlewares.CORS()) + middlewares = append(middlewares, middleware.CORS()) } e.Use(middlewares...) diff --git a/bookback/internal/controller/http/v1/book/models.go b/bookback/internal/controller/http/v1/book/models.go deleted file mode 100644 index 30f7c4e..0000000 --- a/bookback/internal/controller/http/v1/book/models.go +++ /dev/null @@ -1,25 +0,0 @@ -package book - -import ( - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Options struct { - Limit int `json:"limit"` - Offset int `json:"offset"` -} - -type requestModel struct { - Options Options `json:"options"` - Book *entity.Book `json:"book"` -} - -type responseSingleModel struct { - Book *entity.Book `json:"book"` - Status string `json:"status"` -} - -type responseListModel struct { - Books []entity.Book `json:"books"` - Status string `json:"status"` -} diff --git a/bookback/internal/controller/http/v1/bookevents/models.go b/bookback/internal/controller/http/v1/bookevents/models.go deleted file mode 100644 index 4bb9ab4..0000000 --- a/bookback/internal/controller/http/v1/bookevents/models.go +++ /dev/null @@ -1,20 +0,0 @@ -package bookevents - -import "github.com/SShlykov/zeitment/bookback/internal/models" - -type Options struct{} - -type requestModel struct { - Options Options `json:"options"` - BookEvents *models.BookEvent `json:"book_events"` -} - -type responseSingleModel struct { - BookEvent *models.BookEvent `json:"book_event"` - Status string `json:"status"` -} - -type responseListModel struct { - BookEvents []models.BookEvent `json:"book_events"` - Status string `json:"status"` -} diff --git a/bookback/internal/controller/http/v1/chapter/models.go b/bookback/internal/controller/http/v1/chapter/models.go deleted file mode 100644 index cebb62c..0000000 --- a/bookback/internal/controller/http/v1/chapter/models.go +++ /dev/null @@ -1,21 +0,0 @@ -package chapter - -import "github.com/SShlykov/zeitment/bookback/internal/models" - -type Options struct { -} - -type requestModel struct { - Options Options `json:"options"` - Chapter *models.Chapter `json:"chapter"` -} - -type responseSingleModel struct { - Chapter *models.Chapter `json:"chapter"` - Status string `json:"status"` -} - -type responseListModel struct { - Chapters []models.Chapter `json:"chapters"` - Status string `json:"status"` -} diff --git a/bookback/internal/controller/http/v1/mapvariables/models.go b/bookback/internal/controller/http/v1/mapvariables/models.go deleted file mode 100644 index 07d0511..0000000 --- a/bookback/internal/controller/http/v1/mapvariables/models.go +++ /dev/null @@ -1,20 +0,0 @@ -package mapvariables - -import "github.com/SShlykov/zeitment/bookback/internal/models" - -type Options struct{} - -type requestModel struct { - Options Options `json:"options"` - MapVariables *models.MapVariable `json:"map_variables"` -} - -type responseSingleModel struct { - MapVariable *models.MapVariable `json:"map_variable"` - Status string `json:"status"` -} - -type responseListModel struct { - MapVariables []models.MapVariable `json:"map_variables"` - Status string `json:"status"` -} diff --git a/bookback/internal/controller/http/v1/page/models.go b/bookback/internal/controller/http/v1/page/models.go deleted file mode 100644 index 75b06d4..0000000 --- a/bookback/internal/controller/http/v1/page/models.go +++ /dev/null @@ -1,21 +0,0 @@ -package page - -import "github.com/SShlykov/zeitment/bookback/internal/models" - -type Options struct { -} - -type requestModel struct { - Options Options `json:"options"` - Page *models.Page `json:"page"` -} - -type responseSingleModel struct { - Page *models.Page `json:"page"` - Status string `json:"status"` -} - -type responseListModel struct { - Pages []models.Page `json:"pages"` - Status string `json:"status"` -} diff --git a/bookback/internal/controller/http/v1/paragraph/models.go b/bookback/internal/controller/http/v1/paragraph/models.go deleted file mode 100644 index 6c57dd2..0000000 --- a/bookback/internal/controller/http/v1/paragraph/models.go +++ /dev/null @@ -1,21 +0,0 @@ -package paragraph - -import "github.com/SShlykov/zeitment/bookback/internal/models" - -type Options struct { -} - -type requestModel struct { - Options Options `json:"options"` - Paragraph *models.Paragraph `json:"paragraph"` -} - -type responseSingleModel struct { - Paragraph *models.Paragraph `json:"paragraph"` - Status string `json:"status"` -} - -type responseListModel struct { - Paragraphs []models.Paragraph `json:"paragraphs"` - Status string `json:"status"` -} diff --git a/bookback/internal/domain/entity/book.go b/bookback/internal/domain/entity/book.go index 950b49d..1d0e5fd 100644 --- a/bookback/internal/domain/entity/book.go +++ b/bookback/internal/domain/entity/book.go @@ -10,7 +10,7 @@ type Book struct { ID string CreatedAt time.Time UpdatedAt time.Time - DeletedAt sql.NullTime // Обратите внимание на правильное использование типа NullTime + DeletedAt sql.NullTime Owner string Title string Author string diff --git a/bookback/internal/domain/repository/pgrepo/0-genericrepository.go b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go index e559e07..20d2a91 100644 --- a/bookback/internal/domain/repository/pgrepo/0-genericrepository.go +++ b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go @@ -17,25 +17,36 @@ type Simulated interface { ReadList(rows pgx.Rows) ([]any, error) } -type Repository[T Simulated] interface { - Create(item T) (string, error) - Update(id string, item T) (T, error) - HardDelete(id string) (T, error) +// Repository определяет обобщенный интерфейс для операций CRUD над сущностями типа T. +// T представляет тип сущности, который должен быть структурой. +type Repository[T any] interface { + // List извлекает срез сущностей на основе лимита и смещения для пагинации. + List(ctx context.Context, limit uint64, offset uint64) ([]*T, error) - FindByID(id string) (T, error) - FindByKV(key string, value interface{}) ([]T, error) + // Create вставляет новую сущность в репозиторий и возвращает её ID или ошибку. + Create(ctx context.Context, item *T) (string, error) - List(ctx context.Context, limit uint64, offset uint64) ([]T, error) + // Update изменяет существующую сущность, идентифицированную по ID. Возвращает обновленную сущность или ошибку. + Update(ctx context.Context, id string, item *T) (*T, error) + + // HardDelete удаляет сущность, идентифицированную по ID, из репозитория. Может возвращать ошибку, если удаление не + // удалось или сущность не найдена. + HardDelete(ctx context.Context, id string) error + + // FindByID извлекает сущность по её ID. Возвращает указатель на сущность или ошибку. + FindByID(ctx context.Context, id string) (*T, error) + + // FindByKV ищет сущности, соответствующие определенной паре ключ-значение. + FindByKV(ctx context.Context, key string, value any) ([]*T, error) } type repository[T Simulated] struct { Name string entity T db postgres.Client - ctx context.Context } -func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ([]T, error) { +func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ([]*T, error) { query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). @@ -49,16 +60,21 @@ func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ( } q := postgres.Query{Name: r.Name + ".List", Raw: query} - rows, err := r.db.DB().QueryContext(ctx, q, args...) + + var rows pgx.Rows + rows, err = r.db.DB().QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } return r.readList(rows) } -func (r *repository[T]) Create(entity T) (string, error) { +func (r *repository[T]) Create(ctx context.Context, item *T) (string, error) { query, args, err := r.db.Builder(). - Insert(entity.TableName()). - Columns(entity.InsertFields()...). - Values(entity.EntityToInsertValues(entity)...). + Insert(r.entity.TableName()). + Columns(r.entity.InsertFields()...). + Values(r.entity.EntityToInsertValues(&item)...). Prefix("RETURNING id"). ToSql() @@ -69,19 +85,17 @@ func (r *repository[T]) Create(entity T) (string, error) { q := postgres.Query{Name: r.Name + ".Insert", Raw: query} var id string - if err = r.db.DB().QueryRowContext(r.ctx, q, args...).Scan(&id); err != nil { + if err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { return "", err } return id, nil } -func (r *repository[T]) Update(id string, entity T) (T, error) { - var zero T - - updateQuery := r.db.Builder().Update(entity.TableName()) - insertList := entity.EntityToInsertValues(entity) - for i, f := range entity.InsertFields() { +func (r *repository[T]) Update(ctx context.Context, id string, item *T) (*T, error) { + updateQuery := r.db.Builder().Update(r.entity.TableName()) + insertList := r.entity.EntityToInsertValues(item) + for i, f := range r.entity.InsertFields() { updateQuery = updateQuery.Set(f, insertList[i]) } @@ -91,39 +105,37 @@ func (r *repository[T]) Update(id string, entity T) (T, error) { ToSql() if err != nil { - return zero, err + return nil, err } q := postgres.Query{Name: r.Name + ".Update", Raw: query} - row := r.db.DB().QueryRowContext(r.ctx, q, args...) + row := r.db.DB().QueryRowContext(ctx, q, args...) return r.readItem(row) } // HardDelete is a hard delete; But it expects that all children are deleted before -func (r *repository[T]) HardDelete(id string) (T, error) { - var zero T - +func (r *repository[T]) HardDelete(ctx context.Context, id string) error { query, args, err := r.db.Builder(). Delete(r.entity.TableName()). Where("? = ?", r.entity.TableName()+`.id`, id). - Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + Suffix("RETURNING id"). ToSql() if err != nil { - return zero, err + return err } q := postgres.Query{Name: r.Name + ".Delete", Raw: query} - row := r.db.DB().QueryRowContext(r.ctx, q, args...) - return r.readItem(row) + if err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id); err != nil { + return err + } + return nil } // FindByKV ONLY FOR QUERY COMPOSITION; KEY WILL NOT BE ESCAPED/CHECKED SO IT'S UNSAFE // e.g. you need a function FindByTitle(title string) ([]Book, error) -> FindByKV("title", title) -func (r *repository[T]) FindByKV(key string, value interface{}) ([]T, error) { - zero := make([]T, 0) - +func (r *repository[T]) FindByKV(ctx context.Context, key string, value interface{}) ([]*T, error) { query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). @@ -132,21 +144,21 @@ func (r *repository[T]) FindByKV(key string, value interface{}) ([]T, error) { ToSql() if err != nil { - return zero, err + return nil, err } q := postgres.Query{Name: r.Name + ".FindByKV", Raw: query} - rows, err := r.db.DB().QueryContext(r.ctx, q, args...) + + var rows pgx.Rows + rows, err = r.db.DB().QueryContext(ctx, q, args...) if err != nil { - return zero, err + return nil, err } return r.readList(rows) } -func (r *repository[T]) FindByID(id string) (T, error) { - var zero T - +func (r *repository[T]) FindByID(ctx context.Context, id string) (*T, error) { query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). @@ -155,39 +167,48 @@ func (r *repository[T]) FindByID(id string) (T, error) { ToSql() if err != nil { - return zero, err + return nil, err } q := postgres.Query{Name: r.Name + ".FindByID", Raw: query} - row := r.db.DB().QueryRowContext(r.ctx, q, args...) + row := r.db.DB().QueryRowContext(ctx, q, args...) return r.readItem(row) } -func (r *repository[T]) readItem(row pgx.Row) (T, error) { - var zero T +func (r *repository[T]) readItem(row pgx.Row) (*T, error) { res, err := r.entity.ReadItem(row) if err != nil { - return zero, err + return nil, err } - result, ok := res.(T) + result, ok := res.(*T) if !ok { - return zero, fmt.Errorf("ошибка приведения типа в %s.Update", r.Name) + return nil, fmt.Errorf("ошибка приведения типа в %s.Update", r.Name) } return result, nil } -func (r *repository[T]) readList(rows pgx.Rows) ([]T, error) { - zero := make([]T, 0) - res, err := r.entity.ReadList(rows) - if err != nil { - return zero, err +func (r *repository[T]) readList(rows pgx.Rows) ([]*T, error) { + var result []*T + defer rows.Close() + + for rows.Next() { + item, err := r.entity.ReadItem(rows) + if err != nil { + return nil, err + } + + typedItem, ok := item.(*T) + if !ok { + return nil, fmt.Errorf("не удалось привести тип элемента в readList") + } + + result = append(result, typedItem) } - result := make([]T, len(res)) - for i, r := range res { - result[i] = r.(T) + if err := rows.Err(); err != nil { + return nil, err } return result, nil diff --git a/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go b/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go index 7a7b40c..cc1ff5f 100644 --- a/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go +++ b/bookback/internal/domain/repository/pgrepo/bookeventsrepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type bookEventsRepo struct { repository[entity.BookEvent] } -func NewBookEventsRepository(db postgres.Client, ctx context.Context) BookEventsRepo { +func NewBookEventsRepository(db postgres.Client) BookEventsRepo { return &bookEventsRepo{ repository: repository[entity.BookEvent]{ Name: "BookEventsRepository", entity: entity.BookEvent{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/repository/pgrepo/bookrepo.go b/bookback/internal/domain/repository/pgrepo/bookrepo.go index 618d860..614ebb5 100644 --- a/bookback/internal/domain/repository/pgrepo/bookrepo.go +++ b/bookback/internal/domain/repository/pgrepo/bookrepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type bookRepo struct { repository[entity.Book] } -func NewBookRepository(db postgres.Client, ctx context.Context) BookRepo { +func NewBookRepository(db postgres.Client) BookRepo { return &bookRepo{ repository: repository[entity.Book]{ Name: "BookRepository", entity: entity.Book{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/repository/pgrepo/chapterrepo.go b/bookback/internal/domain/repository/pgrepo/chapterrepo.go index 71a0315..23d15bd 100644 --- a/bookback/internal/domain/repository/pgrepo/chapterrepo.go +++ b/bookback/internal/domain/repository/pgrepo/chapterrepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type chapterRepo struct { repository[entity.Chapter] } -func NewChapterRepository(db postgres.Client, ctx context.Context) ChapterRepo { +func NewChapterRepository(db postgres.Client) ChapterRepo { return &chapterRepo{ repository: repository[entity.Chapter]{ Name: "ChapterRepository", entity: entity.Chapter{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go b/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go index 76302c8..9e9bae6 100644 --- a/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go +++ b/bookback/internal/domain/repository/pgrepo/mapvariablesrepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type mapVariablesRepo struct { repository[entity.MapVariable] } -func NewMapVariablesRepository(db postgres.Client, ctx context.Context) MapVariablesRepo { +func NewMapVariablesRepository(db postgres.Client) MapVariablesRepo { return &mapVariablesRepo{ repository: repository[entity.MapVariable]{ Name: "MapVariablesRepository", entity: entity.MapVariable{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/repository/pgrepo/pagerepo.go b/bookback/internal/domain/repository/pgrepo/pagerepo.go index 377d773..3426cac 100644 --- a/bookback/internal/domain/repository/pgrepo/pagerepo.go +++ b/bookback/internal/domain/repository/pgrepo/pagerepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type pageRepo struct { repository[entity.Page] } -func NewPageRepository(db postgres.Client, ctx context.Context) PageRepo { +func NewPageRepository(db postgres.Client) PageRepo { return &pageRepo{ repository: repository[entity.Page]{ Name: "PageRepository", entity: entity.Page{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/repository/pgrepo/paragraphrepo.go b/bookback/internal/domain/repository/pgrepo/paragraphrepo.go index a3ce31e..e80f84d 100644 --- a/bookback/internal/domain/repository/pgrepo/paragraphrepo.go +++ b/bookback/internal/domain/repository/pgrepo/paragraphrepo.go @@ -1,7 +1,6 @@ package pgrepo import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/pkg/postgres" ) @@ -14,12 +13,11 @@ type paragraphRepo struct { repository[entity.Paragraph] } -func NewParagraphRepository(db postgres.Client, ctx context.Context) ParagraphRepo { +func NewParagraphRepository(db postgres.Client) ParagraphRepo { return ¶graphRepo{ repository: repository[entity.Paragraph]{ Name: "ParagraphRepository", entity: entity.Paragraph{}, - ctx: ctx, db: db, }, } diff --git a/bookback/internal/domain/services/book/interface.go b/bookback/internal/domain/services/book/interface.go index 59a4f53..0e58f57 100644 --- a/bookback/internal/domain/services/book/interface.go +++ b/bookback/internal/domain/services/book/interface.go @@ -9,6 +9,6 @@ type Repo interface { Create(ctx context.Context, book *entity.Book) (string, error) FindByID(ctx context.Context, id string) (*entity.Book, error) Update(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) - Delete(ctx context.Context, id string) (*entity.Book, error) - List(ctx context.Context) ([]entity.Book, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Book, error) } diff --git a/bookback/internal/domain/services/book/service.go b/bookback/internal/domain/services/book/service.go index f1ea2cc..77fb2ba 100644 --- a/bookback/internal/domain/services/book/service.go +++ b/bookback/internal/domain/services/book/service.go @@ -3,15 +3,17 @@ package book import ( "context" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" ) // Service описывает сервис для работы с книгами. type Service interface { - CreateBook(ctx context.Context, book *entity.Book) (*entity.Book, error) - GetBookByID(ctx context.Context, id string) (*entity.Book, error) - UpdateBook(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) - DeleteBook(ctx context.Context, id string) (*entity.Book, error) - ListBooks(ctx context.Context) ([]entity.Book, error) + CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) + GetBookByID(ctx context.Context, id string) (*models.Book, error) + UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) + DeleteBook(ctx context.Context, id string) (*models.Book, error) + ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) } type service struct { @@ -23,7 +25,9 @@ func NewService(repo Repo) Service { return &service{repo} } -func (s *service) CreateBook(ctx context.Context, book *entity.Book) (*entity.Book, error) { +func (s *service) CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) { + book := converter.BookModelToEntity(request.Book) + if book.Variables == nil { book.Variables = []string{} } @@ -33,31 +37,51 @@ func (s *service) CreateBook(ctx context.Context, book *entity.Book) (*entity.Bo } var newBook *entity.Book - newBook, err = s.GetBookByID(ctx, id) + newBook, err = s.repo.FindByID(ctx, id) if err != nil { return nil, err } - return newBook, err + return converter.BookEntityToModel(newBook), err } -func (s *service) GetBookByID(ctx context.Context, id string) (*entity.Book, error) { - return s.repo.FindByID(ctx, id) -} +func (s *service) GetBookByID(ctx context.Context, id string) (*models.Book, error) { + book, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } -func (s *service) UpdateBook(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) { - return s.repo.Update(ctx, id, book) + return converter.BookEntityToModel(book), nil } -func (s *service) DeleteBook(ctx context.Context, id string) (*entity.Book, error) { - return s.repo.Delete(ctx, id) +func (s *service) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) { + book, err := s.repo.Update(ctx, id, converter.BookModelToEntity(request.Book)) + if err != nil { + return nil, err + } + + return converter.BookEntityToModel(book), nil } -func (s *service) ListBooks(ctx context.Context) ([]entity.Book, error) { - return s.repo.List(ctx) +func (s *service) DeleteBook(ctx context.Context, id string) (*models.Book, error) { + book, err := s.GetBookByID(ctx, id) + if err != nil { + return nil, err + } + + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + + return book, err } -func (s *service) isBookExisted(ctx context.Context, id string) bool { - _, err := s.GetBookByID(ctx, id) - return err == nil +func (s *service) ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) { + books, err := s.repo.List(ctx, limit, offset) + if err != nil { + return nil, err + } + + return converter.BooksEntityToModel(books), nil } diff --git a/bookback/internal/domain/services/bookevents/interface.go b/bookback/internal/domain/services/bookevents/interface.go index 3b11390..e75c723 100644 --- a/bookback/internal/domain/services/bookevents/interface.go +++ b/bookback/internal/domain/services/bookevents/interface.go @@ -6,12 +6,10 @@ import ( ) type Repository interface { - Create(ctx context.Context, event *entity.BookEvent) (string, error) + Create(ctx context.Context, chapter *entity.BookEvent) (string, error) FindByID(ctx context.Context, id string) (*entity.BookEvent, error) - Update(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) - Delete(ctx context.Context, id string) (*entity.BookEvent, error) - GetByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) - GetByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) - GetByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) - GetByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) + Update(ctx context.Context, id string, chapter *entity.BookEvent) (*entity.BookEvent, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.BookEvent, error) + FindByKV(ctx context.Context, key string, value any) ([]*entity.BookEvent, error) } diff --git a/bookback/internal/domain/services/bookevents/service.go b/bookback/internal/domain/services/bookevents/service.go index 83bfbaf..80c027c 100644 --- a/bookback/internal/domain/services/bookevents/service.go +++ b/bookback/internal/domain/services/bookevents/service.go @@ -2,6 +2,7 @@ package bookevents import ( "context" + "errors" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) @@ -10,10 +11,11 @@ type Service interface { GetBookEventByID(ctx context.Context, id string) (*entity.BookEvent, error) UpdateBookEvent(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) - GetBookEventsByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) - GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) - GetBookEventsByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) - GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) + + GetBookEventsByBookID(ctx context.Context, bookID string) ([]*entity.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*entity.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string) ([]*entity.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*entity.BookEvent, error) } type service struct { @@ -43,21 +45,31 @@ func (s *service) UpdateBookEvent(ctx context.Context, id string, event *entity. } func (s *service) DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) { - return s.repo.Delete(ctx, id) + bookEvent, err := s.GetBookEventByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("BookEvent not found"), err) + } + + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + + return bookEvent, err } -func (s *service) GetBookEventsByBookID(ctx context.Context, bookID string) ([]entity.BookEvent, error) { - return s.repo.GetByBookID(ctx, bookID) +func (s *service) GetBookEventsByBookID(ctx context.Context, bookID string) ([]*entity.BookEvent, error) { + return s.repo.FindByKV(ctx, "book_id", bookID) } -func (s *service) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]entity.BookEvent, error) { - return s.repo.GetByChapterID(ctx, chapterID) +func (s *service) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*entity.BookEvent, error) { + return s.repo.FindByKV(ctx, "chapter_id", chapterID) } -func (s *service) GetBookEventsByPageID(ctx context.Context, pageID string) ([]entity.BookEvent, error) { - return s.repo.GetByPageID(ctx, pageID) +func (s *service) GetBookEventsByPageID(ctx context.Context, pageID string) ([]*entity.BookEvent, error) { + return s.repo.FindByKV(ctx, "page_id", pageID) } -func (s *service) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]entity.BookEvent, error) { - return s.repo.GetByParagraphID(ctx, paragraphID) +func (s *service) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*entity.BookEvent, error) { + return s.repo.FindByKV(ctx, "paragraph_id", paragraphID) } diff --git a/bookback/internal/domain/services/chapter/interface.go b/bookback/internal/domain/services/chapter/interface.go index 7631617..93a92fb 100644 --- a/bookback/internal/domain/services/chapter/interface.go +++ b/bookback/internal/domain/services/chapter/interface.go @@ -9,7 +9,7 @@ type Repository interface { Create(ctx context.Context, chapter *entity.Chapter) (string, error) FindByID(ctx context.Context, id string) (*entity.Chapter, error) Update(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) - Delete(ctx context.Context, id string) (*entity.Chapter, error) - List(ctx context.Context) ([]entity.Chapter, error) - GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) + FindByKV(ctx context.Context, key string, value any) ([]*entity.Chapter, error) } diff --git a/bookback/internal/domain/services/chapter/service.go b/bookback/internal/domain/services/chapter/service.go index 752096d..b3f8b88 100644 --- a/bookback/internal/domain/services/chapter/service.go +++ b/bookback/internal/domain/services/chapter/service.go @@ -2,6 +2,7 @@ package chapter import ( "context" + "errors" "fmt" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) @@ -11,9 +12,9 @@ type Service interface { GetChapterByID(ctx context.Context, id string) (*entity.Chapter, error) UpdateChapter(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) - ListChapters(ctx context.Context) ([]entity.Chapter, error) + ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) - GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) + GetChapterByBookID(ctx context.Context, bookID string) ([]*entity.Chapter, error) } type service struct { @@ -48,13 +49,21 @@ func (ch *service) UpdateChapter(ctx context.Context, id string, chapter *entity } func (ch *service) DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) { - return ch.chapterRepo.Delete(ctx, id) + chapter, err := ch.GetChapterByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("chapter not found"), err) + } + err = ch.chapterRepo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return chapter, err } -func (ch *service) ListChapters(ctx context.Context) ([]entity.Chapter, error) { - return ch.chapterRepo.List(ctx) +func (ch *service) ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) { + return ch.chapterRepo.List(ctx, limit, offset) } -func (ch *service) GetChapterByBookID(ctx context.Context, bookID string) ([]entity.Chapter, error) { - return ch.chapterRepo.GetChapterByBookID(ctx, bookID) +func (ch *service) GetChapterByBookID(ctx context.Context, bookID string) ([]*entity.Chapter, error) { + return ch.chapterRepo.FindByKV(ctx, "book_id", bookID) } diff --git a/bookback/internal/domain/services/mapvariables/interface.go b/bookback/internal/domain/services/mapvariables/interface.go index c11d1ed..57dec97 100644 --- a/bookback/internal/domain/services/mapvariables/interface.go +++ b/bookback/internal/domain/services/mapvariables/interface.go @@ -6,5 +6,10 @@ import ( ) type Repository interface { - Create(ctx context.Context, event *entity.BookEvent) (string, error) + Create(ctx context.Context, chapter *entity.MapVariable) (string, error) + FindByID(ctx context.Context, id string) (*entity.MapVariable, error) + Update(ctx context.Context, id string, chapter *entity.MapVariable) (*entity.MapVariable, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.MapVariable, error) + FindByKV(ctx context.Context, key string, value any) ([]*entity.MapVariable, error) } diff --git a/bookback/internal/domain/services/mapvariables/service.go b/bookback/internal/domain/services/mapvariables/service.go index d8207b5..1db9de0 100644 --- a/bookback/internal/domain/services/mapvariables/service.go +++ b/bookback/internal/domain/services/mapvariables/service.go @@ -2,7 +2,7 @@ package mapvariables import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/mapvarrepo" + "errors" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) @@ -11,17 +11,18 @@ type Service interface { GetMapVariableByID(ctx context.Context, id string) (*entity.MapVariable, error) UpdateMapVariable(ctx context.Context, id string, variable *entity.MapVariable) (*entity.MapVariable, error) DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) - GetMapVariablesByBookID(ctx context.Context, mapID string) ([]entity.MapVariable, error) - GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]entity.MapVariable, error) - GetMapVariablesByPageID(ctx context.Context, pageID string) ([]entity.MapVariable, error) - GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]entity.MapVariable, error) + + GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*entity.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*entity.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*entity.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*entity.MapVariable, error) } type service struct { - repo mapvarrepo.Repository + repo Repository } -func NewService(repo mapvarrepo.Repository) Service { +func NewService(repo Repository) Service { return &service{repo} } @@ -44,21 +45,31 @@ func (s *service) UpdateMapVariable(ctx context.Context, id string, variable *en } func (s *service) DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) { - return s.repo.Delete(ctx, id) + mapVariable, err := s.GetMapVariableByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("MapVariable not found"), err) + } + + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + + return mapVariable, err } -func (s *service) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]entity.MapVariable, error) { - return s.repo.GetByBookID(ctx, mapID) +func (s *service) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*entity.MapVariable, error) { + return s.repo.FindByKV(ctx, "map_id", mapID) } -func (s *service) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]entity.MapVariable, error) { - return s.repo.GetByChapterID(ctx, chapterID) +func (s *service) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*entity.MapVariable, error) { + return s.repo.FindByKV(ctx, "chapter_id", chapterID) } -func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]entity.MapVariable, error) { - return s.repo.GetByPageID(ctx, pageID) +func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*entity.MapVariable, error) { + return s.repo.FindByKV(ctx, "page_id", pageID) } -func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]entity.MapVariable, error) { - return s.repo.GetByParagraphID(ctx, paragraphID) +func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*entity.MapVariable, error) { + return s.repo.FindByKV(ctx, "paragraph_id", paragraphID) } diff --git a/bookback/internal/domain/services/page/interface.go b/bookback/internal/domain/services/page/interface.go index e1e50c6..151a1f9 100644 --- a/bookback/internal/domain/services/page/interface.go +++ b/bookback/internal/domain/services/page/interface.go @@ -9,7 +9,7 @@ type Repository interface { Create(ctx context.Context, page *entity.Page) (string, error) FindByID(ctx context.Context, id string) (*entity.Page, error) Update(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) - Delete(ctx context.Context, id string) (*entity.Page, error) - List(ctx context.Context) ([]entity.Page, error) - GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) + FindByKV(ctx context.Context, key string, value any) ([]*entity.Page, error) } diff --git a/bookback/internal/domain/services/page/service.go b/bookback/internal/domain/services/page/service.go index 133e188..4d50d10 100644 --- a/bookback/internal/domain/services/page/service.go +++ b/bookback/internal/domain/services/page/service.go @@ -2,6 +2,7 @@ package page import ( "context" + "errors" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" ) @@ -10,9 +11,9 @@ type Service interface { GetPageByID(ctx context.Context, id string) (*entity.Page, error) UpdatePage(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) DeletePage(ctx context.Context, id string) (*entity.Page, error) - ListPages(ctx context.Context) ([]entity.Page, error) + ListPages(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) - GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) + GetPagesByChapterID(ctx context.Context, chapterID string) ([]*entity.Page, error) } type service struct { @@ -42,13 +43,21 @@ func (s *service) UpdatePage(ctx context.Context, id string, page *entity.Page) } func (s *service) DeletePage(ctx context.Context, id string) (*entity.Page, error) { - return s.repo.Delete(ctx, id) + page, err := s.GetPageByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("page not found"), err) + } + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return page, err } -func (s *service) ListPages(ctx context.Context) ([]entity.Page, error) { - return s.repo.List(ctx) +func (s *service) ListPages(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) { + return s.repo.List(ctx, limit, offset) } -func (s *service) GetPagesByChapterID(ctx context.Context, chapterID string) ([]entity.Page, error) { - return s.repo.GetPagesByChapterID(ctx, chapterID) +func (s *service) GetPagesByChapterID(ctx context.Context, chapterID string) ([]*entity.Page, error) { + return s.repo.FindByKV(ctx, "chapter_id", chapterID) } diff --git a/bookback/internal/domain/services/paragraph/interface.go b/bookback/internal/domain/services/paragraph/interface.go index 683a7b4..e779c28 100644 --- a/bookback/internal/domain/services/paragraph/interface.go +++ b/bookback/internal/domain/services/paragraph/interface.go @@ -6,12 +6,10 @@ import ( ) type Repository interface { - List(ctx context.Context) ([]entity.Paragraph, error) + List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) Create(ctx context.Context, paragraph *entity.Paragraph) (string, error) FindByID(ctx context.Context, id string) (*entity.Paragraph, error) Update(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) - Delete(ctx context.Context, id string) (*entity.Paragraph, error) - GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) - GetParagraphsByChapterID(ctx context.Context, chapterID string) ([]entity.Paragraph, error) - GetParagraphsByBookID(ctx context.Context, bookID string) ([]entity.Paragraph, error) + HardDelete(ctx context.Context, id string) error + FindByKV(ctx context.Context, key string, value interface{}) ([]*entity.Paragraph, error) } diff --git a/bookback/internal/domain/services/paragraph/service.go b/bookback/internal/domain/services/paragraph/service.go index 0e9bbf5..b5e4172 100644 --- a/bookback/internal/domain/services/paragraph/service.go +++ b/bookback/internal/domain/services/paragraph/service.go @@ -10,9 +10,9 @@ type Service interface { GetParagraphByID(ctx context.Context, id string) (*entity.Paragraph, error) UpdateParagraph(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) - ListParagraphs(ctx context.Context) ([]entity.Paragraph, error) + ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) - GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string) ([]*entity.Paragraph, error) } type service struct { @@ -42,13 +42,21 @@ func (s *service) UpdateParagraph(ctx context.Context, id string, paragraph *ent } func (s *service) DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) { - return s.repo.Delete(ctx, id) + paragraph, err := s.GetParagraphByID(ctx, id) + if err != nil { + return nil, err + } + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return paragraph, err } -func (s *service) ListParagraphs(ctx context.Context) ([]entity.Paragraph, error) { - return s.repo.List(ctx) +func (s *service) ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) { + return s.repo.List(ctx, limit, offset) } -func (s *service) GetParagraphsByPageID(ctx context.Context, pageID string) ([]entity.Paragraph, error) { - return s.repo.GetParagraphsByPageID(ctx, pageID) +func (s *service) GetParagraphsByPageID(ctx context.Context, pageID string) ([]*entity.Paragraph, error) { + return s.repo.FindByKV(ctx, "page_id", pageID) } diff --git a/bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go b/bookback/internal/infrastructure/http/middleware/circuitbreaker.go similarity index 84% rename from bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go rename to bookback/internal/infrastructure/http/middleware/circuitbreaker.go index 6cf8c50..907d01f 100644 --- a/bookback/internal/controller/http/httpmiddlewares/circuitbreaker.go +++ b/bookback/internal/infrastructure/http/middleware/circuitbreaker.go @@ -1,8 +1,8 @@ -package httpmiddlewares +package middleware import ( "errors" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/circuitbreaker" + "github.com/SShlykov/zeitment/bookback/pkg/circuitbreaker" "github.com/labstack/echo/v4" "net/http" ) diff --git a/bookback/internal/controller/http/httpmiddlewares/cors.go b/bookback/internal/infrastructure/http/middleware/cors.go similarity index 96% rename from bookback/internal/controller/http/httpmiddlewares/cors.go rename to bookback/internal/infrastructure/http/middleware/cors.go index c1d6017..127014d 100644 --- a/bookback/internal/controller/http/httpmiddlewares/cors.go +++ b/bookback/internal/infrastructure/http/middleware/cors.go @@ -1,4 +1,4 @@ -package httpmiddlewares +package middleware import ( "github.com/labstack/echo/v4" diff --git a/bookback/internal/controller/http/httpmiddlewares/httplogger.go b/bookback/internal/infrastructure/http/middleware/httplogger.go similarity index 97% rename from bookback/internal/controller/http/httpmiddlewares/httplogger.go rename to bookback/internal/infrastructure/http/middleware/httplogger.go index 6ddb637..f1662ef 100644 --- a/bookback/internal/controller/http/httpmiddlewares/httplogger.go +++ b/bookback/internal/infrastructure/http/middleware/httplogger.go @@ -1,4 +1,4 @@ -package httpmiddlewares +package middleware import ( "context" diff --git a/bookback/internal/controller/http/httpmiddlewares/middleware.go b/bookback/internal/infrastructure/http/middleware/middleware.go similarity index 96% rename from bookback/internal/controller/http/httpmiddlewares/middleware.go rename to bookback/internal/infrastructure/http/middleware/middleware.go index f5fad9f..32189d6 100644 --- a/bookback/internal/controller/http/httpmiddlewares/middleware.go +++ b/bookback/internal/infrastructure/http/middleware/middleware.go @@ -1,4 +1,4 @@ -package httpmiddlewares +package middleware import ( "fmt" diff --git a/bookback/internal/controller/http/v1/book/config.go b/bookback/internal/infrastructure/http/v1/book/config.go similarity index 100% rename from bookback/internal/controller/http/v1/book/config.go rename to bookback/internal/infrastructure/http/v1/book/config.go diff --git a/bookback/internal/controller/http/v1/book/controller.go b/bookback/internal/infrastructure/http/v1/book/controller.go similarity index 81% rename from bookback/internal/controller/http/v1/book/controller.go rename to bookback/internal/infrastructure/http/v1/book/controller.go index 7760359..4aa3be0 100644 --- a/bookback/internal/controller/http/v1/book/controller.go +++ b/bookback/internal/infrastructure/http/v1/book/controller.go @@ -4,6 +4,7 @@ import ( "context" service "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" "github.com/labstack/echo/v4" "log/slog" "net/http" @@ -31,16 +32,16 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @success 200 {array} entity.Book // @failure 500 {object} string func (bc *Controller) ListBooks(c echo.Context) error { - var request requestModel + var request models.RequestBook if err := c.Bind(&request); err != nil { return ErrorValidationFailed } - books, err := bc.Service.ListBooks(bc.Ctx) + books, err := bc.Service.ListBooks(bc.Ctx, request.Options.Limit, request.Options.Offset) if err != nil { return ErrorUnknown } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Books: books}) + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Book]{Data: books, Status: "ok"}) } // CreateBook обрабатывает создание новой книги. @@ -55,16 +56,16 @@ func (bc *Controller) ListBooks(c echo.Context) error { // @failure 400 {object} string // @failure 500 {object} string func (bc *Controller) CreateBook(c echo.Context) error { - var request requestModel + var request models.CreateBookRequest if err := c.Bind(&request); err != nil { return ErrorValidationFailed } - createdBook, err := bc.Service.CreateBook(bc.Ctx, request.Book) + createdBook, err := bc.Service.CreateBook(bc.Ctx, request) if err != nil { return ErrorBookNotCreated } - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Book: createdBook}) + return c.JSON(http.StatusCreated, models.WebResponse[*models.Book]{Data: createdBook, Status: "created"}) } // GetBookByID обрабатывает запросы на получение книги по ID. @@ -89,7 +90,7 @@ func (bc *Controller) GetBookByID(c echo.Context) error { return ErrorBookNotFound } - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Book: book}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "ok"}) } // UpdateBook обрабатывает обновление книги. @@ -111,16 +112,16 @@ func (bc *Controller) UpdateBook(c echo.Context) error { return ErrorValidationFailed } - var request requestModel + var request models.UpdateBookRequest if err := c.Bind(&request); err != nil { return ErrorValidationFailed } - updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request.Book) + updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request) if err != nil { return ErrorUnknown } - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Book: updatedBook}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: updatedBook, Status: "updated"}) } // DeleteBook обрабатывает удаление книги по ID. @@ -141,5 +142,5 @@ func (bc *Controller) DeleteBook(c echo.Context) error { if err != nil { return ErrorDeleteBook } - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Book: book}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "deleted"}) } diff --git a/bookback/internal/controller/http/v1/book/errors.go b/bookback/internal/infrastructure/http/v1/book/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/book/errors.go rename to bookback/internal/infrastructure/http/v1/book/errors.go diff --git a/bookback/internal/controller/http/v1/book/routes.go b/bookback/internal/infrastructure/http/v1/book/routes.go similarity index 70% rename from bookback/internal/controller/http/v1/book/routes.go rename to bookback/internal/infrastructure/http/v1/book/routes.go index fcb7c61..73433ba 100644 --- a/bookback/internal/controller/http/v1/book/routes.go +++ b/bookback/internal/infrastructure/http/v1/book/routes.go @@ -2,9 +2,9 @@ package book import ( "context" - bookrepo2 "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - bookrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -13,7 +13,8 @@ import ( // SetBookController регистрирует контроллер книг в маршрутизаторе. func SetBookController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := bookrepo.NewService(bookrepo2.NewRepository(database)) + repo := pgrepo.NewBookRepository(database) + service := book.NewService(repo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -22,7 +23,7 @@ func SetBookController(e *echo.Echo, database postgres.Client, metrics metrics.M // RegisterRoutes регистрирует маршруты для обработки запросов к книгам. func (bc *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(bc.Metrics)) + group.Use(middleware.MetricsLogger(bc.Metrics)) group.POST("", bc.ListBooks) group.POST("", bc.CreateBook) diff --git a/bookback/internal/controller/http/v1/bookevents/config.go b/bookback/internal/infrastructure/http/v1/bookevents/config.go similarity index 100% rename from bookback/internal/controller/http/v1/bookevents/config.go rename to bookback/internal/infrastructure/http/v1/bookevents/config.go diff --git a/bookback/internal/controller/http/v1/bookevents/controller.go b/bookback/internal/infrastructure/http/v1/bookevents/controller.go similarity index 100% rename from bookback/internal/controller/http/v1/bookevents/controller.go rename to bookback/internal/infrastructure/http/v1/bookevents/controller.go diff --git a/bookback/internal/controller/http/v1/bookevents/errors.go b/bookback/internal/infrastructure/http/v1/bookevents/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/bookevents/errors.go rename to bookback/internal/infrastructure/http/v1/bookevents/errors.go diff --git a/bookback/internal/infrastructure/http/v1/bookevents/models.go b/bookback/internal/infrastructure/http/v1/bookevents/models.go new file mode 100644 index 0000000..a8dedaa --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/bookevents/models.go @@ -0,0 +1,22 @@ +package bookevents + +import ( + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" +) + +type Options struct{} + +type requestModel struct { + Options Options `json:"options,omitempty"` + BookEvents *entity.BookEvent `json:"book_events,omitempty"` +} + +type responseSingleModel struct { + BookEvent *entity.BookEvent `json:"book_event"` + Status string `json:"status"` +} + +type responseListModel struct { + BookEvents []*entity.BookEvent `json:"book_events"` + Status string `json:"status"` +} diff --git a/bookback/internal/controller/http/v1/bookevents/routes.go b/bookback/internal/infrastructure/http/v1/bookevents/routes.go similarity index 70% rename from bookback/internal/controller/http/v1/bookevents/routes.go rename to bookback/internal/infrastructure/http/v1/bookevents/routes.go index 042a188..8f2c1ed 100644 --- a/bookback/internal/controller/http/v1/bookevents/routes.go +++ b/bookback/internal/infrastructure/http/v1/bookevents/routes.go @@ -2,9 +2,9 @@ package bookevents import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - bookeventsrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -12,7 +12,8 @@ import ( ) func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := bookeventsrepo.NewService(bookrepo.NewRepository(database)) + repo := pgrepo.NewBookEventsRepository(database) + service := bookevents.NewService(repo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -20,7 +21,7 @@ func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metr func (bec *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(bec.Metrics)) + group.Use(middleware.MetricsLogger(bec.Metrics)) group.POST("", bec.CreateBookEvent) group.GET("/:id", bec.GetBookEventByID) diff --git a/bookback/internal/controller/http/v1/chapter/config.go b/bookback/internal/infrastructure/http/v1/chapter/config.go similarity index 100% rename from bookback/internal/controller/http/v1/chapter/config.go rename to bookback/internal/infrastructure/http/v1/chapter/config.go diff --git a/bookback/internal/controller/http/v1/chapter/controller.go b/bookback/internal/infrastructure/http/v1/chapter/controller.go similarity index 96% rename from bookback/internal/controller/http/v1/chapter/controller.go rename to bookback/internal/infrastructure/http/v1/chapter/controller.go index 65ea9d3..49b27e3 100644 --- a/bookback/internal/controller/http/v1/chapter/controller.go +++ b/bookback/internal/infrastructure/http/v1/chapter/controller.go @@ -30,7 +30,12 @@ func NewController(srv chapter.Service, metric metrics.Metrics, logger *slog.Log // @success 200 {array} entity.Chapter // @failure 500 {object} config.HTTPError func (ch *Controller) ListChapters(c echo.Context) error { - chapters, err := ch.Service.ListChapters(ch.Ctx) + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + + chapters, err := ch.Service.ListChapters(ch.Ctx, request.Options.Limit, request.Options.Offset) if err != nil { return ErrorUnknown } diff --git a/bookback/internal/controller/http/v1/chapter/errors.go b/bookback/internal/infrastructure/http/v1/chapter/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/chapter/errors.go rename to bookback/internal/infrastructure/http/v1/chapter/errors.go diff --git a/bookback/internal/infrastructure/http/v1/chapter/models.go b/bookback/internal/infrastructure/http/v1/chapter/models.go new file mode 100644 index 0000000..795d058 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/chapter/models.go @@ -0,0 +1,23 @@ +package chapter + +import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + +type Options struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset,omitempty"` +} + +type requestModel struct { + Options Options `json:"options,omitempty"` + Chapter *entity.Chapter `json:"chapter,omitempty"` +} + +type responseSingleModel struct { + Chapter *entity.Chapter `json:"chapter"` + Status string `json:"status"` +} + +type responseListModel struct { + Chapters []*entity.Chapter `json:"chapters"` + Status string `json:"status"` +} diff --git a/bookback/internal/controller/http/v1/chapter/routes.go b/bookback/internal/infrastructure/http/v1/chapter/routes.go similarity index 61% rename from bookback/internal/controller/http/v1/chapter/routes.go rename to bookback/internal/infrastructure/http/v1/chapter/routes.go index 83e4c49..c4bba5b 100644 --- a/bookback/internal/controller/http/v1/chapter/routes.go +++ b/bookback/internal/infrastructure/http/v1/chapter/routes.go @@ -2,9 +2,9 @@ package chapter import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - chapterrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -12,9 +12,8 @@ import ( ) func SetChapterController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - chapterRepo := bookrepo.NewRepository(database) - bookRepo := bookrepo.NewRepository(database) - service := chapterrepo.NewService(chapterRepo, bookRepo) + chapterRepo := pgrepo.NewChapterRepository(database) + service := chapter.NewService(chapterRepo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -22,7 +21,7 @@ func SetChapterController(e *echo.Echo, database postgres.Client, metrics metric func (ch *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(pathPrefix) - group.Use(httpmiddlewares.MetricsLogger(ch.Metrics)) + group.Use(middleware.MetricsLogger(ch.Metrics)) group.GET("", ch.ListChapters) group.POST("", ch.CreateChapter) diff --git a/bookback/internal/controller/http/v1/health/config.go b/bookback/internal/infrastructure/http/v1/health/config.go similarity index 100% rename from bookback/internal/controller/http/v1/health/config.go rename to bookback/internal/infrastructure/http/v1/health/config.go diff --git a/bookback/internal/controller/http/v1/health/controller.go b/bookback/internal/infrastructure/http/v1/health/controller.go similarity index 100% rename from bookback/internal/controller/http/v1/health/controller.go rename to bookback/internal/infrastructure/http/v1/health/controller.go diff --git a/bookback/internal/controller/http/v1/health/routes.go b/bookback/internal/infrastructure/http/v1/health/routes.go similarity index 79% rename from bookback/internal/controller/http/v1/health/routes.go rename to bookback/internal/infrastructure/http/v1/health/routes.go index ce50bc9..9c3fce8 100644 --- a/bookback/internal/controller/http/v1/health/routes.go +++ b/bookback/internal/infrastructure/http/v1/health/routes.go @@ -2,7 +2,7 @@ package health import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -17,7 +17,7 @@ func SetHealthController(e *echo.Echo, _ postgres.Client, metrics metrics.Metric func (hc *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(hc.Metrics)) + group.Use(middleware.MetricsLogger(hc.Metrics)) group.GET("/", hc.GetHealthCheck) } diff --git a/bookback/internal/controller/http/v1/mapvariables/config.go b/bookback/internal/infrastructure/http/v1/mapvariables/config.go similarity index 100% rename from bookback/internal/controller/http/v1/mapvariables/config.go rename to bookback/internal/infrastructure/http/v1/mapvariables/config.go diff --git a/bookback/internal/controller/http/v1/mapvariables/controller.go b/bookback/internal/infrastructure/http/v1/mapvariables/controller.go similarity index 100% rename from bookback/internal/controller/http/v1/mapvariables/controller.go rename to bookback/internal/infrastructure/http/v1/mapvariables/controller.go diff --git a/bookback/internal/controller/http/v1/mapvariables/errors.go b/bookback/internal/infrastructure/http/v1/mapvariables/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/mapvariables/errors.go rename to bookback/internal/infrastructure/http/v1/mapvariables/errors.go diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/models.go b/bookback/internal/infrastructure/http/v1/mapvariables/models.go new file mode 100644 index 0000000..b78846f --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/mapvariables/models.go @@ -0,0 +1,20 @@ +package mapvariables + +import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + +type Options struct{} + +type requestModel struct { + Options Options `json:"options,omitempty"` + MapVariables *entity.MapVariable `json:"map_variables,omitempty"` +} + +type responseSingleModel struct { + MapVariable *entity.MapVariable `json:"map_variable"` + Status string `json:"status"` +} + +type responseListModel struct { + MapVariables []*entity.MapVariable `json:"map_variables"` + Status string `json:"status"` +} diff --git a/bookback/internal/controller/http/v1/mapvariables/routes.go b/bookback/internal/infrastructure/http/v1/mapvariables/routes.go similarity index 70% rename from bookback/internal/controller/http/v1/mapvariables/routes.go rename to bookback/internal/infrastructure/http/v1/mapvariables/routes.go index ff33e55..49f28ed 100644 --- a/bookback/internal/controller/http/v1/mapvariables/routes.go +++ b/bookback/internal/infrastructure/http/v1/mapvariables/routes.go @@ -2,9 +2,9 @@ package mapvariables import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - mapvariablesrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -12,7 +12,8 @@ import ( ) func SetMapVariablesController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := mapvariablesrepo.NewService(bookrepo.NewRepository(database)) + repo := pgrepo.NewMapVariablesRepository(database) + service := mapvariables.NewService(repo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -20,7 +21,7 @@ func SetMapVariablesController(e *echo.Echo, database postgres.Client, metrics m func (mvc *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(mvc.Metrics)) + group.Use(middleware.MetricsLogger(mvc.Metrics)) group.GET("/:id", mvc.GetMapVariableByID) group.PUT("/:id", mvc.UpdateMapVariable) diff --git a/bookback/internal/controller/http/v1/page/config.go b/bookback/internal/infrastructure/http/v1/page/config.go similarity index 100% rename from bookback/internal/controller/http/v1/page/config.go rename to bookback/internal/infrastructure/http/v1/page/config.go diff --git a/bookback/internal/controller/http/v1/page/controller.go b/bookback/internal/infrastructure/http/v1/page/controller.go similarity index 96% rename from bookback/internal/controller/http/v1/page/controller.go rename to bookback/internal/infrastructure/http/v1/page/controller.go index 038e1ea..b4f3d48 100644 --- a/bookback/internal/controller/http/v1/page/controller.go +++ b/bookback/internal/infrastructure/http/v1/page/controller.go @@ -29,7 +29,11 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @success 200 {array} entity.Page // @failure 500 {object} config.HTTPError func (p *Controller) ListPages(c echo.Context) error { - pages, err := p.Service.ListPages(p.Ctx) + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + pages, err := p.Service.ListPages(p.Ctx, request.Options.Limit, request.Options.Offset) if err != nil { return ErrorUnknown } diff --git a/bookback/internal/controller/http/v1/page/errors.go b/bookback/internal/infrastructure/http/v1/page/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/page/errors.go rename to bookback/internal/infrastructure/http/v1/page/errors.go diff --git a/bookback/internal/infrastructure/http/v1/page/models.go b/bookback/internal/infrastructure/http/v1/page/models.go new file mode 100644 index 0000000..9c503d8 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/page/models.go @@ -0,0 +1,23 @@ +package page + +import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + +type Options struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset,omitempty"` +} + +type requestModel struct { + Options Options `json:"options,omitempty"` + Page *entity.Page `json:"page,omitempty"` +} + +type responseSingleModel struct { + Page *entity.Page `json:"page"` + Status string `json:"status"` +} + +type responseListModel struct { + Pages []*entity.Page `json:"pages"` + Status string `json:"status"` +} diff --git a/bookback/internal/controller/http/v1/page/routes.go b/bookback/internal/infrastructure/http/v1/page/routes.go similarity index 66% rename from bookback/internal/controller/http/v1/page/routes.go rename to bookback/internal/infrastructure/http/v1/page/routes.go index d449f08..62951e4 100644 --- a/bookback/internal/controller/http/v1/page/routes.go +++ b/bookback/internal/infrastructure/http/v1/page/routes.go @@ -2,9 +2,9 @@ package page import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - pagerepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -12,7 +12,8 @@ import ( ) func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := pagerepo.NewService(bookrepo.NewRepository(database)) + repo := pgrepo.NewPageRepository(database) + service := page.NewService(repo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -20,7 +21,7 @@ func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.M func (p *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(p.Metrics)) + group.Use(middleware.MetricsLogger(p.Metrics)) group.GET("", p.ListPages) group.POST("", p.CreatePage) diff --git a/bookback/internal/controller/http/v1/paragraph/config.go b/bookback/internal/infrastructure/http/v1/paragraph/config.go similarity index 100% rename from bookback/internal/controller/http/v1/paragraph/config.go rename to bookback/internal/infrastructure/http/v1/paragraph/config.go diff --git a/bookback/internal/controller/http/v1/paragraph/controller.go b/bookback/internal/infrastructure/http/v1/paragraph/controller.go similarity index 96% rename from bookback/internal/controller/http/v1/paragraph/controller.go rename to bookback/internal/infrastructure/http/v1/paragraph/controller.go index 29f75be..d609922 100644 --- a/bookback/internal/controller/http/v1/paragraph/controller.go +++ b/bookback/internal/infrastructure/http/v1/paragraph/controller.go @@ -30,7 +30,12 @@ func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Log // @success 200 {array} entity.Paragraph // @failure 500 {object} config.HTTPError func (p *Controller) ListParagraphs(c echo.Context) error { - paragraphs, err := p.Service.ListParagraphs(p.Ctx) + var request requestModel + if err := c.Bind(&request); err != nil { + return ErrorValidationFailed + } + + paragraphs, err := p.Service.ListParagraphs(p.Ctx, request.Options.Limit, request.Options.Offset) if err != nil { return ErrorUnknown } diff --git a/bookback/internal/controller/http/v1/paragraph/errors.go b/bookback/internal/infrastructure/http/v1/paragraph/errors.go similarity index 100% rename from bookback/internal/controller/http/v1/paragraph/errors.go rename to bookback/internal/infrastructure/http/v1/paragraph/errors.go diff --git a/bookback/internal/infrastructure/http/v1/paragraph/models.go b/bookback/internal/infrastructure/http/v1/paragraph/models.go new file mode 100644 index 0000000..158a6b5 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/paragraph/models.go @@ -0,0 +1,23 @@ +package paragraph + +import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + +type Options struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset,omitempty"` +} + +type requestModel struct { + Options Options `json:"options,omitempty"` + Paragraph *entity.Paragraph `json:"paragraph,omitempty"` +} + +type responseSingleModel struct { + Paragraph *entity.Paragraph `json:"paragraph"` + Status string `json:"status"` +} + +type responseListModel struct { + Paragraphs []*entity.Paragraph `json:"paragraphs"` + Status string `json:"status"` +} diff --git a/bookback/internal/controller/http/v1/paragraph/routes.go b/bookback/internal/infrastructure/http/v1/paragraph/routes.go similarity index 66% rename from bookback/internal/controller/http/v1/paragraph/routes.go rename to bookback/internal/infrastructure/http/v1/paragraph/routes.go index 8bd7a95..981ecf5 100644 --- a/bookback/internal/controller/http/v1/paragraph/routes.go +++ b/bookback/internal/infrastructure/http/v1/paragraph/routes.go @@ -2,9 +2,9 @@ package paragraph import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/adapters/db/postgres/bookrepo" - "github.com/SShlykov/zeitment/bookback/internal/controller/http/httpmiddlewares" - paragraphrepo "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" "github.com/SShlykov/zeitment/bookback/internal/metrics" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" @@ -12,7 +12,8 @@ import ( ) func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - service := paragraphrepo.NewService(bookrepo.NewRepository(database)) + repo := pgrepo.NewParagraphRepository(database) + service := paragraph.NewService(repo) controller := NewController(service, metrics, logger, ctx) controller.RegisterRoutes(e) @@ -20,7 +21,7 @@ func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metr func (p *Controller) RegisterRoutes(e *echo.Echo) { group := e.Group(PathPrefix) - group.Use(httpmiddlewares.MetricsLogger(p.Metrics)) + group.Use(middleware.MetricsLogger(p.Metrics)) group.GET("", p.ListParagraphs) group.POST("", p.CreateParagraph) diff --git a/bookback/internal/controller/http/v1/swagger/controller.go b/bookback/internal/infrastructure/http/v1/swagger/controller.go similarity index 100% rename from bookback/internal/controller/http/v1/swagger/controller.go rename to bookback/internal/infrastructure/http/v1/swagger/controller.go diff --git a/bookback/internal/models/book_model.go b/bookback/internal/models/book_model.go new file mode 100644 index 0000000..e6127b9 --- /dev/null +++ b/bookback/internal/models/book_model.go @@ -0,0 +1,34 @@ +package models + +import ( + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type Book struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt types.Null[time.Time] + Owner string + Title string + Author string + Description string + IsPublic bool + Publication types.Null[time.Time] + ImageLink types.Null[string] + MapLink types.Null[string] + Variables []string +} + +type CreateBookRequest struct { + Book *Book `json:"book"` +} +type UpdateBookRequest struct { + Book *Book `json:"book"` +} + +type RequestBook struct { + Options PageOptions `json:"options,omitempty"` + Book *Book `json:"book,omitempty"` +} diff --git a/bookback/internal/models/converter/book.go b/bookback/internal/models/converter/book.go new file mode 100644 index 0000000..64cab2c --- /dev/null +++ b/bookback/internal/models/converter/book.go @@ -0,0 +1,52 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "time" +) + +func BooksEntityToModel(books []*entity.Book) []*models.Book { + var result []*models.Book + for _, book := range books { + result = append(result, BookEntityToModel(book)) + } + return result +} + +func BookModelToEntity(book *models.Book) *entity.Book { + return &entity.Book{ + ID: book.ID, + CreatedAt: book.CreatedAt, + UpdatedAt: book.UpdatedAt, + DeletedAt: sql.NullTime{Valid: book.DeletedAt.Valid, Time: book.DeletedAt.Value}, + Owner: book.Owner, + Title: book.Title, + Author: book.Author, + Description: book.Description, + IsPublic: book.IsPublic, + Publication: sql.NullTime{Valid: book.Publication.Valid, Time: book.Publication.Value}, + ImageLink: sql.NullString{Valid: book.ImageLink.Valid, String: book.ImageLink.Value}, + MapLink: sql.NullString{Valid: book.MapLink.Valid, String: book.MapLink.Value}, + Variables: book.Variables, + } +} + +func BookEntityToModel(book *entity.Book) *models.Book { + return &models.Book{ + ID: book.ID, + CreatedAt: book.CreatedAt, + UpdatedAt: book.UpdatedAt, + DeletedAt: models.Null[time.Time]{Valid: book.DeletedAt.Valid, Value: book.DeletedAt.Time}, + Owner: book.Owner, + Title: book.Title, + Author: book.Author, + Description: book.Description, + IsPublic: book.IsPublic, + Publication: models.Null[time.Time]{Valid: book.Publication.Valid, Value: book.Publication.Time}, + ImageLink: models.Null[string]{Valid: book.ImageLink.Valid, Value: book.ImageLink.String}, + MapLink: models.Null[string]{Valid: book.MapLink.Valid, Value: book.MapLink.String}, + Variables: book.Variables, + } +} diff --git a/bookback/internal/models/model.go b/bookback/internal/models/model.go new file mode 100644 index 0000000..a045ce8 --- /dev/null +++ b/bookback/internal/models/model.go @@ -0,0 +1,25 @@ +package models + +type WebResponse[T any] struct { + Status string `json:"status"` + Data T `json:"data"` + Paging *PageMetadata `json:"paging,omitempty"` + Errors string `json:"errors,omitempty"` +} + +type PageResponse[T any] struct { + Data []T `json:"data,omitempty"` + PageMetadata PageMetadata `json:"paging,omitempty"` +} + +type PageMetadata struct { + Page int `json:"page"` + Size int `json:"size"` + TotalItem int64 `json:"total_item"` + TotalPage int64 `json:"total_page"` +} + +type PageOptions struct { + Limit uint64 `json:"limit,omitempty"` + Offset uint64 `json:"offset,omitempty"` +} diff --git a/bookback/internal/models/types/null.go b/bookback/internal/models/types/null.go new file mode 100644 index 0000000..587a66d --- /dev/null +++ b/bookback/internal/models/types/null.go @@ -0,0 +1,31 @@ +package types + +import "encoding/json" + +type Null[T any] struct { + Value T + Valid bool +} + +func (n *Null[T]) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + n.Valid = false + return nil + } + + var v T + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + n.Value = v + n.Valid = true + return nil +} + +func (n *Null[T]) MarshalJSON() ([]byte, error) { + if !n.Valid { + return []byte("null"), nil + } + return json.Marshal(n.Value) +} diff --git a/bookback/internal/controller/http/circuitbreaker/circuitbreaker.go b/bookback/pkg/circuitbreaker/circuitbreaker.go similarity index 100% rename from bookback/internal/controller/http/circuitbreaker/circuitbreaker.go rename to bookback/pkg/circuitbreaker/circuitbreaker.go diff --git a/bookback/internal/controller/http/circuitbreaker/circuitbreaker_test.go b/bookback/pkg/circuitbreaker/circuitbreaker_test.go similarity index 100% rename from bookback/internal/controller/http/circuitbreaker/circuitbreaker_test.go rename to bookback/pkg/circuitbreaker/circuitbreaker_test.go diff --git a/bookback/pkg/postgres/interface.go b/bookback/pkg/postgres/interface.go index 2024fdb..1003d01 100644 --- a/bookback/pkg/postgres/interface.go +++ b/bookback/pkg/postgres/interface.go @@ -33,6 +33,8 @@ type Query struct { type Transactor interface { BeginTx(ctx context.Context, options pgx.TxOptions) (pgx.Tx, error) + Commit(ctx context.Context) error + Rollback(ctx context.Context) error } type SQLScanner interface { From 2ca9db37f009d87650ae4aedbed0b63730f48441 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Thu, 7 Mar 2024 17:07:45 +0300 Subject: [PATCH 8/9] feat: cleaning --- bookback/internal/bootstrap/app/app.go | 2 +- bookback/internal/bootstrap/app/endpoint.go | 8 +- bookback/internal/bootstrap/app/metrics.go | 2 +- .../domain/services/0_generic_repo.go | 14 ++ .../domain/services/book/interface.go | 14 -- .../{book/service.go => book_service.go} | 34 ++- .../domain/services/bookevents/interface.go | 15 -- .../domain/services/bookevents/service.go | 75 ------ .../domain/services/bookevents_service.go | 112 +++++++++ .../domain/services/chapter/interface.go | 15 -- .../domain/services/chapter/service.go | 69 ------ .../domain/services/chapter_service.go | 95 ++++++++ .../domain/services/mapvariables/interface.go | 15 -- .../domain/services/mapvariables/service.go | 75 ------ .../domain/services/mapvariables_service.go | 114 +++++++++ .../domain/services/page/interface.go | 15 -- .../internal/domain/services/page/service.go | 63 ----- .../internal/domain/services/page_service.go | 88 +++++++ .../domain/services/paragraph/interface.go | 15 -- .../domain/services/paragraph/service.go | 62 ----- .../domain/services/paragraph_service.go | 87 +++++++ .../http/middleware/middleware.go | 2 +- .../infrastructure/http/v1/book/config.go | 6 - .../infrastructure/http/v1/book/controller.go | 146 ------------ .../infrastructure/http/v1/book/errors.go | 31 --- .../infrastructure/http/v1/book/routes.go | 33 --- .../http/v1/bookevents/config.go | 3 - .../http/v1/bookevents/controller.go | 212 ----------------- .../http/v1/bookevents/errors.go | 31 --- .../http/v1/bookevents/models.go | 22 -- .../http/v1/bookevents/routes.go | 34 --- .../infrastructure/http/v1/chapter/config.go | 3 - .../http/v1/chapter/controller.go | 161 ------------- .../infrastructure/http/v1/chapter/errors.go | 31 --- .../infrastructure/http/v1/chapter/models.go | 23 -- .../infrastructure/http/v1/chapter/routes.go | 32 --- .../http/v1/controllers/book_controller.go | 103 +++++++++ .../v1/controllers/bookevents_controller.go | 151 ++++++++++++ .../http/v1/controllers/chapter_controller.go | 115 ++++++++++ .../health_controller.go} | 14 +- .../v1/controllers/mapvariables_controller.go | 154 +++++++++++++ .../http/v1/controllers/page_controller.go | 115 ++++++++++ .../v1/controllers/paragraph_controller.go | 116 ++++++++++ .../infrastructure/http/v1/errors/errors.go | 24 ++ .../infrastructure/http/v1/health/config.go | 3 - .../infrastructure/http/v1/health/routes.go | 23 -- .../http/v1/mapvariables/config.go | 3 - .../http/v1/mapvariables/controller.go | 216 ------------------ .../http/v1/mapvariables/errors.go | 29 --- .../http/v1/mapvariables/models.go | 20 -- .../http/v1/mapvariables/routes.go | 34 --- .../infrastructure/http/v1/page/config.go | 3 - .../infrastructure/http/v1/page/controller.go | 160 ------------- .../infrastructure/http/v1/page/errors.go | 31 --- .../infrastructure/http/v1/page/models.go | 23 -- .../infrastructure/http/v1/page/routes.go | 32 --- .../http/v1/paragraph/config.go | 3 - .../http/v1/paragraph/controller.go | 161 ------------- .../http/v1/paragraph/errors.go | 31 --- .../http/v1/paragraph/models.go | 23 -- .../http/v1/paragraph/routes.go | 32 --- .../http/v1/routes/absolute_paths.go | 12 + .../http/v1/routes/book_router.go | 29 +++ .../http/v1/routes/bookevents_router.go | 31 +++ .../http/v1/routes/chapter_router.go | 29 +++ .../http/v1/routes/health_router.go | 20 ++ .../http/v1/routes/mapvariables_router.go | 31 +++ .../http/v1/routes/page_router.go | 29 +++ .../http/v1/routes/paragraph_router.go | 29 +++ .../swagger_router.go} | 2 +- .../internal/infrastructure/metrics/Readme.md | 1 + .../{ => infrastructure}/metrics/interface.go | 0 .../metrics/localmetrics/metrics.go | 0 .../models/{model.go => basic_model.go} | 0 bookback/internal/models/bookevents_model.go | 37 +++ bookback/internal/models/chapter_model.go | 33 +++ bookback/internal/models/converter/book.go | 9 +- .../internal/models/converter/bookevent.go | 59 +++++ bookback/internal/models/converter/chapter.go | 49 ++++ .../internal/models/converter/mapvariables.go | 62 +++++ bookback/internal/models/converter/page.go | 45 ++++ .../internal/models/converter/paragraph.go | 45 ++++ .../internal/models/mapvariables_model.go | 41 ++++ bookback/internal/models/page_model.go | 31 +++ bookback/internal/models/paragraph_model.go | 31 +++ 85 files changed, 1966 insertions(+), 2067 deletions(-) create mode 100644 bookback/internal/domain/services/0_generic_repo.go delete mode 100644 bookback/internal/domain/services/book/interface.go rename bookback/internal/domain/services/{book/service.go => book_service.go} (60%) delete mode 100644 bookback/internal/domain/services/bookevents/interface.go delete mode 100644 bookback/internal/domain/services/bookevents/service.go create mode 100644 bookback/internal/domain/services/bookevents_service.go delete mode 100644 bookback/internal/domain/services/chapter/interface.go delete mode 100644 bookback/internal/domain/services/chapter/service.go create mode 100644 bookback/internal/domain/services/chapter_service.go delete mode 100644 bookback/internal/domain/services/mapvariables/interface.go delete mode 100644 bookback/internal/domain/services/mapvariables/service.go create mode 100644 bookback/internal/domain/services/mapvariables_service.go delete mode 100644 bookback/internal/domain/services/page/interface.go delete mode 100644 bookback/internal/domain/services/page/service.go create mode 100644 bookback/internal/domain/services/page_service.go delete mode 100644 bookback/internal/domain/services/paragraph/interface.go delete mode 100644 bookback/internal/domain/services/paragraph/service.go create mode 100644 bookback/internal/domain/services/paragraph_service.go delete mode 100644 bookback/internal/infrastructure/http/v1/book/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/book/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/book/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/book/routes.go delete mode 100644 bookback/internal/infrastructure/http/v1/bookevents/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/bookevents/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/bookevents/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/bookevents/models.go delete mode 100644 bookback/internal/infrastructure/http/v1/bookevents/routes.go delete mode 100644 bookback/internal/infrastructure/http/v1/chapter/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/chapter/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/chapter/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/chapter/models.go delete mode 100644 bookback/internal/infrastructure/http/v1/chapter/routes.go create mode 100644 bookback/internal/infrastructure/http/v1/controllers/book_controller.go create mode 100644 bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go create mode 100644 bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go rename bookback/internal/infrastructure/http/v1/{health/controller.go => controllers/health_controller.go} (56%) create mode 100644 bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go create mode 100644 bookback/internal/infrastructure/http/v1/controllers/page_controller.go create mode 100644 bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go create mode 100644 bookback/internal/infrastructure/http/v1/errors/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/health/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/health/routes.go delete mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/models.go delete mode 100644 bookback/internal/infrastructure/http/v1/mapvariables/routes.go delete mode 100644 bookback/internal/infrastructure/http/v1/page/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/page/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/page/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/page/models.go delete mode 100644 bookback/internal/infrastructure/http/v1/page/routes.go delete mode 100644 bookback/internal/infrastructure/http/v1/paragraph/config.go delete mode 100644 bookback/internal/infrastructure/http/v1/paragraph/controller.go delete mode 100644 bookback/internal/infrastructure/http/v1/paragraph/errors.go delete mode 100644 bookback/internal/infrastructure/http/v1/paragraph/models.go delete mode 100644 bookback/internal/infrastructure/http/v1/paragraph/routes.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/absolute_paths.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/book_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/bookevents_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/chapter_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/health_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/page_router.go create mode 100644 bookback/internal/infrastructure/http/v1/routes/paragraph_router.go rename bookback/internal/infrastructure/http/v1/{swagger/controller.go => routes/swagger_router.go} (93%) create mode 100644 bookback/internal/infrastructure/metrics/Readme.md rename bookback/internal/{ => infrastructure}/metrics/interface.go (100%) rename bookback/internal/{ => infrastructure}/metrics/localmetrics/metrics.go (100%) rename bookback/internal/models/{model.go => basic_model.go} (100%) create mode 100644 bookback/internal/models/bookevents_model.go create mode 100644 bookback/internal/models/chapter_model.go create mode 100644 bookback/internal/models/converter/bookevent.go create mode 100644 bookback/internal/models/converter/chapter.go create mode 100644 bookback/internal/models/converter/mapvariables.go create mode 100644 bookback/internal/models/converter/page.go create mode 100644 bookback/internal/models/converter/paragraph.go create mode 100644 bookback/internal/models/mapvariables_model.go create mode 100644 bookback/internal/models/page_model.go create mode 100644 bookback/internal/models/paragraph_model.go diff --git a/bookback/internal/bootstrap/app/app.go b/bookback/internal/bootstrap/app/app.go index 56f10ec..0158e5d 100644 --- a/bookback/internal/bootstrap/app/app.go +++ b/bookback/internal/bootstrap/app/app.go @@ -2,7 +2,7 @@ package app import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" cfg "github.com/SShlykov/zeitment/bookback/pkg/config" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" diff --git a/bookback/internal/bootstrap/app/endpoint.go b/bookback/internal/bootstrap/app/endpoint.go index d790541..3cf5d8b 100644 --- a/bookback/internal/bootstrap/app/endpoint.go +++ b/bookback/internal/bootstrap/app/endpoint.go @@ -3,15 +3,15 @@ package app import ( "context" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/book" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/chapter" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/book" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/bookevents" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/chapter" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/health" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/mapvariables" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/page" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/paragraph" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/swagger" - "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" "github.com/SShlykov/zeitment/bookback/pkg/circuitbreaker" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/labstack/echo/v4" diff --git a/bookback/internal/bootstrap/app/metrics.go b/bookback/internal/bootstrap/app/metrics.go index fe1b679..6ee41ed 100644 --- a/bookback/internal/bootstrap/app/metrics.go +++ b/bookback/internal/bootstrap/app/metrics.go @@ -2,7 +2,7 @@ package app import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/metrics/localmetrics" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics/localmetrics" ) func (app *App) initMetrics(_ context.Context) error { diff --git a/bookback/internal/domain/services/0_generic_repo.go b/bookback/internal/domain/services/0_generic_repo.go new file mode 100644 index 0000000..c332eed --- /dev/null +++ b/bookback/internal/domain/services/0_generic_repo.go @@ -0,0 +1,14 @@ +package services + +import ( + "context" +) + +type SimpleRepo[T any] interface { + Create(ctx context.Context, ent T) (string, error) + FindByID(ctx context.Context, id string) (T, error) + FindByKV(ctx context.Context, key string, value interface{}) ([]T, error) + Update(ctx context.Context, id string, ent T) (T, error) + HardDelete(ctx context.Context, id string) error + List(ctx context.Context, limit uint64, offset uint64) ([]T, error) +} diff --git a/bookback/internal/domain/services/book/interface.go b/bookback/internal/domain/services/book/interface.go deleted file mode 100644 index 0e58f57..0000000 --- a/bookback/internal/domain/services/book/interface.go +++ /dev/null @@ -1,14 +0,0 @@ -package book - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repo interface { - Create(ctx context.Context, book *entity.Book) (string, error) - FindByID(ctx context.Context, id string) (*entity.Book, error) - Update(ctx context.Context, id string, book *entity.Book) (*entity.Book, error) - HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Book, error) -} diff --git a/bookback/internal/domain/services/book/service.go b/bookback/internal/domain/services/book_service.go similarity index 60% rename from bookback/internal/domain/services/book/service.go rename to bookback/internal/domain/services/book_service.go index 77fb2ba..772a946 100644 --- a/bookback/internal/domain/services/book/service.go +++ b/bookback/internal/domain/services/book_service.go @@ -1,4 +1,4 @@ -package book +package services import ( "context" @@ -7,8 +7,8 @@ import ( "github.com/SShlykov/zeitment/bookback/internal/models/converter" ) -// Service описывает сервис для работы с книгами. -type Service interface { +// BookService описывает сервис для работы с книгами. +type BookService interface { CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) GetBookByID(ctx context.Context, id string) (*models.Book, error) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) @@ -16,16 +16,16 @@ type Service interface { ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) } -type service struct { - repo Repo +type bookService struct { + repo SimpleRepo[*entity.Book] } -// NewService создает новый экземпляр Service. -func NewService(repo Repo) Service { - return &service{repo} +// NewBookService создает новый экземпляр Service. +func NewBookService(repo SimpleRepo[*entity.Book]) BookService { + return &bookService{repo} } -func (s *service) CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) { +func (s *bookService) CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) { book := converter.BookModelToEntity(request.Book) if book.Variables == nil { @@ -36,16 +36,10 @@ func (s *service) CreateBook(ctx context.Context, request models.CreateBookReque return nil, err } - var newBook *entity.Book - newBook, err = s.repo.FindByID(ctx, id) - if err != nil { - return nil, err - } - - return converter.BookEntityToModel(newBook), err + return s.GetBookByID(ctx, id) } -func (s *service) GetBookByID(ctx context.Context, id string) (*models.Book, error) { +func (s *bookService) GetBookByID(ctx context.Context, id string) (*models.Book, error) { book, err := s.repo.FindByID(ctx, id) if err != nil { return nil, err @@ -54,7 +48,7 @@ func (s *service) GetBookByID(ctx context.Context, id string) (*models.Book, err return converter.BookEntityToModel(book), nil } -func (s *service) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) { +func (s *bookService) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) { book, err := s.repo.Update(ctx, id, converter.BookModelToEntity(request.Book)) if err != nil { return nil, err @@ -63,7 +57,7 @@ func (s *service) UpdateBook(ctx context.Context, id string, request models.Upda return converter.BookEntityToModel(book), nil } -func (s *service) DeleteBook(ctx context.Context, id string) (*models.Book, error) { +func (s *bookService) DeleteBook(ctx context.Context, id string) (*models.Book, error) { book, err := s.GetBookByID(ctx, id) if err != nil { return nil, err @@ -77,7 +71,7 @@ func (s *service) DeleteBook(ctx context.Context, id string) (*models.Book, erro return book, err } -func (s *service) ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) { +func (s *bookService) ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) { books, err := s.repo.List(ctx, limit, offset) if err != nil { return nil, err diff --git a/bookback/internal/domain/services/bookevents/interface.go b/bookback/internal/domain/services/bookevents/interface.go deleted file mode 100644 index e75c723..0000000 --- a/bookback/internal/domain/services/bookevents/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package bookevents - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repository interface { - Create(ctx context.Context, chapter *entity.BookEvent) (string, error) - FindByID(ctx context.Context, id string) (*entity.BookEvent, error) - Update(ctx context.Context, id string, chapter *entity.BookEvent) (*entity.BookEvent, error) - HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.BookEvent, error) - FindByKV(ctx context.Context, key string, value any) ([]*entity.BookEvent, error) -} diff --git a/bookback/internal/domain/services/bookevents/service.go b/bookback/internal/domain/services/bookevents/service.go deleted file mode 100644 index 80c027c..0000000 --- a/bookback/internal/domain/services/bookevents/service.go +++ /dev/null @@ -1,75 +0,0 @@ -package bookevents - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Service interface { - CreateBookEvent(ctx context.Context, event *entity.BookEvent) (*entity.BookEvent, error) - GetBookEventByID(ctx context.Context, id string) (*entity.BookEvent, error) - UpdateBookEvent(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) - DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) - - GetBookEventsByBookID(ctx context.Context, bookID string) ([]*entity.BookEvent, error) - GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*entity.BookEvent, error) - GetBookEventsByPageID(ctx context.Context, pageID string) ([]*entity.BookEvent, error) - GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*entity.BookEvent, error) -} - -type service struct { - repo Repository -} - -func NewService(repo Repository) Service { - return &service{repo} -} - -func (s *service) CreateBookEvent(ctx context.Context, event *entity.BookEvent) (*entity.BookEvent, error) { - id, err := s.repo.Create(ctx, event) - if err != nil { - return nil, err - } - event.ID = id - - return event, err -} - -func (s *service) GetBookEventByID(ctx context.Context, id string) (*entity.BookEvent, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *service) UpdateBookEvent(ctx context.Context, id string, event *entity.BookEvent) (*entity.BookEvent, error) { - return s.repo.Update(ctx, id, event) -} - -func (s *service) DeleteBookEvent(ctx context.Context, id string) (*entity.BookEvent, error) { - bookEvent, err := s.GetBookEventByID(ctx, id) - if err != nil { - return nil, errors.Join(errors.New("BookEvent not found"), err) - } - - err = s.repo.HardDelete(ctx, id) - if err != nil { - return nil, err - } - - return bookEvent, err -} - -func (s *service) GetBookEventsByBookID(ctx context.Context, bookID string) ([]*entity.BookEvent, error) { - return s.repo.FindByKV(ctx, "book_id", bookID) -} - -func (s *service) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*entity.BookEvent, error) { - return s.repo.FindByKV(ctx, "chapter_id", chapterID) -} - -func (s *service) GetBookEventsByPageID(ctx context.Context, pageID string) ([]*entity.BookEvent, error) { - return s.repo.FindByKV(ctx, "page_id", pageID) -} - -func (s *service) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*entity.BookEvent, error) { - return s.repo.FindByKV(ctx, "paragraph_id", paragraphID) -} diff --git a/bookback/internal/domain/services/bookevents_service.go b/bookback/internal/domain/services/bookevents_service.go new file mode 100644 index 0000000..02078ba --- /dev/null +++ b/bookback/internal/domain/services/bookevents_service.go @@ -0,0 +1,112 @@ +package services + +import ( + "context" + "errors" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" +) + +type BookEventsService interface { + CreateBookEvent(ctx context.Context, request models.CreateBookEventRequest) (*models.BookEvent, error) + GetBookEventByID(ctx context.Context, id string) (*models.BookEvent, error) + UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) + DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) + + GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) +} + +type bookEventsService struct { + repo SimpleRepo[*entity.BookEvent] +} + +func NewBookEventsService(repo SimpleRepo[*entity.BookEvent]) BookEventsService { + return &bookEventsService{repo} +} + +func (s *bookEventsService) CreateBookEvent(ctx context.Context, request models.CreateBookEventRequest) (*models.BookEvent, error) { + bookEvent := converter.BookEventModelToEntity(request.BookEvent) + + id, err := s.repo.Create(ctx, bookEvent) + if err != nil { + return nil, err + } + + var newBook *models.BookEvent + newBook, err = s.GetBookEventByID(ctx, id) + if err != nil { + return nil, err + } + + return newBook, err +} + +func (s *bookEventsService) GetBookEventByID(ctx context.Context, id string) (*models.BookEvent, error) { + bookEvent, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + return converter.BookEventEntityToModel(bookEvent), nil +} + +func (s *bookEventsService) UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) { + bookEvent := converter.BookEventModelToEntity(request.BookEvent) + + updatedEvent, err := s.repo.Update(ctx, id, bookEvent) + if err != nil { + return nil, err + } + + return converter.BookEventEntityToModel(updatedEvent), nil +} + +func (s *bookEventsService) DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) { + bookEvent, err := s.GetBookEventByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("BookEvent not found"), err) + } + + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + + return bookEvent, err +} + +func (s *bookEventsService) GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) { + events, err := s.repo.FindByKV(ctx, "book_id", bookID) + if err != nil { + return nil, err + } + return converter.BookEventsEntityToModel(events), nil +} + +func (s *bookEventsService) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) { + events, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) + if err != nil { + return nil, err + } + return converter.BookEventsEntityToModel(events), nil +} + +func (s *bookEventsService) GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) { + events, err := s.repo.FindByKV(ctx, "page_id", pageID) + if err != nil { + return nil, err + } + return converter.BookEventsEntityToModel(events), nil +} + +func (s *bookEventsService) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) { + events, err := s.repo.FindByKV(ctx, "paragraph_id", paragraphID) + if err != nil { + return nil, err + } + return converter.BookEventsEntityToModel(events), nil +} diff --git a/bookback/internal/domain/services/chapter/interface.go b/bookback/internal/domain/services/chapter/interface.go deleted file mode 100644 index 93a92fb..0000000 --- a/bookback/internal/domain/services/chapter/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package chapter - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repository interface { - Create(ctx context.Context, chapter *entity.Chapter) (string, error) - FindByID(ctx context.Context, id string) (*entity.Chapter, error) - Update(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) - HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) - FindByKV(ctx context.Context, key string, value any) ([]*entity.Chapter, error) -} diff --git a/bookback/internal/domain/services/chapter/service.go b/bookback/internal/domain/services/chapter/service.go deleted file mode 100644 index b3f8b88..0000000 --- a/bookback/internal/domain/services/chapter/service.go +++ /dev/null @@ -1,69 +0,0 @@ -package chapter - -import ( - "context" - "errors" - "fmt" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Service interface { - CreateChapter(ctx context.Context, chapter *entity.Chapter) (*entity.Chapter, error) - GetChapterByID(ctx context.Context, id string) (*entity.Chapter, error) - UpdateChapter(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) - DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) - ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) - - GetChapterByBookID(ctx context.Context, bookID string) ([]*entity.Chapter, error) -} - -type service struct { - chapterRepo Repository -} - -func NewService(chapterRepo Repository) Service { - return &service{chapterRepo: chapterRepo} -} - -func (ch *service) CreateChapter(ctx context.Context, chapter *entity.Chapter) (*entity.Chapter, error) { - id, err := ch.chapterRepo.Create(ctx, chapter) - if err != nil { - return nil, err - } - - chapter, err = ch.GetChapterByID(ctx, id) - if err != nil { - fmt.Println(err) - return nil, err - } - - return chapter, nil -} - -func (ch *service) GetChapterByID(ctx context.Context, id string) (*entity.Chapter, error) { - return ch.chapterRepo.FindByID(ctx, id) -} - -func (ch *service) UpdateChapter(ctx context.Context, id string, chapter *entity.Chapter) (*entity.Chapter, error) { - return ch.chapterRepo.Update(ctx, id, chapter) -} - -func (ch *service) DeleteChapter(ctx context.Context, id string) (*entity.Chapter, error) { - chapter, err := ch.GetChapterByID(ctx, id) - if err != nil { - return nil, errors.Join(errors.New("chapter not found"), err) - } - err = ch.chapterRepo.HardDelete(ctx, id) - if err != nil { - return nil, err - } - return chapter, err -} - -func (ch *service) ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*entity.Chapter, error) { - return ch.chapterRepo.List(ctx, limit, offset) -} - -func (ch *service) GetChapterByBookID(ctx context.Context, bookID string) ([]*entity.Chapter, error) { - return ch.chapterRepo.FindByKV(ctx, "book_id", bookID) -} diff --git a/bookback/internal/domain/services/chapter_service.go b/bookback/internal/domain/services/chapter_service.go new file mode 100644 index 0000000..239bec5 --- /dev/null +++ b/bookback/internal/domain/services/chapter_service.go @@ -0,0 +1,95 @@ +package services + +import ( + "context" + "errors" + "fmt" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" +) + +type ChapterService interface { + CreateChapter(ctx context.Context, request models.CreateChapterRequest) (*models.Chapter, error) + GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) + UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) + DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) + ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) + + GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) +} + +type chapterService struct { + chapterRepo SimpleRepo[*entity.Chapter] +} + +func NewChapterService(chapterRepo SimpleRepo[*entity.Chapter]) ChapterService { + return &chapterService{chapterRepo: chapterRepo} +} + +func (ch *chapterService) CreateChapter(ctx context.Context, request models.CreateChapterRequest) (*models.Chapter, error) { + chapter := converter.ChapterModelToEntity(request.Chapter) + + id, err := ch.chapterRepo.Create(ctx, chapter) + if err != nil { + return nil, err + } + + var newChapter *models.Chapter + newChapter, err = ch.GetChapterByID(ctx, id) + if err != nil { + fmt.Println(err) + return nil, err + } + + return newChapter, nil +} + +func (ch *chapterService) GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) { + chapter, err := ch.chapterRepo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + return converter.ChapterEntityToModel(chapter), nil +} + +func (ch *chapterService) UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) { + chapter := converter.ChapterModelToEntity(request.Chapter) + + updatedChapter, err := ch.chapterRepo.Update(ctx, id, chapter) + if err != nil { + return nil, err + } + + return converter.ChapterEntityToModel(updatedChapter), err +} + +func (ch *chapterService) DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) { + chapter, err := ch.GetChapterByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("chapter not found"), err) + } + err = ch.chapterRepo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return chapter, err +} + +func (ch *chapterService) ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) { + chapters, err := ch.chapterRepo.List(ctx, limit, offset) + if err != nil { + return nil, err + } + + return converter.ChaptersEntityToModel(chapters), nil +} + +func (ch *chapterService) GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) { + chapters, err := ch.chapterRepo.FindByKV(ctx, "book_id", bookID) + if err != nil { + return nil, err + } + return converter.ChaptersEntityToModel(chapters), nil +} diff --git a/bookback/internal/domain/services/mapvariables/interface.go b/bookback/internal/domain/services/mapvariables/interface.go deleted file mode 100644 index 57dec97..0000000 --- a/bookback/internal/domain/services/mapvariables/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package mapvariables - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repository interface { - Create(ctx context.Context, chapter *entity.MapVariable) (string, error) - FindByID(ctx context.Context, id string) (*entity.MapVariable, error) - Update(ctx context.Context, id string, chapter *entity.MapVariable) (*entity.MapVariable, error) - HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.MapVariable, error) - FindByKV(ctx context.Context, key string, value any) ([]*entity.MapVariable, error) -} diff --git a/bookback/internal/domain/services/mapvariables/service.go b/bookback/internal/domain/services/mapvariables/service.go deleted file mode 100644 index 1db9de0..0000000 --- a/bookback/internal/domain/services/mapvariables/service.go +++ /dev/null @@ -1,75 +0,0 @@ -package mapvariables - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Service interface { - CreateMapVariable(ctx context.Context, variable *entity.MapVariable) (*entity.MapVariable, error) - GetMapVariableByID(ctx context.Context, id string) (*entity.MapVariable, error) - UpdateMapVariable(ctx context.Context, id string, variable *entity.MapVariable) (*entity.MapVariable, error) - DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) - - GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*entity.MapVariable, error) - GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*entity.MapVariable, error) - GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*entity.MapVariable, error) - GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*entity.MapVariable, error) -} - -type service struct { - repo Repository -} - -func NewService(repo Repository) Service { - return &service{repo} -} - -func (s *service) CreateMapVariable(ctx context.Context, variable *entity.MapVariable) (*entity.MapVariable, error) { - id, err := s.repo.Create(ctx, variable) - if err != nil { - return nil, err - } - variable.ID = id - - return variable, err -} - -func (s *service) GetMapVariableByID(ctx context.Context, id string) (*entity.MapVariable, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *service) UpdateMapVariable(ctx context.Context, id string, variable *entity.MapVariable) (*entity.MapVariable, error) { - return s.repo.Update(ctx, id, variable) -} - -func (s *service) DeleteMapVariable(ctx context.Context, id string) (*entity.MapVariable, error) { - mapVariable, err := s.GetMapVariableByID(ctx, id) - if err != nil { - return nil, errors.Join(errors.New("MapVariable not found"), err) - } - - err = s.repo.HardDelete(ctx, id) - if err != nil { - return nil, err - } - - return mapVariable, err -} - -func (s *service) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*entity.MapVariable, error) { - return s.repo.FindByKV(ctx, "map_id", mapID) -} - -func (s *service) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*entity.MapVariable, error) { - return s.repo.FindByKV(ctx, "chapter_id", chapterID) -} - -func (s *service) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*entity.MapVariable, error) { - return s.repo.FindByKV(ctx, "page_id", pageID) -} - -func (s *service) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*entity.MapVariable, error) { - return s.repo.FindByKV(ctx, "paragraph_id", paragraphID) -} diff --git a/bookback/internal/domain/services/mapvariables_service.go b/bookback/internal/domain/services/mapvariables_service.go new file mode 100644 index 0000000..352abec --- /dev/null +++ b/bookback/internal/domain/services/mapvariables_service.go @@ -0,0 +1,114 @@ +package services + +import ( + "context" + "errors" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" +) + +type MapVariablesService interface { + CreateMapVariable(ctx context.Context, request models.CreateMapVariableRequest) (*models.MapVariable, error) + GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) + UpdateMapVariable(ctx context.Context, id string, variable models.UpdateMapVariableRequest) (*models.MapVariable, error) + DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) + + GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) +} + +type mapVariablesService struct { + repo SimpleRepo[*entity.MapVariable] +} + +func NewMapVariablesService(repo SimpleRepo[*entity.MapVariable]) MapVariablesService { + return &mapVariablesService{repo} +} + +func (s *mapVariablesService) CreateMapVariable(ctx context.Context, request models.CreateMapVariableRequest) (*models.MapVariable, error) { + variable := converter.MapVariableModelToEntity(request.MapVariable) + + id, err := s.repo.Create(ctx, variable) + if err != nil { + return nil, err + } + + return s.GetMapVariableByID(ctx, id) +} + +func (s *mapVariablesService) GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) { + variable, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + return converter.MapVariableEntityToModel(variable), nil +} + +func (s *mapVariablesService) UpdateMapVariable(ctx context.Context, id string, request models.UpdateMapVariableRequest) (*models.MapVariable, error) { + mapVariable := converter.MapVariableModelToEntity(request.MapVariable) + + updatedVariable, err := s.repo.Update(ctx, id, mapVariable) + if err != nil { + return nil, err + } + + return converter.MapVariableEntityToModel(updatedVariable), nil +} + +func (s *mapVariablesService) DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) { + mapVariable, err := s.GetMapVariableByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("MapVariable not found"), err) + } + + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + + return mapVariable, err +} + +func (s *mapVariablesService) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) { + variable, err := s.repo.FindByKV(ctx, "map_id", mapID) + + if err != nil { + return nil, err + } + + return converter.MapVariablesEntityToModel(variable), nil +} + +func (s *mapVariablesService) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) { + variable, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) + + if err != nil { + return nil, err + } + + return converter.MapVariablesEntityToModel(variable), nil +} + +func (s *mapVariablesService) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) { + variable, err := s.repo.FindByKV(ctx, "page_id", pageID) + + if err != nil { + return nil, err + } + + return converter.MapVariablesEntityToModel(variable), nil +} + +func (s *mapVariablesService) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) { + variable, err := s.repo.FindByKV(ctx, "paragraph_id", paragraphID) + + if err != nil { + return nil, err + } + + return converter.MapVariablesEntityToModel(variable), nil +} diff --git a/bookback/internal/domain/services/page/interface.go b/bookback/internal/domain/services/page/interface.go deleted file mode 100644 index 151a1f9..0000000 --- a/bookback/internal/domain/services/page/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package page - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repository interface { - Create(ctx context.Context, page *entity.Page) (string, error) - FindByID(ctx context.Context, id string) (*entity.Page, error) - Update(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) - HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) - FindByKV(ctx context.Context, key string, value any) ([]*entity.Page, error) -} diff --git a/bookback/internal/domain/services/page/service.go b/bookback/internal/domain/services/page/service.go deleted file mode 100644 index 4d50d10..0000000 --- a/bookback/internal/domain/services/page/service.go +++ /dev/null @@ -1,63 +0,0 @@ -package page - -import ( - "context" - "errors" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Service interface { - CreatePage(ctx context.Context, page *entity.Page) (*entity.Page, error) - GetPageByID(ctx context.Context, id string) (*entity.Page, error) - UpdatePage(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) - DeletePage(ctx context.Context, id string) (*entity.Page, error) - ListPages(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) - - GetPagesByChapterID(ctx context.Context, chapterID string) ([]*entity.Page, error) -} - -type service struct { - repo Repository -} - -func NewService(repo Repository) Service { - return &service{repo} -} - -func (s *service) CreatePage(ctx context.Context, page *entity.Page) (*entity.Page, error) { - id, err := s.repo.Create(ctx, page) - if err != nil { - return nil, err - } - page.ID = id - - return page, err -} - -func (s *service) GetPageByID(ctx context.Context, id string) (*entity.Page, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *service) UpdatePage(ctx context.Context, id string, page *entity.Page) (*entity.Page, error) { - return s.repo.Update(ctx, id, page) -} - -func (s *service) DeletePage(ctx context.Context, id string) (*entity.Page, error) { - page, err := s.GetPageByID(ctx, id) - if err != nil { - return nil, errors.Join(errors.New("page not found"), err) - } - err = s.repo.HardDelete(ctx, id) - if err != nil { - return nil, err - } - return page, err -} - -func (s *service) ListPages(ctx context.Context, limit uint64, offset uint64) ([]*entity.Page, error) { - return s.repo.List(ctx, limit, offset) -} - -func (s *service) GetPagesByChapterID(ctx context.Context, chapterID string) ([]*entity.Page, error) { - return s.repo.FindByKV(ctx, "chapter_id", chapterID) -} diff --git a/bookback/internal/domain/services/page_service.go b/bookback/internal/domain/services/page_service.go new file mode 100644 index 0000000..3816404 --- /dev/null +++ b/bookback/internal/domain/services/page_service.go @@ -0,0 +1,88 @@ +package services + +import ( + "context" + "errors" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" +) + +type PageService interface { + CreatePage(ctx context.Context, request models.CreatePageRequest) (*models.Page, error) + GetPageByID(ctx context.Context, id string) (*models.Page, error) + UpdatePage(ctx context.Context, id string, prequest models.UpdatePageRequest) (*models.Page, error) + DeletePage(ctx context.Context, id string) (*models.Page, error) + ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) + + GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) +} + +type pageService struct { + repo SimpleRepo[*entity.Page] +} + +func NewPageService(repo SimpleRepo[*entity.Page]) PageService { + return &pageService{repo} +} + +func (s *pageService) CreatePage(ctx context.Context, request models.CreatePageRequest) (*models.Page, error) { + page := converter.PageModelToEntity(request.Page) + + id, err := s.repo.Create(ctx, page) + if err != nil { + return nil, err + } + + return s.GetPageByID(ctx, id) +} + +func (s *pageService) GetPageByID(ctx context.Context, id string) (*models.Page, error) { + page, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + return converter.PageEntityToModel(page), nil +} + +func (s *pageService) UpdatePage(ctx context.Context, id string, request models.UpdatePageRequest) (*models.Page, error) { + page := converter.PageModelToEntity(request.Page) + + updatedPage, err := s.repo.Update(ctx, id, page) + if err != nil { + return nil, err + } + + return converter.PageEntityToModel(updatedPage), nil +} + +func (s *pageService) DeletePage(ctx context.Context, id string) (*models.Page, error) { + page, err := s.GetPageByID(ctx, id) + if err != nil { + return nil, errors.Join(errors.New("page not found"), err) + } + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return page, err +} + +func (s *pageService) ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) { + pages, err := s.repo.List(ctx, limit, offset) + if err != nil { + return nil, err + } + + return converter.PagesEntityToModel(pages), nil +} + +func (s *pageService) GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) { + pages, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) + if err != nil { + return nil, err + } + + return converter.PagesEntityToModel(pages), nil +} diff --git a/bookback/internal/domain/services/paragraph/interface.go b/bookback/internal/domain/services/paragraph/interface.go deleted file mode 100644 index e779c28..0000000 --- a/bookback/internal/domain/services/paragraph/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package paragraph - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Repository interface { - List(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) - Create(ctx context.Context, paragraph *entity.Paragraph) (string, error) - FindByID(ctx context.Context, id string) (*entity.Paragraph, error) - Update(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) - HardDelete(ctx context.Context, id string) error - FindByKV(ctx context.Context, key string, value interface{}) ([]*entity.Paragraph, error) -} diff --git a/bookback/internal/domain/services/paragraph/service.go b/bookback/internal/domain/services/paragraph/service.go deleted file mode 100644 index b5e4172..0000000 --- a/bookback/internal/domain/services/paragraph/service.go +++ /dev/null @@ -1,62 +0,0 @@ -package paragraph - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Service interface { - CreateParagraph(ctx context.Context, paragraph *entity.Paragraph) (*entity.Paragraph, error) - GetParagraphByID(ctx context.Context, id string) (*entity.Paragraph, error) - UpdateParagraph(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) - DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) - ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) - - GetParagraphsByPageID(ctx context.Context, pageID string) ([]*entity.Paragraph, error) -} - -type service struct { - repo Repository -} - -func NewService(repo Repository) Service { - return &service{repo} -} - -func (s *service) CreateParagraph(ctx context.Context, paragraph *entity.Paragraph) (*entity.Paragraph, error) { - id, err := s.repo.Create(ctx, paragraph) - if err != nil { - return nil, err - } - paragraph.ID = id - - return paragraph, err -} - -func (s *service) GetParagraphByID(ctx context.Context, id string) (*entity.Paragraph, error) { - return s.repo.FindByID(ctx, id) -} - -func (s *service) UpdateParagraph(ctx context.Context, id string, paragraph *entity.Paragraph) (*entity.Paragraph, error) { - return s.repo.Update(ctx, id, paragraph) -} - -func (s *service) DeleteParagraph(ctx context.Context, id string) (*entity.Paragraph, error) { - paragraph, err := s.GetParagraphByID(ctx, id) - if err != nil { - return nil, err - } - err = s.repo.HardDelete(ctx, id) - if err != nil { - return nil, err - } - return paragraph, err -} - -func (s *service) ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*entity.Paragraph, error) { - return s.repo.List(ctx, limit, offset) -} - -func (s *service) GetParagraphsByPageID(ctx context.Context, pageID string) ([]*entity.Paragraph, error) { - return s.repo.FindByKV(ctx, "page_id", pageID) -} diff --git a/bookback/internal/domain/services/paragraph_service.go b/bookback/internal/domain/services/paragraph_service.go new file mode 100644 index 0000000..0ad7d2d --- /dev/null +++ b/bookback/internal/domain/services/paragraph_service.go @@ -0,0 +1,87 @@ +package services + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/converter" +) + +type ParagraphService interface { + CreateParagraph(ctx context.Context, request models.CreateParagraphRequest) (*models.Paragraph, error) + GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) + UpdateParagraph(ctx context.Context, id string, request models.UpdateParagraphRequest) (*models.Paragraph, error) + DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) + ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) + + GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) +} + +type paragraphService struct { + repo SimpleRepo[*entity.Paragraph] +} + +func NewParagraphService(repo SimpleRepo[*entity.Paragraph]) ParagraphService { + return ¶graphService{repo} +} + +func (s *paragraphService) CreateParagraph(ctx context.Context, request models.CreateParagraphRequest) (*models.Paragraph, error) { + paragraph := converter.ParagraphModelToEntity(request.Paragraph) + + id, err := s.repo.Create(ctx, paragraph) + if err != nil { + return nil, err + } + + return s.GetParagraphByID(ctx, id) +} + +func (s *paragraphService) GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) { + paragraph, err := s.repo.FindByID(ctx, id) + if err != nil { + return nil, err + } + + return converter.ParagraphEntityToModel(paragraph), nil +} + +func (s *paragraphService) UpdateParagraph(ctx context.Context, id string, request models.UpdateParagraphRequest) (*models.Paragraph, error) { + paragraph := converter.ParagraphModelToEntity(request.Paragraph) + + updatedParagraph, err := s.repo.Update(ctx, id, paragraph) + if err != nil { + return nil, err + } + + return converter.ParagraphEntityToModel(updatedParagraph), nil +} + +func (s *paragraphService) DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) { + paragraph, err := s.GetParagraphByID(ctx, id) + if err != nil { + return nil, err + } + err = s.repo.HardDelete(ctx, id) + if err != nil { + return nil, err + } + return paragraph, err +} + +func (s *paragraphService) ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) { + paragraph, err := s.repo.List(ctx, limit, offset) + if err != nil { + return nil, err + } + + return converter.ParagraphsEntityToModel(paragraph), nil +} + +func (s *paragraphService) GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) { + paragraphs, err := s.repo.FindByKV(ctx, "page_id", pageID) + if err != nil { + return nil, err + } + + return converter.ParagraphsEntityToModel(paragraphs), nil +} diff --git a/bookback/internal/infrastructure/http/middleware/middleware.go b/bookback/internal/infrastructure/http/middleware/middleware.go index 32189d6..e5e2f86 100644 --- a/bookback/internal/infrastructure/http/middleware/middleware.go +++ b/bookback/internal/infrastructure/http/middleware/middleware.go @@ -2,7 +2,7 @@ package middleware import ( "fmt" - "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" "github.com/labstack/echo/v4" "time" ) diff --git a/bookback/internal/infrastructure/http/v1/book/config.go b/bookback/internal/infrastructure/http/v1/book/config.go deleted file mode 100644 index 1f4fa44..0000000 --- a/bookback/internal/infrastructure/http/v1/book/config.go +++ /dev/null @@ -1,6 +0,0 @@ -package book - -// Тут будет конфигурация для контроллера книг. -// Права доступа, роли, константы и т.п. - -const PathPrefix = "/api/v1/books" diff --git a/bookback/internal/infrastructure/http/v1/book/controller.go b/bookback/internal/infrastructure/http/v1/book/controller.go deleted file mode 100644 index 4aa3be0..0000000 --- a/bookback/internal/infrastructure/http/v1/book/controller.go +++ /dev/null @@ -1,146 +0,0 @@ -package book - -import ( - "context" - service "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -// Controller структура для HTTP-контроллера книг. -type Controller struct { - Service service.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -// NewController создает новый экземпляр Controller. -func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// ListBooks обрабатывает запросы на получение списка книг. -// @router /books [post] -// @summary Получить список книг -// @description Извлекает список всех книг -// @tags Книги -// @produce application/json -// @success 200 {array} entity.Book -// @failure 500 {object} string -func (bc *Controller) ListBooks(c echo.Context) error { - var request models.RequestBook - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - books, err := bc.Service.ListBooks(bc.Ctx, request.Options.Limit, request.Options.Offset) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, models.WebResponse[[]*models.Book]{Data: books, Status: "ok"}) -} - -// CreateBook обрабатывает создание новой книги. -// @router /books [post] -// @summary Создать книгу -// @description Создает новую книгу -// @tags Книги -// @accept application/json -// @produce application/json -// @param book body entity.Book true "Book object" -// @success 201 {object} entity.Book -// @failure 400 {object} string -// @failure 500 {object} string -func (bc *Controller) CreateBook(c echo.Context) error { - var request models.CreateBookRequest - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - createdBook, err := bc.Service.CreateBook(bc.Ctx, request) - if err != nil { - return ErrorBookNotCreated - } - return c.JSON(http.StatusCreated, models.WebResponse[*models.Book]{Data: createdBook, Status: "created"}) -} - -// GetBookByID обрабатывает запросы на получение книги по ID. -// @router /books/{id} [get] -// @summary Получить книгу по ID -// @description Извлекает книгу по ее ID -// @tags Книги -// @param id path string true "Book ID" -// @produce application/json -// @success 200 {object} entity.Book -// @failure 400 {object} string -// @failure 404 {object} string -// @failure 500 {object} string -func (bc *Controller) GetBookByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - book, err := bc.Service.GetBookByID(bc.Ctx, id) - if err != nil { - return ErrorBookNotFound - } - - return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "ok"}) -} - -// UpdateBook обрабатывает обновление книги. -// @router /books/{id} [put] -// @summary Обновить книгу -// @description Обновляет книгу по ее ID -// @tags Книги -// @accept application/json -// @produce application/json -// @param id path string true "Book ID" -// @param book body entity.Book true "Book object" -// @success 200 {object} responseSingleModel -// @failure 400 {object} string -// @failure 404 {object} string -// @failure 500 {object} string -func (bc *Controller) UpdateBook(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - var request models.UpdateBookRequest - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: updatedBook, Status: "updated"}) -} - -// DeleteBook обрабатывает удаление книги по ID. -// @router /books/{id} [delete] -// @summary Удалить книгу -// @description Удаляет книгу по ее ID -// @tags Книги -// @param id path string true "Book ID" -// @success 204 {object} entity.Book -// @failure 400 {object} string -func (bc *Controller) DeleteBook(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - book, err := bc.Service.DeleteBook(bc.Ctx, id) - if err != nil { - return ErrorDeleteBook - } - return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "deleted"}) -} diff --git a/bookback/internal/infrastructure/http/v1/book/errors.go b/bookback/internal/infrastructure/http/v1/book/errors.go deleted file mode 100644 index e9a0a6b..0000000 --- a/bookback/internal/infrastructure/http/v1/book/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package book - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -// Возможные ошибки при работе с книгами. - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorBookNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Книга не найдена", - ) - ErrorBookNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания книги. Книга с такими параметрами уже существует.", - ) - ErrorDeleteBook = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления книги", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/book/routes.go b/bookback/internal/infrastructure/http/v1/book/routes.go deleted file mode 100644 index 73433ba..0000000 --- a/bookback/internal/infrastructure/http/v1/book/routes.go +++ /dev/null @@ -1,33 +0,0 @@ -package book - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/book" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -// SetBookController регистрирует контроллер книг в маршрутизаторе. -func SetBookController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - repo := pgrepo.NewBookRepository(database) - service := book.NewService(repo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -// RegisterRoutes регистрирует маршруты для обработки запросов к книгам. -func (bc *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(bc.Metrics)) - - group.POST("", bc.ListBooks) - group.POST("", bc.CreateBook) - group.GET("/:id", bc.GetBookByID) - group.PUT("/:id", bc.UpdateBook) - group.DELETE("/:id", bc.DeleteBook) -} diff --git a/bookback/internal/infrastructure/http/v1/bookevents/config.go b/bookback/internal/infrastructure/http/v1/bookevents/config.go deleted file mode 100644 index 08ef6ae..0000000 --- a/bookback/internal/infrastructure/http/v1/bookevents/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package bookevents - -const PathPrefix = "/api/v1/bookevents" diff --git a/bookback/internal/infrastructure/http/v1/bookevents/controller.go b/bookback/internal/infrastructure/http/v1/bookevents/controller.go deleted file mode 100644 index 95febf8..0000000 --- a/bookback/internal/infrastructure/http/v1/bookevents/controller.go +++ /dev/null @@ -1,212 +0,0 @@ -package bookevents - -import ( - "context" - service "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -type Controller struct { - Service service.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -// NewController создает новый экземпляр Controller. -func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// GetBookEventByID обрабатывает запросы на получение события книги по идентификатору. -// @router /bookevents/{id} [get] -// @summary Получить событие книги по идентификатору -// @description Извлекает событие книги по идентификатору -// @tags События книги -// @produce application/json -// @param id path string true "ID события книги" -// @success 200 {object} entity.BookEvent -// @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - event, err := bec.Service.GetBookEventByID(bec.Ctx, id) - if err != nil { - return ErrorBookEventNotFound - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", BookEvent: event}) -} - -// UpdateBookEvent обрабатывает запросы на обновление события книги. -// @router /bookevents/{id} [put] -// @summary Обновить событие книги -// @description Обновляет событие книги -// @tags События книги -// @accept application/json -// @produce application/json -// @param id path string true "ID события книги" -// @param event body entity.BookEvent true "BookEvent object" -// @success 200 {object} entity.BookEvent -// @failure 400 {object} config.HTTPError -func (bec *Controller) UpdateBookEvent(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - updatedEvent, err := bec.Service.UpdateBookEvent(bec.Ctx, id, request.BookEvents) - if err != nil { - return ErrorUnknown - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", BookEvent: updatedEvent}) -} - -// DeleteBookEvent обрабатывает запросы на удаление события книги. -// @router /bookevents/{id} [delete] -// @summary Удалить событие книги -// @description Удаляет событие книги -// @tags События книги -// @produce application/json -// @param id path string true "ID события книги" -// @success 204 -// @failure 404 {object} config.HTTPError -func (bec *Controller) DeleteBookEvent(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - deletedEvent, err := bec.Service.DeleteBookEvent(bec.Ctx, id) - if err != nil { - return ErrorDeleteBookEvent - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", BookEvent: deletedEvent}) -} - -// CreateBookEvent обрабатывает запросы на создание события книги. -// @router /bookevents [post] -// @summary Создать событие книги -// @description Создает событие книги -// @tags События книги -// @accept application/json -// @produce application/json -// @param event body entity.BookEvent true "BookEvent object" -// @success 201 {object} entity.BookEvent -// @failure 400 {object} config.HTTPError -func (bec *Controller) CreateBookEvent(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - createdEvent, err := bec.Service.CreateBookEvent(bec.Ctx, request.BookEvents) - if err != nil { - return ErrorBookEventNotCreated - } - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", BookEvent: createdEvent}) -} - -// GetBookEventsByBookID обрабатывает запросы на получение событий книги по ID книги. -// @router /bookevents/book/{id} [get] -// @summary Получить события книги по ID книги -// @description Извлекает события книги по ID книги -// @tags События книги -// @produce application/json -// @param id path string true "ID книги" -// @success 200 {object} []entity.BookEvent -// @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByBookID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - events, err := bec.Service.GetBookEventsByBookID(bec.Ctx, id) - if err != nil { - return ErrorBookEventNotFound - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) -} - -// GetBookEventsByChapterID обрабатывает запросы на получение событий книги по ID главы. -// @router /bookevents/chapter/{id} [get] -// @summary Получить события книги по ID главы -// @description Извлекает события книги по ID главы -// @tags События книги -// @produce application/json -// @param id path string true "ID главы" -// @success 200 {object} []entity.BookEvent -// @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByChapterID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - events, err := bec.Service.GetBookEventsByChapterID(bec.Ctx, id) - if err != nil { - return ErrorBookEventNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) -} - -// GetBookEventsByPageID обрабатывает запросы на получение событий книги по ID страницы. -// @router /bookevents/page/{id} [get] -// @summary Получить события книги по ID страницы -// @description Извлекает события книги по ID страницы -// @tags События книги -// @produce application/json -// @param id path string true "ID страницы" -// @success 200 {object} []entity.BookEvent -// @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByPageID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - events, err := bec.Service.GetBookEventsByPageID(bec.Ctx, id) - if err != nil { - return ErrorBookEventNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) -} - -// GetBookEventsByParagraphID обрабатывает запросы на получение событий книги по ID параграфа. -// @router /bookevents/paragraph/{id} [get] -// @summary Получить события книги по ID параграфа -// @description Извлекает события книги по ID параграфа -// @tags События книги -// @produce application/json -// @param id path string true "ID параграфа" -// @success 200 {object} []entity.BookEvent -// @failure 404 {object} config.HTTPError -func (bec *Controller) GetBookEventsByParagraphID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - events, err := bec.Service.GetBookEventsByParagraphID(bec.Ctx, id) - if err != nil { - return ErrorBookEventNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", BookEvents: events}) -} diff --git a/bookback/internal/infrastructure/http/v1/bookevents/errors.go b/bookback/internal/infrastructure/http/v1/bookevents/errors.go deleted file mode 100644 index f5151aa..0000000 --- a/bookback/internal/infrastructure/http/v1/bookevents/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package bookevents - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -// Возможные ошибки при работе с событиями книг. - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorBookEventNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Событие книги не найдено", - ) - ErrorBookEventNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания события книги. Событие с такими параметрами уже существует.", - ) - ErrorDeleteBookEvent = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления события книги", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/bookevents/models.go b/bookback/internal/infrastructure/http/v1/bookevents/models.go deleted file mode 100644 index a8dedaa..0000000 --- a/bookback/internal/infrastructure/http/v1/bookevents/models.go +++ /dev/null @@ -1,22 +0,0 @@ -package bookevents - -import ( - "github.com/SShlykov/zeitment/bookback/internal/domain/entity" -) - -type Options struct{} - -type requestModel struct { - Options Options `json:"options,omitempty"` - BookEvents *entity.BookEvent `json:"book_events,omitempty"` -} - -type responseSingleModel struct { - BookEvent *entity.BookEvent `json:"book_event"` - Status string `json:"status"` -} - -type responseListModel struct { - BookEvents []*entity.BookEvent `json:"book_events"` - Status string `json:"status"` -} diff --git a/bookback/internal/infrastructure/http/v1/bookevents/routes.go b/bookback/internal/infrastructure/http/v1/bookevents/routes.go deleted file mode 100644 index 8f2c1ed..0000000 --- a/bookback/internal/infrastructure/http/v1/bookevents/routes.go +++ /dev/null @@ -1,34 +0,0 @@ -package bookevents - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - repo := pgrepo.NewBookEventsRepository(database) - service := bookevents.NewService(repo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (bec *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(bec.Metrics)) - - group.POST("", bec.CreateBookEvent) - group.GET("/:id", bec.GetBookEventByID) - group.PUT("/:id", bec.UpdateBookEvent) - group.DELETE("/:id", bec.DeleteBookEvent) - group.GET("/book/:id", bec.GetBookEventsByBookID) - group.GET("/chapter/:id", bec.GetBookEventsByChapterID) - group.GET("/page/:id", bec.GetBookEventsByPageID) - group.GET("/paragraph/:id", bec.GetBookEventsByParagraphID) -} diff --git a/bookback/internal/infrastructure/http/v1/chapter/config.go b/bookback/internal/infrastructure/http/v1/chapter/config.go deleted file mode 100644 index de90e41..0000000 --- a/bookback/internal/infrastructure/http/v1/chapter/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package chapter - -const pathPrefix = "/api/v1/chapters" diff --git a/bookback/internal/infrastructure/http/v1/chapter/controller.go b/bookback/internal/infrastructure/http/v1/chapter/controller.go deleted file mode 100644 index 49b27e3..0000000 --- a/bookback/internal/infrastructure/http/v1/chapter/controller.go +++ /dev/null @@ -1,161 +0,0 @@ -package chapter - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -type Controller struct { - Service chapter.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -// NewController создает новый экземпляр Controller. -func NewController(srv chapter.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// ListChapters список глав -// @router /chapters [get] -// @summary Получить список глав -// @description Извлекает список всех глав -// @tags Главы -// @produce application/json -// @success 200 {array} entity.Chapter -// @failure 500 {object} config.HTTPError -func (ch *Controller) ListChapters(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - chapters, err := ch.Service.ListChapters(ch.Ctx, request.Options.Limit, request.Options.Offset) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Chapters: chapters}) -} - -// CreateChapter создание новой главы -// @router /chapters [post] -// @summary Создать главу -// @description Создает новую главу -// @tags Главы -// @accept application/json -// @produce application/json -// @param chapter body entity.Chapter true "Chapter object" -// @success 201 {object} entity.Chapter -// @failure 400 {object} config.HTTPError -func (ch *Controller) CreateChapter(c echo.Context) error { - var chap requestModel - if err := c.Bind(&chap); err != nil { - return ErrorValidationFailed - } - - createdChapter, err := ch.Service.CreateChapter(ch.Ctx, chap.Chapter) - if err != nil { - return ErrorChapterNotCreated - } - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Chapter: createdChapter}) -} - -// GetChapterByID получение главы по ID -// @router /chapters/{id} [get] -// @summary Получить главу по ID -// @description Извлекает главу по ее ID -// @tags Главы -// @param id path string true "ID главы" -// @produce application/json -// @success 200 {object} entity.Chapter -// @failure 404 {object} config.HTTPError -func (ch *Controller) GetChapterByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - chapt, err := ch.Service.GetChapterByID(ch.Ctx, id) - if err != nil { - return ErrorChapterNotFound - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Chapter: chapt}) -} - -// UpdateChapter обновление главы -// @router /chapters/{id} [put] -// @summary Обновить главу -// @description Обновляет главу по ее ID -// @tags Главы -// @accept application/json -// @produce application/json -// @param id path string true "ID главы" -// @param chapter body entity.Chapter true "Chapter object" -// @success 200 {object} entity.Chapter -// @failure 400 {object} config.HTTPError -func (ch *Controller) UpdateChapter(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - var request requestModel - if err := c.Bind(&request); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, ErrorValidationFailed) - } - - updatedChapter, err := ch.Service.UpdateChapter(ch.Ctx, id, request.Chapter) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Chapter: updatedChapter}) -} - -// DeleteChapter удаление главы -// @router /chapters/{id} [delete] -// @summary Удалить главу -// @description Удаляет главу по ее ID -// @tags Главы -// @param id path string true "ID главы" -// @produce application/json -// @success 200 {object} entity.Chapter -// @failure 406 {object} config.HTTPError -func (ch *Controller) DeleteChapter(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - chapt, err := ch.Service.DeleteChapter(ch.Ctx, id) - if err != nil { - return echo.NewHTTPError(http.StatusNotAcceptable, ErrorDeleteChapter) - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Chapter: chapt}) -} - -// GetChapterByBookID получение глав по ID книги -// @router /chapters/book/{id} [get] -// @summary Получить главы по ID книги -// @description Извлекает главы по ID книги -// @tags Главы -// @param id path string true "ID книги" -// @produce application/json -// @success 200 {array} entity.Chapter -// @failure 404 {object} config.HTTPError -func (ch *Controller) GetChapterByBookID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - chapters, err := ch.Service.GetChapterByBookID(ch.Ctx, id) - if err != nil { - return ErrorDeleteChapter - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Chapters: chapters}) -} diff --git a/bookback/internal/infrastructure/http/v1/chapter/errors.go b/bookback/internal/infrastructure/http/v1/chapter/errors.go deleted file mode 100644 index 0e98c3e..0000000 --- a/bookback/internal/infrastructure/http/v1/chapter/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package chapter - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -// Возможные ошибки при работе с главами книги. - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorChapterNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Глава не найдена", - ) - ErrorChapterNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания главы. Глава с такими параметрами уже существует.", - ) - ErrorDeleteChapter = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления главы", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/chapter/models.go b/bookback/internal/infrastructure/http/v1/chapter/models.go deleted file mode 100644 index 795d058..0000000 --- a/bookback/internal/infrastructure/http/v1/chapter/models.go +++ /dev/null @@ -1,23 +0,0 @@ -package chapter - -import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" - -type Options struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` -} - -type requestModel struct { - Options Options `json:"options,omitempty"` - Chapter *entity.Chapter `json:"chapter,omitempty"` -} - -type responseSingleModel struct { - Chapter *entity.Chapter `json:"chapter"` - Status string `json:"status"` -} - -type responseListModel struct { - Chapters []*entity.Chapter `json:"chapters"` - Status string `json:"status"` -} diff --git a/bookback/internal/infrastructure/http/v1/chapter/routes.go b/bookback/internal/infrastructure/http/v1/chapter/routes.go deleted file mode 100644 index c4bba5b..0000000 --- a/bookback/internal/infrastructure/http/v1/chapter/routes.go +++ /dev/null @@ -1,32 +0,0 @@ -package chapter - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/chapter" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetChapterController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - chapterRepo := pgrepo.NewChapterRepository(database) - service := chapter.NewService(chapterRepo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (ch *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(pathPrefix) - group.Use(middleware.MetricsLogger(ch.Metrics)) - - group.GET("", ch.ListChapters) - group.POST("", ch.CreateChapter) - group.GET("/:id", ch.GetChapterByID) - group.PUT("/:id", ch.UpdateChapter) - group.DELETE("/:id", ch.DeleteChapter) - group.GET("/book/:id", ch.GetChapterByBookID) -} diff --git a/bookback/internal/infrastructure/http/v1/controllers/book_controller.go b/bookback/internal/infrastructure/http/v1/controllers/book_controller.go new file mode 100644 index 0000000..15fe6af --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/book_controller.go @@ -0,0 +1,103 @@ +package controllers + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type bookService interface { + CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) + GetBookByID(ctx context.Context, id string) (*models.Book, error) + UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) + DeleteBook(ctx context.Context, id string) (*models.Book, error) + ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) +} + +// BookController структура для HTTP-контроллера книг. +type BookController struct { + Service bookService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +// NewBookController создает новый экземпляр Controller. +func NewBookController(srv bookService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *BookController { + return &BookController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (bc *BookController) ListBooks(c echo.Context) error { + var request models.RequestBook + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + books, err := bc.Service.ListBooks(bc.Ctx, request.Options.Limit, request.Options.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Book]{Data: books, Status: "ok"}) +} + +func (bc *BookController) GetBookByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + book, err := bc.Service.GetBookByID(bc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.BookNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "ok"}) +} + +func (bc *BookController) CreateBook(c echo.Context) error { + var request models.CreateBookRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + createdBook, err := bc.Service.CreateBook(bc.Ctx, request) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookNotCreated) + } + return c.JSON(http.StatusCreated, models.WebResponse[*models.Book]{Data: createdBook, Status: "created"}) +} + +func (bc *BookController) UpdateBook(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + var request models.UpdateBookRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: updatedBook, Status: "updated"}) +} + +func (bc *BookController) DeleteBook(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + book, err := bc.Service.DeleteBook(bc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookNotDeleted) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "deleted"}) +} diff --git a/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go b/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go new file mode 100644 index 0000000..2430183 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go @@ -0,0 +1,151 @@ +package controllers + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type bookEventService interface { + CreateBookEvent(ctx context.Context, request models.CreateBookEventRequest) (*models.BookEvent, error) + GetBookEventByID(ctx context.Context, id string) (*models.BookEvent, error) + UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) + DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) + + GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) +} + +// BookEventController структура для HTTP-контроллера событий книги. +type BookEventController struct { + Service bookEventService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +// NewBookEventController создает новый экземпляр BookEventController. +func NewBookEventController(srv bookEventService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *BookEventController { + return &BookEventController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (bec *BookEventController) GetBookEventByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + event, err := bec.Service.GetBookEventByID(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.BookEvent]{Data: event, Status: "ok"}) +} + +func (bec *BookEventController) CreateBookEvent(c echo.Context) error { + var request models.CreateBookEventRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + createdEvent, err := bec.Service.CreateBookEvent(bec.Ctx, request) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotCreated) + } + return c.JSON(http.StatusCreated, models.WebResponse[*models.BookEvent]{Data: createdEvent, Status: "created"}) +} + +func (bec *BookEventController) UpdateBookEvent(c echo.Context) error { + var request models.UpdateBookEventRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + event, err := bec.Service.UpdateBookEvent(bec.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.BookEvent]{Data: event, Status: "ok"}) +} + +func (bec *BookEventController) DeleteBookEvent(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + deletedEvent, err := bec.Service.DeleteBookEvent(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotDeleted) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.BookEvent]{Data: deletedEvent, Status: "deleted"}) +} + +func (bec *BookEventController) GetBookEventsByBookID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByBookID(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) +} + +func (bec *BookEventController) GetBookEventsByChapterID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByChapterID(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) +} + +func (bec *BookEventController) GetBookEventsByPageID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByPageID(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) +} + +func (bec *BookEventController) GetBookEventsByParagraphID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByParagraphID(bec.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) +} diff --git a/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go b/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go new file mode 100644 index 0000000..90823dc --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go @@ -0,0 +1,115 @@ +package controllers + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type chapterService interface { + CreateChapter(ctx context.Context, request models.CreateChapterRequest) (*models.Chapter, error) + GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) + UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) + DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) + ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) + GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) +} + +type ChapterController struct { + Service chapterService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +// NewChapterController создает новый экземпляр ChapterController. +func NewChapterController(srv chapterService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *ChapterController { + return &ChapterController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (ch *ChapterController) ListChapters(c echo.Context) error { + var request models.RequestChapter + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + chapters, err := ch.Service.ListChapters(ch.Ctx, request.Options.Limit, request.Options.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Chapter]{Data: chapters, Status: "ok"}) +} + +func (ch *ChapterController) CreateChapter(c echo.Context) error { + var chap models.CreateChapterRequest + if err := c.Bind(&chap); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + createdChapter, err := ch.Service.CreateChapter(ch.Ctx, chap) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ChapterNotCreated) + } + return c.JSON(http.StatusCreated, models.WebResponse[*models.Chapter]{Data: createdChapter, Status: "created"}) +} + +func (ch *ChapterController) GetChapterByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + chapter, err := ch.Service.GetChapterByID(ch.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.ChapterNotFound) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) +} + +func (ch *ChapterController) UpdateChapter(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + var request models.UpdateChapterRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed)) + } + + chapter, err := ch.Service.UpdateChapter(ch.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) +} + +func (ch *ChapterController) DeleteChapter(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + chapter, err := ch.Service.DeleteChapter(ch.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotAcceptable, errors.ChapterNotDeleted) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) +} + +func (ch *ChapterController) GetChapterByBookID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + chapters, err := ch.Service.GetChapterByBookID(ch.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.ChapterNotFound) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Chapter]{Data: chapters, Status: "ok"}) +} diff --git a/bookback/internal/infrastructure/http/v1/health/controller.go b/bookback/internal/infrastructure/http/v1/controllers/health_controller.go similarity index 56% rename from bookback/internal/infrastructure/http/v1/health/controller.go rename to bookback/internal/infrastructure/http/v1/controllers/health_controller.go index d80dec2..78ff55f 100644 --- a/bookback/internal/infrastructure/http/v1/health/controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/health_controller.go @@ -1,22 +1,22 @@ -package health +package controllers import ( "context" - "github.com/SShlykov/zeitment/bookback/internal/metrics" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" "github.com/labstack/echo/v4" "log/slog" "net/http" ) -type Controller struct { +type HealthController struct { Metrics metrics.Metrics Logger *slog.Logger Ctx context.Context } -// NewController создает новый экземпляр Controller. -func NewController(metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Metrics: metric, Logger: logger, Ctx: ctx} +// NewHealthController создает новый экземпляр Controller. +func NewHealthController(metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *HealthController { + return &HealthController{Metrics: metric, Logger: logger, Ctx: ctx} } // GetHealthCheck возвращает статус приложения. @@ -27,6 +27,6 @@ func NewController(metric metrics.Metrics, logger *slog.Logger, ctx context.Cont // @produce application/json // @success 200 {string} string "healthy" // @failure 500 {object} config.HTTPError -func (hc *Controller) GetHealthCheck(c echo.Context) error { +func (hc *HealthController) GetHealthCheck(c echo.Context) error { return c.JSON(http.StatusOK, "healthy") } diff --git a/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go b/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go new file mode 100644 index 0000000..ac7adf3 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go @@ -0,0 +1,154 @@ +package controllers + +import ( + "context" + "fmt" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type mapVariablesService interface { + CreateMapVariable(ctx context.Context, request models.CreateMapVariableRequest) (*models.MapVariable, error) + GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) + UpdateMapVariable(ctx context.Context, id string, request models.UpdateMapVariableRequest) (*models.MapVariable, error) + DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) + GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) +} + +// MapVariablesController структура для HTTP-контроллера книг. +type MapVariablesController struct { + Service mapVariablesService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +// NewMapVariablesController создает новый экземпляр MapVariablesController. +func NewMapVariablesController(srv mapVariablesService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *MapVariablesController { + return &MapVariablesController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (mvc *MapVariablesController) GetMapVariableByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variable, err := mvc.Service.GetMapVariableByID(mvc.Ctx, id) + if err != nil { + fmt.Println(err) + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.MapVariable]{Data: variable, Status: "ok"}) +} + +func (mvc *MapVariablesController) UpdateMapVariable(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + var request models.UpdateMapVariableRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variable, err := mvc.Service.UpdateMapVariable(mvc.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.MapVariable]{Data: variable, Status: "ok"}) +} + +func (mvc *MapVariablesController) DeleteMapVariable(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variable, err := mvc.Service.DeleteMapVariable(mvc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotDeleted) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.MapVariable]{Data: variable, Status: "ok"}) +} + +func (mvc *MapVariablesController) GetMapVariablesByBookID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByBookID(mvc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.MapVariable]{Data: variables, Status: "ok"}) +} + +func (mvc *MapVariablesController) GetMapVariablesByChapterID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByChapterID(mvc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.MapVariable]{Data: variables, Status: "ok"}) +} + +func (mvc *MapVariablesController) GetMapVariablesByPageID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByPageID(mvc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.MapVariable]{Data: variables, Status: "ok"}) +} + +func (mvc *MapVariablesController) GetMapVariablesByParagraphID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByParagraphID(mvc.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[[]*models.MapVariable]{Data: variables, Status: "ok"}) +} + +func (mvc *MapVariablesController) CreateMapVariable(c echo.Context) error { + var request models.CreateMapVariableRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variable, err := mvc.Service.CreateMapVariable(mvc.Ctx, request) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.MapVariablesNotCreated) + } + + return c.JSON(http.StatusCreated, models.WebResponse[*models.MapVariable]{Data: variable, Status: "ok"}) +} diff --git a/bookback/internal/infrastructure/http/v1/controllers/page_controller.go b/bookback/internal/infrastructure/http/v1/controllers/page_controller.go new file mode 100644 index 0000000..32bf58b --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/page_controller.go @@ -0,0 +1,115 @@ +package controllers + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type pageService interface { + CreatePage(ctx context.Context, request models.CreatePageRequest) (*models.Page, error) + GetPageByID(ctx context.Context, id string) (*models.Page, error) + UpdatePage(ctx context.Context, id string, request models.UpdatePageRequest) (*models.Page, error) + DeletePage(ctx context.Context, id string) (*models.Page, error) + ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) + GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) +} + +type PageController struct { + Service pageService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +func NewPageController(srv pageService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *PageController { + return &PageController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (p *PageController) ListPages(c echo.Context) error { + var request models.RequestBook + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + pages, err := p.Service.ListPages(p.Ctx, request.Options.Limit, request.Options.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Page]{Data: pages, Status: "ok"}) +} + +func (p *PageController) CreatePage(c echo.Context) error { + var request models.CreatePageRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + page, err := p.Service.CreatePage(p.Ctx, request) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.PageNotCreated) + } + return c.JSON(http.StatusCreated, models.WebResponse[*models.Page]{Data: page, Status: "created"}) +} + +func (p *PageController) GetPageByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + page, err := p.Service.GetPageByID(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.BookNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.Page]{Data: page, Status: "ok"}) +} + +func (p *PageController) UpdatePage(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + var request models.UpdatePageRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + page, err := p.Service.UpdatePage(p.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Page]{Data: page, Status: "updated"}) +} + +func (p *PageController) DeletePage(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + page, err := p.Service.DeletePage(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.PageNotDeleted) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Page]{Data: page, Status: "deleted"}) +} + +func (p *PageController) GetPagesByChapterID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + pages, err := p.Service.GetPagesByChapterID(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, errors.BookNotFound) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Page]{Data: pages, Status: "ok"}) +} diff --git a/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go b/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go new file mode 100644 index 0000000..5a8a7bf --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go @@ -0,0 +1,116 @@ +package controllers + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/errors" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" +) + +type paragraphService interface { + CreateParagraph(ctx context.Context, paragraph models.CreateParagraphRequest) (*models.Paragraph, error) + GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) + UpdateParagraph(ctx context.Context, id string, paragraph models.UpdateParagraphRequest) (*models.Paragraph, error) + DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) + ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) +} + +type ParagraphController struct { + Service paragraphService + Metrics metrics.Metrics + Logger *slog.Logger + Ctx context.Context +} + +// NewParagraphController создает новый экземпляр ParagraphController. +func NewParagraphController(srv paragraphService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *ParagraphController { + return &ParagraphController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} +} + +func (p *ParagraphController) ListParagraphs(c echo.Context) error { + var request models.RequestParagraph + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraphs, err := p.Service.ListParagraphs(p.Ctx, request.Options.Limit, request.Options.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Paragraph]{Data: paragraphs, Status: "ok"}) +} + +func (p *ParagraphController) CreateParagraph(c echo.Context) error { + var request models.CreateParagraphRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraph, err := p.Service.CreateParagraph(p.Ctx, request) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ParagraphNotCreated) + } + return c.JSON(http.StatusCreated, models.WebResponse[*models.Paragraph]{Data: paragraph, Status: "created"}) +} + +func (p *ParagraphController) GetParagraphByID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraph, err := p.Service.GetParagraphByID(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ParagraphNotFound) + } + + return c.JSON(http.StatusOK, models.WebResponse[*models.Paragraph]{Data: paragraph, Status: "ok"}) +} + +func (p *ParagraphController) UpdateParagraph(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + var request models.UpdateParagraphRequest + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraph, err := p.Service.UpdateParagraph(p.Ctx, id, request) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Paragraph]{Data: paragraph, Status: "ok"}) +} + +func (p *ParagraphController) DeleteParagraph(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraph, err := p.Service.DeleteParagraph(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ParagraphNotDeleted) + } + return c.JSON(http.StatusOK, models.WebResponse[*models.Paragraph]{Data: paragraph, Status: "deleted"}) +} + +func (p *ParagraphController) GetParagraphsByPageID(c echo.Context) error { + id := c.Param("id") + if id == "" { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraphs, err := p.Service.GetParagraphsByPageID(p.Ctx, id) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) + } + return c.JSON(http.StatusOK, models.WebResponse[[]*models.Paragraph]{Data: paragraphs, Status: "deleted"}) +} diff --git a/bookback/internal/infrastructure/http/v1/errors/errors.go b/bookback/internal/infrastructure/http/v1/errors/errors.go new file mode 100644 index 0000000..f4e1de6 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/errors/errors.go @@ -0,0 +1,24 @@ +package errors + +const ( + ValidationFailed = "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова." + Unknown = "Неизвестная ошибка" + BookNotFound = "Книга не найдена" + BookNotCreated = "Ошибка создания книги. Книга с такими параметрами уже существует." + BookNotDeleted = "Ошибка удаления книги" + BookEventNotFound = "Событие книги не найдено" + BookEventNotCreated = "Ошибка создания события книги. Событие с такими параметрами уже существует." + BookEventNotDeleted = "Ошибка удаления события книги" + ChapterNotFound = "Глава не найдена" + ChapterNotCreated = "Ошибка создания главы. Глава с такими параметрами уже существует." + ChapterNotDeleted = "Ошибка удаления главы" + MapVariablesNotFound = "Переменные карты не найдены" + MapVariablesNotCreated = "Ошибка создания переменных карты. Переменные с такими параметрами уже существуют." + MapVariablesNotDeleted = "Ошибка удаления переменных карты" + PageNotFound = "Страница не найдена" + PageNotCreated = "Ошибка создания страницы. Страница с такими параметрами уже существует." + PageNotDeleted = "Ошибка удаления страницы" + ParagraphNotFound = "Параграф не найден" + ParagraphNotCreated = "Ошибка создания параграфа. Параграф с такими параметрами уже существует." + ParagraphNotDeleted = "Ошибка удаления параграфа" +) diff --git a/bookback/internal/infrastructure/http/v1/health/config.go b/bookback/internal/infrastructure/http/v1/health/config.go deleted file mode 100644 index a3c7e5b..0000000 --- a/bookback/internal/infrastructure/http/v1/health/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package health - -const PathPrefix = "/api/v1/health" diff --git a/bookback/internal/infrastructure/http/v1/health/routes.go b/bookback/internal/infrastructure/http/v1/health/routes.go deleted file mode 100644 index 9c3fce8..0000000 --- a/bookback/internal/infrastructure/http/v1/health/routes.go +++ /dev/null @@ -1,23 +0,0 @@ -package health - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetHealthController(e *echo.Echo, _ postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - controller := NewController(metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (hc *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(hc.Metrics)) - - group.GET("/", hc.GetHealthCheck) -} diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/config.go b/bookback/internal/infrastructure/http/v1/mapvariables/config.go deleted file mode 100644 index d4be744..0000000 --- a/bookback/internal/infrastructure/http/v1/mapvariables/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package mapvariables - -const PathPrefix = "/api/v1/mapvariables" diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/controller.go b/bookback/internal/infrastructure/http/v1/mapvariables/controller.go deleted file mode 100644 index 9c3c195..0000000 --- a/bookback/internal/infrastructure/http/v1/mapvariables/controller.go +++ /dev/null @@ -1,216 +0,0 @@ -package mapvariables - -import ( - "context" - "fmt" - service "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -// Controller структура для HTTP-контроллера книг. -type Controller struct { - Service service.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -// NewController создает новый экземпляр Controller. -func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// GetMapVariableByID обрабатывает запросы на получение переменной карты по идентификатору. -// @router /mapvariables/{id} [get] -// @summary Получить переменную карты по идентификатору -// @description Извлекает переменную карты по идентификатору -// @tags Переменные карты -// @produce application/json -// @param id path string true "ID переменной карты" -// @success 200 {object} entity.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariableByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - variable, err := mvc.Service.GetMapVariableByID(mvc.Ctx, id) - if err != nil { - fmt.Println(err) - return ErrorMapVariableNotFound - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", MapVariable: variable}) -} - -// UpdateMapVariable обрабатывает запросы на обновление переменной карты. -// @router /mapvariables/{id} [put] -// @summary Обновить переменную карты -// @description Обновляет переменную карты -// @tags Переменные карты -// @accept application/json -// @produce application/json -// @param id path string true "ID переменной карты" -// @param variable body entity.MapVariable true "MapVariable object" -// @success 200 {object} entity.MapVariable -// @failure 400 {object} config.HTTPError -func (mvc *Controller) UpdateMapVariable(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - updatedVariable, err := mvc.Service.UpdateMapVariable(mvc.Ctx, id, request.MapVariables) - if err != nil { - return ErrorUnknown - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", MapVariable: updatedVariable}) -} - -// DeleteMapVariable обрабатывает запросы на удаление переменной карты. -// @router /mapvariables/{id} [delete] -// @summary Удалить переменную карты -// @description Удаляет переменную карты -// @tags Переменные карты -// @param id path string true "ID переменной карты" -// @success 204 -// @failure 404 {object} config.HTTPError -func (mvc *Controller) DeleteMapVariable(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - mapVariable, err := mvc.Service.DeleteMapVariable(mvc.Ctx, id) - if err != nil { - return echo.NewHTTPError(http.StatusNotFound, ErrorDeleteMapVariable) - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", MapVariable: mapVariable}) -} - -// GetMapVariablesByBookID обрабатывает запросы на получение переменных карты по идентификатору книги. -// @router /mapvariables/book/{id} [get] -// @summary Получить переменные карты по идентификатору книги -// @description Извлекает переменные карты по идентификатору книги -// @tags Переменные карты -// @produce application/json -// @param id path string true "ID книги" -// @success 200 {array} entity.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByBookID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - variables, err := mvc.Service.GetMapVariablesByBookID(mvc.Ctx, id) - if err != nil { - return ErrorMapVariableNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) -} - -// GetMapVariablesByChapterID обрабатывает запросы на получение переменных карты по идентификатору главы. -// @router /mapvariables/chapter/{id} [get] -// @summary Получить переменные карты по идентификатору главы -// @description Извлекает переменные карты по идентификатору главы -// @tags Переменные карты -// @produce application/json -// @param id path string true "ID главы" -// @success 200 {array} entity.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByChapterID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - variables, err := mvc.Service.GetMapVariablesByChapterID(mvc.Ctx, id) - if err != nil { - return ErrorMapVariableNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) -} - -// GetMapVariablesByPageID обрабатывает запросы на получение переменных карты по идентификатору страницы. -// @router /mapvariables/page/{id} [get] -// @summary Получить переменные карты по идентификатору страницы -// @description Извлекает переменные карты по идентификатору страницы -// @tags Переменные карты -// @produce application/json -// @param id path string true "ID страницы" -// @success 200 {array} entity.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByPageID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - variables, err := mvc.Service.GetMapVariablesByPageID(mvc.Ctx, id) - if err != nil { - return ErrorMapVariableNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) -} - -// GetMapVariablesByParagraphID обрабатывает запросы на получение переменных карты по идентификатору параграфа. -// @router /mapvariables/paragraph/{id} [get] -// @summary Получить переменные карты по идентификатору параграфа -// @description Извлекает переменные карты по идентификатору параграфа -// @tags Переменные карты -// @produce application/json -// @param id path string true "ID параграфа" -// @success 200 {array} entity.MapVariable -// @failure 404 {object} config.HTTPError -func (mvc *Controller) GetMapVariablesByParagraphID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - variables, err := mvc.Service.GetMapVariablesByParagraphID(mvc.Ctx, id) - if err != nil { - return ErrorMapVariableNotFound - } - - return c.JSON(http.StatusOK, responseListModel{Status: "ok", MapVariables: variables}) -} - -// CreateMapVariable обрабатывает создание новой переменной карты. -// @router /mapvariables [post] -// @summary Создать переменную карты -// @description Создает новую переменную карты -// @tags Переменные карты -// @accept application/json -// @produce application/json -// @param variable body entity.MapVariable true "MapVariable object" -// @success 201 {object} entity.MapVariable -// @failure 400 {object} config.HTTPError -func (mvc *Controller) CreateMapVariable(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - createdVariable, err := mvc.Service.CreateMapVariable(mvc.Ctx, request.MapVariables) - if err != nil { - return ErrorMapVariableNotCreated - } - - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", MapVariable: createdVariable}) -} diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/errors.go b/bookback/internal/infrastructure/http/v1/mapvariables/errors.go deleted file mode 100644 index 672992d..0000000 --- a/bookback/internal/infrastructure/http/v1/mapvariables/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -package mapvariables - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorMapVariableNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Переменная не найдена", - ) - ErrorMapVariableNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания переменной. Переменная с такими параметрами уже существует.", - ) - ErrorDeleteMapVariable = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления переменной", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/models.go b/bookback/internal/infrastructure/http/v1/mapvariables/models.go deleted file mode 100644 index b78846f..0000000 --- a/bookback/internal/infrastructure/http/v1/mapvariables/models.go +++ /dev/null @@ -1,20 +0,0 @@ -package mapvariables - -import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" - -type Options struct{} - -type requestModel struct { - Options Options `json:"options,omitempty"` - MapVariables *entity.MapVariable `json:"map_variables,omitempty"` -} - -type responseSingleModel struct { - MapVariable *entity.MapVariable `json:"map_variable"` - Status string `json:"status"` -} - -type responseListModel struct { - MapVariables []*entity.MapVariable `json:"map_variables"` - Status string `json:"status"` -} diff --git a/bookback/internal/infrastructure/http/v1/mapvariables/routes.go b/bookback/internal/infrastructure/http/v1/mapvariables/routes.go deleted file mode 100644 index 49f28ed..0000000 --- a/bookback/internal/infrastructure/http/v1/mapvariables/routes.go +++ /dev/null @@ -1,34 +0,0 @@ -package mapvariables - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetMapVariablesController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - repo := pgrepo.NewMapVariablesRepository(database) - service := mapvariables.NewService(repo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (mvc *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(mvc.Metrics)) - - group.GET("/:id", mvc.GetMapVariableByID) - group.PUT("/:id", mvc.UpdateMapVariable) - group.DELETE("/:id", mvc.DeleteMapVariable) - group.POST("", mvc.CreateMapVariable) - group.GET("/book/:id", mvc.GetMapVariablesByBookID) - group.GET("/chapter/:id", mvc.GetMapVariablesByChapterID) - group.GET("/page/:id", mvc.GetMapVariablesByPageID) - group.GET("/paragraph/:id", mvc.GetMapVariablesByParagraphID) -} diff --git a/bookback/internal/infrastructure/http/v1/page/config.go b/bookback/internal/infrastructure/http/v1/page/config.go deleted file mode 100644 index 940710e..0000000 --- a/bookback/internal/infrastructure/http/v1/page/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package page - -const PathPrefix = "/api/v1/pages" diff --git a/bookback/internal/infrastructure/http/v1/page/controller.go b/bookback/internal/infrastructure/http/v1/page/controller.go deleted file mode 100644 index b4f3d48..0000000 --- a/bookback/internal/infrastructure/http/v1/page/controller.go +++ /dev/null @@ -1,160 +0,0 @@ -package page - -import ( - "context" - service "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -type Controller struct { - Service service.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// ListPages список страниц -// @router /pages [get] -// @summary Получить список страниц -// @description Извлекает список всех страниц -// @tags Страницы -// @produce application/json -// @success 200 {array} entity.Page -// @failure 500 {object} config.HTTPError -func (p *Controller) ListPages(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - pages, err := p.Service.ListPages(p.Ctx, request.Options.Limit, request.Options.Offset) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Pages: pages}) -} - -// CreatePage создание новой страницы -// @router /pages [post] -// @summary Создать страницу -// @description Создает новую страницу -// @tags Страницы -// @accept application/json -// @produce application/json -// @param page body entity.Page true "Page object" -// @success 201 {object} entity.Page -// @failure 400 {object} config.HTTPError -func (p *Controller) CreatePage(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - createdPage, err := p.Service.CreatePage(p.Ctx, request.Page) - if err != nil { - return ErrorPageNotCreated - } - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Page: createdPage}) -} - -// GetPageByID получение страницы по ID -// @router /pages/{id} [get] -// @summary Получить страницу по ID -// @description Извлекает страницу по ее ID -// @tags Страницы -// @produce application/json -// @param id path string true "ID страницы" -// @success 200 {object} entity.Page -// @failure 404 {object} config.HTTPError -func (p *Controller) GetPageByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - page, err := p.Service.GetPageByID(p.Ctx, id) - if err != nil { - return ErrorPageNotFound - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Page: page}) -} - -// UpdatePage обновление страницы -// @router /pages/{id} [put] -// @summary Обновить страницу -// @description Обновляет страницу -// @tags Страницы -// @accept application/json -// @produce application/json -// @param id path string true "ID страницы" -// @param page body entity.Page true "Page object" -// @success 200 {object} entity.Page -// @failure 400 {object} config.HTTPError -func (p *Controller) UpdatePage(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - updatedPage, err := p.Service.UpdatePage(p.Ctx, id, request.Page) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Page: updatedPage}) -} - -// DeletePage удаление страницы -// @router /pages/{id} [delete] -// @summary Удалить страницу -// @description Удаляет страницу -// @tags Страницы -// @param id path string true "ID страницы" -// @produce application/json -// @success 200 {object} entity.Page -// @failure 500 {object} config.HTTPError -func (p *Controller) DeletePage(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - deletedPage, err := p.Service.DeletePage(p.Ctx, id) - if err != nil { - return ErrorDeletePage - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Page: deletedPage}) -} - -// GetPagesByChapterID получение страниц по ID главы -// @router /chapters/{id}/pages [get] -// @summary Получить страницы по ID главы -// @description Извлекает страницы по ID главы -// @tags Страницы -// @produce application/json -// @param id path string true "ID главы" -// @success 200 {array} entity.Page -// @failure 404 {object} config.HTTPError -func (p *Controller) GetPagesByChapterID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - pages, err := p.Service.GetPagesByChapterID(p.Ctx, id) - if err != nil { - return ErrorPageNotFound - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Pages: pages}) -} diff --git a/bookback/internal/infrastructure/http/v1/page/errors.go b/bookback/internal/infrastructure/http/v1/page/errors.go deleted file mode 100644 index 38e1e3b..0000000 --- a/bookback/internal/infrastructure/http/v1/page/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package page - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -// Возможные ошибки, которые могут возникнуть при работе с контроллером страниц. - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorPageNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Страница не найдена", - ) - ErrorPageNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания страницы. Страница с такими параметрами уже существует.", - ) - ErrorDeletePage = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления страницы", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/page/models.go b/bookback/internal/infrastructure/http/v1/page/models.go deleted file mode 100644 index 9c503d8..0000000 --- a/bookback/internal/infrastructure/http/v1/page/models.go +++ /dev/null @@ -1,23 +0,0 @@ -package page - -import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" - -type Options struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` -} - -type requestModel struct { - Options Options `json:"options,omitempty"` - Page *entity.Page `json:"page,omitempty"` -} - -type responseSingleModel struct { - Page *entity.Page `json:"page"` - Status string `json:"status"` -} - -type responseListModel struct { - Pages []*entity.Page `json:"pages"` - Status string `json:"status"` -} diff --git a/bookback/internal/infrastructure/http/v1/page/routes.go b/bookback/internal/infrastructure/http/v1/page/routes.go deleted file mode 100644 index 62951e4..0000000 --- a/bookback/internal/infrastructure/http/v1/page/routes.go +++ /dev/null @@ -1,32 +0,0 @@ -package page - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/page" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - repo := pgrepo.NewPageRepository(database) - service := page.NewService(repo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (p *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(p.Metrics)) - - group.GET("", p.ListPages) - group.POST("", p.CreatePage) - group.GET("/:id", p.GetPageByID) - group.PUT("/:id", p.UpdatePage) - group.DELETE("/:id", p.DeletePage) - group.GET("/chapters/:id", p.GetPagesByChapterID) -} diff --git a/bookback/internal/infrastructure/http/v1/paragraph/config.go b/bookback/internal/infrastructure/http/v1/paragraph/config.go deleted file mode 100644 index fdea98e..0000000 --- a/bookback/internal/infrastructure/http/v1/paragraph/config.go +++ /dev/null @@ -1,3 +0,0 @@ -package paragraph - -const PathPrefix = "/api/v1/paragraphs" diff --git a/bookback/internal/infrastructure/http/v1/paragraph/controller.go b/bookback/internal/infrastructure/http/v1/paragraph/controller.go deleted file mode 100644 index d609922..0000000 --- a/bookback/internal/infrastructure/http/v1/paragraph/controller.go +++ /dev/null @@ -1,161 +0,0 @@ -package paragraph - -import ( - "context" - service "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/labstack/echo/v4" - "log/slog" - "net/http" -) - -type Controller struct { - Service service.Service - Metrics metrics.Metrics - Logger *slog.Logger - Ctx context.Context -} - -// NewController создает новый экземпляр Controller. -func NewController(srv service.Service, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *Controller { - return &Controller{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} -} - -// ListParagraphs список параграфов -// @router /paragraphs [get] -// @summary Получить список параграфов -// @description Извлекает список всех параграфов -// @tags Параграфы -// @produce application/json -// @success 200 {array} entity.Paragraph -// @failure 500 {object} config.HTTPError -func (p *Controller) ListParagraphs(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - paragraphs, err := p.Service.ListParagraphs(p.Ctx, request.Options.Limit, request.Options.Offset) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Paragraphs: paragraphs}) -} - -// CreateParagraph создание нового параграфа -// @router /paragraphs [post] -// @summary Создать параграф -// @description Создает новый параграф -// @tags Параграфы -// @accept application/json -// @produce application/json -// @param paragraph body entity.Paragraph true "Paragraph object" -// @success 201 {object} entity.Paragraph -// @failure 400 {object} config.HTTPError -func (p *Controller) CreateParagraph(c echo.Context) error { - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - createdParagraph, err := p.Service.CreateParagraph(p.Ctx, request.Paragraph) - if err != nil { - return ErrorParagraphNotFound - } - return c.JSON(http.StatusCreated, responseSingleModel{Status: "created", Paragraph: createdParagraph}) -} - -// GetParagraphByID получение параграфа по идентификатору -// @router /paragraphs/{id} [get] -// @summary Получить параграф по ID -// @description Извлекает параграф по его ID -// @tags Параграфы -// @param id path string true "ID параграфа" -// @produce application/json -// @success 200 {object} entity.Paragraph -// @failure 404 {object} config.HTTPError -func (p *Controller) GetParagraphByID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - paragraph, err := p.Service.GetParagraphByID(p.Ctx, id) - if err != nil { - return ErrorParagraphNotFound - } - - return c.JSON(http.StatusOK, responseSingleModel{Status: "ok", Paragraph: paragraph}) -} - -// UpdateParagraph обновление параграфа -// @router /paragraphs/{id} [put] -// @summary Обновить параграф -// @description Обновляет параграф по его ID -// @tags Параграфы -// @accept application/json -// @produce application/json -// @param id path string true "ID параграфа" -// @param paragraph body entity.Paragraph true "Paragraph object" -// @success 200 {object} entity.Paragraph -// @failure 400 {object} config.HTTPError -func (p *Controller) UpdateParagraph(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - var request requestModel - if err := c.Bind(&request); err != nil { - return ErrorValidationFailed - } - - updatedParagraph, err := p.Service.UpdateParagraph(p.Ctx, id, request.Paragraph) - if err != nil { - return ErrorUnknown - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "updated", Paragraph: updatedParagraph}) -} - -// DeleteParagraph удаление параграфа -// @router /paragraphs/{id} [delete] -// @summary Удалить параграф -// @description Удаляет параграф по его ID -// @tags Параграфы -// @param id path string true "ID параграфа" -// @success 200 {object} entity.Paragraph -// @failure 500 {object} config.HTTPError -func (p *Controller) DeleteParagraph(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - deletedParagraph, err := p.Service.DeleteParagraph(p.Ctx, id) - if err != nil { - return ErrorDeleteParagraph - } - return c.JSON(http.StatusOK, responseSingleModel{Status: "deleted", Paragraph: deletedParagraph}) -} - -// GetParagraphsByPageID получение параграфов по ID страницы -// @router /paragraphs/pages/{id} [get] -// @summary Получить параграфы по ID страницы -// @description Извлекает параграфы по ID страницы -// @tags Параграфы -// @param id path string true "ID страницы" -// @produce application/json -// @success 200 {array} entity.Paragraph -// @failure 404 {object} config.HTTPError -func (p *Controller) GetParagraphsByPageID(c echo.Context) error { - id := c.Param("id") - if id == "" { - return ErrorValidationFailed - } - - paragraphs, err := p.Service.GetParagraphsByPageID(p.Ctx, id) - if err != nil { - return ErrorParagraphNotFound - } - return c.JSON(http.StatusOK, responseListModel{Status: "ok", Paragraphs: paragraphs}) -} diff --git a/bookback/internal/infrastructure/http/v1/paragraph/errors.go b/bookback/internal/infrastructure/http/v1/paragraph/errors.go deleted file mode 100644 index 28dc31f..0000000 --- a/bookback/internal/infrastructure/http/v1/paragraph/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -package paragraph - -import ( - "github.com/labstack/echo/v4" - "net/http" -) - -// Возможные ошибки при работе с книгами. - -var ( - ErrorValidationFailed = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка валидации полей ввода! Проверьте введенные данные и попробуйте снова.", - ) - ErrorParagraphNotFound = echo.NewHTTPError( - http.StatusNotFound, - "Параграф не найдена", - ) - ErrorParagraphNotCreated = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка создания параграфа. Параграф с такими параметрами уже существует.", - ) - ErrorDeleteParagraph = echo.NewHTTPError( - http.StatusBadRequest, - "Ошибка удаления параграфа", - ) - ErrorUnknown = echo.NewHTTPError( - http.StatusInternalServerError, - "Неизвестная ошибка", - ) -) diff --git a/bookback/internal/infrastructure/http/v1/paragraph/models.go b/bookback/internal/infrastructure/http/v1/paragraph/models.go deleted file mode 100644 index 158a6b5..0000000 --- a/bookback/internal/infrastructure/http/v1/paragraph/models.go +++ /dev/null @@ -1,23 +0,0 @@ -package paragraph - -import "github.com/SShlykov/zeitment/bookback/internal/domain/entity" - -type Options struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` -} - -type requestModel struct { - Options Options `json:"options,omitempty"` - Paragraph *entity.Paragraph `json:"paragraph,omitempty"` -} - -type responseSingleModel struct { - Paragraph *entity.Paragraph `json:"paragraph"` - Status string `json:"status"` -} - -type responseListModel struct { - Paragraphs []*entity.Paragraph `json:"paragraphs"` - Status string `json:"status"` -} diff --git a/bookback/internal/infrastructure/http/v1/paragraph/routes.go b/bookback/internal/infrastructure/http/v1/paragraph/routes.go deleted file mode 100644 index 981ecf5..0000000 --- a/bookback/internal/infrastructure/http/v1/paragraph/routes.go +++ /dev/null @@ -1,32 +0,0 @@ -package paragraph - -import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" - "github.com/SShlykov/zeitment/bookback/internal/domain/services/paragraph" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - "log/slog" -) - -func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { - repo := pgrepo.NewParagraphRepository(database) - service := paragraph.NewService(repo) - controller := NewController(service, metrics, logger, ctx) - - controller.RegisterRoutes(e) -} - -func (p *Controller) RegisterRoutes(e *echo.Echo) { - group := e.Group(PathPrefix) - group.Use(middleware.MetricsLogger(p.Metrics)) - - group.GET("", p.ListParagraphs) - group.POST("", p.CreateParagraph) - group.GET("/:id", p.GetParagraphByID) - group.PUT("/:id", p.UpdateParagraph) - group.DELETE("/:id", p.DeleteParagraph) - group.GET("/pages/:id", p.GetParagraphsByPageID) -} diff --git a/bookback/internal/infrastructure/http/v1/routes/absolute_paths.go b/bookback/internal/infrastructure/http/v1/routes/absolute_paths.go new file mode 100644 index 0000000..572c037 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/absolute_paths.go @@ -0,0 +1,12 @@ +package routes + +const ( + BasePath = "/api/v1" + BooksPath = BasePath + "/books" + BookEventsPath = BasePath + "/bookevents" + ChaptersPath = BasePath + "/chapters" + MapVariablesPath = BasePath + "/mapvariables" + HealthPath = BasePath + "/health" + PagesPath = BasePath + "/pages" + ParagraphsPath = BasePath + "/paragraphs" +) diff --git a/bookback/internal/infrastructure/http/v1/routes/book_router.go b/bookback/internal/infrastructure/http/v1/routes/book_router.go new file mode 100644 index 0000000..9b820fa --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/book_router.go @@ -0,0 +1,29 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +// SetBookRoutes регистрирует контроллер книг в маршрутизаторе. +func SetBookRoutes(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + repo := pgrepo.NewBookRepository(database) + service := services.NewBookService(repo) + cntr := controllers.NewBookController(service, metrics, logger, ctx) + + group := e.Group(BooksPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.POST("", cntr.ListBooks) + group.POST("", cntr.CreateBook) + group.GET("/:id", cntr.GetBookByID) + group.PUT("/:id", cntr.UpdateBook) + group.DELETE("/:id", cntr.DeleteBook) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go b/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go new file mode 100644 index 0000000..3c8f26a --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go @@ -0,0 +1,31 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + repo := pgrepo.NewBookEventsRepository(database) + service := services.NewBookEventsService(repo) + cntr := controllers.NewBookEventController(service, metrics, logger, ctx) + + group := e.Group(BookEventsPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.POST("", cntr.CreateBookEvent) + group.GET("/:id", cntr.GetBookEventByID) + group.PUT("/:id", cntr.UpdateBookEvent) + group.DELETE("/:id", cntr.DeleteBookEvent) + group.GET("/book/:id", cntr.GetBookEventsByBookID) + group.GET("/chapter/:id", cntr.GetBookEventsByChapterID) + group.GET("/page/:id", cntr.GetBookEventsByPageID) + group.GET("/paragraph/:id", cntr.GetBookEventsByParagraphID) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/chapter_router.go b/bookback/internal/infrastructure/http/v1/routes/chapter_router.go new file mode 100644 index 0000000..5139583 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/chapter_router.go @@ -0,0 +1,29 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetChapterController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + chapterRepo := pgrepo.NewChapterRepository(database) + service := services.NewChapterService(chapterRepo) + cnt := controllers.NewChapterController(service, metrics, logger, ctx) + + group := e.Group(ChaptersPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.GET("", cnt.ListChapters) + group.POST("", cnt.CreateChapter) + group.GET("/:id", cnt.GetChapterByID) + group.PUT("/:id", cnt.UpdateChapter) + group.DELETE("/:id", cnt.DeleteChapter) + group.GET("/book/:id", cnt.GetChapterByBookID) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/health_router.go b/bookback/internal/infrastructure/http/v1/routes/health_router.go new file mode 100644 index 0000000..0f4ed48 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/health_router.go @@ -0,0 +1,20 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetHealthController(e *echo.Echo, _ postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + cntr := controllers.NewHealthController(metrics, logger, ctx) + + group := e.Group(HealthPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.GET("/", cntr.GetHealthCheck) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go b/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go new file mode 100644 index 0000000..1ba0519 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go @@ -0,0 +1,31 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetMapVariablesRoutes(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + repo := pgrepo.NewMapVariablesRepository(database) + service := services.NewMapVariablesService(repo) + cnt := controllers.NewMapVariablesController(service, metrics, logger, ctx) + + group := e.Group(MapVariablesPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.GET("/:id", cnt.GetMapVariableByID) + group.PUT("/:id", cnt.UpdateMapVariable) + group.DELETE("/:id", cnt.DeleteMapVariable) + group.POST("", cnt.CreateMapVariable) + group.GET("/book/:id", cnt.GetMapVariablesByBookID) + group.GET("/chapter/:id", cnt.GetMapVariablesByChapterID) + group.GET("/page/:id", cnt.GetMapVariablesByPageID) + group.GET("/paragraph/:id", cnt.GetMapVariablesByParagraphID) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/page_router.go b/bookback/internal/infrastructure/http/v1/routes/page_router.go new file mode 100644 index 0000000..29a681d --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/page_router.go @@ -0,0 +1,29 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + repo := pgrepo.NewPageRepository(database) + service := services.NewPageService(repo) + cnt := controllers.NewPageController(service, metrics, logger, ctx) + + group := e.Group(PagesPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.GET("", cnt.ListPages) + group.POST("", cnt.CreatePage) + group.GET("/:id", cnt.GetPageByID) + group.PUT("/:id", cnt.UpdatePage) + group.DELETE("/:id", cnt.DeletePage) + group.GET("/chapters/:id", cnt.GetPagesByChapterID) +} diff --git a/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go b/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go new file mode 100644 index 0000000..30d3d83 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go @@ -0,0 +1,29 @@ +package routes + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/domain/repository/pgrepo" + "github.com/SShlykov/zeitment/bookback/internal/domain/services" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" +) + +func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { + repo := pgrepo.NewParagraphRepository(database) + service := services.NewParagraphService(repo) + cnt := controllers.NewParagraphController(service, metrics, logger, ctx) + + group := e.Group(ParagraphsPath) + group.Use(middleware.MetricsLogger(metrics)) + + group.GET("", cnt.ListParagraphs) + group.POST("", cnt.CreateParagraph) + group.GET("/:id", cnt.GetParagraphByID) + group.PUT("/:id", cnt.UpdateParagraph) + group.DELETE("/:id", cnt.DeleteParagraph) + group.GET("/pages/:id", cnt.GetParagraphsByPageID) +} diff --git a/bookback/internal/infrastructure/http/v1/swagger/controller.go b/bookback/internal/infrastructure/http/v1/routes/swagger_router.go similarity index 93% rename from bookback/internal/infrastructure/http/v1/swagger/controller.go rename to bookback/internal/infrastructure/http/v1/routes/swagger_router.go index 74f598b..e043c84 100644 --- a/bookback/internal/infrastructure/http/v1/swagger/controller.go +++ b/bookback/internal/infrastructure/http/v1/routes/swagger_router.go @@ -1,4 +1,4 @@ -package swagger +package routes import ( "github.com/labstack/echo/v4" diff --git a/bookback/internal/infrastructure/metrics/Readme.md b/bookback/internal/infrastructure/metrics/Readme.md new file mode 100644 index 0000000..de48288 --- /dev/null +++ b/bookback/internal/infrastructure/metrics/Readme.md @@ -0,0 +1 @@ +## Перенесутся в elasticsearch \ No newline at end of file diff --git a/bookback/internal/metrics/interface.go b/bookback/internal/infrastructure/metrics/interface.go similarity index 100% rename from bookback/internal/metrics/interface.go rename to bookback/internal/infrastructure/metrics/interface.go diff --git a/bookback/internal/metrics/localmetrics/metrics.go b/bookback/internal/infrastructure/metrics/localmetrics/metrics.go similarity index 100% rename from bookback/internal/metrics/localmetrics/metrics.go rename to bookback/internal/infrastructure/metrics/localmetrics/metrics.go diff --git a/bookback/internal/models/model.go b/bookback/internal/models/basic_model.go similarity index 100% rename from bookback/internal/models/model.go rename to bookback/internal/models/basic_model.go diff --git a/bookback/internal/models/bookevents_model.go b/bookback/internal/models/bookevents_model.go new file mode 100644 index 0000000..0d30ec6 --- /dev/null +++ b/bookback/internal/models/bookevents_model.go @@ -0,0 +1,37 @@ +package models + +import ( + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type BookEvent struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + BookID string + ChapterID types.Null[string] + PageID types.Null[string] + ParagraphID types.Null[string] + EventType types.Null[string] // Тип события (начало главы, начало страницы, начало параграфа...) + IsPublic bool + Key string + Value string + Link types.Null[string] + LinkText types.Null[string] + LinkType types.Null[string] + LinkImage types.Null[string] + Description types.Null[string] +} + +type CreateBookEventRequest struct { + BookEvent *BookEvent `json:"book_event"` +} +type UpdateBookEventRequest struct { + BookEvent *BookEvent `json:"book_event"` +} + +type RequestBookEvent struct { + Options PageOptions `json:"options,omitempty"` + BookEvent *BookEvent `json:"book_event,omitempty"` +} diff --git a/bookback/internal/models/chapter_model.go b/bookback/internal/models/chapter_model.go new file mode 100644 index 0000000..7a21ba8 --- /dev/null +++ b/bookback/internal/models/chapter_model.go @@ -0,0 +1,33 @@ +package models + +import ( + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type Chapter struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt types.Null[time.Time] + Title string + Number int // Порядковый номер главы (не уникальный т.к. главы могут быть скрыты) + Text string // Превью текста главы + BookID string + IsPublic bool + MapLink types.Null[string] // карта + MapParamsID types.Null[string] // параметры карты (координаты и тп.) +} + +type CreateChapterRequest struct { + Chapter *Chapter `json:"chapter"` +} + +type UpdateChapterRequest struct { + Chapter *Chapter `json:"chapter"` +} + +type RequestChapter struct { + Options PageOptions `json:"options,omitempty"` + Chapter *Chapter `json:"chapter,omitempty"` +} diff --git a/bookback/internal/models/converter/book.go b/bookback/internal/models/converter/book.go index 64cab2c..24740b6 100644 --- a/bookback/internal/models/converter/book.go +++ b/bookback/internal/models/converter/book.go @@ -4,6 +4,7 @@ import ( "database/sql" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" "time" ) @@ -38,15 +39,15 @@ func BookEntityToModel(book *entity.Book) *models.Book { ID: book.ID, CreatedAt: book.CreatedAt, UpdatedAt: book.UpdatedAt, - DeletedAt: models.Null[time.Time]{Valid: book.DeletedAt.Valid, Value: book.DeletedAt.Time}, + DeletedAt: types.Null[time.Time]{Valid: book.DeletedAt.Valid, Value: book.DeletedAt.Time}, Owner: book.Owner, Title: book.Title, Author: book.Author, Description: book.Description, IsPublic: book.IsPublic, - Publication: models.Null[time.Time]{Valid: book.Publication.Valid, Value: book.Publication.Time}, - ImageLink: models.Null[string]{Valid: book.ImageLink.Valid, Value: book.ImageLink.String}, - MapLink: models.Null[string]{Valid: book.MapLink.Valid, Value: book.MapLink.String}, + Publication: types.Null[time.Time]{Valid: book.Publication.Valid, Value: book.Publication.Time}, + ImageLink: types.Null[string]{Valid: book.ImageLink.Valid, Value: book.ImageLink.String}, + MapLink: types.Null[string]{Valid: book.MapLink.Valid, Value: book.MapLink.String}, Variables: book.Variables, } } diff --git a/bookback/internal/models/converter/bookevent.go b/bookback/internal/models/converter/bookevent.go new file mode 100644 index 0000000..3ea8845 --- /dev/null +++ b/bookback/internal/models/converter/bookevent.go @@ -0,0 +1,59 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" +) + +func BookEventsEntityToModel(bookevents []*entity.BookEvent) []*models.BookEvent { + bookEventModels := make([]*models.BookEvent, 0) + for _, bookevent := range bookevents { + bookEventModel := BookEventEntityToModel(bookevent) + bookEventModels = append(bookEventModels, bookEventModel) + } + return bookEventModels +} + +func BookEventEntityToModel(bookevent *entity.BookEvent) *models.BookEvent { + return &models.BookEvent{ + ID: bookevent.ID, + CreatedAt: bookevent.CreatedAt, + UpdatedAt: bookevent.UpdatedAt, + BookID: bookevent.BookID, + ChapterID: types.Null[string]{Valid: bookevent.ChapterID.Valid, Value: bookevent.ChapterID.String}, + PageID: types.Null[string]{Valid: bookevent.PageID.Valid, Value: bookevent.PageID.String}, + ParagraphID: types.Null[string]{Valid: bookevent.ParagraphID.Valid, Value: bookevent.ParagraphID.String}, + EventType: types.Null[string]{Valid: bookevent.EventType.Valid, Value: bookevent.EventType.String}, + IsPublic: bookevent.IsPublic, + Key: bookevent.Key, + Value: bookevent.Value, + Link: types.Null[string]{Valid: bookevent.Link.Valid, Value: bookevent.Link.String}, + LinkText: types.Null[string]{Valid: bookevent.LinkText.Valid, Value: bookevent.LinkText.String}, + LinkType: types.Null[string]{Valid: bookevent.LinkType.Valid, Value: bookevent.LinkType.String}, + LinkImage: types.Null[string]{Valid: bookevent.LinkImage.Valid, Value: bookevent.LinkImage.String}, + Description: types.Null[string]{Valid: bookevent.Description.Valid, Value: bookevent.Description.String}, + } +} + +func BookEventModelToEntity(bookeventModel *models.BookEvent) *entity.BookEvent { + return &entity.BookEvent{ + ID: bookeventModel.ID, + CreatedAt: bookeventModel.CreatedAt, + UpdatedAt: bookeventModel.UpdatedAt, + BookID: bookeventModel.BookID, + ChapterID: sql.NullString{Valid: bookeventModel.ChapterID.Valid, String: bookeventModel.ChapterID.Value}, + PageID: sql.NullString{Valid: bookeventModel.PageID.Valid, String: bookeventModel.PageID.Value}, + ParagraphID: sql.NullString{Valid: bookeventModel.ParagraphID.Valid, String: bookeventModel.ParagraphID.Value}, + EventType: sql.NullString{Valid: bookeventModel.EventType.Valid, String: bookeventModel.EventType.Value}, + IsPublic: bookeventModel.IsPublic, + Key: bookeventModel.Key, + Value: bookeventModel.Value, + Link: sql.NullString{Valid: bookeventModel.Link.Valid, String: bookeventModel.Link.Value}, + LinkText: sql.NullString{Valid: bookeventModel.LinkText.Valid, String: bookeventModel.LinkText.Value}, + LinkType: sql.NullString{Valid: bookeventModel.LinkType.Valid, String: bookeventModel.LinkType.Value}, + LinkImage: sql.NullString{Valid: bookeventModel.LinkImage.Valid, String: bookeventModel.LinkImage.Value}, + Description: sql.NullString{Valid: bookeventModel.Description.Valid, String: bookeventModel.Description.Value}, + } +} diff --git a/bookback/internal/models/converter/chapter.go b/bookback/internal/models/converter/chapter.go new file mode 100644 index 0000000..0922200 --- /dev/null +++ b/bookback/internal/models/converter/chapter.go @@ -0,0 +1,49 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +func ChaptersEntityToModel(chapters []*entity.Chapter) []*models.Chapter { + var result []*models.Chapter + for _, chapter := range chapters { + result = append(result, ChapterEntityToModel(chapter)) + } + return result +} + +func ChapterEntityToModel(chapter *entity.Chapter) *models.Chapter { + return &models.Chapter{ + ID: chapter.ID, + CreatedAt: chapter.CreatedAt, + UpdatedAt: chapter.UpdatedAt, + DeletedAt: types.Null[time.Time]{Valid: chapter.DeletedAt.Valid, Value: chapter.DeletedAt.Time}, + Title: chapter.Title, + Number: chapter.Number, + Text: chapter.Text, + BookID: chapter.BookID, + IsPublic: chapter.IsPublic, + MapLink: types.Null[string]{Valid: chapter.MapLink.Valid, Value: chapter.MapLink.String}, + MapParamsID: types.Null[string]{Valid: chapter.MapLink.Valid, Value: chapter.MapLink.String}, + } +} + +func ChapterModelToEntity(chapter *models.Chapter) *entity.Chapter { + return &entity.Chapter{ + ID: chapter.ID, + CreatedAt: chapter.CreatedAt, + UpdatedAt: chapter.UpdatedAt, + DeletedAt: sql.NullTime{Valid: chapter.DeletedAt.Valid, Time: chapter.DeletedAt.Value}, + Title: chapter.Title, + Number: chapter.Number, + Text: chapter.Text, + BookID: chapter.BookID, + IsPublic: chapter.IsPublic, + MapLink: sql.NullString{Valid: chapter.MapLink.Valid, String: chapter.MapLink.Value}, + MapParamsID: sql.NullString{Valid: chapter.MapLink.Valid, String: chapter.MapLink.Value}, + } +} diff --git a/bookback/internal/models/converter/mapvariables.go b/bookback/internal/models/converter/mapvariables.go new file mode 100644 index 0000000..1084eee --- /dev/null +++ b/bookback/internal/models/converter/mapvariables.go @@ -0,0 +1,62 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" +) + +func MapVariablesEntityToModel(variables []*entity.MapVariable) []*models.MapVariable { + var result []*models.MapVariable + for _, variable := range variables { + result = append(result, MapVariableEntityToModel(variable)) + } + return result +} + +func MapVariableModelToEntity(variable *models.MapVariable) *entity.MapVariable { + return &entity.MapVariable{ + ID: variable.ID, + CreatedAt: variable.CreatedAt, + UpdatedAt: variable.UpdatedAt, + BookID: variable.BookID, + ChapterID: sql.NullString{Valid: variable.ChapterID.Valid, String: variable.ChapterID.Value}, + PageID: sql.NullString{Valid: variable.PageID.Valid, String: variable.PageID.Value}, + ParagraphID: sql.NullString{Valid: variable.ParagraphID.Valid, String: variable.ParagraphID.Value}, + MapLink: variable.MapLink, + Lat: variable.Lat, + Lng: variable.Lng, + Zoom: variable.Zoom, + Date: sql.NullString{Valid: variable.Date.Valid, String: variable.Date.Value}, + Description: sql.NullString{Valid: variable.Description.Valid, String: variable.Description.Value}, + Link: sql.NullString{Valid: variable.Link.Valid, String: variable.Link.Value}, + LinkText: sql.NullString{Valid: variable.LinkText.Valid, String: variable.LinkText.Value}, + LinkType: sql.NullString{Valid: variable.LinkType.Valid, String: variable.LinkType.Value}, + LinkImage: sql.NullString{Valid: variable.LinkImage.Valid, String: variable.LinkImage.Value}, + Image: sql.NullString{Valid: variable.Image.Valid, String: variable.Image.Value}, + } +} + +func MapVariableEntityToModel(variable *entity.MapVariable) *models.MapVariable { + return &models.MapVariable{ + ID: variable.ID, + CreatedAt: variable.CreatedAt, + UpdatedAt: variable.UpdatedAt, + BookID: variable.BookID, + ChapterID: types.Null[string]{Valid: variable.ChapterID.Valid, Value: variable.ChapterID.String}, + PageID: types.Null[string]{Valid: variable.PageID.Valid, Value: variable.PageID.String}, + ParagraphID: types.Null[string]{Valid: variable.ParagraphID.Valid, Value: variable.ParagraphID.String}, + MapLink: variable.MapLink, + Lat: variable.Lat, + Lng: variable.Lng, + Zoom: variable.Zoom, + Date: types.Null[string]{Valid: variable.Date.Valid, Value: variable.Date.String}, + Description: types.Null[string]{Valid: variable.Description.Valid, Value: variable.Description.String}, + Link: types.Null[string]{Valid: variable.Link.Valid, Value: variable.Link.String}, + LinkText: types.Null[string]{Valid: variable.LinkText.Valid, Value: variable.LinkText.String}, + LinkType: types.Null[string]{Valid: variable.LinkType.Valid, Value: variable.LinkType.String}, + LinkImage: types.Null[string]{Valid: variable.LinkImage.Valid, Value: variable.LinkImage.String}, + Image: types.Null[string]{Valid: variable.Image.Valid, Value: variable.Image.String}, + } +} diff --git a/bookback/internal/models/converter/page.go b/bookback/internal/models/converter/page.go new file mode 100644 index 0000000..cc8f2e9 --- /dev/null +++ b/bookback/internal/models/converter/page.go @@ -0,0 +1,45 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +func PagesEntityToModel(pages []*entity.Page) []*models.Page { + var result []*models.Page + for _, page := range pages { + result = append(result, PageEntityToModel(page)) + } + return result +} + +func PageModelToEntity(m *models.Page) *entity.Page { + return &entity.Page{ + ID: m.ID, + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + DeletedAt: sql.NullTime{Valid: m.DeletedAt.Valid, Time: m.DeletedAt.Value}, + Title: m.Title, + Text: m.Text, + ChapterID: m.ChapterID, + IsPublic: m.IsPublic, + MapParamsID: sql.NullString{Valid: m.MapParamsID.Valid, String: m.MapParamsID.Value}, + } +} + +func PageEntityToModel(e *entity.Page) *models.Page { + return &models.Page{ + ID: e.ID, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + DeletedAt: types.Null[time.Time]{Valid: e.DeletedAt.Valid, Value: e.DeletedAt.Time}, + Title: e.Title, + Text: e.Text, + ChapterID: e.ChapterID, + IsPublic: e.IsPublic, + MapParamsID: types.Null[string]{Valid: e.MapParamsID.Valid, Value: e.MapParamsID.String}, + } +} diff --git a/bookback/internal/models/converter/paragraph.go b/bookback/internal/models/converter/paragraph.go new file mode 100644 index 0000000..5b3be1e --- /dev/null +++ b/bookback/internal/models/converter/paragraph.go @@ -0,0 +1,45 @@ +package converter + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/domain/entity" + "github.com/SShlykov/zeitment/bookback/internal/models" + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +func ParagraphsEntityToModel(paragraphs []*entity.Paragraph) []*models.Paragraph { + var result []*models.Paragraph + for _, paragraph := range paragraphs { + result = append(result, ParagraphEntityToModel(paragraph)) + } + return result +} + +func ParagraphEntityToModel(paragraph *entity.Paragraph) *models.Paragraph { + return &models.Paragraph{ + ID: paragraph.ID, + CreatedAt: paragraph.CreatedAt, + UpdatedAt: paragraph.UpdatedAt, + DeletedAt: types.Null[time.Time]{Valid: paragraph.DeletedAt.Valid, Value: paragraph.DeletedAt.Time}, + Title: paragraph.Title, + Text: paragraph.Text, + Type: paragraph.Type, + IsPublic: paragraph.IsPublic, + PageID: paragraph.PageID, + } +} + +func ParagraphModelToEntity(paragraph *models.Paragraph) *entity.Paragraph { + return &entity.Paragraph{ + ID: paragraph.ID, + CreatedAt: paragraph.CreatedAt, + UpdatedAt: paragraph.UpdatedAt, + DeletedAt: sql.NullTime{Valid: paragraph.DeletedAt.Valid, Time: paragraph.DeletedAt.Value}, + Title: paragraph.Title, + Text: paragraph.Text, + Type: paragraph.Type, + IsPublic: paragraph.IsPublic, + PageID: paragraph.PageID, + } +} diff --git a/bookback/internal/models/mapvariables_model.go b/bookback/internal/models/mapvariables_model.go new file mode 100644 index 0000000..1d23eb6 --- /dev/null +++ b/bookback/internal/models/mapvariables_model.go @@ -0,0 +1,41 @@ +package models + +import ( + "database/sql" + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type MapVariable struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + BookID string + ChapterID types.Null[string] + PageID types.Null[string] + ParagraphID types.Null[string] + MapLink string + Lat float64 + Lng float64 + Zoom sql.NullInt64 + Date types.Null[string] + Description types.Null[string] + Link types.Null[string] + LinkText types.Null[string] + LinkType types.Null[string] + LinkImage types.Null[string] + Image types.Null[string] +} + +type CreateMapVariableRequest struct { + MapVariable *MapVariable `json:"map_variable"` +} + +type UpdateMapVariableRequest struct { + MapVariable *MapVariable `json:"map_variable"` +} + +type RequestMapVariable struct { + Options PageOptions `json:"options,omitempty"` + MapVariable *MapVariable `json:"map_variable,omitempty"` +} diff --git a/bookback/internal/models/page_model.go b/bookback/internal/models/page_model.go new file mode 100644 index 0000000..ee317d7 --- /dev/null +++ b/bookback/internal/models/page_model.go @@ -0,0 +1,31 @@ +package models + +import ( + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type Page struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt types.Null[time.Time] + Title string + Text string + ChapterID string + IsPublic bool + MapParamsID types.Null[string] +} + +type CreatePageRequest struct { + Page *Page `json:"page"` +} + +type UpdatePageRequest struct { + Page *Page `json:"page"` +} + +type RequestPage struct { + Options PageOptions `json:"options,omitempty"` + Page *Page `json:"page,omitempty"` +} diff --git a/bookback/internal/models/paragraph_model.go b/bookback/internal/models/paragraph_model.go new file mode 100644 index 0000000..75a3fc2 --- /dev/null +++ b/bookback/internal/models/paragraph_model.go @@ -0,0 +1,31 @@ +package models + +import ( + "github.com/SShlykov/zeitment/bookback/internal/models/types" + "time" +) + +type Paragraph struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt types.Null[time.Time] + Title string + Text string + Type string + IsPublic bool + PageID string +} + +type CreateParagraphRequest struct { + Paragraph *Paragraph `json:"paragraph"` +} + +type UpdateParagraphRequest struct { + Paragraph *Paragraph `json:"paragraph"` +} + +type RequestParagraph struct { + Options PageOptions `json:"options,omitempty"` + Paragraph *Paragraph `json:"paragraph,omitempty"` +} From 1f81dabfccbbf71975826765af58affc993e37e0 Mon Sep 17 00:00:00 2001 From: SShlykov Date: Sat, 9 Mar 2024 22:49:14 +0300 Subject: [PATCH 9/9] feat: cleaned structure --- bookback/.golangci.yml | 2 +- bookback/cmd/bookback/main.go | 2 +- bookback/config/default.yml | 12 --- bookback/config/server.yml | 12 +++ .../{models/converter => adapters}/book.go | 2 +- .../converter => adapters}/bookevent.go | 2 +- .../{models/converter => adapters}/chapter.go | 2 +- .../converter => adapters}/mapvariables.go | 2 +- .../{models/converter => adapters}/page.go | 2 +- .../converter => adapters}/paragraph.go | 2 +- bookback/internal/bootstrap/app/app.go | 25 ++--- bookback/internal/bootstrap/app/closer.go | 2 +- bookback/internal/bootstrap/app/config.go | 3 +- bookback/internal/bootstrap/app/db.go | 8 +- bookback/internal/bootstrap/app/endpoint.go | 100 ++++++------------ bookback/internal/bootstrap/app/logger.go | 3 +- bookback/internal/bootstrap/app/metrics.go | 3 +- bookback/internal/domain/entity/book.go | 25 ++--- bookback/internal/domain/entity/bookevents.go | 23 ++-- bookback/internal/domain/entity/chapter.go | 20 ++-- .../internal/domain/entity/mapvariables.go | 20 ++-- bookback/internal/domain/entity/page.go | 17 ++- bookback/internal/domain/entity/paragraph.go | 17 ++- .../repository/pgrepo/0-genericrepository.go | 69 ++++++------ .../domain/services/0_generic_repo.go | 5 +- .../internal/domain/services/book_service.go | 21 ++-- .../domain/services/bookevents_service.go | 66 ++++++++---- .../domain/services/chapter_service.go | 34 +++--- .../domain/services/mapvariables_service.go | 69 ++++++++---- .../internal/domain/services/page_service.go | 36 ++++--- .../domain/services/paragraph_service.go | 38 ++++--- .../infrastructure/http/middleware/recover.go | 10 ++ .../http/v1/controllers/book_controller.go | 14 ++- .../v1/controllers/bookevents_controller.go | 60 ++++++++--- .../http/v1/controllers/chapter_controller.go | 31 ++++-- .../v1/controllers/mapvariables_controller.go | 39 +++++-- .../http/v1/controllers/page_controller.go | 15 ++- .../v1/controllers/paragraph_controller.go | 13 ++- .../http/v1/endpoint/handler.go | 96 +++++++++++++++++ .../infrastructure/http/v1/endpoint/server.go | 20 ++++ .../http/v1/routes/book_router.go | 6 +- .../http/v1/routes/bookevents_router.go | 10 +- .../http/v1/routes/chapter_router.go | 6 +- .../http/v1/routes/health_router.go | 2 +- .../http/v1/routes/mapvariables_router.go | 10 +- .../http/v1/routes/page_router.go | 6 +- .../http/v1/routes/paragraph_router.go | 6 +- .../http/v1/routes/swagger_router.go | 6 +- bookback/internal/models/basic_model.go | 8 +- bookback/internal/models/book_model.go | 28 ++--- bookback/internal/models/bookevents_model.go | 32 +++--- bookback/internal/models/chapter_model.go | 22 ++-- bookback/internal/models/dbutils/db_utils.go | 56 ++++++++++ .../internal/models/mapvariables_model.go | 36 +++---- bookback/internal/models/page_model.go | 18 ++-- bookback/internal/models/paragraph_model.go | 18 ++-- bookback/pkg/config/config.go | 20 +--- bookback/pkg/config/webserver.go | 42 ++++++++ bookback/pkg/postgres/client.go | 33 ++++-- bookback/pkg/postgres/interface.go | 2 - bookback/pkg/postgres/{pg => }/pg.go | 21 ++-- .../pkg/postgres/transaction/transaction.go | 5 +- 62 files changed, 835 insertions(+), 500 deletions(-) create mode 100644 bookback/config/server.yml rename bookback/internal/{models/converter => adapters}/book.go (99%) rename bookback/internal/{models/converter => adapters}/bookevent.go (99%) rename bookback/internal/{models/converter => adapters}/chapter.go (98%) rename bookback/internal/{models/converter => adapters}/mapvariables.go (99%) rename bookback/internal/{models/converter => adapters}/page.go (98%) rename bookback/internal/{models/converter => adapters}/paragraph.go (98%) create mode 100644 bookback/internal/infrastructure/http/middleware/recover.go create mode 100644 bookback/internal/infrastructure/http/v1/endpoint/handler.go create mode 100644 bookback/internal/infrastructure/http/v1/endpoint/server.go create mode 100644 bookback/internal/models/dbutils/db_utils.go create mode 100644 bookback/pkg/config/webserver.go rename bookback/pkg/postgres/{pg => }/pg.go (64%) diff --git a/bookback/.golangci.yml b/bookback/.golangci.yml index 437c32d..1dfc88a 100644 --- a/bookback/.golangci.yml +++ b/bookback/.golangci.yml @@ -86,7 +86,7 @@ linters: - bodyclose - depguard - dogsled - - dupl +# - dupl - errcheck - errorlint - exportloopref diff --git a/bookback/cmd/bookback/main.go b/bookback/cmd/bookback/main.go index 1395901..24b8bc8 100644 --- a/bookback/cmd/bookback/main.go +++ b/bookback/cmd/bookback/main.go @@ -18,7 +18,7 @@ var configPath string // @produces application/json // @consumes application/json func main() { - flag.StringVar(&configPath, "config", "./config/default.yml", "path to the configuration file") + flag.StringVar(&configPath, "config", "./config", "path to the configuration files") app, err := pkg.NewApp(configPath) if err != nil { fmt.Printf("failed to create app: %+v\n", err) diff --git a/bookback/config/default.yml b/bookback/config/default.yml index 1a26ab5..1b1a163 100644 --- a/bookback/config/default.yml +++ b/bookback/config/default.yml @@ -1,15 +1,3 @@ -cors_enabled: true shutdown_timeout: 10s -swagger: - enabled: true -http_server: - address: "0.0.0.0:7077" - timeout: 4s - iddle_timeout: 60s - request_limit: 100 - min_requests: 10 - error_threshold_percentage: 0.6 - interval_duration: 10s - open_state_timeout: 10s logger: level: "debug" diff --git a/bookback/config/server.yml b/bookback/config/server.yml new file mode 100644 index 0000000..2918301 --- /dev/null +++ b/bookback/config/server.yml @@ -0,0 +1,12 @@ +address: "0.0.0.0:7077" +timeout: 4s +iddle_timeout: 60s + +request_limit: 100 +min_requests: 10 +error_threshold_percentage: 0.6 +interval_duration: 10s +open_state_timeout: 10s + +cors_enabled: true +swagger_enabled: true \ No newline at end of file diff --git a/bookback/internal/models/converter/book.go b/bookback/internal/adapters/book.go similarity index 99% rename from bookback/internal/models/converter/book.go rename to bookback/internal/adapters/book.go index 24740b6..be4e26d 100644 --- a/bookback/internal/models/converter/book.go +++ b/bookback/internal/adapters/book.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/models/converter/bookevent.go b/bookback/internal/adapters/bookevent.go similarity index 99% rename from bookback/internal/models/converter/bookevent.go rename to bookback/internal/adapters/bookevent.go index 3ea8845..574d5a0 100644 --- a/bookback/internal/models/converter/bookevent.go +++ b/bookback/internal/adapters/bookevent.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/models/converter/chapter.go b/bookback/internal/adapters/chapter.go similarity index 98% rename from bookback/internal/models/converter/chapter.go rename to bookback/internal/adapters/chapter.go index 0922200..0724bbe 100644 --- a/bookback/internal/models/converter/chapter.go +++ b/bookback/internal/adapters/chapter.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/models/converter/mapvariables.go b/bookback/internal/adapters/mapvariables.go similarity index 99% rename from bookback/internal/models/converter/mapvariables.go rename to bookback/internal/adapters/mapvariables.go index 1084eee..6bd5f8f 100644 --- a/bookback/internal/models/converter/mapvariables.go +++ b/bookback/internal/adapters/mapvariables.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/models/converter/page.go b/bookback/internal/adapters/page.go similarity index 98% rename from bookback/internal/models/converter/page.go rename to bookback/internal/adapters/page.go index cc8f2e9..d143001 100644 --- a/bookback/internal/models/converter/page.go +++ b/bookback/internal/adapters/page.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/models/converter/paragraph.go b/bookback/internal/adapters/paragraph.go similarity index 98% rename from bookback/internal/models/converter/paragraph.go rename to bookback/internal/adapters/paragraph.go index 5b3be1e..105d339 100644 --- a/bookback/internal/models/converter/paragraph.go +++ b/bookback/internal/adapters/paragraph.go @@ -1,4 +1,4 @@ -package converter +package adapters import ( "database/sql" diff --git a/bookback/internal/bootstrap/app/app.go b/bookback/internal/bootstrap/app/app.go index 0158e5d..39974cc 100644 --- a/bookback/internal/bootstrap/app/app.go +++ b/bookback/internal/bootstrap/app/app.go @@ -2,10 +2,10 @@ package app import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/endpoint" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" - cfg "github.com/SShlykov/zeitment/bookback/pkg/config" + "github.com/SShlykov/zeitment/bookback/pkg/config" "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" "log/slog" "os" "os/signal" @@ -15,9 +15,9 @@ import ( type App struct { configPath string logger *slog.Logger - config *cfg.Config + config *config.Config db postgres.Client - Echo *echo.Echo + web *endpoint.Handler metrics metrics.Metrics ctx context.Context @@ -28,17 +28,16 @@ func NewApp(configPath string) (*App, error) { ctx, closeCtx := context.WithCancel(context.Background()) app := &App{ctx: ctx, closeCtx: closeCtx, configPath: configPath} - inits := []func(ctx context.Context) error{ + inits := []func() error{ app.initConfig, app.initLogger, app.initMetrics, app.initDB, - app.initEndpoint, - app.initRouter, + app.initWebServer, } for _, init := range inits { - if err := init(ctx); err != nil { + if err := init(); err != nil { return nil, err } } @@ -51,18 +50,12 @@ func (app *App) Run() error { defer stop() logger := app.logger - logger.Info("starting book app", slog.String("at", app.config.Address)) + logger.Info("starting book app", slog.String("at", app.web.Address)) logger.Debug("debug messages enabled") var wg sync.WaitGroup - runs := []func(*sync.WaitGroup, context.Context){ - app.runWebServer, - } - - for _, run := range runs { - run(&wg, ctx) - } + app.RunWebServer(&wg) return app.closer(ctx) } diff --git a/bookback/internal/bootstrap/app/closer.go b/bookback/internal/bootstrap/app/closer.go index 371455b..3768cee 100644 --- a/bookback/internal/bootstrap/app/closer.go +++ b/bookback/internal/bootstrap/app/closer.go @@ -12,7 +12,7 @@ func (app *App) closer(ctx context.Context) error { shutdownCtx, cancel := context.WithTimeout(context.Background(), app.config.ShutdownTimeout) app.logger.Log(context.Background(), slog.LevelInfo, "Shutting down controller") defer cancel() - if err := app.Echo.Shutdown(shutdownCtx); err != nil { + if err := app.web.Shutdown(shutdownCtx); err != nil { return errors.New("failed to shutdown controller: " + err.Error()) } diff --git a/bookback/internal/bootstrap/app/config.go b/bookback/internal/bootstrap/app/config.go index eeb1fe2..f9ad646 100644 --- a/bookback/internal/bootstrap/app/config.go +++ b/bookback/internal/bootstrap/app/config.go @@ -1,12 +1,11 @@ package app import ( - "context" "errors" "github.com/SShlykov/zeitment/bookback/pkg/config" ) -func (app *App) initConfig(_ context.Context) error { +func (app *App) initConfig() error { cfg, err := config.LoadConfig(app.configPath) if err != nil { return errors.New("failed to load config: " + err.Error()) diff --git a/bookback/internal/bootstrap/app/db.go b/bookback/internal/bootstrap/app/db.go index fb8fc7b..8c542e4 100644 --- a/bookback/internal/bootstrap/app/db.go +++ b/bookback/internal/bootstrap/app/db.go @@ -1,19 +1,19 @@ package app import ( - "context" "errors" "github.com/SShlykov/zeitment/bookback/pkg/config" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "time" ) -func (app *App) initDB(ctx context.Context) error { +func (app *App) initDB() error { pgConf, err := config.NewPGConfig() if err != nil { return errors.New("failed to init pg config: " + err.Error()) } - db, err := postgres.NewClient(ctx, app.logger, pgConf.DSN()) + + db, err := postgres.NewClient(app.ctx, app.logger, pgConf.DSN()) if err != nil { return errors.New("failed to init pg client: " + err.Error()) } @@ -23,7 +23,7 @@ func (app *App) initDB(ctx context.Context) error { var broken int ticker := time.NewTicker(pgConf.PingInterval()) for range ticker.C { - err = db.DB().Ping(ctx) + err = db.DB().Ping(app.ctx) if err != nil { broken++ if broken > pgConf.MaxPingAttempts() { diff --git a/bookback/internal/bootstrap/app/endpoint.go b/bookback/internal/bootstrap/app/endpoint.go index 3cf5d8b..07b9352 100644 --- a/bookback/internal/bootstrap/app/endpoint.go +++ b/bookback/internal/bootstrap/app/endpoint.go @@ -1,89 +1,59 @@ package app import ( - "context" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/book" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/bookevents" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/controllers/chapter" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/health" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/mapvariables" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/page" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/paragraph" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/swagger" - "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" - "github.com/SShlykov/zeitment/bookback/pkg/circuitbreaker" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/labstack/echo/v4" - echomv "github.com/labstack/echo/v4/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/endpoint" + "github.com/SShlykov/zeitment/bookback/pkg/config" "log/slog" - "net/http" "sync" ) -func (app *App) runWebServer(wg *sync.WaitGroup, _ context.Context) { +func (app *App) initWebServer() error { + cfg, err := getConfig(app.configPath) + if err != nil { + return err + } + app.web, err = endpoint.NewHandler(app.db, app.metrics, app.logger, app.ctx, cfg) + + if err != nil { + return err + } + + return nil +} + +func (app *App) RunWebServer(wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Done() - httpServer := &http.Server{ - ReadHeaderTimeout: app.config.Timeout, - ReadTimeout: app.config.Timeout, - WriteTimeout: app.config.Timeout, - IdleTimeout: app.config.IddleTimeout, - Addr: app.config.Address, - Handler: app.Echo, - } - app.logger.Info("HTTP server started") - err := httpServer.ListenAndServe() + err := endpoint.RunServer(app.web, app.logger) if err != nil { app.logger.Error("HTTP server stopped", slog.Group("err", err)) } }() } -func (app *App) initEndpoint(_ context.Context) error { - e := echo.New() - cb := circuitbreaker.NewCircuitBreaker( - app.config.RequestLimit, - app.config.MinRequests, - app.config.ErrorThresholdPercentage, - app.config.IntervalDuration, - app.config.OpenStateTimeout, - ) +func getConfig(configPath string) (*endpoint.HTTPServerConfig, error) { + cfg, err := config.LoadServerConfig(configPath) - middlewares := []echo.MiddlewareFunc{ - middleware.LoggerConfiguration(app.logger), - echomv.Recover(), - middleware.CreateCircuitBreakerMiddleware(cb), + if err != nil { + return nil, err } - if app.config.CorsEnabled { - middlewares = append(middlewares, middleware.CORS()) - } - - e.Use(middlewares...) - - app.Echo = e - return nil + return FileConfigToServerConfig(cfg), nil } -func (app *App) initRouter(_ context.Context) error { - controllers := []func(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context){ - health.SetHealthController, - book.SetBookController, - chapter.SetChapterController, - page.SetPageController, - paragraph.SetParagraphController, - bookevents.SetBookEventController, - mapvariables.SetMapVariablesController, - } - - for _, controller := range controllers { - controller(app.Echo, app.db, app.metrics, app.logger, app.ctx) +func FileConfigToServerConfig(cfg *config.HTTPServer) *endpoint.HTTPServerConfig { + return &endpoint.HTTPServerConfig{ + RequestLimit: cfg.RequestLimit, + MinRequests: cfg.MinRequests, + ErrorThresholdPercentage: cfg.ErrorThresholdPercentage, + IntervalDuration: cfg.IntervalDuration, + OpenStateTimeout: cfg.OpenStateTimeout, + CorsEnabled: cfg.CorsEnabled, + SwaggerEnabled: cfg.SwaggerEnabled, + Timeout: cfg.Timeout, + IddleTimeout: cfg.IddleTimeout, + Address: cfg.Address, } - - swagger.SetSwagger(app.Echo, app.config.SwaggerEnabled) - - return nil } diff --git a/bookback/internal/bootstrap/app/logger.go b/bookback/internal/bootstrap/app/logger.go index 7665dc4..e8cccdc 100644 --- a/bookback/internal/bootstrap/app/logger.go +++ b/bookback/internal/bootstrap/app/logger.go @@ -1,12 +1,11 @@ package app import ( - "context" "errors" "github.com/SShlykov/zeitment/bookback/pkg/logger" ) -func (app *App) initLogger(ctx context.Context) error { +func (app *App) initLogger() error { if app.config == nil { return errors.New("config is nil") } diff --git a/bookback/internal/bootstrap/app/metrics.go b/bookback/internal/bootstrap/app/metrics.go index 6ee41ed..4ebf38b 100644 --- a/bookback/internal/bootstrap/app/metrics.go +++ b/bookback/internal/bootstrap/app/metrics.go @@ -1,11 +1,10 @@ package app import ( - "context" "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics/localmetrics" ) -func (app *App) initMetrics(_ context.Context) error { +func (app *App) initMetrics() error { logger := app.logger logger.Info("initializing metrics as local metrics") diff --git a/bookback/internal/domain/entity/book.go b/bookback/internal/domain/entity/book.go index 1d0e5fd..3d2f66d 100644 --- a/bookback/internal/domain/entity/book.go +++ b/bookback/internal/domain/entity/book.go @@ -28,8 +28,8 @@ func (b Book) TableName() string { } func (b Book) AllFields() []string { - return []string{"id", "created_at", "updated_at", "deleted_at", "owner", "title", "author", "description", - "is_public", "publication", "image_link", "map_link", "map_params_id", "variables"} + return []string{"id", "created_at", "updated_at", "deleted_at", "owner", "title", "author", + "description", "is_public", "publication", "image_link", "map_link", "map_params_id", "variables"} } func (b Book) InsertFields() []string { @@ -37,28 +37,25 @@ func (b Book) InsertFields() []string { "map_params_id", "variables"} } -func (b Book) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(Book); ok { - return []interface{}{ - e.Owner, e.Title, e.Author, e.Description, e.IsPublic, e.Publication, - e.ImageLink, e.MapLink, e.MapParamsID, e.Variables, - } +func (b Book) EntityToInsertValues(impl *Book) []interface{} { + return []interface{}{ + impl.Owner, impl.Title, impl.Author, impl.Description, impl.IsPublic, impl.Publication, + impl.ImageLink, impl.MapLink, impl.MapParamsID, impl.Variables, } - return nil } -func (b Book) ReadItem(row pgx.Row) (any, error) { +func (b Book) ReadItem(row pgx.Row) (Book, error) { var book Book err := row.Scan(&book.ID, &book.CreatedAt, &book.UpdatedAt, &book.DeletedAt, &book.Owner, &book.Title, &book.Author, - &book.Description, &book.IsPublic, &book.Publication, &book.ImageLink, &book.MapLink, &book.MapParamsID) + &book.Description, &book.IsPublic, &book.Publication, &book.ImageLink, &book.MapLink, &book.MapParamsID, &book.Variables) if err != nil { - return nil, err + return Book{}, err } return book, nil } -func (b Book) ReadList(rows pgx.Rows) ([]any, error) { - var books []any +func (b Book) ReadList(rows pgx.Rows) ([]Book, error) { + var books []Book for rows.Next() { book, err := b.ReadItem(rows) if err != nil { diff --git a/bookback/internal/domain/entity/bookevents.go b/bookback/internal/domain/entity/bookevents.go index eba98bd..beba4b0 100644 --- a/bookback/internal/domain/entity/bookevents.go +++ b/bookback/internal/domain/entity/bookevents.go @@ -26,7 +26,7 @@ type BookEvent struct { } func (be BookEvent) TableName() string { - return "chapters" + return "book_events" } func (be BookEvent) AllFields() []string { @@ -39,32 +39,29 @@ func (be BookEvent) InsertFields() []string { "is_public", "key", "value", "link", "link_text", "link_type", "link_image", "description"} } -func (be BookEvent) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(BookEvent); ok { - return []interface{}{e.BookID, e.ChapterID, e.PageID, e.ParagraphID, e.EventType, - e.IsPublic, e.Key, e.Value, e.Link, e.LinkText, e.LinkType, e.LinkImage, e.Description} - } - return nil +func (be BookEvent) EntityToInsertValues(entity *BookEvent) []interface{} { + return []interface{}{entity.BookID, entity.ChapterID, entity.PageID, entity.ParagraphID, entity.EventType, + entity.IsPublic, entity.Key, entity.Value, entity.Link, entity.LinkText, entity.LinkType, entity.LinkImage, entity.Description} } -func (be BookEvent) ReadItem(row pgx.Row) (any, error) { +func (be BookEvent) ReadItem(row pgx.Row) (BookEvent, error) { var e BookEvent err := row.Scan(&e.ID, &e.CreatedAt, &e.UpdatedAt, &e.BookID, &e.ChapterID, &e.PageID, &e.ParagraphID, &e.EventType, &e.IsPublic, &e.Key, &e.Value, &e.Link, &e.LinkText, &e.LinkType, &e.LinkImage, &e.Description) if err != nil { - return nil, err + return BookEvent{}, err } return e, nil } -func (be BookEvent) ReadList(rows pgx.Rows) ([]any, error) { - var entities []any +func (be BookEvent) ReadList(rows pgx.Rows) ([]BookEvent, error) { + var entities []BookEvent for rows.Next() { - chapter, err := be.ReadItem(rows) + bookEvent, err := be.ReadItem(rows) if err != nil { return nil, err } - entities = append(entities, chapter) + entities = append(entities, bookEvent) } return entities, nil } diff --git a/bookback/internal/domain/entity/chapter.go b/bookback/internal/domain/entity/chapter.go index 6821823..76d365d 100644 --- a/bookback/internal/domain/entity/chapter.go +++ b/bookback/internal/domain/entity/chapter.go @@ -33,25 +33,23 @@ func (c Chapter) InsertFields() []string { return []string{"title", "number", "text", "book_id", "is_public", "map_link", "map_params_id"} } -func (c Chapter) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(Chapter); ok { - return []interface{}{e.Title, e.Number, e.Text, e.BookID, e.IsPublic, e.MapLink, e.MapParamsID} - } - return nil +func (c Chapter) EntityToInsertValues(entity *Chapter) []interface{} { + return []interface{}{entity.Title, entity.Number, entity.Text, entity.BookID, entity.IsPublic, + entity.MapLink, entity.MapParamsID} } -func (c Chapter) ReadItem(row pgx.Row) (any, error) { +func (c Chapter) ReadItem(row pgx.Row) (Chapter, error) { var e Chapter - err := row.Scan(&e.ID, &e.CreatedAt, &e.UpdatedAt, &e.Title, &e.Number, &e.Text, &e.BookID, &e.IsPublic, - &e.MapLink, &e.MapParamsID) + err := row.Scan(&e.ID, &e.CreatedAt, &e.UpdatedAt, &e.DeletedAt, &e.Title, &e.Number, &e.Text, &e.BookID, + &e.IsPublic, &e.MapLink, &e.MapParamsID) if err != nil { - return nil, err + return Chapter{}, err } return e, nil } -func (c Chapter) ReadList(rows pgx.Rows) ([]any, error) { - var entities []any +func (c Chapter) ReadList(rows pgx.Rows) ([]Chapter, error) { + var entities []Chapter for rows.Next() { chapter, err := c.ReadItem(rows) if err != nil { diff --git a/bookback/internal/domain/entity/mapvariables.go b/bookback/internal/domain/entity/mapvariables.go index 5ed14cb..00aa2d3 100644 --- a/bookback/internal/domain/entity/mapvariables.go +++ b/bookback/internal/domain/entity/mapvariables.go @@ -46,32 +46,30 @@ func (mv MapVariable) InsertFields() []string { } // EntityToInsertValues преобразует сущность в список значений для вставки -func (mv MapVariable) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(MapVariable); ok { - return []interface{}{ - e.BookID, e.ChapterID, e.PageID, e.ParagraphID, e.MapLink, e.Lat, e.Lng, e.Zoom, e.Date, - e.Description, e.Link, e.LinkText, e.LinkType, e.LinkImage, e.Image, - } +func (mv MapVariable) EntityToInsertValues(entity *MapVariable) []interface{} { + return []interface{}{ + entity.BookID, entity.ChapterID, entity.PageID, entity.ParagraphID, entity.MapLink, entity.Lat, entity.Lng, + entity.Zoom, entity.Date, entity.Description, entity.Link, entity.LinkText, entity.LinkType, + entity.LinkImage, entity.Image, } - return nil } // ReadItem читает одну запись из строки запроса -func (mv MapVariable) ReadItem(row pgx.Row) (any, error) { +func (mv MapVariable) ReadItem(row pgx.Row) (MapVariable, error) { var mapVariable MapVariable err := row.Scan(&mapVariable.ID, &mapVariable.CreatedAt, &mapVariable.UpdatedAt, &mapVariable.BookID, &mapVariable.ChapterID, &mapVariable.PageID, &mapVariable.ParagraphID, &mapVariable.MapLink, &mapVariable.Lat, &mapVariable.Lng, &mapVariable.Zoom, &mapVariable.Date, &mapVariable.Description, &mapVariable.Link, &mapVariable.LinkText, &mapVariable.LinkType, &mapVariable.LinkImage, &mapVariable.Image) if err != nil { - return nil, err + return MapVariable{}, err } return mapVariable, nil } // ReadList читает список записей из результатов запроса -func (mv MapVariable) ReadList(rows pgx.Rows) ([]any, error) { - var mapVariables []any +func (mv MapVariable) ReadList(rows pgx.Rows) ([]MapVariable, error) { + var mapVariables []MapVariable for rows.Next() { mapVariable, err := mv.ReadItem(rows) if err != nil { diff --git a/bookback/internal/domain/entity/page.go b/bookback/internal/domain/entity/page.go index 8e19cb1..f0e8cd3 100644 --- a/bookback/internal/domain/entity/page.go +++ b/bookback/internal/domain/entity/page.go @@ -35,29 +35,24 @@ func (p Page) InsertFields() []string { } // EntityToInsertValues преобразует сущность Page в список значений для вставки -func (p Page) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(Page); ok { - return []interface{}{ - e.Title, e.Text, e.ChapterID, e.IsPublic, e.MapParamsID, - } - } - return nil +func (p Page) EntityToInsertValues(entity *Page) []interface{} { + return []interface{}{entity.Title, entity.Text, entity.ChapterID, entity.IsPublic, entity.MapParamsID} } // ReadItem читает одну запись из строки запроса -func (p Page) ReadItem(row pgx.Row) (any, error) { +func (p Page) ReadItem(row pgx.Row) (Page, error) { var page Page err := row.Scan(&page.ID, &page.CreatedAt, &page.UpdatedAt, &page.DeletedAt, &page.Title, &page.Text, &page.ChapterID, &page.IsPublic, &page.MapParamsID) if err != nil { - return nil, err + return Page{}, err } return page, nil } // ReadList читает список записей из результатов запроса -func (p Page) ReadList(rows pgx.Rows) ([]any, error) { - var pages []any +func (p Page) ReadList(rows pgx.Rows) ([]Page, error) { + var pages []Page for rows.Next() { page, err := p.ReadItem(rows) if err != nil { diff --git a/bookback/internal/domain/entity/paragraph.go b/bookback/internal/domain/entity/paragraph.go index ddcf249..114e946 100644 --- a/bookback/internal/domain/entity/paragraph.go +++ b/bookback/internal/domain/entity/paragraph.go @@ -35,29 +35,24 @@ func (p Paragraph) InsertFields() []string { } // EntityToInsertValues преобразует сущность Paragraph в список значений для вставки -func (p Paragraph) EntityToInsertValues(entity any) []interface{} { - if e, ok := entity.(Paragraph); ok { - return []interface{}{ - e.Title, e.Text, e.Type, e.IsPublic, e.PageID, - } - } - return nil +func (p Paragraph) EntityToInsertValues(entity *Paragraph) []interface{} { + return []interface{}{entity.Title, entity.Text, entity.Type, entity.IsPublic, entity.PageID} } // ReadItem читает одну запись из строки запроса -func (p Paragraph) ReadItem(row pgx.Row) (any, error) { +func (p Paragraph) ReadItem(row pgx.Row) (Paragraph, error) { var paragraph Paragraph err := row.Scan(¶graph.ID, ¶graph.CreatedAt, ¶graph.UpdatedAt, ¶graph.DeletedAt, ¶graph.Title, ¶graph.Text, ¶graph.Type, ¶graph.IsPublic, ¶graph.PageID) if err != nil { - return nil, err + return Paragraph{}, err } return paragraph, nil } // ReadList читает список записей из результатов запроса -func (p Paragraph) ReadList(rows pgx.Rows) ([]any, error) { - var paragraphs []any +func (p Paragraph) ReadList(rows pgx.Rows) ([]Paragraph, error) { + var paragraphs []Paragraph for rows.Next() { paragraph, err := p.ReadItem(rows) if err != nil { diff --git a/bookback/internal/domain/repository/pgrepo/0-genericrepository.go b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go index 20d2a91..3e8fa10 100644 --- a/bookback/internal/domain/repository/pgrepo/0-genericrepository.go +++ b/bookback/internal/domain/repository/pgrepo/0-genericrepository.go @@ -3,25 +3,28 @@ package pgrepo import ( "context" "fmt" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/jackc/pgx/v5" "strings" ) -type Simulated interface { +const Equals = " = ?" + +type Simulated[T any] interface { TableName() string AllFields() []string InsertFields() []string - EntityToInsertValues(entity any) []interface{} - ReadItem(row pgx.Row) (any, error) - ReadList(rows pgx.Rows) ([]any, error) + EntityToInsertValues(impl *T) []interface{} + ReadItem(row pgx.Row) (T, error) + ReadList(rows pgx.Rows) ([]T, error) } // Repository определяет обобщенный интерфейс для операций CRUD над сущностями типа T. // T представляет тип сущности, который должен быть структурой. type Repository[T any] interface { // List извлекает срез сущностей на основе лимита и смещения для пагинации. - List(ctx context.Context, limit uint64, offset uint64) ([]*T, error) + List(ctx context.Context, options dbutils.Pagination) ([]*T, error) // Create вставляет новую сущность в репозиторий и возвращает её ID или ошибку. Create(ctx context.Context, item *T) (string, error) @@ -37,22 +40,22 @@ type Repository[T any] interface { FindByID(ctx context.Context, id string) (*T, error) // FindByKV ищет сущности, соответствующие определенной паре ключ-значение. - FindByKV(ctx context.Context, key string, value any) ([]*T, error) + FindByKV(ctx context.Context, options dbutils.QueryOptions) ([]*T, error) } -type repository[T Simulated] struct { +type repository[T Simulated[T]] struct { Name string entity T db postgres.Client } -func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ([]*T, error) { +func (r *repository[T]) List(ctx context.Context, options dbutils.Pagination) ([]*T, error) { + limit, offset := options.GetPagination() query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). - Offset(offset). Limit(limit). - Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + Offset(offset). ToSql() if err != nil { @@ -71,17 +74,20 @@ func (r *repository[T]) List(ctx context.Context, limit uint64, offset uint64) ( } func (r *repository[T]) Create(ctx context.Context, item *T) (string, error) { + fields := r.entity.InsertFields() + insertValues := r.entity.EntityToInsertValues(item) + query, args, err := r.db.Builder(). Insert(r.entity.TableName()). - Columns(r.entity.InsertFields()...). - Values(r.entity.EntityToInsertValues(&item)...). - Prefix("RETURNING id"). + Columns(fields...). + Values(insertValues...). + Suffix("RETURNING id"). ToSql() if err != nil { return "", err } - + fmt.Println("query", query) q := postgres.Query{Name: r.Name + ".Insert", Raw: query} var id string @@ -100,7 +106,7 @@ func (r *repository[T]) Update(ctx context.Context, id string, item *T) (*T, err } query, args, err := updateQuery. - Where("? = ?", r.entity.TableName()+`.id`, id). + Where(r.entity.TableName()+Equals, id). Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). ToSql() @@ -118,7 +124,7 @@ func (r *repository[T]) Update(ctx context.Context, id string, item *T) (*T, err func (r *repository[T]) HardDelete(ctx context.Context, id string) error { query, args, err := r.db.Builder(). Delete(r.entity.TableName()). - Where("? = ?", r.entity.TableName()+`.id`, id). + Where(r.tableKey("id")+Equals, id). Suffix("RETURNING id"). ToSql() @@ -135,12 +141,16 @@ func (r *repository[T]) HardDelete(ctx context.Context, id string) error { // FindByKV ONLY FOR QUERY COMPOSITION; KEY WILL NOT BE ESCAPED/CHECKED SO IT'S UNSAFE // e.g. you need a function FindByTitle(title string) ([]Book, error) -> FindByKV("title", title) -func (r *repository[T]) FindByKV(ctx context.Context, key string, value interface{}) ([]*T, error) { +func (r *repository[T]) FindByKV(ctx context.Context, options dbutils.QueryOptions) ([]*T, error) { + key, value := options.GetFilter() + limit, offset := options.GetPagination() + query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). - Where("? = ?", key, value). - Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + Where(r.tableKey(key)+Equals, value). + Limit(limit). + Offset(offset). ToSql() if err != nil { @@ -162,8 +172,7 @@ func (r *repository[T]) FindByID(ctx context.Context, id string) (*T, error) { query, args, err := r.db.Builder(). Select(r.entity.AllFields()...). From(r.entity.TableName()). - Where(r.entity.TableName()+".id = ?", id). - Suffix("RETURNING " + strings.Join(r.entity.AllFields(), ", ")). + Where(r.tableKey("id")+Equals, id). ToSql() if err != nil { @@ -181,12 +190,7 @@ func (r *repository[T]) readItem(row pgx.Row) (*T, error) { return nil, err } - result, ok := res.(*T) - if !ok { - return nil, fmt.Errorf("ошибка приведения типа в %s.Update", r.Name) - } - - return result, nil + return &res, nil } func (r *repository[T]) readList(rows pgx.Rows) ([]*T, error) { @@ -199,12 +203,7 @@ func (r *repository[T]) readList(rows pgx.Rows) ([]*T, error) { return nil, err } - typedItem, ok := item.(*T) - if !ok { - return nil, fmt.Errorf("не удалось привести тип элемента в readList") - } - - result = append(result, typedItem) + result = append(result, &item) } if err := rows.Err(); err != nil { @@ -213,3 +212,7 @@ func (r *repository[T]) readList(rows pgx.Rows) ([]*T, error) { return result, nil } + +func (r *repository[T]) tableKey(key string) string { + return r.entity.TableName() + "." + key +} diff --git a/bookback/internal/domain/services/0_generic_repo.go b/bookback/internal/domain/services/0_generic_repo.go index c332eed..7fe3108 100644 --- a/bookback/internal/domain/services/0_generic_repo.go +++ b/bookback/internal/domain/services/0_generic_repo.go @@ -2,13 +2,14 @@ package services import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type SimpleRepo[T any] interface { Create(ctx context.Context, ent T) (string, error) FindByID(ctx context.Context, id string) (T, error) - FindByKV(ctx context.Context, key string, value interface{}) ([]T, error) Update(ctx context.Context, id string, ent T) (T, error) HardDelete(ctx context.Context, id string) error - List(ctx context.Context, limit uint64, offset uint64) ([]T, error) + List(ctx context.Context, options dbutils.Pagination) ([]T, error) + FindByKV(ctx context.Context, options dbutils.QueryOptions) ([]T, error) } diff --git a/bookback/internal/domain/services/book_service.go b/bookback/internal/domain/services/book_service.go index 772a946..4a63fda 100644 --- a/bookback/internal/domain/services/book_service.go +++ b/bookback/internal/domain/services/book_service.go @@ -2,9 +2,10 @@ package services import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) // BookService описывает сервис для работы с книгами. @@ -13,7 +14,7 @@ type BookService interface { GetBookByID(ctx context.Context, id string) (*models.Book, error) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) DeleteBook(ctx context.Context, id string) (*models.Book, error) - ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) + ListBooks(ctx context.Context, request models.RequestBook) ([]*models.Book, error) } type bookService struct { @@ -26,7 +27,7 @@ func NewBookService(repo SimpleRepo[*entity.Book]) BookService { } func (s *bookService) CreateBook(ctx context.Context, request models.CreateBookRequest) (*models.Book, error) { - book := converter.BookModelToEntity(request.Book) + book := adapters.BookModelToEntity(request.Book) if book.Variables == nil { book.Variables = []string{} @@ -45,16 +46,16 @@ func (s *bookService) GetBookByID(ctx context.Context, id string) (*models.Book, return nil, err } - return converter.BookEntityToModel(book), nil + return adapters.BookEntityToModel(book), nil } func (s *bookService) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) { - book, err := s.repo.Update(ctx, id, converter.BookModelToEntity(request.Book)) + book, err := s.repo.Update(ctx, id, adapters.BookModelToEntity(request.Book)) if err != nil { return nil, err } - return converter.BookEntityToModel(book), nil + return adapters.BookEntityToModel(book), nil } func (s *bookService) DeleteBook(ctx context.Context, id string) (*models.Book, error) { @@ -71,11 +72,13 @@ func (s *bookService) DeleteBook(ctx context.Context, id string) (*models.Book, return book, err } -func (s *bookService) ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) { - books, err := s.repo.List(ctx, limit, offset) +func (s *bookService) ListBooks(ctx context.Context, request models.RequestBook) ([]*models.Book, error) { + options := dbutils.NewPagination(&request.Options) + + books, err := s.repo.List(ctx, options) if err != nil { return nil, err } - return converter.BooksEntityToModel(books), nil + return adapters.BooksEntityToModel(books), nil } diff --git a/bookback/internal/domain/services/bookevents_service.go b/bookback/internal/domain/services/bookevents_service.go index 02078ba..6e5b330 100644 --- a/bookback/internal/domain/services/bookevents_service.go +++ b/bookback/internal/domain/services/bookevents_service.go @@ -3,9 +3,10 @@ package services import ( "context" "errors" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type BookEventsService interface { @@ -14,10 +15,10 @@ type BookEventsService interface { UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) - GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) - GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) - GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) - GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) + GetBookEventsByBookID(ctx context.Context, bookID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string, request models.RequestBookEvent) ([]*models.BookEvent, error) } type bookEventsService struct { @@ -29,7 +30,7 @@ func NewBookEventsService(repo SimpleRepo[*entity.BookEvent]) BookEventsService } func (s *bookEventsService) CreateBookEvent(ctx context.Context, request models.CreateBookEventRequest) (*models.BookEvent, error) { - bookEvent := converter.BookEventModelToEntity(request.BookEvent) + bookEvent := adapters.BookEventModelToEntity(request.BookEvent) id, err := s.repo.Create(ctx, bookEvent) if err != nil { @@ -51,18 +52,19 @@ func (s *bookEventsService) GetBookEventByID(ctx context.Context, id string) (*m return nil, err } - return converter.BookEventEntityToModel(bookEvent), nil + return adapters.BookEventEntityToModel(bookEvent), nil } -func (s *bookEventsService) UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) { - bookEvent := converter.BookEventModelToEntity(request.BookEvent) +func (s *bookEventsService) UpdateBookEvent(ctx context.Context, id string, + request models.UpdateBookEventRequest) (*models.BookEvent, error) { + bookEvent := adapters.BookEventModelToEntity(request.BookEvent) updatedEvent, err := s.repo.Update(ctx, id, bookEvent) if err != nil { return nil, err } - return converter.BookEventEntityToModel(updatedEvent), nil + return adapters.BookEventEntityToModel(updatedEvent), nil } func (s *bookEventsService) DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) { @@ -79,34 +81,54 @@ func (s *bookEventsService) DeleteBookEvent(ctx context.Context, id string) (*mo return bookEvent, err } -func (s *bookEventsService) GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) { - events, err := s.repo.FindByKV(ctx, "book_id", bookID) +func (s *bookEventsService) GetBookEventsByBookID(ctx context.Context, bookID string, + request models.RequestBookEvent) ([]*models.BookEvent, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("book_id", bookID), + dbutils.NewPagination(&request.Options), + ) + events, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.BookEventsEntityToModel(events), nil + return adapters.BookEventsEntityToModel(events), nil } -func (s *bookEventsService) GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) { - events, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) +func (s *bookEventsService) GetBookEventsByChapterID(ctx context.Context, chapterID string, + request models.RequestBookEvent) ([]*models.BookEvent, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("chapter_id", chapterID), + dbutils.NewPagination(&request.Options), + ) + events, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.BookEventsEntityToModel(events), nil + return adapters.BookEventsEntityToModel(events), nil } -func (s *bookEventsService) GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) { - events, err := s.repo.FindByKV(ctx, "page_id", pageID) +func (s *bookEventsService) GetBookEventsByPageID(ctx context.Context, pageID string, + request models.RequestBookEvent) ([]*models.BookEvent, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("page_id", pageID), + dbutils.NewPagination(&request.Options), + ) + events, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.BookEventsEntityToModel(events), nil + return adapters.BookEventsEntityToModel(events), nil } -func (s *bookEventsService) GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) { - events, err := s.repo.FindByKV(ctx, "paragraph_id", paragraphID) +func (s *bookEventsService) GetBookEventsByParagraphID(ctx context.Context, paragraphID string, + request models.RequestBookEvent) ([]*models.BookEvent, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("paragraph_id", paragraphID), + dbutils.NewPagination(&request.Options), + ) + events, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.BookEventsEntityToModel(events), nil + return adapters.BookEventsEntityToModel(events), nil } diff --git a/bookback/internal/domain/services/chapter_service.go b/bookback/internal/domain/services/chapter_service.go index 239bec5..f197444 100644 --- a/bookback/internal/domain/services/chapter_service.go +++ b/bookback/internal/domain/services/chapter_service.go @@ -4,9 +4,10 @@ import ( "context" "errors" "fmt" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type ChapterService interface { @@ -14,9 +15,9 @@ type ChapterService interface { GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) - ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) + ListChapters(ctx context.Context, request models.RequestChapter) ([]*models.Chapter, error) - GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) + GetChapterByBookID(ctx context.Context, bookID string, request models.RequestChapter) ([]*models.Chapter, error) } type chapterService struct { @@ -28,7 +29,7 @@ func NewChapterService(chapterRepo SimpleRepo[*entity.Chapter]) ChapterService { } func (ch *chapterService) CreateChapter(ctx context.Context, request models.CreateChapterRequest) (*models.Chapter, error) { - chapter := converter.ChapterModelToEntity(request.Chapter) + chapter := adapters.ChapterModelToEntity(request.Chapter) id, err := ch.chapterRepo.Create(ctx, chapter) if err != nil { @@ -51,18 +52,18 @@ func (ch *chapterService) GetChapterByID(ctx context.Context, id string) (*model return nil, err } - return converter.ChapterEntityToModel(chapter), nil + return adapters.ChapterEntityToModel(chapter), nil } func (ch *chapterService) UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) { - chapter := converter.ChapterModelToEntity(request.Chapter) + chapter := adapters.ChapterModelToEntity(request.Chapter) updatedChapter, err := ch.chapterRepo.Update(ctx, id, chapter) if err != nil { return nil, err } - return converter.ChapterEntityToModel(updatedChapter), err + return adapters.ChapterEntityToModel(updatedChapter), err } func (ch *chapterService) DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) { @@ -77,19 +78,26 @@ func (ch *chapterService) DeleteChapter(ctx context.Context, id string) (*models return chapter, err } -func (ch *chapterService) ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) { - chapters, err := ch.chapterRepo.List(ctx, limit, offset) +func (ch *chapterService) ListChapters(ctx context.Context, request models.RequestChapter) ([]*models.Chapter, error) { + options := dbutils.NewPagination(&request.Options) + + chapters, err := ch.chapterRepo.List(ctx, options) if err != nil { return nil, err } - return converter.ChaptersEntityToModel(chapters), nil + return adapters.ChaptersEntityToModel(chapters), nil } -func (ch *chapterService) GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) { - chapters, err := ch.chapterRepo.FindByKV(ctx, "book_id", bookID) +func (ch *chapterService) GetChapterByBookID(ctx context.Context, bookID string, request models.RequestChapter) ([]*models.Chapter, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("book_id", bookID), + dbutils.NewPagination(&request.Options), + ) + + chapters, err := ch.chapterRepo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.ChaptersEntityToModel(chapters), nil + return adapters.ChaptersEntityToModel(chapters), nil } diff --git a/bookback/internal/domain/services/mapvariables_service.go b/bookback/internal/domain/services/mapvariables_service.go index 352abec..a1ab3ca 100644 --- a/bookback/internal/domain/services/mapvariables_service.go +++ b/bookback/internal/domain/services/mapvariables_service.go @@ -3,9 +3,10 @@ package services import ( "context" "errors" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type MapVariablesService interface { @@ -14,10 +15,10 @@ type MapVariablesService interface { UpdateMapVariable(ctx context.Context, id string, variable models.UpdateMapVariableRequest) (*models.MapVariable, error) DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) - GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) - GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) - GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) - GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) + GetMapVariablesByBookID(ctx context.Context, mapID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string, request models.RequestMapVariable) ([]*models.MapVariable, error) } type mapVariablesService struct { @@ -29,7 +30,7 @@ func NewMapVariablesService(repo SimpleRepo[*entity.MapVariable]) MapVariablesSe } func (s *mapVariablesService) CreateMapVariable(ctx context.Context, request models.CreateMapVariableRequest) (*models.MapVariable, error) { - variable := converter.MapVariableModelToEntity(request.MapVariable) + variable := adapters.MapVariableModelToEntity(request.MapVariable) id, err := s.repo.Create(ctx, variable) if err != nil { @@ -45,18 +46,19 @@ func (s *mapVariablesService) GetMapVariableByID(ctx context.Context, id string) return nil, err } - return converter.MapVariableEntityToModel(variable), nil + return adapters.MapVariableEntityToModel(variable), nil } -func (s *mapVariablesService) UpdateMapVariable(ctx context.Context, id string, request models.UpdateMapVariableRequest) (*models.MapVariable, error) { - mapVariable := converter.MapVariableModelToEntity(request.MapVariable) +func (s *mapVariablesService) UpdateMapVariable(ctx context.Context, id string, + request models.UpdateMapVariableRequest) (*models.MapVariable, error) { + mapVariable := adapters.MapVariableModelToEntity(request.MapVariable) updatedVariable, err := s.repo.Update(ctx, id, mapVariable) if err != nil { return nil, err } - return converter.MapVariableEntityToModel(updatedVariable), nil + return adapters.MapVariableEntityToModel(updatedVariable), nil } func (s *mapVariablesService) DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) { @@ -73,42 +75,65 @@ func (s *mapVariablesService) DeleteMapVariable(ctx context.Context, id string) return mapVariable, err } -func (s *mapVariablesService) GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) { - variable, err := s.repo.FindByKV(ctx, "map_id", mapID) +func (s *mapVariablesService) GetMapVariablesByBookID(ctx context.Context, mapID string, + request models.RequestMapVariable) ([]*models.MapVariable, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("book_id", mapID), + dbutils.NewPagination(&request.Options), + ) + + variable, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.MapVariablesEntityToModel(variable), nil + return adapters.MapVariablesEntityToModel(variable), nil } -func (s *mapVariablesService) GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) { - variable, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) +func (s *mapVariablesService) GetMapVariablesByChapterID(ctx context.Context, chapterID string, + request models.RequestMapVariable) ([]*models.MapVariable, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("chapter_id", chapterID), + dbutils.NewPagination(&request.Options), + ) + + variable, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.MapVariablesEntityToModel(variable), nil + return adapters.MapVariablesEntityToModel(variable), nil } -func (s *mapVariablesService) GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) { - variable, err := s.repo.FindByKV(ctx, "page_id", pageID) +func (s *mapVariablesService) GetMapVariablesByPageID(ctx context.Context, pageID string, + request models.RequestMapVariable) ([]*models.MapVariable, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("page_id", pageID), + dbutils.NewPagination(&request.Options), + ) + + variable, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.MapVariablesEntityToModel(variable), nil + return adapters.MapVariablesEntityToModel(variable), nil } -func (s *mapVariablesService) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) { - variable, err := s.repo.FindByKV(ctx, "paragraph_id", paragraphID) +func (s *mapVariablesService) GetMapVariablesByParagraphID(ctx context.Context, paragraphID string, + request models.RequestMapVariable) ([]*models.MapVariable, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("paragraph_id", paragraphID), + dbutils.NewPagination(&request.Options), + ) + variable, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.MapVariablesEntityToModel(variable), nil + return adapters.MapVariablesEntityToModel(variable), nil } diff --git a/bookback/internal/domain/services/page_service.go b/bookback/internal/domain/services/page_service.go index 3816404..acea12e 100644 --- a/bookback/internal/domain/services/page_service.go +++ b/bookback/internal/domain/services/page_service.go @@ -3,19 +3,20 @@ package services import ( "context" "errors" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type PageService interface { CreatePage(ctx context.Context, request models.CreatePageRequest) (*models.Page, error) GetPageByID(ctx context.Context, id string) (*models.Page, error) - UpdatePage(ctx context.Context, id string, prequest models.UpdatePageRequest) (*models.Page, error) + UpdatePage(ctx context.Context, id string, request models.UpdatePageRequest) (*models.Page, error) DeletePage(ctx context.Context, id string) (*models.Page, error) - ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) + ListPages(ctx context.Context, request models.RequestPage) ([]*models.Page, error) - GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) + GetPagesByChapterID(ctx context.Context, chapterID string, request models.RequestPage) ([]*models.Page, error) } type pageService struct { @@ -27,7 +28,7 @@ func NewPageService(repo SimpleRepo[*entity.Page]) PageService { } func (s *pageService) CreatePage(ctx context.Context, request models.CreatePageRequest) (*models.Page, error) { - page := converter.PageModelToEntity(request.Page) + page := adapters.PageModelToEntity(request.Page) id, err := s.repo.Create(ctx, page) if err != nil { @@ -43,18 +44,18 @@ func (s *pageService) GetPageByID(ctx context.Context, id string) (*models.Page, return nil, err } - return converter.PageEntityToModel(page), nil + return adapters.PageEntityToModel(page), nil } func (s *pageService) UpdatePage(ctx context.Context, id string, request models.UpdatePageRequest) (*models.Page, error) { - page := converter.PageModelToEntity(request.Page) + page := adapters.PageModelToEntity(request.Page) updatedPage, err := s.repo.Update(ctx, id, page) if err != nil { return nil, err } - return converter.PageEntityToModel(updatedPage), nil + return adapters.PageEntityToModel(updatedPage), nil } func (s *pageService) DeletePage(ctx context.Context, id string) (*models.Page, error) { @@ -69,20 +70,27 @@ func (s *pageService) DeletePage(ctx context.Context, id string) (*models.Page, return page, err } -func (s *pageService) ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) { - pages, err := s.repo.List(ctx, limit, offset) +func (s *pageService) ListPages(ctx context.Context, request models.RequestPage) ([]*models.Page, error) { + options := dbutils.NewPagination(&request.Options) + + pages, err := s.repo.List(ctx, options) if err != nil { return nil, err } - return converter.PagesEntityToModel(pages), nil + return adapters.PagesEntityToModel(pages), nil } -func (s *pageService) GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) { - pages, err := s.repo.FindByKV(ctx, "chapter_id", chapterID) +func (s *pageService) GetPagesByChapterID(ctx context.Context, chapterID string, request models.RequestPage) ([]*models.Page, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("chapter_id", chapterID), + dbutils.NewPagination(&request.Options), + ) + + pages, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.PagesEntityToModel(pages), nil + return adapters.PagesEntityToModel(pages), nil } diff --git a/bookback/internal/domain/services/paragraph_service.go b/bookback/internal/domain/services/paragraph_service.go index 0ad7d2d..057a118 100644 --- a/bookback/internal/domain/services/paragraph_service.go +++ b/bookback/internal/domain/services/paragraph_service.go @@ -2,9 +2,10 @@ package services import ( "context" + "github.com/SShlykov/zeitment/bookback/internal/adapters" "github.com/SShlykov/zeitment/bookback/internal/domain/entity" "github.com/SShlykov/zeitment/bookback/internal/models" - "github.com/SShlykov/zeitment/bookback/internal/models/converter" + "github.com/SShlykov/zeitment/bookback/internal/models/dbutils" ) type ParagraphService interface { @@ -12,9 +13,9 @@ type ParagraphService interface { GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) UpdateParagraph(ctx context.Context, id string, request models.UpdateParagraphRequest) (*models.Paragraph, error) DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) - ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) + ListParagraphs(ctx context.Context, request models.RequestParagraph) ([]*models.Paragraph, error) - GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string, request models.RequestParagraph) ([]*models.Paragraph, error) } type paragraphService struct { @@ -26,7 +27,7 @@ func NewParagraphService(repo SimpleRepo[*entity.Paragraph]) ParagraphService { } func (s *paragraphService) CreateParagraph(ctx context.Context, request models.CreateParagraphRequest) (*models.Paragraph, error) { - paragraph := converter.ParagraphModelToEntity(request.Paragraph) + paragraph := adapters.ParagraphModelToEntity(request.Paragraph) id, err := s.repo.Create(ctx, paragraph) if err != nil { @@ -42,18 +43,19 @@ func (s *paragraphService) GetParagraphByID(ctx context.Context, id string) (*mo return nil, err } - return converter.ParagraphEntityToModel(paragraph), nil + return adapters.ParagraphEntityToModel(paragraph), nil } -func (s *paragraphService) UpdateParagraph(ctx context.Context, id string, request models.UpdateParagraphRequest) (*models.Paragraph, error) { - paragraph := converter.ParagraphModelToEntity(request.Paragraph) +func (s *paragraphService) UpdateParagraph(ctx context.Context, id string, + request models.UpdateParagraphRequest) (*models.Paragraph, error) { + paragraph := adapters.ParagraphModelToEntity(request.Paragraph) updatedParagraph, err := s.repo.Update(ctx, id, paragraph) if err != nil { return nil, err } - return converter.ParagraphEntityToModel(updatedParagraph), nil + return adapters.ParagraphEntityToModel(updatedParagraph), nil } func (s *paragraphService) DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) { @@ -68,20 +70,28 @@ func (s *paragraphService) DeleteParagraph(ctx context.Context, id string) (*mod return paragraph, err } -func (s *paragraphService) ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) { - paragraph, err := s.repo.List(ctx, limit, offset) +func (s *paragraphService) ListParagraphs(ctx context.Context, request models.RequestParagraph) ([]*models.Paragraph, error) { + options := dbutils.NewPagination(&request.Options) + + paragraph, err := s.repo.List(ctx, options) if err != nil { return nil, err } - return converter.ParagraphsEntityToModel(paragraph), nil + return adapters.ParagraphsEntityToModel(paragraph), nil } -func (s *paragraphService) GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) { - paragraphs, err := s.repo.FindByKV(ctx, "page_id", pageID) +func (s *paragraphService) GetParagraphsByPageID(ctx context.Context, pageID string, + request models.RequestParagraph) ([]*models.Paragraph, error) { + options := dbutils.NewQueryOptions( + dbutils.NewFilter("page_id", pageID), + dbutils.NewPagination(&request.Options), + ) + + paragraphs, err := s.repo.FindByKV(ctx, options) if err != nil { return nil, err } - return converter.ParagraphsEntityToModel(paragraphs), nil + return adapters.ParagraphsEntityToModel(paragraphs), nil } diff --git a/bookback/internal/infrastructure/http/middleware/recover.go b/bookback/internal/infrastructure/http/middleware/recover.go new file mode 100644 index 0000000..9e8a326 --- /dev/null +++ b/bookback/internal/infrastructure/http/middleware/recover.go @@ -0,0 +1,10 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func Recover() echo.MiddlewareFunc { + return middleware.Recover() +} diff --git a/bookback/internal/infrastructure/http/v1/controllers/book_controller.go b/bookback/internal/infrastructure/http/v1/controllers/book_controller.go index 15fe6af..3277a6f 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/book_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/book_controller.go @@ -15,7 +15,7 @@ type bookService interface { GetBookByID(ctx context.Context, id string) (*models.Book, error) UpdateBook(ctx context.Context, id string, request models.UpdateBookRequest) (*models.Book, error) DeleteBook(ctx context.Context, id string) (*models.Book, error) - ListBooks(ctx context.Context, limit uint64, offset uint64) ([]*models.Book, error) + ListBooks(ctx context.Context, request models.RequestBook) ([]*models.Book, error) } // BookController структура для HTTP-контроллера книг. @@ -37,8 +37,10 @@ func (bc *BookController) ListBooks(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - books, err := bc.Service.ListBooks(bc.Ctx, request.Options.Limit, request.Options.Offset) + books, err := bc.Service.ListBooks(bc.Ctx, request) if err != nil { + bc.Logger.Info("error", slog.Group("err", err)) + bc.Metrics.IncCounter("controller.book.ListBooks.error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } return c.JSON(http.StatusOK, models.WebResponse[[]*models.Book]{Data: books, Status: "ok"}) @@ -52,6 +54,8 @@ func (bc *BookController) GetBookByID(c echo.Context) error { book, err := bc.Service.GetBookByID(bc.Ctx, id) if err != nil { + bc.Logger.Info("error", slog.String("id", id), slog.String("err", err.Error())) + bc.Metrics.IncCounter("controller.book.GetBookByID.error", err.Error()) return echo.NewHTTPError(http.StatusNotFound, errors.BookNotFound) } @@ -66,6 +70,8 @@ func (bc *BookController) CreateBook(c echo.Context) error { createdBook, err := bc.Service.CreateBook(bc.Ctx, request) if err != nil { + bc.Logger.Info("error", slog.String("err", err.Error())) + bc.Metrics.IncCounter("controller.book.CreateBook.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookNotCreated) } return c.JSON(http.StatusCreated, models.WebResponse[*models.Book]{Data: createdBook, Status: "created"}) @@ -84,6 +90,8 @@ func (bc *BookController) UpdateBook(c echo.Context) error { updatedBook, err := bc.Service.UpdateBook(bc.Ctx, id, request) if err != nil { + bc.Logger.Info("error", slog.String("err", err.Error())) + bc.Metrics.IncCounter("controller.book.UpdateBook.error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: updatedBook, Status: "updated"}) @@ -97,6 +105,8 @@ func (bc *BookController) DeleteBook(c echo.Context) error { book, err := bc.Service.DeleteBook(bc.Ctx, id) if err != nil { + bc.Logger.Info("error", slog.String("err", err.Error())) + bc.Metrics.IncCounter("controller.book.DeleteBook.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookNotDeleted) } return c.JSON(http.StatusOK, models.WebResponse[*models.Book]{Data: book, Status: "deleted"}) diff --git a/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go b/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go index 2430183..26072e2 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/bookevents_controller.go @@ -16,10 +16,10 @@ type bookEventService interface { UpdateBookEvent(ctx context.Context, id string, request models.UpdateBookEventRequest) (*models.BookEvent, error) DeleteBookEvent(ctx context.Context, id string) (*models.BookEvent, error) - GetBookEventsByBookID(ctx context.Context, bookID string) ([]*models.BookEvent, error) - GetBookEventsByChapterID(ctx context.Context, chapterID string) ([]*models.BookEvent, error) - GetBookEventsByPageID(ctx context.Context, pageID string) ([]*models.BookEvent, error) - GetBookEventsByParagraphID(ctx context.Context, paragraphID string) ([]*models.BookEvent, error) + GetBookEventsByBookID(ctx context.Context, bookID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByChapterID(ctx context.Context, chapterID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByPageID(ctx context.Context, pageID string, request models.RequestBookEvent) ([]*models.BookEvent, error) + GetBookEventsByParagraphID(ctx context.Context, paragraphID string, request models.RequestBookEvent) ([]*models.BookEvent, error) } // BookEventController структура для HTTP-контроллера событий книги. @@ -43,6 +43,8 @@ func (bec *BookEventController) GetBookEventByID(c echo.Context) error { event, err := bec.Service.GetBookEventByID(bec.Ctx, id) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.GetBookEventByID.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) } @@ -57,6 +59,8 @@ func (bec *BookEventController) CreateBookEvent(c echo.Context) error { createdEvent, err := bec.Service.CreateBookEvent(bec.Ctx, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.CreateBookEvent.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotCreated) } return c.JSON(http.StatusCreated, models.WebResponse[*models.BookEvent]{Data: createdEvent, Status: "created"}) @@ -75,6 +79,8 @@ func (bec *BookEventController) UpdateBookEvent(c echo.Context) error { event, err := bec.Service.UpdateBookEvent(bec.Ctx, id, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.UpdateBookEvent.error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } @@ -89,6 +95,8 @@ func (bec *BookEventController) DeleteBookEvent(c echo.Context) error { deletedEvent, err := bec.Service.DeleteBookEvent(bec.Ctx, id) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.DeleteBookEvent.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotDeleted) } @@ -101,11 +109,18 @@ func (bec *BookEventController) GetBookEventsByBookID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - events, err := bec.Service.GetBookEventsByBookID(bec.Ctx, id) + var request models.RequestBookEvent + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByBookID(bec.Ctx, id, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.GetBookEventsByBookID.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) } - return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "ok"}) } func (bec *BookEventController) GetBookEventsByChapterID(c echo.Context) error { @@ -114,12 +129,19 @@ func (bec *BookEventController) GetBookEventsByChapterID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - events, err := bec.Service.GetBookEventsByChapterID(bec.Ctx, id) + var request models.RequestBookEvent + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByChapterID(bec.Ctx, id, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.GetBookEventsByChapterID.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) } - return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "ok"}) } func (bec *BookEventController) GetBookEventsByPageID(c echo.Context) error { @@ -128,12 +150,19 @@ func (bec *BookEventController) GetBookEventsByPageID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - events, err := bec.Service.GetBookEventsByPageID(bec.Ctx, id) + var request models.RequestBookEvent + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByPageID(bec.Ctx, id, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.GetBookEventsByPageID.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) } - return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "ok"}) } func (bec *BookEventController) GetBookEventsByParagraphID(c echo.Context) error { @@ -142,10 +171,17 @@ func (bec *BookEventController) GetBookEventsByParagraphID(c echo.Context) error return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - events, err := bec.Service.GetBookEventsByParagraphID(bec.Ctx, id) + var request models.RequestBookEvent + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + events, err := bec.Service.GetBookEventsByParagraphID(bec.Ctx, id, request) if err != nil { + bec.Logger.Info("error", slog.String("err", err.Error())) + bec.Metrics.IncCounter("controller.BookEvent.GetBookEventsByPageID.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.BookEventNotFound) } - return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "deleted"}) + return c.JSON(http.StatusOK, models.WebResponse[[]*models.BookEvent]{Data: events, Status: "ok"}) } diff --git a/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go b/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go index 90823dc..57218bb 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/chapter_controller.go @@ -15,8 +15,8 @@ type chapterService interface { GetChapterByID(ctx context.Context, id string) (*models.Chapter, error) UpdateChapter(ctx context.Context, id string, request models.UpdateChapterRequest) (*models.Chapter, error) DeleteChapter(ctx context.Context, id string) (*models.Chapter, error) - ListChapters(ctx context.Context, limit uint64, offset uint64) ([]*models.Chapter, error) - GetChapterByBookID(ctx context.Context, bookID string) ([]*models.Chapter, error) + ListChapters(ctx context.Context, request models.RequestChapter) ([]*models.Chapter, error) + GetChapterByBookID(ctx context.Context, bookID string, request models.RequestChapter) ([]*models.Chapter, error) } type ChapterController struct { @@ -37,8 +37,10 @@ func (ch *ChapterController) ListChapters(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - chapters, err := ch.Service.ListChapters(ch.Ctx, request.Options.Limit, request.Options.Offset) + chapters, err := ch.Service.ListChapters(ch.Ctx, request) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.ListChapters.error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } return c.JSON(http.StatusOK, models.WebResponse[[]*models.Chapter]{Data: chapters, Status: "ok"}) @@ -52,6 +54,8 @@ func (ch *ChapterController) CreateChapter(c echo.Context) error { createdChapter, err := ch.Service.CreateChapter(ch.Ctx, chap) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.CreateChapter.error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, errors.ChapterNotCreated) } return c.JSON(http.StatusCreated, models.WebResponse[*models.Chapter]{Data: createdChapter, Status: "created"}) @@ -65,9 +69,11 @@ func (ch *ChapterController) GetChapterByID(c echo.Context) error { chapter, err := ch.Service.GetChapterByID(ch.Ctx, id) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.GetChapterByID.error", err.Error()) return echo.NewHTTPError(http.StatusNotFound, errors.ChapterNotFound) } - return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "ok"}) } func (ch *ChapterController) UpdateChapter(c echo.Context) error { @@ -83,9 +89,11 @@ func (ch *ChapterController) UpdateChapter(c echo.Context) error { chapter, err := ch.Service.UpdateChapter(ch.Ctx, id, request) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.UpdateChapter.error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } - return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "updated"}) } func (ch *ChapterController) DeleteChapter(c echo.Context) error { @@ -96,9 +104,11 @@ func (ch *ChapterController) DeleteChapter(c echo.Context) error { chapter, err := ch.Service.DeleteChapter(ch.Ctx, id) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.DeleteChapter.error", err.Error()) return echo.NewHTTPError(http.StatusNotAcceptable, errors.ChapterNotDeleted) } - return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "created"}) + return c.JSON(http.StatusOK, models.WebResponse[*models.Chapter]{Data: chapter, Status: "deleted"}) } func (ch *ChapterController) GetChapterByBookID(c echo.Context) error { @@ -107,8 +117,15 @@ func (ch *ChapterController) GetChapterByBookID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - chapters, err := ch.Service.GetChapterByBookID(ch.Ctx, id) + var request models.RequestChapter + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + chapters, err := ch.Service.GetChapterByBookID(ch.Ctx, id, request) if err != nil { + ch.Logger.Info("error", slog.String("err", err.Error())) + ch.Metrics.IncCounter("controller.chapter.DeleteChapter.error", err.Error()) return echo.NewHTTPError(http.StatusNotFound, errors.ChapterNotFound) } return c.JSON(http.StatusOK, models.WebResponse[[]*models.Chapter]{Data: chapters, Status: "ok"}) diff --git a/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go b/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go index ac7adf3..ca9207b 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/mapvariables_controller.go @@ -16,10 +16,10 @@ type mapVariablesService interface { GetMapVariableByID(ctx context.Context, id string) (*models.MapVariable, error) UpdateMapVariable(ctx context.Context, id string, request models.UpdateMapVariableRequest) (*models.MapVariable, error) DeleteMapVariable(ctx context.Context, id string) (*models.MapVariable, error) - GetMapVariablesByBookID(ctx context.Context, mapID string) ([]*models.MapVariable, error) - GetMapVariablesByChapterID(ctx context.Context, chapterID string) ([]*models.MapVariable, error) - GetMapVariablesByPageID(ctx context.Context, pageID string) ([]*models.MapVariable, error) - GetMapVariablesByParagraphID(ctx context.Context, paragraphID string) ([]*models.MapVariable, error) + GetMapVariablesByBookID(ctx context.Context, mapID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByChapterID(ctx context.Context, chapterID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByPageID(ctx context.Context, pageID string, request models.RequestMapVariable) ([]*models.MapVariable, error) + GetMapVariablesByParagraphID(ctx context.Context, paragraphID string, request models.RequestMapVariable) ([]*models.MapVariable, error) } // MapVariablesController структура для HTTP-контроллера книг. @@ -31,7 +31,8 @@ type MapVariablesController struct { } // NewMapVariablesController создает новый экземпляр MapVariablesController. -func NewMapVariablesController(srv mapVariablesService, metric metrics.Metrics, logger *slog.Logger, ctx context.Context) *MapVariablesController { +func NewMapVariablesController(srv mapVariablesService, metric metrics.Metrics, logger *slog.Logger, + ctx context.Context) *MapVariablesController { return &MapVariablesController{Service: srv, Metrics: metric, Logger: logger, Ctx: ctx} } @@ -89,7 +90,12 @@ func (mvc *MapVariablesController) GetMapVariablesByBookID(c echo.Context) error return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - variables, err := mvc.Service.GetMapVariablesByBookID(mvc.Ctx, id) + var request models.RequestMapVariable + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByBookID(mvc.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) } @@ -103,7 +109,12 @@ func (mvc *MapVariablesController) GetMapVariablesByChapterID(c echo.Context) er return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - variables, err := mvc.Service.GetMapVariablesByChapterID(mvc.Ctx, id) + var request models.RequestMapVariable + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByChapterID(mvc.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) } @@ -117,7 +128,12 @@ func (mvc *MapVariablesController) GetMapVariablesByPageID(c echo.Context) error return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - variables, err := mvc.Service.GetMapVariablesByPageID(mvc.Ctx, id) + var request models.RequestMapVariable + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByPageID(mvc.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) } @@ -131,7 +147,12 @@ func (mvc *MapVariablesController) GetMapVariablesByParagraphID(c echo.Context) return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - variables, err := mvc.Service.GetMapVariablesByParagraphID(mvc.Ctx, id) + var request models.RequestMapVariable + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + variables, err := mvc.Service.GetMapVariablesByParagraphID(mvc.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusNotFound, errors.MapVariablesNotFound) } diff --git a/bookback/internal/infrastructure/http/v1/controllers/page_controller.go b/bookback/internal/infrastructure/http/v1/controllers/page_controller.go index 32bf58b..8a11ab3 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/page_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/page_controller.go @@ -15,8 +15,8 @@ type pageService interface { GetPageByID(ctx context.Context, id string) (*models.Page, error) UpdatePage(ctx context.Context, id string, request models.UpdatePageRequest) (*models.Page, error) DeletePage(ctx context.Context, id string) (*models.Page, error) - ListPages(ctx context.Context, limit uint64, offset uint64) ([]*models.Page, error) - GetPagesByChapterID(ctx context.Context, chapterID string) ([]*models.Page, error) + ListPages(ctx context.Context, page models.RequestPage) ([]*models.Page, error) + GetPagesByChapterID(ctx context.Context, chapterID string, page models.RequestPage) ([]*models.Page, error) } type PageController struct { @@ -31,12 +31,12 @@ func NewPageController(srv pageService, metric metrics.Metrics, logger *slog.Log } func (p *PageController) ListPages(c echo.Context) error { - var request models.RequestBook + var request models.RequestPage if err := c.Bind(&request); err != nil { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - pages, err := p.Service.ListPages(p.Ctx, request.Options.Limit, request.Options.Offset) + pages, err := p.Service.ListPages(p.Ctx, request) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } @@ -107,7 +107,12 @@ func (p *PageController) GetPagesByChapterID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - pages, err := p.Service.GetPagesByChapterID(p.Ctx, id) + var request models.RequestPage + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + pages, err := p.Service.GetPagesByChapterID(p.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusNotFound, errors.BookNotFound) } diff --git a/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go b/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go index 5a8a7bf..d29f7d9 100644 --- a/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go +++ b/bookback/internal/infrastructure/http/v1/controllers/paragraph_controller.go @@ -15,8 +15,8 @@ type paragraphService interface { GetParagraphByID(ctx context.Context, id string) (*models.Paragraph, error) UpdateParagraph(ctx context.Context, id string, paragraph models.UpdateParagraphRequest) (*models.Paragraph, error) DeleteParagraph(ctx context.Context, id string) (*models.Paragraph, error) - ListParagraphs(ctx context.Context, limit uint64, offset uint64) ([]*models.Paragraph, error) - GetParagraphsByPageID(ctx context.Context, pageID string) ([]*models.Paragraph, error) + ListParagraphs(ctx context.Context, request models.RequestParagraph) ([]*models.Paragraph, error) + GetParagraphsByPageID(ctx context.Context, pageID string, request models.RequestParagraph) ([]*models.Paragraph, error) } type ParagraphController struct { @@ -37,7 +37,7 @@ func (p *ParagraphController) ListParagraphs(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - paragraphs, err := p.Service.ListParagraphs(p.Ctx, request.Options.Limit, request.Options.Offset) + paragraphs, err := p.Service.ListParagraphs(p.Ctx, request) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } @@ -108,7 +108,12 @@ func (p *ParagraphController) GetParagraphsByPageID(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) } - paragraphs, err := p.Service.GetParagraphsByPageID(p.Ctx, id) + var request models.RequestParagraph + if err := c.Bind(&request); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.ValidationFailed) + } + + paragraphs, err := p.Service.GetParagraphsByPageID(p.Ctx, id, request) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, errors.Unknown) } diff --git a/bookback/internal/infrastructure/http/v1/endpoint/handler.go b/bookback/internal/infrastructure/http/v1/endpoint/handler.go new file mode 100644 index 0000000..bd3ff53 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/endpoint/handler.go @@ -0,0 +1,96 @@ +package endpoint + +import ( + "context" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/middleware" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/http/v1/routes" + "github.com/SShlykov/zeitment/bookback/internal/infrastructure/metrics" + "github.com/SShlykov/zeitment/bookback/pkg/circuitbreaker" + "github.com/SShlykov/zeitment/bookback/pkg/postgres" + "github.com/labstack/echo/v4" + "log/slog" + "time" +) + +type Handler struct { + Timeout time.Duration + IddleTimeout time.Duration + Address string + + e *echo.Echo +} + +type HTTPServerConfig struct { + RequestLimit int + MinRequests int + ErrorThresholdPercentage float64 + IntervalDuration time.Duration + OpenStateTimeout time.Duration + CorsEnabled bool + SwaggerEnabled bool + Timeout time.Duration + IddleTimeout time.Duration + Address string +} + +func (h *Handler) Shutdown(ctx context.Context) error { + return h.e.Shutdown(ctx) +} + +func NewHandler(database postgres.Client, metric metrics.Metrics, + logger *slog.Logger, ctx context.Context, cfg *HTTPServerConfig) (*Handler, error) { + e := echo.New() + + setMiddlewares(e, logger, cfg) + + setRouter(e, database, metric, logger, ctx) + + if cfg.SwaggerEnabled { + routes.SetSwagger(e) + } + + return &Handler{e: e, Timeout: cfg.Timeout, IddleTimeout: cfg.IddleTimeout, Address: cfg.Address}, nil +} + +func setMiddlewares(e *echo.Echo, logger *slog.Logger, config *HTTPServerConfig) { + cb := circuitbreaker.NewCircuitBreaker( + config.RequestLimit, + config.MinRequests, + config.ErrorThresholdPercentage, + config.IntervalDuration, + config.OpenStateTimeout, + ) + + middlewares := []echo.MiddlewareFunc{ + middleware.LoggerConfiguration(logger), + middleware.Recover(), + middleware.CreateCircuitBreakerMiddleware(cb), + } + + if config.CorsEnabled { + middlewares = append(middlewares, middleware.CORS()) + } + + e.Use(middlewares...) +} + +func setRouter(e *echo.Echo, database postgres.Client, metric metrics.Metrics, + logger *slog.Logger, ctx context.Context) { + controllers := + []func(e *echo.Echo, database postgres.Client, metric metrics.Metrics, + logger *slog.Logger, ctx context.Context){ + routes.Health, + routes.Book, + routes.Chapter, + routes.Page, + routes.Paragraph, + routes.BookEvent, + routes.MapVariables, + } + + for _, controller := range controllers { + controller(e, database, metric, logger, ctx) + } + + return +} diff --git a/bookback/internal/infrastructure/http/v1/endpoint/server.go b/bookback/internal/infrastructure/http/v1/endpoint/server.go new file mode 100644 index 0000000..77852d4 --- /dev/null +++ b/bookback/internal/infrastructure/http/v1/endpoint/server.go @@ -0,0 +1,20 @@ +package endpoint + +import ( + "log/slog" + "net/http" +) + +func RunServer(handler *Handler, logger *slog.Logger) error { + httpServer := &http.Server{ + ReadHeaderTimeout: handler.Timeout, + ReadTimeout: handler.Timeout, + WriteTimeout: handler.Timeout, + IdleTimeout: handler.IddleTimeout, + Addr: handler.Address, + Handler: handler.e, + } + + logger.Info("HTTP server started") + return httpServer.ListenAndServe() +} diff --git a/bookback/internal/infrastructure/http/v1/routes/book_router.go b/bookback/internal/infrastructure/http/v1/routes/book_router.go index 9b820fa..44772b4 100644 --- a/bookback/internal/infrastructure/http/v1/routes/book_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/book_router.go @@ -12,8 +12,8 @@ import ( "log/slog" ) -// SetBookRoutes регистрирует контроллер книг в маршрутизаторе. -func SetBookRoutes(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +// Book регистрирует контроллер книг в маршрутизаторе. +func Book(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { repo := pgrepo.NewBookRepository(database) service := services.NewBookService(repo) cntr := controllers.NewBookController(service, metrics, logger, ctx) @@ -21,7 +21,7 @@ func SetBookRoutes(e *echo.Echo, database postgres.Client, metrics metrics.Metri group := e.Group(BooksPath) group.Use(middleware.MetricsLogger(metrics)) - group.POST("", cntr.ListBooks) + group.POST("/list", cntr.ListBooks) group.POST("", cntr.CreateBook) group.GET("/:id", cntr.GetBookByID) group.PUT("/:id", cntr.UpdateBook) diff --git a/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go b/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go index 3c8f26a..360a61f 100644 --- a/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/bookevents_router.go @@ -12,7 +12,7 @@ import ( "log/slog" ) -func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func BookEvent(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { repo := pgrepo.NewBookEventsRepository(database) service := services.NewBookEventsService(repo) cntr := controllers.NewBookEventController(service, metrics, logger, ctx) @@ -24,8 +24,8 @@ func SetBookEventController(e *echo.Echo, database postgres.Client, metrics metr group.GET("/:id", cntr.GetBookEventByID) group.PUT("/:id", cntr.UpdateBookEvent) group.DELETE("/:id", cntr.DeleteBookEvent) - group.GET("/book/:id", cntr.GetBookEventsByBookID) - group.GET("/chapter/:id", cntr.GetBookEventsByChapterID) - group.GET("/page/:id", cntr.GetBookEventsByPageID) - group.GET("/paragraph/:id", cntr.GetBookEventsByParagraphID) + group.POST("/book/:id", cntr.GetBookEventsByBookID) + group.POST("/chapter/:id", cntr.GetBookEventsByChapterID) + group.POST("/page/:id", cntr.GetBookEventsByPageID) + group.POST("/paragraph/:id", cntr.GetBookEventsByParagraphID) } diff --git a/bookback/internal/infrastructure/http/v1/routes/chapter_router.go b/bookback/internal/infrastructure/http/v1/routes/chapter_router.go index 5139583..920274c 100644 --- a/bookback/internal/infrastructure/http/v1/routes/chapter_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/chapter_router.go @@ -12,7 +12,7 @@ import ( "log/slog" ) -func SetChapterController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func Chapter(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { chapterRepo := pgrepo.NewChapterRepository(database) service := services.NewChapterService(chapterRepo) cnt := controllers.NewChapterController(service, metrics, logger, ctx) @@ -20,10 +20,10 @@ func SetChapterController(e *echo.Echo, database postgres.Client, metrics metric group := e.Group(ChaptersPath) group.Use(middleware.MetricsLogger(metrics)) - group.GET("", cnt.ListChapters) + group.POST("/list", cnt.ListChapters) group.POST("", cnt.CreateChapter) group.GET("/:id", cnt.GetChapterByID) group.PUT("/:id", cnt.UpdateChapter) group.DELETE("/:id", cnt.DeleteChapter) - group.GET("/book/:id", cnt.GetChapterByBookID) + group.POST("/book/:id", cnt.GetChapterByBookID) } diff --git a/bookback/internal/infrastructure/http/v1/routes/health_router.go b/bookback/internal/infrastructure/http/v1/routes/health_router.go index 0f4ed48..82b7464 100644 --- a/bookback/internal/infrastructure/http/v1/routes/health_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/health_router.go @@ -10,7 +10,7 @@ import ( "log/slog" ) -func SetHealthController(e *echo.Echo, _ postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func Health(e *echo.Echo, _ postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { cntr := controllers.NewHealthController(metrics, logger, ctx) group := e.Group(HealthPath) diff --git a/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go b/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go index 1ba0519..225a567 100644 --- a/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/mapvariables_router.go @@ -12,7 +12,7 @@ import ( "log/slog" ) -func SetMapVariablesRoutes(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func MapVariables(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { repo := pgrepo.NewMapVariablesRepository(database) service := services.NewMapVariablesService(repo) cnt := controllers.NewMapVariablesController(service, metrics, logger, ctx) @@ -24,8 +24,8 @@ func SetMapVariablesRoutes(e *echo.Echo, database postgres.Client, metrics metri group.PUT("/:id", cnt.UpdateMapVariable) group.DELETE("/:id", cnt.DeleteMapVariable) group.POST("", cnt.CreateMapVariable) - group.GET("/book/:id", cnt.GetMapVariablesByBookID) - group.GET("/chapter/:id", cnt.GetMapVariablesByChapterID) - group.GET("/page/:id", cnt.GetMapVariablesByPageID) - group.GET("/paragraph/:id", cnt.GetMapVariablesByParagraphID) + group.POST("/book/:id", cnt.GetMapVariablesByBookID) + group.POST("/chapter/:id", cnt.GetMapVariablesByChapterID) + group.POST("/page/:id", cnt.GetMapVariablesByPageID) + group.POST("/paragraph/:id", cnt.GetMapVariablesByParagraphID) } diff --git a/bookback/internal/infrastructure/http/v1/routes/page_router.go b/bookback/internal/infrastructure/http/v1/routes/page_router.go index 29a681d..4774f40 100644 --- a/bookback/internal/infrastructure/http/v1/routes/page_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/page_router.go @@ -12,7 +12,7 @@ import ( "log/slog" ) -func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func Page(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { repo := pgrepo.NewPageRepository(database) service := services.NewPageService(repo) cnt := controllers.NewPageController(service, metrics, logger, ctx) @@ -20,10 +20,10 @@ func SetPageController(e *echo.Echo, database postgres.Client, metrics metrics.M group := e.Group(PagesPath) group.Use(middleware.MetricsLogger(metrics)) - group.GET("", cnt.ListPages) + group.POST("/list", cnt.ListPages) group.POST("", cnt.CreatePage) group.GET("/:id", cnt.GetPageByID) group.PUT("/:id", cnt.UpdatePage) group.DELETE("/:id", cnt.DeletePage) - group.GET("/chapters/:id", cnt.GetPagesByChapterID) + group.POST("/chapters/:id", cnt.GetPagesByChapterID) } diff --git a/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go b/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go index 30d3d83..3390d08 100644 --- a/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/paragraph_router.go @@ -12,7 +12,7 @@ import ( "log/slog" ) -func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { +func Paragraph(e *echo.Echo, database postgres.Client, metrics metrics.Metrics, logger *slog.Logger, ctx context.Context) { repo := pgrepo.NewParagraphRepository(database) service := services.NewParagraphService(repo) cnt := controllers.NewParagraphController(service, metrics, logger, ctx) @@ -20,10 +20,10 @@ func SetParagraphController(e *echo.Echo, database postgres.Client, metrics metr group := e.Group(ParagraphsPath) group.Use(middleware.MetricsLogger(metrics)) - group.GET("", cnt.ListParagraphs) + group.POST("/list", cnt.ListParagraphs) group.POST("", cnt.CreateParagraph) group.GET("/:id", cnt.GetParagraphByID) group.PUT("/:id", cnt.UpdateParagraph) group.DELETE("/:id", cnt.DeleteParagraph) - group.GET("/pages/:id", cnt.GetParagraphsByPageID) + group.POST("/pages/:id", cnt.GetParagraphsByPageID) } diff --git a/bookback/internal/infrastructure/http/v1/routes/swagger_router.go b/bookback/internal/infrastructure/http/v1/routes/swagger_router.go index e043c84..0a2211b 100644 --- a/bookback/internal/infrastructure/http/v1/routes/swagger_router.go +++ b/bookback/internal/infrastructure/http/v1/routes/swagger_router.go @@ -5,8 +5,6 @@ import ( echoSwagger "github.com/swaggo/echo-swagger" ) -func SetSwagger(e *echo.Echo, swaggerEnabled bool) { - if swaggerEnabled { - e.GET("/swagger/*", echoSwagger.WrapHandler) - } +func SetSwagger(e *echo.Echo) { + e.GET("/swagger/*", echoSwagger.WrapHandler) } diff --git a/bookback/internal/models/basic_model.go b/bookback/internal/models/basic_model.go index a045ce8..e49abad 100644 --- a/bookback/internal/models/basic_model.go +++ b/bookback/internal/models/basic_model.go @@ -20,6 +20,10 @@ type PageMetadata struct { } type PageOptions struct { - Limit uint64 `json:"limit,omitempty"` - Offset uint64 `json:"offset,omitempty"` + PageSize uint64 `json:"page_size,omitempty"` + Page uint64 `json:"page,omitempty"` +} + +func (po *PageOptions) GetPageAndPageSize() (uint64, uint64) { + return po.Page, po.PageSize } diff --git a/bookback/internal/models/book_model.go b/bookback/internal/models/book_model.go index e6127b9..754ff0b 100644 --- a/bookback/internal/models/book_model.go +++ b/bookback/internal/models/book_model.go @@ -6,19 +6,19 @@ import ( ) type Book struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt types.Null[time.Time] - Owner string - Title string - Author string - Description string - IsPublic bool - Publication types.Null[time.Time] - ImageLink types.Null[string] - MapLink types.Null[string] - Variables []string + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt types.Null[time.Time] `json:"deleted_at"` + Owner string `json:"owner"` + Title string `json:"title"` + Author string `json:"author"` + Description string `json:"description"` + IsPublic bool `json:"is_public"` + Publication types.Null[time.Time] `json:"publication"` + ImageLink types.Null[string] `json:"image_link"` + MapLink types.Null[string] `json:"map_link"` + Variables []string `json:"variables"` } type CreateBookRequest struct { @@ -29,6 +29,6 @@ type UpdateBookRequest struct { } type RequestBook struct { - Options PageOptions `json:"options,omitempty"` + Options PageOptions `json:"options"` Book *Book `json:"book,omitempty"` } diff --git a/bookback/internal/models/bookevents_model.go b/bookback/internal/models/bookevents_model.go index 0d30ec6..5e770cc 100644 --- a/bookback/internal/models/bookevents_model.go +++ b/bookback/internal/models/bookevents_model.go @@ -6,22 +6,22 @@ import ( ) type BookEvent struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - BookID string - ChapterID types.Null[string] - PageID types.Null[string] - ParagraphID types.Null[string] - EventType types.Null[string] // Тип события (начало главы, начало страницы, начало параграфа...) - IsPublic bool - Key string - Value string - Link types.Null[string] - LinkText types.Null[string] - LinkType types.Null[string] - LinkImage types.Null[string] - Description types.Null[string] + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + BookID string `json:"book_id"` + ChapterID types.Null[string] `json:"chapter_id"` + PageID types.Null[string] `json:"page_id"` + ParagraphID types.Null[string] `json:"paragraph_id"` + EventType types.Null[string] `json:"event_type"` // Тип события (начало главы, начало страницы, начало параграфа...) + IsPublic bool `json:"is_public"` + Key string `json:"key"` + Value string `json:"value"` + Link types.Null[string] `json:"link"` + LinkText types.Null[string] `json:"link_text"` + LinkType types.Null[string] `json:"link_type"` + LinkImage types.Null[string] `json:"link_image"` + Description types.Null[string] `json:"description"` } type CreateBookEventRequest struct { diff --git a/bookback/internal/models/chapter_model.go b/bookback/internal/models/chapter_model.go index 7a21ba8..421c9c4 100644 --- a/bookback/internal/models/chapter_model.go +++ b/bookback/internal/models/chapter_model.go @@ -6,17 +6,17 @@ import ( ) type Chapter struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt types.Null[time.Time] - Title string - Number int // Порядковый номер главы (не уникальный т.к. главы могут быть скрыты) - Text string // Превью текста главы - BookID string - IsPublic bool - MapLink types.Null[string] // карта - MapParamsID types.Null[string] // параметры карты (координаты и тп.) + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt types.Null[time.Time] `json:"deleted_at"` + Title string `json:"title"` + Number int `json:"number"` // Порядковый номер главы (не уникальный т.к. главы могут быть скрыты) + Text string `json:"text"` // Превью текста главы + BookID string `json:"book_id"` + IsPublic bool `json:"is_public"` + MapLink types.Null[string] `json:"map_link"` // карта + MapParamsID types.Null[string] `json:"map_params_id"` // параметры карты (координаты и тп.) } type CreateChapterRequest struct { diff --git a/bookback/internal/models/dbutils/db_utils.go b/bookback/internal/models/dbutils/db_utils.go new file mode 100644 index 0000000..8f19912 --- /dev/null +++ b/bookback/internal/models/dbutils/db_utils.go @@ -0,0 +1,56 @@ +package dbutils + +type QueryOptions interface { + Pagination + Filter +} +type Pagination interface { + GetPagination() (limit uint64, offset uint64) +} +type Filter interface { + GetFilter() (key string, value any) +} + +func NewQueryOptions(filter FilterImpl, pagination PaginationImpl) QueryOptions { + return queryOptions{filter: filter, pagination: pagination} +} + +type queryOptions struct { + pagination PaginationImpl + filter FilterImpl +} + +func (q queryOptions) GetFilter() (key string, value any) { + return q.filter.Key, q.filter.Value +} + +func (q queryOptions) GetPagination() (limit uint64, offset uint64) { + return q.pagination.Limit, q.pagination.Offset +} + +type PaginationImpl struct { + Limit uint64 + Offset uint64 +} + +func NewPagination(pageOptions PagedOptions) PaginationImpl { + page, pageSize := pageOptions.GetPageAndPageSize() + return PaginationImpl{Limit: pageSize, Offset: (page - 1) * pageSize} +} + +type FilterImpl struct { + Key string + Value any +} + +func NewFilter(key string, value any) FilterImpl { + return FilterImpl{Key: key, Value: value} +} + +func (p PaginationImpl) GetPagination() (limit uint64, offset uint64) { + return p.Limit, p.Offset +} + +type PagedOptions interface { + GetPageAndPageSize() (page uint64, pageSize uint64) +} diff --git a/bookback/internal/models/mapvariables_model.go b/bookback/internal/models/mapvariables_model.go index 1d23eb6..26b8ae7 100644 --- a/bookback/internal/models/mapvariables_model.go +++ b/bookback/internal/models/mapvariables_model.go @@ -7,24 +7,24 @@ import ( ) type MapVariable struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - BookID string - ChapterID types.Null[string] - PageID types.Null[string] - ParagraphID types.Null[string] - MapLink string - Lat float64 - Lng float64 - Zoom sql.NullInt64 - Date types.Null[string] - Description types.Null[string] - Link types.Null[string] - LinkText types.Null[string] - LinkType types.Null[string] - LinkImage types.Null[string] - Image types.Null[string] + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + BookID string `json:"book_id"` + ChapterID types.Null[string] `json:"chapter_id"` + PageID types.Null[string] `json:"page_id"` + ParagraphID types.Null[string] `json:"paragraph_id"` + MapLink string `json:"map_link"` + Lat float64 `json:"lat"` + Lng float64 `json:"lng"` + Zoom sql.NullInt64 `json:"zoom"` + Date types.Null[string] `json:"date"` + Description types.Null[string] `json:"description"` + Link types.Null[string] `json:"link"` + LinkText types.Null[string] `json:"link_text"` + LinkType types.Null[string] `json:"link_type"` + LinkImage types.Null[string] `json:"link_image"` + Image types.Null[string] `json:"image"` } type CreateMapVariableRequest struct { diff --git a/bookback/internal/models/page_model.go b/bookback/internal/models/page_model.go index ee317d7..9017ed9 100644 --- a/bookback/internal/models/page_model.go +++ b/bookback/internal/models/page_model.go @@ -6,15 +6,15 @@ import ( ) type Page struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt types.Null[time.Time] - Title string - Text string - ChapterID string - IsPublic bool - MapParamsID types.Null[string] + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt types.Null[time.Time] `json:"deleted_at"` + Title string `json:"title"` + Text string `json:"text"` + ChapterID string `json:"chapter_id"` + IsPublic bool `json:"is_public"` + MapParamsID types.Null[string] `json:"map_params_id"` } type CreatePageRequest struct { diff --git a/bookback/internal/models/paragraph_model.go b/bookback/internal/models/paragraph_model.go index 75a3fc2..0ceb287 100644 --- a/bookback/internal/models/paragraph_model.go +++ b/bookback/internal/models/paragraph_model.go @@ -6,15 +6,15 @@ import ( ) type Paragraph struct { - ID string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt types.Null[time.Time] - Title string - Text string - Type string - IsPublic bool - PageID string + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt types.Null[time.Time] `json:"deleted_at"` + Title string `json:"title"` + Text string `json:"text"` + Type string `json:"type"` + IsPublic bool `json:"is_public"` + PageID string `json:"page_id"` } type CreateParagraphRequest struct { diff --git a/bookback/pkg/config/config.go b/bookback/pkg/config/config.go index 9e43159..70b3ae9 100644 --- a/bookback/pkg/config/config.go +++ b/bookback/pkg/config/config.go @@ -9,26 +9,8 @@ import ( ) type Config struct { - CorsEnabled bool `yaml:"cors_enabled" env:"CORS_ENABLED" env-default:"false" env-required:"true"` ShutdownTimeout time.Duration `yaml:"shutdown_timeout" env:"SHUTDOWN_TIMEOUT" env-default:"10s"` - HTTPServer `yaml:"http_server"` Logger `yaml:"logger"` - Swagger `yaml:"swagger"` -} - -type Swagger struct { - SwaggerEnabled bool `yaml:"enabled" env:"ENABLED" env-default:"false" env-required:"true"` -} - -type HTTPServer struct { - Address string `yaml:"address" env:"ADDRESS" env-default:":8080" env-required:"true"` - Timeout time.Duration `yaml:"timeout" env:"TIMEOUT" env-default:"30s" env-required:"true"` - IddleTimeout time.Duration `yaml:"iddle_timeout" env:"IDDLE_TIMEOUT" env-default:"30s" env-required:"true"` - RequestLimit int `yaml:"request_limit" env:"REQUEST_LIMIT" env-default:"100" env-required:"true"` - MinRequests int `yaml:"min_requests" env:"MIN_REQUESTS" env-default:"10" env-required:"true"` - ErrorThresholdPercentage float64 `yaml:"error_threshold_percentage" env-default:"0.6" env-required:"true"` - IntervalDuration time.Duration `yaml:"interval_duration" env:"INTERVAL_DURATION" env-default:"10s" env-required:"true"` - OpenStateTimeout time.Duration `yaml:"open_state_timeout" env:"OPEN_STATE_TIMEOUT" env-default:"10s" env-required:"true"` } type Logger struct { @@ -40,6 +22,8 @@ func LoadConfig(configPath string) (*Config, error) { return nil, errors.New("CONFIG_PATH is not set") } + configPath = configPath + "/default.yml" + if _, err := os.Stat(configPath); os.IsNotExist(err) { return nil, errors.New(fmt.Sprintf("config file not found: %s", configPath)) } diff --git a/bookback/pkg/config/webserver.go b/bookback/pkg/config/webserver.go new file mode 100644 index 0000000..5bce2a9 --- /dev/null +++ b/bookback/pkg/config/webserver.go @@ -0,0 +1,42 @@ +package config + +import ( + "errors" + "fmt" + "github.com/ilyakaznacheev/cleanenv" + "os" + "time" +) + +type HTTPServer struct { + Address string `yaml:"address" env:"ADDRESS" env-default:":8080" env-required:"true"` + Timeout time.Duration `yaml:"timeout" env:"TIMEOUT" env-default:"30s" env-required:"true"` + IddleTimeout time.Duration `yaml:"iddle_timeout" env:"IDDLE_TIMEOUT" env-default:"30s" env-required:"true"` + RequestLimit int `yaml:"request_limit" env:"REQUEST_LIMIT" env-default:"100" env-required:"true"` + MinRequests int `yaml:"min_requests" env:"MIN_REQUESTS" env-default:"10" env-required:"true"` + ErrorThresholdPercentage float64 `yaml:"error_threshold_percentage" env-default:"0.6" env-required:"true"` + IntervalDuration time.Duration `yaml:"interval_duration" env:"INTERVAL_DURATION" env-default:"10s" env-required:"true"` + OpenStateTimeout time.Duration `yaml:"open_state_timeout" env:"OPEN_STATE_TIMEOUT" env-default:"10s" env-required:"true"` + SwaggerEnabled bool `yaml:"swagger_enabled" env:"ENABLED" env-default:"false" env-required:"true"` + CorsEnabled bool `yaml:"cors_enabled" env:"CORS_ENABLED" env-default:"false" env-required:"true"` +} + +func LoadServerConfig(configPath string) (*HTTPServer, error) { + if configPath == "" { + return nil, errors.New("CONFIG_PATH is not set") + } + + configPath = configPath + "/server.yml" + + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return nil, errors.New(fmt.Sprintf("config file not found: %s", configPath)) + } + + var cfg HTTPServer + + if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { + return nil, errors.New(fmt.Sprintf("config file read error: %v", err)) + } + + return &cfg, nil +} diff --git a/bookback/pkg/postgres/client.go b/bookback/pkg/postgres/client.go index df25a81..cac0418 100644 --- a/bookback/pkg/postgres/client.go +++ b/bookback/pkg/postgres/client.go @@ -2,8 +2,8 @@ package postgres import ( "context" + "errors" "github.com/Masterminds/squirrel" - "github.com/SShlykov/zeitment/bookback/pkg/postgres/pg" "github.com/jackc/pgx/v5/pgxpool" "log/slog" "time" @@ -25,7 +25,12 @@ type pgClient struct { } func NewClient(ctx context.Context, logger *slog.Logger, dsn string) (Client, error) { - client := &pgClient{builder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)} + client := &pgClient{ + builder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar), + maxPoolSize: _defaultMaxPoolSize, + connAttempts: _defaultConnAttempts, + connTimeout: _defaultConnTimeout, + } poolConfig, err := pgxpool.ParseConfig(dsn) if err != nil { @@ -35,20 +40,30 @@ func NewClient(ctx context.Context, logger *slog.Logger, dsn string) (Client, er poolConfig.MaxConns = int32(_defaultMaxPoolSize) - for client.connAttempts > 0 { + return client.Connect(ctx, logger, poolConfig) +} + +func (c *pgClient) Connect(ctx context.Context, logger *slog.Logger, poolConfig *pgxpool.Config) (Client, error) { + logger.Debug( + "connecting to db", + slog.Int("attempts", c.connAttempts), + slog.String("dsn", poolConfig.ConnString()), + slog.Int("maxPoolSize", _defaultMaxPoolSize), + ) + for c.connAttempts > 0 { var pool *pgxpool.Pool - pool, err = pgxpool.NewWithConfig(ctx, poolConfig) + pool, err := pgxpool.NewWithConfig(ctx, poolConfig) if err == nil { - client.db = pg.NewDB(pool, logger) - return client, nil + c.db = NewDB(pool, logger) + return c, nil } - time.Sleep(client.connTimeout) + time.Sleep(c.connTimeout) - client.connAttempts-- + c.connAttempts-- } - return nil, err + return nil, errors.New("failed to connect to db") } func (c *pgClient) DB() DB { diff --git a/bookback/pkg/postgres/interface.go b/bookback/pkg/postgres/interface.go index 1003d01..2024fdb 100644 --- a/bookback/pkg/postgres/interface.go +++ b/bookback/pkg/postgres/interface.go @@ -33,8 +33,6 @@ type Query struct { type Transactor interface { BeginTx(ctx context.Context, options pgx.TxOptions) (pgx.Tx, error) - Commit(ctx context.Context) error - Rollback(ctx context.Context) error } type SQLScanner interface { diff --git a/bookback/pkg/postgres/pg/pg.go b/bookback/pkg/postgres/pg.go similarity index 64% rename from bookback/pkg/postgres/pg/pg.go rename to bookback/pkg/postgres/pg.go index 8e6b23f..51a3d4a 100644 --- a/bookback/pkg/postgres/pg/pg.go +++ b/bookback/pkg/postgres/pg.go @@ -1,8 +1,7 @@ -package pg +package postgres import ( "context" - "github.com/SShlykov/zeitment/bookback/pkg/postgres" "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" @@ -21,11 +20,11 @@ type Postgres struct { logger *slog.Logger } -func NewDB(dbc *pgxpool.Pool, logger *slog.Logger) postgres.DB { +func NewDB(dbc *pgxpool.Pool, logger *slog.Logger) DB { return &Postgres{Pool: dbc, logger: logger} } -func (p *Postgres) ScanSingleContext(ctx context.Context, q postgres.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt +func (p *Postgres) ScanSingleContext(ctx context.Context, q Query, dest interface{}, args ...interface{}) error { //nolint:gofmt logQuery(ctx, p.logger, q, args...) row, err := p.QueryContext(ctx, q, args...) @@ -36,7 +35,7 @@ func (p *Postgres) ScanSingleContext(ctx context.Context, q postgres.Query, dest return pgxscan.ScanOne(dest, row) } -func (p *Postgres) ScanAllContext(ctx context.Context, q postgres.Query, dest interface{}, args ...interface{}) error { //nolint:gofmt +func (p *Postgres) ScanAllContext(ctx context.Context, q Query, dest interface{}, args ...interface{}) error { //nolint:gofmt logQuery(ctx, p.logger, q, args...) rows, err := p.QueryContext(ctx, q, args...) @@ -47,7 +46,7 @@ func (p *Postgres) ScanAllContext(ctx context.Context, q postgres.Query, dest in return pgxscan.ScanAll(dest, rows) } -func (p *Postgres) ExecContext(ctx context.Context, q postgres.Query, args ...interface{}) (pgconn.CommandTag, error) { //nolint:gofmt +func (p *Postgres) ExecContext(ctx context.Context, q Query, args ...interface{}) (pgconn.CommandTag, error) { //nolint:gofmt logQuery(ctx, p.logger, q, args...) tx, ok := ctx.Value(TxKey).(pgx.Tx) @@ -58,7 +57,7 @@ func (p *Postgres) ExecContext(ctx context.Context, q postgres.Query, args ...in return p.Pool.Exec(ctx, q.Raw, args...) } -func (p *Postgres) QueryContext(ctx context.Context, q postgres.Query, args ...interface{}) (pgx.Rows, error) { +func (p *Postgres) QueryContext(ctx context.Context, q Query, args ...interface{}) (pgx.Rows, error) { logQuery(ctx, p.logger, q, args...) tx, ok := ctx.Value(TxKey).(pgx.Tx) @@ -69,7 +68,7 @@ func (p *Postgres) QueryContext(ctx context.Context, q postgres.Query, args ...i return p.Pool.Query(ctx, q.Raw, args...) } -func (p *Postgres) QueryRowContext(ctx context.Context, q postgres.Query, args ...interface{}) pgx.Row { +func (p *Postgres) QueryRowContext(ctx context.Context, q Query, args ...interface{}) pgx.Row { logQuery(ctx, p.logger, q, args...) tx, ok := ctx.Value(TxKey).(pgx.Tx) if ok { @@ -81,7 +80,7 @@ func (p *Postgres) QueryRowContext(ctx context.Context, q postgres.Query, args . return res } -func (p *Postgres) QueryRawContextMulti(ctx context.Context, q postgres.Query, args ...interface{}) (pgx.Rows, error) { +func (p *Postgres) QueryRawContextMulti(ctx context.Context, q Query, args ...interface{}) (pgx.Rows, error) { tx, ok := ctx.Value(TxKey).(pgx.Tx) if ok { return tx.Query(ctx, q.Raw, args...) @@ -108,11 +107,11 @@ func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { return context.WithValue(ctx, TxKey, tx) } -func logQuery(_ context.Context, logger *slog.Logger, q postgres.Query, args ...interface{}) { +func logQuery(_ context.Context, logger *slog.Logger, q Query, args ...interface{}) { logger.Debug( "executing query", slog.String("sql", q.Name), slog.String("query", q.Raw), - slog.Group("args", args), + slog.Any("args", args), ) } diff --git a/bookback/pkg/postgres/transaction/transaction.go b/bookback/pkg/postgres/transaction/transaction.go index 371c79f..a3c32f1 100644 --- a/bookback/pkg/postgres/transaction/transaction.go +++ b/bookback/pkg/postgres/transaction/transaction.go @@ -4,7 +4,6 @@ import ( "context" "errors" "github.com/SShlykov/zeitment/bookback/pkg/postgres" - "github.com/SShlykov/zeitment/bookback/pkg/postgres/pg" "github.com/jackc/pgx/v5" ) @@ -19,7 +18,7 @@ func NewTransactionManager(db postgres.Transactor) postgres.TxManager { // Transaction основная функция, которая выполняет указанный пользователем обработчик в транзакции func (m *manager) Transaction(ctx context.Context, opts pgx.TxOptions, fn postgres.Handler) (err error) { - tx, ok := ctx.Value(pg.TxKey).(pgx.Tx) + tx, ok := ctx.Value(postgres.TxKey).(pgx.Tx) if ok { return fn(ctx) } @@ -29,7 +28,7 @@ func (m *manager) Transaction(ctx context.Context, opts pgx.TxOptions, fn postgr return errors.Join(err, errors.New("can't begin transaction")) } - ctx = pg.MakeContextTx(ctx, tx) + ctx = postgres.MakeContextTx(ctx, tx) defer func() { if r := recover(); r != nil {