- Endpoint for patch verification file(s)
Fix:
  - Rename in verification models and repository
This commit is contained in:
Danil Solovyov 2023-09-15 05:39:38 +05:00
parent 47d0563d07
commit cd30886afb
8 changed files with 161 additions and 53 deletions

@ -1,4 +1,4 @@
openapi: 3.0.1 openapi: 3.0.3
info: info:
title: Сервис логики верификации аккаунта пользователя title: Сервис логики верификации аккаунта пользователя
description: |- description: |-
@ -19,13 +19,13 @@ paths:
tags: tags:
- verification - verification
responses: responses:
'200': "200":
description: успешное получение данных description: успешное получение данных
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Verification' $ref: "#/components/schemas/Verification"
'401': "401":
description: Неавторизован description: Неавторизован
/verification: /verification:
get: get:
@ -33,13 +33,13 @@ paths:
tags: tags:
- verification - verification
responses: responses:
'200': "200":
description: успешное получение данных description: успешное получение данных
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Verification' $ref: "#/components/schemas/Verification"
'401': "401":
description: Неавторизован description: Неавторизован
post: post:
description: метод подания запроса на верификацию. При получении запроса отправить сообщение в канал телеграмма. Айдишник канала и токен бота передавать через переменные окружения. Файл с шаблоном сообщения встраивать в приложение. В тексте сообщения должно быть место для вставки урла для получения страницы админки с этим запросом, вида https://admin.pena.digital/user/{Id}/verification description: метод подания запроса на верификацию. При получении запроса отправить сообщение в канал телеграмма. Айдишник канала и токен бота передавать через переменные окружения. Файл с шаблоном сообщения встраивать в приложение. В тексте сообщения должно быть место для вставки урла для получения страницы админки с этим запросом, вида https://admin.pena.digital/user/{Id}/verification
@ -52,32 +52,28 @@ paths:
type: object type: object
properties: properties:
status: status:
$ref: '#/components/schemas/Status' $ref: "#/components/schemas/Status"
inn: inn:
type: file type: string
contentMediaType: application/pdf format: base64
contentEncoding: base64
rule: rule:
type: file type: string
contentMediaType: application/pdf format: base64
contentEncoding: base64
egrule: egrule:
type: file type: string
contentMediaType: application/pdf format: base64
contentEncoding: base64
certificate: certificate:
type: file type: string
description: только для status == nko description: только для status == nko
contentMediaType: application/pdf format: base64
contentEncoding: base64
responses: responses:
'200': "200":
description: успешный запрос на верификацию description: успешный запрос на верификацию
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Verification' $ref: "#/components/schemas/Verification"
'401': "401":
description: Неавторизован description: Неавторизован
patch: patch:
description: метод подтверждения или отклонения верификации. При подтверждении надо передать статус верификации в customer сервис. Эндпоинт для этого получить из переменных окружения. Документация для этого эндпоинта - https://penahub.gitlab.yandexcloud.net/pena-services/customer/-/blob/dev/openapi.yaml PATCH /account/{userId}. Слать запрос туда лучше через воркер, сохраняя задачи на отправку запросов в базу, чтобы не потерялись при перезагрузке сервиса. description: метод подтверждения или отклонения верификации. При подтверждении надо передать статус верификации в customer сервис. Эндпоинт для этого получить из переменных окружения. Документация для этого эндпоинта - https://penahub.gitlab.yandexcloud.net/pena-services/customer/-/blob/dev/openapi.yaml PATCH /account/{userId}. Слать запрос туда лучше через воркер, сохраняя задачи на отправку запросов в базу, чтобы не потерялись при перезагрузке сервиса.
@ -93,15 +89,44 @@ paths:
type: string type: string
description: айдишник юзера description: айдишник юзера
status: status:
$ref: '#/components/schemas/Status' $ref: "#/components/schemas/Status"
comment: comment:
type: string type: string
accepted: accepted:
type: boolean type: boolean
responses: responses:
'200': "200":
description: успешное подтверждение или отклонение верификации 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: Неавторизован description: Неавторизован
components: components:
schemas: schemas:
@ -114,7 +139,7 @@ components:
accepted: accepted:
type: boolean type: boolean
status: status:
$ref: '#/components/schemas/Status' $ref: "#/components/schemas/Status"
updated_at: updated_at:
type: string type: string
format: "date-time" format: "date-time"
@ -123,7 +148,7 @@ components:
files: files:
type: array type: array
items: items:
$ref: '#/components/schemas/File' $ref: "#/components/schemas/File"
File: File:
type: object type: object
properties: properties:
@ -138,4 +163,4 @@ components:
enum: enum:
- no - no
- nko - nko
- org - org

