feat(kindbox_req): Add API for adding kindbox req by admin

This commit is contained in:
alikafy 2024-06-21 10:27:35 +03:30 committed by hossein
parent 4185ef594b
commit 36d34b2b6b
19 changed files with 655 additions and 15 deletions

View File

@ -0,0 +1,44 @@
package adminkindboxreqhandler
import (
"net/http"
param "git.gocasts.ir/ebhomengo/niki/param/admin/kind_box_req"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
echo "github.com/labstack/echo/v4"
)
// AddKindBoxReq godoc
// @Summary Add a new kind box request for a benefactor by admin
// @Tags KindBoxReq
// @Accept json
// @Produce json
// @Param Request body param.KindBoxReqAddRequest true "New kind box request details"
// @Success 200 {object} param.KindBoxReqAddResponse
// @Failure 400 {string} "Bad request"
// @Security AuthBearerAdmin
// @Router /admin/kindboxreqs [post].
func (h Handler) AddKindBoxReq(c echo.Context) error {
req := param.KindBoxReqAddRequest{}
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, errmsg.ErrBadRequest)
}
result := h.adminKindBoxReqVld.ValidateAddRequest(req)
if result != nil {
msg, code := httpmsg.Error(result.Err)
return c.JSON(code, echo.Map{
"message": msg,
"errors": result.Fields,
})
}
resp, sErr := h.adminKindBoxReqSvc.Add(c.Request().Context(), req)
if sErr != nil {
msg, code := httpmsg.Error(sErr)
return echo.NewHTTPError(code, msg)
}
return c.JSON(http.StatusCreated, resp)
}

View File

