refactor, add question.Usecase implementation

This commit is contained in:
Dawid Wysokiński 2021-03-05 19:52:15 +01:00
parent 4746b4c153
commit 838a93b8b5
12 changed files with 201 additions and 105 deletions

View File

@ -8,7 +8,7 @@ import (
type Usecase interface {
Store(ctx context.Context, input *models.ProfessionInput) (*models.Profession, error)
UpdateOne(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, error)
UpdateOneByID(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, error)
Delete(ctx context.Context, f *models.ProfessionFilter) ([]*models.Profession, error)
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.Profession, int, error)
GetByID(ctx context.Context, id int) (*models.Profession, error)

View File

@ -3,7 +3,6 @@ package usecase
import (
"context"
"fmt"
"strings"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
"github.com/zdam-egzamin-zawodowy/backend/internal/profession"
@ -34,7 +33,7 @@ func (ucase *usecase) Store(ctx context.Context, input *models.ProfessionInput)
return ucase.professionRepository.Store(ctx, input)
}
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, error) {
func (ucase *usecase) UpdateOneByID(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, error) {
if id <= 0 {
return nil, fmt.Errorf(messageInvalidID)
}
@ -114,11 +113,9 @@ func (ucase *usecase) validateInput(input *models.ProfessionInput, opts validate
}
if input.Name != nil {
trimmedName := strings.TrimSpace(*input.Name)
input.Name = &trimmedName
if trimmedName == "" {
if *input.Name == "" {
return fmt.Errorf(messageNameIsRequired)
} else if len(trimmedName) > profession.MaxNameLength {
} else if len(*input.Name) > profession.MaxNameLength {
return fmt.Errorf(messageNameIsTooLong, profession.MaxNameLength)
}
} else if !opts.nameCanBeNil {

View File

@ -8,7 +8,7 @@ import (
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)
UpdateOneByID(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)

View File

@ -3,7 +3,6 @@ package usecase
import (
"context"
"fmt"
"strings"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
"github.com/zdam-egzamin-zawodowy/backend/internal/qualification"
@ -34,7 +33,7 @@ func (ucase *usecase) Store(ctx context.Context, input *models.QualificationInpu
return ucase.qualificationRepository.Store(ctx, input)
}
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.QualificationInput) (*models.Qualification, error) {
func (ucase *usecase) UpdateOneByID(ctx context.Context, id int, input *models.QualificationInput) (*models.Qualification, error) {
if id <= 0 {
return nil, fmt.Errorf(messageInvalidID)
}
@ -114,11 +113,9 @@ func (ucase *usecase) validateInput(input *models.QualificationInput, opts valid
}
if input.Name != nil {
trimmedName := strings.TrimSpace(*input.Name)
input.Name = &trimmedName
if trimmedName == "" {
if *input.Name == "" {
return fmt.Errorf(messageNameIsRequired)
} else if len(trimmedName) > qualification.MaxNameLength {
} else if len(*input.Name) > qualification.MaxNameLength {
return fmt.Errorf(messageNameIsTooLong, qualification.MaxNameLength)
}
} else if !opts.allowNilValues {
@ -126,9 +123,7 @@ func (ucase *usecase) validateInput(input *models.QualificationInput, opts valid
}
if input.Code != nil {
trimmedCode := strings.TrimSpace(*input.Code)
input.Code = &trimmedCode
if trimmedCode == "" {
if *input.Code == "" {
return fmt.Errorf(messageCodeIsRequired)
}
} else if !opts.allowNilValues {

View File

@ -2,4 +2,5 @@ package question
const (
DefaultLimit = 100
TestMaxLimit = 40
)

View File

@ -102,7 +102,7 @@ func (repo *pgRepository) Delete(ctx context.Context, f *models.QuestionFilter)
return nil, errorutils.Wrap(err, messageFailedToDeleteModel)
}
go repo.getAllFilenamesAndDeleteImages(items)
go repo.getAllImagesAndDelete(items)
return items, nil
}

View File

@ -50,20 +50,20 @@ func (repo *repository) saveImages(destination *models.Question, input *models.Q
destination.AnswerDImage = filenames[4]
}
func (repo *repository) deleteImages(filenames []string) {
for _, filename := range filenames {
repo.fileStorage.Remove(filename)
func (repo *repository) deleteImages(images []string) {
for _, image := range images {
repo.fileStorage.Remove(image)
}
}
func (repo *repository) deleteImagesBasedOnInput(question *models.Question, input *models.QuestionInput) {
filenames := []string{}
images := []string{}
if input.DeleteImage != nil &&
*input.DeleteImage &&
input.Image == nil &&
question.Image != "" {
filenames = append(filenames, question.Image)
images = append(images, question.Image)
question.Image = ""
}
@ -71,7 +71,7 @@ func (repo *repository) deleteImagesBasedOnInput(question *models.Question, inpu
*input.DeleteAnswerAImage &&
input.AnswerAImage == nil &&
question.AnswerAImage != "" {
filenames = append(filenames, question.AnswerAImage)
images = append(images, question.AnswerAImage)
question.AnswerAImage = ""
}
@ -79,7 +79,7 @@ func (repo *repository) deleteImagesBasedOnInput(question *models.Question, inpu
*input.DeleteAnswerBImage &&
input.AnswerBImage == nil &&
question.AnswerBImage != "" {
filenames = append(filenames, question.AnswerBImage)
images = append(images, question.AnswerBImage)
question.AnswerBImage = ""
}
@ -87,7 +87,7 @@ func (repo *repository) deleteImagesBasedOnInput(question *models.Question, inpu
*input.DeleteAnswerCImage &&
input.AnswerCImage == nil &&
question.AnswerCImage != "" {
filenames = append(filenames, question.AnswerCImage)
images = append(images, question.AnswerCImage)
question.AnswerCImage = ""
}
@ -95,33 +95,33 @@ func (repo *repository) deleteImagesBasedOnInput(question *models.Question, inpu
*input.DeleteAnswerDImage &&
input.AnswerDImage == nil &&
question.AnswerDImage != "" {
filenames = append(filenames, question.AnswerDImage)
images = append(images, question.AnswerDImage)
question.AnswerDImage = ""
}
repo.deleteImages(filenames)
repo.deleteImages(images)
}
func (repo *repository) getAllFilenamesAndDeleteImages(questions []*models.Question) {
filenames := []string{}
func (repo *repository) getAllImagesAndDelete(questions []*models.Question) {
images := []string{}
for _, question := range questions {
if question.Image != "" {
filenames = append(filenames, question.Image)
images = append(images, question.Image)
}
if question.AnswerAImage != "" {
filenames = append(filenames, question.AnswerAImage)
images = append(images, question.AnswerAImage)
}
if question.AnswerBImage != "" {
filenames = append(filenames, question.AnswerBImage)
images = append(images, question.AnswerBImage)
}
if question.AnswerCImage != "" {
filenames = append(filenames, question.AnswerCImage)
images = append(images, question.AnswerCImage)
}
if question.AnswerDImage != "" {
filenames = append(filenames, question.AnswerDImage)
images = append(images, question.AnswerDImage)
}
}
repo.deleteImages(filenames)
repo.deleteImages(images)
}

View File

@ -8,8 +8,9 @@ import (
type Usecase interface {
Store(ctx context.Context, input *models.QuestionInput) (*models.Question, error)
UpdateOne(ctx context.Context, id int, input *models.QuestionInput) (*models.Question, error)
UpdateOneByID(ctx context.Context, id int, input *models.QuestionInput) (*models.Question, error)
Delete(ctx context.Context, f *models.QuestionFilter) ([]*models.Question, error)
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.Question, int, error)
GetByID(ctx context.Context, id int) (*models.Question, error)
GenerateTest(ctx context.Context, cfg *GenerateTestConfig) ([]*models.Question, error)
}

View File

@ -1,9 +1,14 @@
package usecase
const (
messageInvalidID = "Niepoprawne ID."
messageItemNotFound = "Nie znaleziono zawodu."
messageEmptyPayload = "Nie wprowadzono jakichkolwiek danych."
messageNameIsRequired = "Nazwa zawodu jest wymagana."
messageNameIsTooLong = "Nazwa zawodu może się składać z maksymalnie %d znaków."
messageInvalidID = "Niepoprawne ID."
messageItemNotFound = "Nie znaleziono pytania."
messageEmptyPayload = "Nie wprowadzono jakichkolwiek danych."
messageContentIsRequired = "Treść pytania jest wymagana."
messageCorrectAnswerIsInvalid = "Odpowiedź poprawna na pytanie jest nieprawidłowa."
messageQualificationIDIsRequired = "ID kwalifikacji jest wymagane."
messageAnswerIsRequired = "Odpowiedź %s jest wymagana."
messageAnswerIsInvalid = "Odpowiedź %s jest nieprawidłowa."
messageImageNotAcceptableMIMEType = "%s: Oczekiwany jest obrazek w formacie png or jpg."
messageCannotDeleteImageWithoutNewAnswer = "%s: Nie możesz usunąć obrazka i nie wprowadzić żadnej odpowiedzi."
)

View File

@ -3,78 +3,83 @@ package usecase
import (
"context"
"fmt"
"strings"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
"github.com/zdam-egzamin-zawodowy/backend/internal/profession"
"github.com/zdam-egzamin-zawodowy/backend/internal/question"
sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql"
)
var (
imageAcceptedMIMETypes = map[string]bool{
"image/jpeg": true,
"image/jpg": true,
"image/png": true,
}
)
type usecase struct {
professionRepository profession.Repository
questionRepository question.Repository
}
type Config struct {
ProfessionRepository profession.Repository
QuestionRepository question.Repository
}
func New(cfg *Config) (profession.Usecase, error) {
if cfg == nil || cfg.ProfessionRepository == nil {
return nil, fmt.Errorf("profession/usecase: ProfessionRepository is required")
func New(cfg *Config) (question.Usecase, error) {
if cfg == nil || cfg.QuestionRepository == nil {
return nil, fmt.Errorf("question/usecase: cfg.QuestionRepository is required")
}
return &usecase{
cfg.ProfessionRepository,
cfg.QuestionRepository,
}, nil
}
func (ucase *usecase) Store(ctx context.Context, input *models.ProfessionInput) (*models.Profession, error) {
func (ucase *usecase) Store(ctx context.Context, input *models.QuestionInput) (*models.Question, error) {
if err := ucase.validateInput(input, validateOptions{false}); err != nil {
return nil, err
}
return ucase.professionRepository.Store(ctx, input)
return ucase.questionRepository.Store(ctx, input)
}
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, error) {
func (ucase *usecase) UpdateOneByID(ctx context.Context, id int, input *models.QuestionInput) (*models.Question, error) {
if id <= 0 {
return nil, fmt.Errorf(messageInvalidID)
}
if err := ucase.validateInput(input, validateOptions{true}); err != nil {
return nil, err
}
items, err := ucase.professionRepository.UpdateMany(ctx,
&models.ProfessionFilter{
ID: []int{id},
},
item, err := ucase.questionRepository.UpdateOneByID(ctx,
id,
input)
if err != nil {
return nil, err
}
if len(items) == 0 {
if item == nil {
return nil, fmt.Errorf(messageItemNotFound)
}
return items[0], nil
return item, nil
}
func (ucase *usecase) Delete(ctx context.Context, f *models.ProfessionFilter) ([]*models.Profession, error) {
return ucase.professionRepository.Delete(ctx, f)
func (ucase *usecase) Delete(ctx context.Context, f *models.QuestionFilter) ([]*models.Question, error) {
return ucase.questionRepository.Delete(ctx, f)
}
func (ucase *usecase) Fetch(ctx context.Context, cfg *profession.FetchConfig) ([]*models.Profession, int, error) {
func (ucase *usecase) Fetch(ctx context.Context, cfg *question.FetchConfig) ([]*models.Question, int, error) {
if cfg == nil {
cfg = &profession.FetchConfig{
Limit: profession.DefaultLimit,
cfg = &question.FetchConfig{
Limit: question.DefaultLimit,
Count: true,
}
}
cfg.Sort = sqlutils.SanitizeSortExpressions(cfg.Sort)
return ucase.professionRepository.Fetch(ctx, cfg)
return ucase.questionRepository.Fetch(ctx, cfg)
}
func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.Profession, error) {
items, _, err := ucase.Fetch(ctx, &profession.FetchConfig{
func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.Question, error) {
items, _, err := ucase.Fetch(ctx, &question.FetchConfig{
Limit: 1,
Count: false,
Filter: &models.ProfessionFilter{
Filter: &models.QuestionFilter{
ID: []int{id},
},
})
@ -87,42 +92,138 @@ func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.Profession,
return items[0], nil
}
func (ucase *usecase) GetBySlug(ctx context.Context, slug string) (*models.Profession, error) {
items, _, err := ucase.Fetch(ctx, &profession.FetchConfig{
Limit: 1,
Count: false,
Filter: &models.ProfessionFilter{
Slug: []string{slug},
},
})
if err != nil {
return nil, err
func (ucase *usecase) GenerateTest(ctx context.Context, cfg *question.GenerateTestConfig) ([]*models.Question, error) {
if cfg == nil {
cfg = &question.GenerateTestConfig{
Limit: question.TestMaxLimit,
}
}
if len(items) == 0 {
return nil, fmt.Errorf(messageItemNotFound)
if cfg.Limit > question.TestMaxLimit {
cfg.Limit = question.TestMaxLimit
}
return items[0], nil
return ucase.questionRepository.GenerateTest(ctx, cfg)
}
type validateOptions struct {
nameCanBeNil bool
allowNilValues bool
}
func (ucase *usecase) validateInput(input *models.ProfessionInput, opts validateOptions) error {
func (ucase *usecase) validateInput(input *models.QuestionInput, 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) > profession.MaxNameLength {
return fmt.Errorf(messageNameIsTooLong, profession.MaxNameLength)
if input.Content != nil {
if *input.Content == "" {
return fmt.Errorf(messageContentIsRequired)
}
} else if !opts.allowNilValues {
return fmt.Errorf(messageContentIsRequired)
}
if input.CorrectAnswer != nil {
if !input.CorrectAnswer.IsValid() {
return fmt.Errorf(messageCorrectAnswerIsInvalid)
}
} else if !opts.allowNilValues {
return fmt.Errorf(messageCorrectAnswerIsInvalid)
}
if input.QualificationID != nil {
if *input.QualificationID <= 0 {
return fmt.Errorf(messageQualificationIDIsRequired)
}
} else if !opts.allowNilValues {
return fmt.Errorf(messageQualificationIDIsRequired)
}
if input.AnswerA != nil {
if !input.AnswerA.IsValid() {
return fmt.Errorf(messageAnswerIsInvalid, "A")
}
}
if input.AnswerB != nil {
if !input.AnswerB.IsValid() {
return fmt.Errorf(messageAnswerIsInvalid, "B")
}
}
if input.AnswerC != nil {
if !input.AnswerC.IsValid() {
return fmt.Errorf(messageAnswerIsInvalid, "C")
}
}
if input.AnswerD != nil {
if !input.AnswerD.IsValid() {
return fmt.Errorf(messageAnswerIsInvalid, "D")
}
}
if input.Image != nil {
if !imageAcceptedMIMETypes[input.Image.ContentType] {
return fmt.Errorf(messageImageNotAcceptableMIMEType, "Obrazek pytanie")
}
}
if input.AnswerAImage != nil {
if !imageAcceptedMIMETypes[input.AnswerAImage.ContentType] {
return fmt.Errorf(messageAnswerIsInvalid, "Obrazek odpowiedź A")
}
}
if input.AnswerBImage != nil {
if !imageAcceptedMIMETypes[input.AnswerBImage.ContentType] {
return fmt.Errorf(messageAnswerIsInvalid, "Obrazek odpowiedź B")
}
}
if input.AnswerCImage != nil {
if !imageAcceptedMIMETypes[input.AnswerCImage.ContentType] {
return fmt.Errorf(messageAnswerIsInvalid, "Obrazek odpowiedź C")
}
}
if input.AnswerDImage != nil {
if !imageAcceptedMIMETypes[input.AnswerDImage.ContentType] {
return fmt.Errorf(messageAnswerIsInvalid, "Obrazek odpowiedź D")
}
}
if input.DeleteAnswerAImage != nil && input.AnswerA == nil && input.AnswerAImage == nil {
return fmt.Errorf(messageCannotDeleteImageWithoutNewAnswer, "Obrazek odpowiedź A")
}
if input.DeleteAnswerBImage != nil && input.AnswerB == nil && input.AnswerBImage == nil {
return fmt.Errorf(messageCannotDeleteImageWithoutNewAnswer, "Obrazek odpowiedź B")
}
if input.DeleteAnswerCImage != nil && input.AnswerC == nil && input.AnswerCImage == nil {
return fmt.Errorf(messageCannotDeleteImageWithoutNewAnswer, "Obrazek odpowiedź C")
}
if input.DeleteAnswerDImage != nil && input.AnswerD == nil && input.AnswerDImage == nil {
return fmt.Errorf(messageCannotDeleteImageWithoutNewAnswer, "Obrazek odpowiedź D")
}
if !opts.allowNilValues {
if input.AnswerA == nil && input.AnswerAImage == nil {
return fmt.Errorf(messageAnswerIsRequired, "A")
}
if input.AnswerB == nil && input.AnswerBImage == nil {
return fmt.Errorf(messageAnswerIsRequired, "B")
}
if input.AnswerC == nil && input.AnswerCImage == nil {
return fmt.Errorf(messageAnswerIsRequired, "C")
}
if input.AnswerD == nil && input.AnswerDImage == nil {
return fmt.Errorf(messageAnswerIsRequired, "D")
}
} else if !opts.nameCanBeNil {
return fmt.Errorf(messageNameIsRequired)
}
return nil

View File

@ -8,7 +8,7 @@ import (
type Usecase interface {
Store(ctx context.Context, input *models.UserInput) (*models.User, error)
UpdateOne(ctx context.Context, id int, input *models.UserInput) (*models.User, error)
UpdateOneByID(ctx context.Context, id int, input *models.UserInput) (*models.User, error)
UpdateMany(ctx context.Context, f *models.UserFilter, input *models.UserInput) ([]*models.User, error)
Delete(ctx context.Context, f *models.UserFilter) ([]*models.User, error)
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.User, int, error)

View File

@ -3,7 +3,6 @@ package usecase
import (
"context"
"fmt"
"strings"
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
"github.com/zdam-egzamin-zawodowy/backend/internal/user"
@ -35,7 +34,7 @@ func (ucase *usecase) Store(ctx context.Context, input *models.UserInput) (*mode
return ucase.userRepository.Store(ctx, input)
}
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.UserInput) (*models.User, error) {
func (ucase *usecase) UpdateOneByID(ctx context.Context, id int, input *models.UserInput) (*models.User, error) {
if id <= 0 {
return nil, fmt.Errorf(messageInvalidID)
}
@ -131,11 +130,10 @@ func (ucase *usecase) validateInput(input *models.UserInput, opts validateOption
}
if input.DisplayName != nil {
trimmedDisplayName := strings.TrimSpace(*input.DisplayName)
input.DisplayName = &trimmedDisplayName
if len(trimmedDisplayName) < user.MinDisplayNameLength {
displayNameLength := len(*input.DisplayName)
if displayNameLength < user.MinDisplayNameLength {
return fmt.Errorf(messageDisplayNameIsRequired)
} else if len(trimmedDisplayName) > user.MaxDisplayNameLength {
} else if displayNameLength > user.MaxDisplayNameLength {
return fmt.Errorf(messageDisplayNameIsTooLong, user.MaxDisplayNameLength)
}
} else if !opts.acceptNilValues {
@ -143,9 +141,7 @@ func (ucase *usecase) validateInput(input *models.UserInput, opts validateOption
}
if input.Email != nil {
trimmedEmail := strings.TrimSpace(*input.Email)
input.Email = &trimmedEmail
if !utils.IsEmailValid(trimmedEmail) {
if !utils.IsEmailValid(*input.Email) {
return fmt.Errorf(messageEmailIsInvalid)
}
} else if !opts.acceptNilValues {
@ -153,9 +149,9 @@ func (ucase *usecase) validateInput(input *models.UserInput, opts validateOption
}
if input.Password != nil {
trimmedPassword := strings.ToLower(strings.TrimSpace(*input.Password))
input.Password = &trimmedPassword
if len(trimmedPassword) > user.MaxPasswordLength || len(trimmedPassword) < user.MinPasswordLength {
password := *input.Password
passwordLength := len(password)
if passwordLength > user.MaxPasswordLength || passwordLength < user.MinPasswordLength {
return fmt.Errorf(messagePasswordInvalidLength, user.MinPasswordLength, user.MaxPasswordLength)
}
} else if !opts.acceptNilValues {