@ -1,10 +1,11 @@
package controllers package controllers
import ( import (
"reflect"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/verification/internal/models" "penahub.gitlab.yandexcloud.net/backend/verification/internal/models"
"reflect"
) )
var validate = validator.New() var validate = validator.New()
@ -16,7 +17,7 @@ type Route struct {
Handler fiber.Handler Handler fiber.Handler
} }
// validateStruct - возвращает строку с ошибкой, если структура не прошла валидацию // validateStruct - возвращает строку с ошибкой, если структура не прошла валидацию.
func validateStruct(s any) []*models.RespErrorValidate { func validateStruct(s any) []*models.RespErrorValidate {
err := validate.Struct(s) err := validate.Struct(s)

@ -1,8 +1,11 @@
package controllers package controllers
import ( import (
"github.com/gofiber/fiber/v2" "errors"
"mime/multipart" "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/client"
"penahub.gitlab.yandexcloud.net/backend/verification/internal/models" "penahub.gitlab.yandexcloud.net/backend/verification/internal/models"
"penahub.gitlab.yandexcloud.net/backend/verification/internal/repository" "penahub.gitlab.yandexcloud.net/backend/verification/internal/repository"
@ -23,6 +26,7 @@ func (r *VerificationController) GetRoutes() []Route {
{"GET", "/verification/:userID", "GetVerification", r.GetVerification}, {"GET", "/verification/:userID", "GetVerification", r.GetVerification},
{"POST", "/verification", "CreateVerification", r.CreateVerification}, {"POST", "/verification", "CreateVerification", r.CreateVerification},
{"PATCH", "/verification", "SetVerificationStatus", r.SetVerificationStatus}, {"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 { if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) 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) 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)
}

@ -10,7 +10,7 @@ type VerificationTestSuite struct {
} }
// //
//func (suite *VerificationTestSuite) SetupSuite() { // func (suite *VerificationTestSuite) SetupSuite() {
// cfg, err := config.NewConfig("test.env") // cfg, err := config.NewConfig("test.env")
// suite.NoError(err) // suite.NoError(err)
// //

@ -3,16 +3,16 @@ package models
import "time" import "time"
type Verification struct { type Verification struct {
ID string `json:"_id" bson:"_id,omitempty"` ID string `json:"_id" bson:"_id,omitempty"`
UserID string `json:"userID" bson:"user_id,omitempty"` UserID string `json:"userID" bson:"user_id,omitempty"`
Accepted bool `json:"accepted" bson:"accepted,omitempty"` Accepted bool `json:"accepted" bson:"accepted,omitempty"`
Status string `json:"status" bson:"status,omitempty"` Status string `json:"status" bson:"status,omitempty"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
Comment string `json:"comment" bson:"comment,omitempty"` Comment string `json:"comment" bson:"comment,omitempty"`
Files []VerificationFiles `json:"files" bson:"files,omitempty"` Files []VerificationFile `json:"files" bson:"files,omitempty"`
} }
type VerificationFiles struct { type VerificationFile struct {
Name string `json:"name" bson:"name"` Name string `json:"name" bson:"name"`
Url string `json:"url" bson:"url"` Url string `json:"url" bson:"url"`
} }

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"mime/multipart" "mime/multipart"
"strings"
"time" "time"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
@ -26,9 +27,9 @@ type VerificationRepository struct {
} }
const ( const (
VerificationEndpointURL = "https://hub.pena.digital" VerificationEndpointURL = "https://hub.pena.digital"
VerificationBucket = "verification1" VerificationBucket = "verification1"
VerificationCollection = "verification" VerificationCollection = "verification"
) )
func NewVerificationRepository(logger *zap.Logger, mongoDb *mongo.Database, s3 *minio.Client) *VerificationRepository { 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) { if r.err(err) {
return err return err
} }
} }
return nil return nil
@ -160,7 +160,7 @@ func (r *VerificationRepository) Insert(
return nil, err return nil, err
} }
record.Files = []models.VerificationFiles{ record.Files = []models.VerificationFile{
{ {
Name: "certificate", Name: "certificate",
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, certFH.Filename), Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, certFH.Filename),
@ -169,7 +169,7 @@ func (r *VerificationRepository) Insert(
} }
// Insert to MongoDB // Insert to MongoDB
record.Files = append(record.Files, []models.VerificationFiles{ record.Files = append(record.Files, []models.VerificationFile{
{ {
Name: "inn", Name: "inn",
Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, innFH.Filename), Url: fmt.Sprintf("%s/%s/%s/%s", VerificationEndpointURL, VerificationBucket, userID, innFH.Filename),
@ -193,7 +193,7 @@ func (r *VerificationRepository) Insert(
return record, nil 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 == "" { if userID == "" {
err := errors.New("userID cannot be empty") err := errors.New("userID cannot be empty")
r.logger.Error("VerificationRepositoryError", zap.Error(err)) r.logger.Error("VerificationRepositoryError", zap.Error(err))
@ -254,6 +254,57 @@ func (r *VerificationRepository) Update(ctx context.Context, record *models.Veri
return &result, nil 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 { func (r *VerificationRepository) err(err error) bool {
if err != nil { if err != nil {
r.logger.Error("VerificationRepositoryError", zap.Error(err)) r.logger.Error("VerificationRepositoryError", zap.Error(err))

@ -32,23 +32,22 @@ func NewHTTP(cfg *config.Config, logger *zap.Logger) *HTTP {
return &HTTP{fiber: srv, cfg: cfg, logger: logger} return &HTTP{fiber: srv, cfg: cfg, logger: logger}
} }
// Register - автоматически регистрирует все контроллеры // Register - автоматически регистрирует все контроллеры.
func (srv *HTTP) Register(controllers ...initialize.Controller) *HTTP { func (srv *HTTP) Register(controllers ...initialize.Controller) *HTTP {
for _, controller := range controllers { for _, controller := range controllers {
for _, route := range controller.GetRoutes() { for _, route := range controller.GetRoutes() {
srv.fiber.Add(route.Method, route.Path, route.Handler).Name(route.Name) srv.fiber.Add(route.Method, route.Path, route.Handler).Name(route.Name)
} }
} }
return srv return srv
} }
// Start - запускает http сервер // Start - запускает http сервер.
func (srv *HTTP) Start() error { func (srv *HTTP) Start() error {
return srv.fiber.Listen(srv.cfg.HttpAddress) return srv.fiber.Listen(srv.cfg.HttpAddress)
} }
// Stop - останавливает http сервер // Stop - останавливает http сервер.
func (srv *HTTP) Stop() error { func (srv *HTTP) Stop() error {
return srv.fiber.Shutdown() return srv.fiber.Shutdown()
} }

@ -2,6 +2,7 @@ package server
import ( import (
"strings" "strings"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/jwt_adapter" "penahub.gitlab.yandexcloud.net/backend/penahub_common/jwt_adapter"
) )