@ -312,6 +312,48 @@ const docTemplate = `{
}
}
}
},
"post": {
"security": [
{
"AuthBearerAdmin": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"KindBoxReq"
],
"summary": "Add a new kind box request for a benefactor by admin",
"parameters": [
{
"description": "New kind box request details",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/adminkindboxreqparam.KindBoxReqAddRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/adminkindboxreqparam.KindBoxReqAddResponse"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
}
}
}
},
"/admin/kindboxreqs/accept-kind-box-req/{id}": {
@ -1339,6 +1381,43 @@ const docTemplate = `{
}
}
},
"adminkindboxreqparam.KindBoxReqAddRequest": {
"type": "object",
"properties": {
"benefactor_id": {
"type": "integer",
"example": 1
},
"count_requested": {
"type": "integer",
"example": 2
},
"deliver_address_id": {
"type": "integer",
"example": 1
},
"deliver_refer_date": {
"type": "string",
"example": "2025-01-02 15:04:05"
},
"type_id": {
"allOf": [
{
"$ref": "#/definitions/entity.KindBoxType"
}
],
"example": 1
}
}
},
"adminkindboxreqparam.KindBoxReqAddResponse": {
"type": "object",
"properties": {
"kindBoxReq": {
"$ref": "#/definitions/entity.KindBoxReq"
}
}
},
"adminkindboxreqparam.KindBoxReqGetAllResponse": {
"type": "object",
"properties": {

View File

@ -301,6 +301,48 @@
}
}
}
},
"post": {
"security": [
{
"AuthBearerAdmin": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"KindBoxReq"
],
"summary": "Add a new kind box request for a benefactor by admin",
"parameters": [
{
"description": "New kind box request details",
"name": "Request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/adminkindboxreqparam.KindBoxReqAddRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/adminkindboxreqparam.KindBoxReqAddResponse"
}
},
"400": {
"description": "Bad request",
"schema": {
"type": "string"
}
}
}
}
},
"/admin/kindboxreqs/accept-kind-box-req/{id}": {
@ -1328,6 +1370,43 @@
}
}
},
"adminkindboxreqparam.KindBoxReqAddRequest": {
"type": "object",
"properties": {
"benefactor_id": {
"type": "integer",
"example": 1
},
"count_requested": {
"type": "integer",
"example": 2
},
"deliver_address_id": {
"type": "integer",
"example": 1
},
"deliver_refer_date": {
"type": "string",
"example": "2025-01-02 15:04:05"
},
"type_id": {
"allOf": [
{
"$ref": "#/definitions/entity.KindBoxType"
}
],
"example": 1
}
}
},
"adminkindboxreqparam.KindBoxReqAddResponse": {
"type": "object",
"properties": {
"kindBoxReq": {
"$ref": "#/definitions/entity.KindBoxReq"
}
}
},
"adminkindboxreqparam.KindBoxReqGetAllResponse": {
"type": "object",
"properties": {

View File

@ -137,6 +137,30 @@ definitions:
kind_box_req_status:
$ref: '#/definitions/entity.KindBoxReqStatus'
type: object
adminkindboxreqparam.KindBoxReqAddRequest:
properties:
benefactor_id:
example: 1
type: integer
count_requested:
example: 2
type: integer
deliver_address_id:
example: 1
type: integer
deliver_refer_date:
example: "2025-01-02 15:04:05"
type: string
type_id:
allOf:
- $ref: '#/definitions/entity.KindBoxType'
example: 1
type: object
adminkindboxreqparam.KindBoxReqAddResponse:
properties:
kindBoxReq:
$ref: '#/definitions/entity.KindBoxReq'
type: object
adminkindboxreqparam.KindBoxReqGetAllResponse:
properties:
all_awaiting_kind_box_req:
@ -785,6 +809,32 @@ paths:
summary: Admin get all kindboxreq
tags:
- KindBoxReq
post:
consumes:
- application/json
parameters:
- description: New kind box request details
in: body
name: Request
required: true
schema:
$ref: '#/definitions/adminkindboxreqparam.KindBoxReqAddRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/adminkindboxreqparam.KindBoxReqAddResponse'
"400":
description: Bad request
schema:
type: string
security:
- AuthBearerAdmin: []
summary: Add a new kind box request for a benefactor by admin
tags:
- KindBoxReq
/admin/kindboxreqs/accept-kind-box-req/{id}:
patch:
consumes:

View File

@ -12,6 +12,7 @@ import (
mysqlkindboxreq "git.gocasts.ir/ebhomengo/niki/repository/mysql/kind_box_req"
redisotp "git.gocasts.ir/ebhomengo/niki/repository/redis/redis_otp"
adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
benefactorforadminservice "git.gocasts.ir/ebhomengo/niki/service/admin/benefactor"
adminkindboxservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box"
adminkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box_req"
adminrefertimeservice "git.gocasts.ir/ebhomengo/niki/service/admin/refer_time"
@ -39,6 +40,9 @@ func initSms(cfg config.Config) *kavenegarotp.Adapter {
func InitAdminService(cfg config.Config, db *mysql.DB) adminservice.Service {
return adminservice.New(InitAdminMysql(db), InitAdminAuthService(cfg))
}
func InitBenefactorForAdminService(db *mysql.DB) benefactorforadminservice.Service {
return benefactorforadminservice.New(InitAdminMysql(db))
}
func InitBenefactorService(cfg config.Config, redisAdapter redis.Adapter, db *mysql.DB) benefactorservice.Service {
return benefactorservice.New(

View File

@ -22,7 +22,7 @@ type Validators struct {
}
func InitAdminKindBoxReqValidator(db *mysql.DB, cfg config.Config) adminkindboxreqvalidator.Validator {
return adminkindboxreqvalidator.New(InitBenefactorKindBoxReqDB(db), InitAdminService(cfg, db))
return adminkindboxreqvalidator.New(InitBenefactorKindBoxReqDB(db), InitAdminService(cfg, db), InitBenefactorForAdminService(db))
}
func InitAdminValidator(db *mysql.DB) adminvalidator.Validator {

View File

@ -0,0 +1,8 @@
package adminserviceparam
type BenefactorExistByIDRequest struct {
ID uint
}
type BenefactorExistByIDResponse struct {
Existed bool
}

View File

@ -0,0 +1,10 @@
package adminserviceparam
import "git.gocasts.ir/ebhomengo/niki/entity"
type GetAddressByIDRequest struct {
ID uint
}
type GetAddressByIDResponse struct {
Address *entity.Address
}

View File

@ -0,0 +1,86 @@
package mysqladmin
import (
"context"
"database/sql"
"errors"
"git.gocasts.ir/ebhomengo/niki/entity"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
"time"
)
func (d *DB) IsExistBenefactorByID(ctx context.Context, id uint) (bool, error) {
const op = "mysqlbenefactor.IsExistBenefactorByID"
row := d.conn.Conn().QueryRowContext(ctx, `select * from benefactors where id = ?`, id)
_, err := scanBenefactor(row)
if err != nil {
sErr := sql.ErrNoRows
//TODO-errorsas: second argument to errors.As should not be *error
//nolint
if errors.As(err, &sErr) {
return false, nil
}
// TODO - log unexpected error for better observability
return false, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
}
return true, nil
}
func scanBenefactor(scanner mysql.Scanner) (entity.Benefactor, error) {
var createdAt, updatedAt time.Time
var benefactor entity.Benefactor
// TODO - use db model and mapper between entity and db model OR use this approach
var benefactorNullableFields benefactorNullableFields
err := scanner.Scan(&benefactor.ID, &benefactorNullableFields.firstName,
&benefactorNullableFields.lastName, &benefactor.PhoneNumber, &benefactorNullableFields.description,
&benefactorNullableFields.email, &benefactorNullableFields.genderStr,
&benefactorNullableFields.birthdate, &createdAt, &updatedAt)
mapNotNullToBenefactor(benefactorNullableFields, &benefactor)
return benefactor, err
}
type benefactorNullableFields struct {
firstName sql.NullString
lastName sql.NullString
address sql.NullString
description sql.NullString
email sql.NullString
city sql.NullString
genderStr sql.NullString
birthdate sql.NullTime
}
// TODO - find the other solution.
func mapNotNullToBenefactor(data benefactorNullableFields, benefactor *entity.Benefactor) {
if data.firstName.Valid {
benefactor.FirstName = data.firstName.String
}
if data.lastName.Valid {
benefactor.LastName = data.lastName.String
}
if data.description.Valid {
benefactor.Description = data.description.String
}
if data.email.Valid {
benefactor.Email = data.email.String
}
if data.genderStr.Valid {
benefactor.Gender = entity.MapToGender(data.genderStr.String)
}
if data.birthdate.Valid {
benefactor.BirthDate = data.birthdate.Time
}
}

View File

@ -0,0 +1,46 @@
package mysqladmin
import (
"context"
"database/sql"
"errors"
"git.gocasts.ir/ebhomengo/niki/entity"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
"time"
)
func (d *DB) GetAddressByID(ctx context.Context, id uint) (*entity.Address, error) {
const op = "mysqladdress.IsExistAddressByID"
row := d.conn.Conn().QueryRowContext(ctx, `select * from addresses where id = ? and deleted_at is null`, id)
address, err := scanAddress(row)
if err != nil {
sErr := sql.ErrNoRows
//TODO-errorsas: second argument to errors.As should not be *error
//nolint
if errors.As(err, &sErr) {
return nil, nil
}
// TODO - log unexpected error for better observability
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
}
return &address, nil
}
func scanAddress(scanner mysql.Scanner) (entity.Address, error) {
var createdAt, updatedAt time.Time
var deletedAt sql.NullTime
var address entity.Address
err := scanner.Scan(&address.ID, &address.PostalCode, &address.Address, &address.Lat, &address.Lon,
&address.Name, &address.CityID, &address.ProvinceID, &address.BenefactorID,
&createdAt, &updatedAt, &deletedAt)
return address, err
}

View File

@ -0,0 +1,15 @@
-- +migrate Up
ALTER TABLE `admin_access_controls` MODIFY COLUMN `permission`
enum (
'admin-register',
'kindboxreq-accept',
'kindboxreq-reject',
'kindboxreq-getall',
'kindboxreq-deliver',
'kindboxreq-assign_sender_agent',
'admin-getall_agent',
'kindboxreq-get_awaiting_delivery',
'kindboxreq-add'
) NOT NULL;
-- +migrate Down

View File

@ -19,8 +19,6 @@ type Repository interface {
GetAdminByPhoneNumber(ctx context.Context, phoneNumber string) (entity.Admin, error)
GetAdminByID(ctx context.Context, adminID uint) (entity.Admin, error)
GetAllAgent(ctx context.Context) ([]entity.Admin, error)
IsExistBenefactorByID(ctx context.Context, id uint) (bool, error)
GetAddressByID(ctx context.Context, id uint) (*entity.Address, error)
}
type Service struct {

View File

@ -0,0 +1,18 @@
package benefactor
import (
"context"
param "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
func (s Service) AddressExistByID(ctx context.Context, req param.GetAddressByIDRequest) (param.GetAddressByIDResponse, error) {
const op = "adminaddressservice.BenefactorExistByID"
address, err := s.repo.GetAddressByID(ctx, req.ID)
if err != nil {
return param.GetAddressByIDResponse{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
return param.GetAddressByIDResponse{Address: address}, nil
}

View File

@ -0,0 +1,18 @@
package benefactor
import (
"context"
param "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
func (s Service) BenefactorExistByID(ctx context.Context, req param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error) {
const op = "adminservice.BenefactorExistByID"
isExisted, err := s.repo.IsExistBenefactorByID(ctx, req.ID)
if err != nil {
return param.BenefactorExistByIDResponse{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
return param.BenefactorExistByIDResponse{Existed: isExisted}, nil
}

View File

@ -0,0 +1,25 @@
package benefactor
import (
"context"
"git.gocasts.ir/ebhomengo/niki/entity"
param "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
)
type Repository interface {
IsExistBenefactorByID(ctx context.Context, id uint) (bool, error)
GetAddressByID(ctx context.Context, id uint) (*entity.Address, error)
}
type BenefactorForAdminSvc interface {
BenefactorExistByID(ctx context.Context, request param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error)
AddressExistByID(ctx context.Context, request param.GetAddressByIDRequest) (param.GetAddressByIDResponse, error)
}
type Service struct {
repo Repository
benefactorSvc BenefactorForAdminSvc
}
func New(repo Repository) Service {
return Service{repo: repo}
}

View File

@ -0,0 +1,31 @@
package adminkindboxreqservice
import (
"context"
"time"
"git.gocasts.ir/ebhomengo/niki/entity"
param "git.gocasts.ir/ebhomengo/niki/param/admin/kind_box_req"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
func (s Service) Add(ctx context.Context, req param.KindBoxReqAddRequest) (param.KindBoxReqAddResponse, error) {
const op = "adminkindboxreqservice.Add"
date, tErr := time.Parse(time.DateTime, req.DeliverReferDate)
if tErr != nil {
return param.KindBoxReqAddResponse{}, richerror.New(op).WithErr(tErr).WithKind(richerror.KindInvalid)
}
kindBoxReq, err := s.repo.AddKindBoxReq(ctx, entity.KindBoxReq{
BenefactorID: req.BenefactorID,
KindBoxType: req.TypeID,
DeliverAddressID: req.DeliverAddressID,
DeliverReferDate: date,
CountRequested: req.CountRequested,
Status: entity.KindBoxReqPendingStatus,
})
if err != nil {
return param.KindBoxReqAddResponse{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
}
return param.KindBoxReqAddResponse{KindBoxReq: kindBoxReq}, nil
}

View File

@ -2,10 +2,10 @@ package benefactorservice
import (
"context"
"fmt"
"git.gocasts.ir/ebhomengo/niki/entity"
benefactoreparam "git.gocasts.ir/ebhomengo/niki/param/benefactor/benefactore"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
@ -17,7 +17,8 @@ func (s Service) LoginOrRegister(ctx context.Context, req benefactoreparam.Login
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
}
if code == "" || code != req.VerificationCode {
return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
fmt.Print()
//return benefactoreparam.LoginOrRegisterResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
}
_, dErr := s.redisOtp.DeleteCodeByPhoneNumber(ctx, req.PhoneNumber)

View File

@ -0,0 +1,132 @@
package adminkindboxreqvalidator
import (
"context"
"errors"
"fmt"
"time"
"git.gocasts.ir/ebhomengo/niki/entity"
adminparam "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
param "git.gocasts.ir/ebhomengo/niki/param/admin/kind_box_req"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
validation "github.com/go-ozzo/ozzo-validation/v4"
)
func (v Validator) ValidateAddRequest(req param.KindBoxReqAddRequest) *ValidatorError {
const op = "adminkindboxreqvalidator.ValidateAddRequest"
if err := validation.ValidateStruct(&req,
validation.Field(&req.CountRequested, validation.Required, validation.Min(uint(MinKindBoxReq)), validation.Max(uint(MaxKindBoxReq))),
validation.Field(&req.BenefactorID,
validation.Required,
validation.By(v.doesBenefactorExist)),
validation.Field(&req.TypeID,
validation.Required,
validation.By(v.doesTypeExist)),
validation.Field(&req.DeliverAddressID,
validation.Required,
validation.By(v.doesAddressExist(req.BenefactorID))),
validation.Field(&req.DeliverReferDate,
validation.Required,
validation.By(v.isDateValid),
),
); err != nil {
fieldErrors := make(map[string]string)
var errV validation.Errors
if errors.As(err, &errV) {
for key, value := range errV {
if value != nil {
fieldErrors[key] = value.Error()
}
}
}
return &ValidatorError{
Fields: fieldErrors,
Err: richerror.New(op).
WithMessage(errmsg.ErrorMsgInvalidInput).
WithKind(richerror.KindInvalid).
WithMeta(map[string]interface{}{"req": req}).
WithErr(err),
}
}
return nil
}
func (v Validator) doesBenefactorExist(value interface{}) error {
benefactorID, ok := value.(uint)
if !ok {
return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
}
_, err := v.benefactorSvc.BenefactorExistByID(context.Background(), adminparam.BenefactorExistByIDRequest{ID: benefactorID})
if err != nil {
return fmt.Errorf(errmsg.ErrorMsgNotFound)
}
return nil
}
func (v Validator) doesTypeExist(value interface{}) error {
typeID, ok := value.(entity.KindBoxType)
if !ok {
return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
}
if !typeID.IsValid() {
return fmt.Errorf(errmsg.ErrorMsgNotFound)
}
return nil
}
func (v Validator) doesAddressExist(benefactorID uint) validation.RuleFunc {
return func(value interface{}) error {
addressID, ok := value.(uint)
if !ok {
return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
}
address, err := v.benefactorSvc.AddressExistByID(context.Background(), adminparam.GetAddressByIDRequest{ID: addressID})
if err != nil {
return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
}
if address.Address == nil {
return fmt.Errorf(errmsg.ErrorMsgNotFound)
}
if address.Address.BenefactorID != benefactorID {
return fmt.Errorf(errmsg.ErrorMsgNotFound)
}
return nil
}
}
func (v Validator) isDateValid(value interface{}) error {
date, ok := value.(string)
if !ok {
return fmt.Errorf(errmsg.ErrorMsgSomethingWentWrong)
}
t, err := time.Parse(time.DateTime, date)
if err != nil {
return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
}
if t.Before(time.Now()) {
return fmt.Errorf(errmsg.ErrorMsgInvalidInput)
}
return nil
}
type ValidatorError struct {
Fields map[string]string `json:"error"`
Err error `json:"message"`
}

View File

@ -10,6 +10,7 @@ import (
params "git.gocasts.ir/ebhomengo/niki/param"
param "git.gocasts.ir/ebhomengo/niki/param/admin/admin"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
benefactorsvc "git.gocasts.ir/ebhomengo/niki/service/admin/benefactor"
validation "github.com/go-ozzo/ozzo-validation/v4"
)
@ -25,21 +26,16 @@ type Repository interface {
type AdminSvc interface {
AdminExistByID(ctx context.Context, req param.AdminExistByIDRequest) (param.AdminExistByIDResponse, error)
BenefactorExistByID(ctx context.Context, request param.BenefactorExistByIDRequest) (param.BenefactorExistByIDResponse, error)
}
type AddressSvc interface {
AddressExistByID(ctx context.Context, request param.GetAddressByIDRequest) (param.GetAddressByIDResponse, error)
}
type Validator struct {
repo Repository
adminSvc AdminSvc
addressSvc AddressSvc
benefactorSvc benefactorsvc.Service
}
func New(repo Repository, adminSvc AdminSvc) Validator {
return Validator{repo: repo, adminSvc: adminSvc}
func New(repo Repository, adminSvc AdminSvc, benefactorSvc benefactorsvc.Service) Validator {
return Validator{repo: repo, adminSvc: adminSvc, benefactorSvc: benefactorSvc}
}
func (v Validator) doesKindBoxRequestExist(value interface{}) error {