diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 7b418ae..fdf71b4 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.1 +openapi: 3.0.3 info: title: Сервис логики верификации аккаунта пользователя description: |- @@ -19,13 +19,13 @@ paths: tags: - verification responses: - '200': + "200": description: успешное получение данных content: application/json: schema: - $ref: '#/components/schemas/Verification' - '401': + $ref: "#/components/schemas/Verification" + "401": description: Неавторизован /verification: get: @@ -33,13 +33,13 @@ paths: tags: - verification responses: - '200': + "200": description: успешное получение данных content: application/json: schema: - $ref: '#/components/schemas/Verification' - '401': + $ref: "#/components/schemas/Verification" + "401": description: Неавторизован post: description: метод подания запроса на верификацию. При получении запроса отправить сообщение в канал телеграмма. Айдишник канала и токен бота передавать через переменные окружения. Файл с шаблоном сообщения встраивать в приложение. В тексте сообщения должно быть место для вставки урла для получения страницы админки с этим запросом, вида https://admin.pena.digital/user/{Id}/verification @@ -52,32 +52,28 @@ paths: type: object properties: status: - $ref: '#/components/schemas/Status' + $ref: "#/components/schemas/Status" inn: - type: file - contentMediaType: application/pdf - contentEncoding: base64 + type: string + format: base64 rule: - type: file - contentMediaType: application/pdf - contentEncoding: base64 + type: string + format: base64 egrule: - type: file - contentMediaType: application/pdf - contentEncoding: base64 + type: string + format: base64 certificate: - type: file + type: string description: только для status == nko - contentMediaType: application/pdf - contentEncoding: base64 + format: base64 responses: - '200': + "200": description: успешный запрос на верификацию content: application/json: schema: - $ref: '#/components/schemas/Verification' - '401': + $ref: "#/components/schemas/Verification" + "401": description: Неавторизован patch: description: метод подтверждения или отклонения верификации. При подтверждении надо передать статус верификации в customer сервис. Эндпоинт для этого получить из переменных окружения. Документация для этого эндпоинта - https://penahub.gitlab.yandexcloud.net/pena-services/customer/-/blob/dev/openapi.yaml PATCH /account/{userId}. Слать запрос туда лучше через воркер, сохраняя задачи на отправку запросов в базу, чтобы не потерялись при перезагрузке сервиса. @@ -93,15 +89,44 @@ paths: type: string description: айдишник юзера status: - $ref: '#/components/schemas/Status' + $ref: "#/components/schemas/Status" comment: type: string accepted: type: boolean responses: - '200': + "200": description: успешное подтверждение или отклонение верификации - '401': + "401": + description: Неавторизован + /verification/file: + patch: + description: "метод для обновления файла/файлов верификации" + tags: + - verification + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + inn: + type: string + format: base64 + rule: + type: string + format: base64 + egrule: + type: string + format: base64 + certificate: + type: string + description: только для status == nko + format: base64 + responses: + "200": + description: успешно + "401": description: Неавторизован components: schemas: @@ -114,7 +139,7 @@ components: accepted: type: boolean status: - $ref: '#/components/schemas/Status' + $ref: "#/components/schemas/Status" updated_at: type: string format: "date-time" @@ -123,7 +148,7 @@ components: files: type: array items: - $ref: '#/components/schemas/File' + $ref: "#/components/schemas/File" File: type: object properties: @@ -138,4 +163,4 @@ components: enum: - no - nko - - org \ No newline at end of file + - org diff --git a/internal/controllers/controller.go b/internal/controllers/controller.go index 9cc5330..fb38106 100644 --- a/internal/controllers/controller.go +++ b/internal/controllers/controller.go @@ -1,10 +1,11 @@ package controllers import ( + "reflect" + "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "penahub.gitlab.yandexcloud.net/backend/verification/internal/models" - "reflect" ) var validate = validator.New() @@ -16,7 +17,7 @@ type Route struct { Handler fiber.Handler } -// validateStruct - возвращает строку с ошибкой, если структура не прошла валидацию +// validateStruct - возвращает строку с ошибкой, если структура не прошла валидацию. func validateStruct(s any) []*models.RespErrorValidate { err := validate.Struct(s) diff --git a/internal/controllers/verification.go b/internal/controllers/verification.go index 1e4dac1..b2d4b79 100644 --- a/internal/controllers/verification.go +++ b/internal/controllers/verification.go @@ -1,8 +1,11 @@ package controllers import ( - "github.com/gofiber/fiber/v2" + "errors" "mime/multipart" + + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp" "penahub.gitlab.yandexcloud.net/backend/verification/internal/client" "penahub.gitlab.yandexcloud.net/backend/verification/internal/models" "penahub.gitlab.yandexcloud.net/backend/verification/internal/repository" @@ -23,6 +26,7 @@ func (r *VerificationController) GetRoutes() []Route { {"GET", "/verification/:userID", "GetVerification", r.GetVerification}, {"POST", "/verification", "CreateVerification", r.CreateVerification}, {"PATCH", "/verification", "SetVerificationStatus", r.SetVerificationStatus}, + {Method: "PATCH", Path: "/verification/file", Name: "SetVerificationFile", Handler: r.SetVerificationFile}, } } @@ -36,7 +40,7 @@ func (r *VerificationController) GetVerification(c *fiber.Ctx) error { } } - resp, err := r.repository.GetByUserId(c.Context(), userID) + resp, err := r.repository.GetByUserID(c.Context(), userID) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } @@ -156,3 +160,30 @@ func (r *VerificationController) SetVerificationStatus(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } + +func (r *VerificationController) SetVerificationFile(c *fiber.Ctx) error { + userID := c.Params("userID") + + if userID == "" { + userID = c.Locals("userID").(string) + if userID == "" { + return fiber.NewError(fiber.StatusUnauthorized) + } + } + + availableFiles := []string{"inn", "rule", "egrule", "certificate"} + + for _, fileName := range availableFiles { + fileHeader, err := c.FormFile(fileName) + + if err != nil && !errors.Is(err, fasthttp.ErrMissingFile) { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + if err := r.repository.UpdateFile(c.Context(), userID, fileName, fileHeader); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + } + + return c.SendStatus(fiber.StatusOK) +} diff --git a/internal/controllers/verification_test.go b/internal/controllers/verification_test.go index cd04e2e..c797b12 100644 --- a/internal/controllers/verification_test.go +++ b/internal/controllers/verification_test.go @@ -10,7 +10,7 @@ type VerificationTestSuite struct { } // -//func (suite *VerificationTestSuite) SetupSuite() { +// func (suite *VerificationTestSuite) SetupSuite() { // cfg, err := config.NewConfig("test.env") // suite.NoError(err) // diff --git a/internal/models/verification.go b/internal/models/verification.go index dcd93d1..1fe1a8c 100644 --- a/internal/models/verification.go +++ b/internal/models/verification.go @@ -3,16 +3,16 @@ package models import "time" type Verification struct { - ID string `json:"_id" bson:"_id,omitempty"` - UserID string `json:"userID" bson:"user_id,omitempty"` - Accepted bool `json:"accepted" bson:"accepted,omitempty"` - Status string `json:"status" bson:"status,omitempty"` - UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` - Comment string `json:"comment" bson:"comment,omitempty"` - Files []VerificationFiles `json:"files" bson:"files,omitempty"` + ID string `json:"_id" bson:"_id,omitempty"` + UserID string `json:"userID" bson:"user_id,omitempty"` + Accepted bool `json:"accepted" bson:"accepted,omitempty"` + Status string `json:"status" bson:"status,omitempty"` + UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` + Comment string `json:"comment" bson:"comment,omitempty"` + Files []VerificationFile `json:"files" bson:"files,omitempty"` } -type VerificationFiles struct { +type VerificationFile struct { Name string `json:"name" bson:"name"` Url string `json:"url" bson:"url"` } diff --git a/internal/repository/verification.go b/internal/repository/verification.go index e1b4c21..3224144 100644 --- a/internal/repository/verification.go +++ b/internal/repository/verification.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "mime/multipart" + "strings" "time" "github.com/minio/minio-go/v7" @@ -26,9 +27,9 @@ type VerificationRepository struct { } const ( - VerificationEndpointURL = "https://hub.pena.digital" - VerificationBucket = "verification1" - VerificationCollection = "verification" + VerificationEndpointURL = "https://hub.pena.digital" + VerificationBucket = "verification1" + VerificationCollection = "verification" ) func NewVerificationRepository(logger *zap.Logger, mongoDb *mongo.Database, s3 *minio.Client) *VerificationRepository { @@ -100,7 +101,6 @@ func (r *VerificationRepository) Init(ctx context.Context) error { if r.err(err) { return err } - } return nil @@ -160,7 +160,7 @@ func (r *VerificationRepository) Insert( return nil, err } - record.Files = []models.VerificationFiles{ + record.Files = []models.VerificationFile{ { Name: "certificate", Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, certFH.Filename), @@ -169,7 +169,7 @@ func (r *VerificationRepository) Insert( } // Insert to MongoDB - record.Files = append(record.Files, []models.VerificationFiles{ + record.Files = append(record.Files, []models.VerificationFile{ { Name: "inn", Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, innFH.Filename), @@ -193,7 +193,7 @@ func (r *VerificationRepository) Insert( return record, nil } -func (r *VerificationRepository) GetByUserId(ctx context.Context, userID string) (*models.Verification, error) { +func (r *VerificationRepository) GetByUserID(ctx context.Context, userID string) (*models.Verification, error) { if userID == "" { err := errors.New("userID cannot be empty") r.logger.Error("VerificationRepositoryError", zap.Error(err)) @@ -254,6 +254,57 @@ func (r *VerificationRepository) Update(ctx context.Context, record *models.Veri return &result, nil } + +func (r *VerificationRepository) UpdateFile(ctx context.Context, userID, fileName string, fileHeader *multipart.FileHeader) error { + var err error + + // put file + fileReader, err := fileHeader.Open() + if r.err(err) { + return err + } + + _, err = r.s3.PutObject(ctx, VerificationBucket, fmt.Sprintf("%s/%s", userID, fileHeader.Filename), fileReader, fileHeader.Size, minio.PutObjectOptions{}) + if r.err(err) { + return err + } + + fileUrl := fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, fileHeader.Filename) + + // remove old file + verification, err := r.GetByUserID(ctx, userID) + if r.err(err) { + return err + } + + found := false + for iterator, file := range verification.Files { + if file.Name != fileName { + continue + } + + objectName := strings.ReplaceAll(file.Name, fmt.Sprintf("%v/%v/", VerificationEndpointURL, VerificationBucket), "") + if err = r.s3.RemoveObject(ctx, VerificationBucket, objectName, minio.RemoveObjectOptions{}); r.err(err) { + return err + } + + verification.Files[iterator] = models.VerificationFile{Name: file.Name, Url: fileUrl} + found = true + } + + if !found { + verification.Files = append(verification.Files, models.VerificationFile{Name: fileName, Url: fileUrl}) + } + + // update in mongodb + _, err = r.Update(ctx, &models.Verification{ID: verification.ID, Files: verification.Files}) + if r.err(err) { + return err + } + + return nil +} + func (r *VerificationRepository) err(err error) bool { if err != nil { r.logger.Error("VerificationRepositoryError", zap.Error(err)) diff --git a/internal/server/http.go b/internal/server/http.go index 97d01ea..176fbe1 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -32,23 +32,22 @@ func NewHTTP(cfg *config.Config, logger *zap.Logger) *HTTP { return &HTTP{fiber: srv, cfg: cfg, logger: logger} } -// Register - автоматически регистрирует все контроллеры +// Register - автоматически регистрирует все контроллеры. func (srv *HTTP) Register(controllers ...initialize.Controller) *HTTP { for _, controller := range controllers { for _, route := range controller.GetRoutes() { srv.fiber.Add(route.Method, route.Path, route.Handler).Name(route.Name) } - } return srv } -// Start - запускает http сервер +// Start - запускает http сервер. func (srv *HTTP) Start() error { return srv.fiber.Listen(srv.cfg.HttpAddress) } -// Stop - останавливает http сервер +// Stop - останавливает http сервер. func (srv *HTTP) Stop() error { return srv.fiber.Shutdown() } diff --git a/internal/server/middleware.go b/internal/server/middleware.go index 39b0998..17a8421 100644 --- a/internal/server/middleware.go +++ b/internal/server/middleware.go @@ -2,6 +2,7 @@ package server import ( "strings" + "github.com/gofiber/fiber/v2" "penahub.gitlab.yandexcloud.net/backend/penahub_common/jwt_adapter" )