add qualification repository and usecase
This commit is contained in:
parent
a9d7250623
commit
8fae4a2b03
|
@ -82,7 +82,7 @@ func createSchema(db *pg.DB) error {
|
|||
}
|
||||
}
|
||||
|
||||
total, err := db.Model(modelsToCreate[0]).Where("role = ?", models.RoleAdmin).Count()
|
||||
total, err := tx.Model(modelsToCreate[0]).Where("role = ?", models.RoleAdmin).Count()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "createSchema")
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ type Profession struct {
|
|||
ID int `json:"id,omitempty" xml:"id" gqlgen:"id"`
|
||||
Slug string `json:"slug" pg:",unique" xml:"slug" gqlgen:"slug"`
|
||||
Name string `json:"name,omitempty" pg:",unique" xml:"name" gqlgen:"name"`
|
||||
Description string `json:"description,omitempty" pg:",use_zero" xml:"description" gqlgen:"description"`
|
||||
Description string `json:"description,omitempty" xml:"description" gqlgen:"description"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" pg:"default:now()" xml:"createdAt" gqlgen:"createdAt"`
|
||||
}
|
||||
|
||||
|
|
|
@ -38,9 +38,9 @@ func (q *Qualification) BeforeUpdate(ctx context.Context) (context.Context, erro
|
|||
}
|
||||
|
||||
type QualificationToProfession struct {
|
||||
QualificationID int `pg:"on_delete:CASCADE" json:"qualificationID" xml:"qualificationID" gqlgen:"qualificationID"`
|
||||
QualificationID int `pg:"on_delete:CASCADE,unique:group_1" json:"qualificationID" xml:"qualificationID" gqlgen:"qualificationID"`
|
||||
Qualification *Qualification `pg:"rel:has-one" json:"qualification" xml:"qualification" gqlgen:"qualification"`
|
||||
ProfessionID int `pg:"on_delete:CASCADE" json:"professionID" xml:"professionID" gqlgen:"professionID"`
|
||||
ProfessionID int `pg:"on_delete:CASCADE,unique:group_1" json:"professionID" xml:"professionID" gqlgen:"professionID"`
|
||||
Profession *Qualification `pg:"rel:has-one" json:"profession" xml:"profession" gqlgen:"profession"`
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,16 @@ type QualificationInput struct {
|
|||
DissociateProfession []int `json:"dissociateProfession" xml:"dissociateProfession" gqlgen:"dissociateProfession"`
|
||||
}
|
||||
|
||||
func (input *QualificationInput) IsEmpty() bool {
|
||||
return input == nil &&
|
||||
input.Name == nil &&
|
||||
input.Code == nil &&
|
||||
input.Formula == nil &&
|
||||
input.Description == nil &&
|
||||
len(input.AssociateProfession) == 0 &&
|
||||
len(input.DissociateProfession) == 0
|
||||
}
|
||||
|
||||
func (input *QualificationInput) ToQualification() *Qualification {
|
||||
q := &Qualification{}
|
||||
if input.Name != nil {
|
||||
|
@ -70,6 +80,19 @@ func (input *QualificationInput) ToQualification() *Qualification {
|
|||
return q
|
||||
}
|
||||
|
||||
func (input *QualificationInput) ApplyUpdate(q *orm.Query) (*orm.Query, error) {
|
||||
if !input.IsEmpty() {
|
||||
if input.Name != nil {
|
||||
q.Set("name = ?", *input.Name)
|
||||
}
|
||||
if input.Description != nil {
|
||||
q.Set("description = ?", *input.Description)
|
||||
}
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
type QualificationFilterOr struct {
|
||||
NameMATCH string `json:"nameMATCH" xml:"nameMATCH" gqlgen:"nameMATCH"`
|
||||
NameIEQ string `gqlgen:"nameIEQ" json:"nameIEQ" xml:"nameIEQ"`
|
||||
|
|
|
@ -4,6 +4,6 @@ const (
|
|||
messageInvalidID = "Niepoprawne ID."
|
||||
messageItemNotFound = "Nie znaleziono zawodu."
|
||||
messageEmptyPayload = "Nie wprowadzono jakichkolwiek danych."
|
||||
messageNameIsRequired = "Nazwa zawodu jest polem wymaganym."
|
||||
messageNameIsRequired = "Nazwa zawodu jest wymagana."
|
||||
messageNameIsTooLong = "Nazwa zawodu może się składać z maksymalnie %d znaków."
|
||||
)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package qualification
|
||||
|
||||
const (
|
||||
DefaultLimit = 100
|
||||
MaxNameLength = 100
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package qualification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
)
|
||||
|
||||
type FetchConfig struct {
|
||||
Filter *models.QualificationFilter
|
||||
Offset int
|
||||
Limit int
|
||||
Sort []string
|
||||
Count bool
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
Store(ctx context.Context, input *models.QualificationInput) (*models.Qualification, error)
|
||||
UpdateMany(ctx context.Context, f *models.QualificationFilter, input *models.QualificationInput) ([]*models.Qualification, error)
|
||||
Delete(ctx context.Context, f *models.QualificationFilter) ([]*models.Qualification, error)
|
||||
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.Qualification, int, error)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package repository
|
||||
|
||||
const (
|
||||
messageNameIsAlreadyTaken = "Istnieje już kwalifikacja o podanej nazwie."
|
||||
messageCodeIsAlreadyTaken = "Istnieje już kwalifikacja o podanym oznaczeniu."
|
||||
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania kwalifikacji, prosimy spróbować później."
|
||||
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania kwalifikacji, prosimy spróbować później."
|
||||
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania kwalifikacji, prosimy spróbować później."
|
||||
)
|
|
@ -0,0 +1,152 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql"
|
||||
|
||||
errorutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/error"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/qualification"
|
||||
)
|
||||
|
||||
type pgRepository struct {
|
||||
*pg.DB
|
||||
}
|
||||
|
||||
type PGRepositoryConfig struct {
|
||||
DB *pg.DB
|
||||
}
|
||||
|
||||
func NewPGRepository(cfg *PGRepositoryConfig) (qualification.Repository, error) {
|
||||
if cfg == nil || cfg.DB == nil {
|
||||
return nil, fmt.Errorf("qualification/pg_repository: *pg.DB is required")
|
||||
}
|
||||
return &pgRepository{
|
||||
cfg.DB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Store(ctx context.Context, input *models.QualificationInput) (*models.Qualification, error) {
|
||||
item := input.ToQualification()
|
||||
err := repo.RunInTransaction(ctx, func(tx *pg.Tx) error {
|
||||
if _, err := tx.
|
||||
Model(item).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Insert(); err != nil {
|
||||
if strings.Contains(err.Error(), "name") {
|
||||
return errorutils.Wrap(err, messageNameIsAlreadyTaken)
|
||||
} else if strings.Contains(err.Error(), "code") {
|
||||
return errorutils.Wrap(err, messageCodeIsAlreadyTaken)
|
||||
}
|
||||
return errorutils.Wrap(err, messageFailedToSaveModel)
|
||||
}
|
||||
|
||||
for _, professionID := range input.AssociateProfession {
|
||||
tx.
|
||||
Model(&models.QualificationToProfession{
|
||||
QualificationID: item.ID,
|
||||
ProfessionID: professionID,
|
||||
}).
|
||||
Insert()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return item, err
|
||||
}
|
||||
|
||||
func (repo *pgRepository) UpdateMany(
|
||||
ctx context.Context,
|
||||
f *models.QualificationFilter,
|
||||
input *models.QualificationInput,
|
||||
) ([]*models.Qualification, error) {
|
||||
items := []*models.Qualification{}
|
||||
err := repo.RunInTransaction(ctx, func(tx *pg.Tx) error {
|
||||
if _, err := tx.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Apply(input.ApplyUpdate).
|
||||
Apply(f.Where).
|
||||
Update(); err != nil && err != pg.ErrNoRows {
|
||||
if strings.Contains(err.Error(), "name") {
|
||||
return errorutils.Wrap(err, messageNameIsAlreadyTaken)
|
||||
} else if strings.Contains(err.Error(), "code") {
|
||||
return errorutils.Wrap(err, messageCodeIsAlreadyTaken)
|
||||
}
|
||||
return errorutils.Wrap(err, messageFailedToSaveModel)
|
||||
}
|
||||
|
||||
qualificationIDs := make([]int, len(items))
|
||||
for index, item := range items {
|
||||
qualificationIDs[index] = item.ID
|
||||
}
|
||||
|
||||
if len(qualificationIDs) > 0 {
|
||||
if len(input.DissociateProfession) > 0 {
|
||||
tx.
|
||||
Model(&models.QualificationToProfession{}).
|
||||
Where(sqlutils.BuildConditionArray("profession_id"), input.DissociateProfession).
|
||||
Where(sqlutils.BuildConditionArray("qualification_id"), qualificationIDs).
|
||||
Delete()
|
||||
}
|
||||
|
||||
if len(input.AssociateProfession) > 0 {
|
||||
toInsert := []*models.QualificationToProfession{}
|
||||
for _, professionID := range input.AssociateProfession {
|
||||
for _, qualificationID := range qualificationIDs {
|
||||
toInsert = append(toInsert, &models.QualificationToProfession{
|
||||
ProfessionID: professionID,
|
||||
QualificationID: qualificationID,
|
||||
})
|
||||
}
|
||||
}
|
||||
tx.Model(&toInsert).Insert()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return items, err
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Delete(ctx context.Context, f *models.QualificationFilter) ([]*models.Qualification, error) {
|
||||
items := []*models.Qualification{}
|
||||
if _, err := repo.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Apply(f.Where).
|
||||
Delete(); err != nil && err != pg.ErrNoRows {
|
||||
return nil, errorutils.Wrap(err, messageFailedToDeleteModel)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Fetch(ctx context.Context, cfg *qualification.FetchConfig) ([]*models.Qualification, int, error) {
|
||||
var err error
|
||||
items := []*models.Qualification{}
|
||||
total := 0
|
||||
query := repo.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Limit(cfg.Limit).
|
||||
Offset(cfg.Offset).
|
||||
Apply(cfg.Filter.Where)
|
||||
|
||||
if cfg.Count {
|
||||
total, err = query.SelectAndCount()
|
||||
} else {
|
||||
err = query.Select()
|
||||
}
|
||||
if err != nil && err != pg.ErrNoRows {
|
||||
return nil, 0, errorutils.Wrap(err, messageFailedToFetchModel)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package qualification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
)
|
||||
|
||||
type Usecase interface {
|
||||
Store(ctx context.Context, input *models.QualificationInput) (*models.Qualification, error)
|
||||
UpdateOne(ctx context.Context, id int, input *models.QualificationInput) (*models.Qualification, error)
|
||||
Delete(ctx context.Context, f *models.QualificationFilter) ([]*models.Qualification, error)
|
||||
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.Qualification, int, error)
|
||||
GetByID(ctx context.Context, id int) (*models.Qualification, error)
|
||||
GetBySlug(ctx context.Context, slug string) (*models.Qualification, error)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package usecase
|
||||
|
||||
const (
|
||||
messageInvalidID = "Niepoprawne ID."
|
||||
messageItemNotFound = "Nie znaleziono zawodu."
|
||||
messageEmptyPayload = "Nie wprowadzono jakichkolwiek danych."
|
||||
messageNameIsRequired = "Nazwa zawodu jest wymagana."
|
||||
messageCodeIsRequired = "Oznaczenie kwalifikacji jest wymagane."
|
||||
messageNameIsTooLong = "Nazwa zawodu może się składać z maksymalnie %d znaków."
|
||||
)
|
|
@ -0,0 +1,139 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/qualification"
|
||||
sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql"
|
||||
)
|
||||
|
||||
type usecase struct {
|
||||
qualificationRepository qualification.Repository
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
QualificationRepository qualification.Repository
|
||||
}
|
||||
|
||||
func New(cfg *Config) (qualification.Usecase, error) {
|
||||
if cfg == nil || cfg.QualificationRepository == nil {
|
||||
return nil, fmt.Errorf("qualification/usecase: QualificationRepository is required")
|
||||
}
|
||||
return &usecase{
|
||||
cfg.QualificationRepository,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) Store(ctx context.Context, input *models.QualificationInput) (*models.Qualification, error) {
|
||||
if err := ucase.validateInput(input, validateOptions{false}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ucase.qualificationRepository.Store(ctx, input)
|
||||
}
|
||||
|
||||
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.QualificationInput) (*models.Qualification, error) {
|
||||
if id <= 0 {
|
||||
return nil, fmt.Errorf(messageInvalidID)
|
||||
}
|
||||
if err := ucase.validateInput(input, validateOptions{true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := ucase.qualificationRepository.UpdateMany(ctx,
|
||||
&models.QualificationFilter{
|
||||
ID: []int{id},
|
||||
},
|
||||
input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageItemNotFound)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) Delete(ctx context.Context, f *models.QualificationFilter) ([]*models.Qualification, error) {
|
||||
return ucase.qualificationRepository.Delete(ctx, f)
|
||||
}
|
||||
|
||||
func (ucase *usecase) Fetch(ctx context.Context, cfg *qualification.FetchConfig) ([]*models.Qualification, int, error) {
|
||||
if cfg == nil {
|
||||
cfg = &qualification.FetchConfig{
|
||||
Limit: qualification.DefaultLimit,
|
||||
Count: true,
|
||||
}
|
||||
}
|
||||
cfg.Sort = sqlutils.SanitizeSortExpressions(cfg.Sort)
|
||||
return ucase.qualificationRepository.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.Qualification, error) {
|
||||
items, _, err := ucase.Fetch(ctx, &qualification.FetchConfig{
|
||||
Limit: 1,
|
||||
Count: false,
|
||||
Filter: &models.QualificationFilter{
|
||||
ID: []int{id},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageItemNotFound)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) GetBySlug(ctx context.Context, slug string) (*models.Qualification, error) {
|
||||
items, _, err := ucase.Fetch(ctx, &qualification.FetchConfig{
|
||||
Limit: 1,
|
||||
Count: false,
|
||||
Filter: &models.QualificationFilter{
|
||||
Slug: []string{slug},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageItemNotFound)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
type validateOptions struct {
|
||||
allowNilValues bool
|
||||
}
|
||||
|
||||
func (ucase *usecase) validateInput(input *models.QualificationInput, opts validateOptions) error {
|
||||
if input.IsEmpty() {
|
||||
return fmt.Errorf(messageEmptyPayload)
|
||||
}
|
||||
|
||||
if input.Name != nil {
|
||||
trimmedName := strings.TrimSpace(*input.Name)
|
||||
input.Name = &trimmedName
|
||||
if trimmedName == "" {
|
||||
return fmt.Errorf(messageNameIsRequired)
|
||||
} else if len(trimmedName) > qualification.MaxNameLength {
|
||||
return fmt.Errorf(messageNameIsTooLong, qualification.MaxNameLength)
|
||||
}
|
||||
} else if !opts.allowNilValues {
|
||||
return fmt.Errorf(messageNameIsRequired)
|
||||
}
|
||||
|
||||
if input.Code != nil {
|
||||
trimmedCode := strings.TrimSpace(*input.Code)
|
||||
input.Code = &trimmedCode
|
||||
if trimmedCode == "" {
|
||||
return fmt.Errorf(messageCodeIsRequired)
|
||||
}
|
||||
} else if !opts.allowNilValues {
|
||||
return fmt.Errorf(messageCodeIsRequired)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue