From ff9e6289eedc3a743bf856973c7a05cadb31ce56 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Sun, 28 Feb 2021 17:54:40 +0100 Subject: [PATCH] [WIP]: initial version of question repository --- go.mod | 1 + go.sum | 2 + internal/models/question.go | 103 ++++++++++---- internal/question/constants.go | 5 + internal/question/repository.go | 28 ++++ internal/question/repository/helpers.go | 123 +++++++++++++++++ internal/question/repository/message.go | 9 ++ internal/question/repository/pg_repository.go | 129 ++++++++++++++++++ internal/question/usecase.go | 15 ++ internal/question/usecase/message.go | 9 ++ internal/question/usecase/usecase.go | 129 ++++++++++++++++++ pkg/filestorage/config.go | 5 +- pkg/filestorage/filestorage.go | 4 +- pkg/filestorage/interface.go | 2 +- pkg/utils/generate_filename.go | 14 ++ pkg/utils/sql/sql.go | 6 +- 16 files changed, 547 insertions(+), 37 deletions(-) create mode 100644 internal/question/constants.go create mode 100644 internal/question/repository.go create mode 100644 internal/question/repository/helpers.go create mode 100644 internal/question/repository/message.go create mode 100644 internal/question/repository/pg_repository.go create mode 100644 internal/question/usecase.go create mode 100644 internal/question/usecase/message.go create mode 100644 internal/question/usecase/usecase.go create mode 100644 pkg/utils/generate_filename.go diff --git a/go.mod b/go.mod index 57ec1cf..a0abd51 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.6.3 github.com/go-pg/pg/v10 v10.7.7 + github.com/google/uuid v1.2.0 github.com/gosimple/slug v1.9.0 github.com/joho/godotenv v1.3.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 22cfe5d..e342ba4 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/internal/models/question.go b/internal/models/question.go index 7d8235d..f737d2b 100644 --- a/internal/models/question.go +++ b/internal/models/question.go @@ -30,33 +30,54 @@ type Question struct { QualificationID int `pg:",unique:group_1,on_delete:CASCADE" json:"qualificationID" xml:"qualificationID" gqlgen:"qualificationID"` Qualification *Qualification `pg:"rel:has-one" json:"qualification" xml:"qualification" gqlgen:"qualification"` CreatedAt time.Time `json:"createdAt,omitempty" pg:"default:now()" xml:"createdAt" gqlgen:"createdAt"` + UpdatedAt time.Time `pg:"default:now()" json:"updatedAt" xml:"updatedAt" gqlgen:"updatedAt"` } func (q *Question) BeforeInsert(ctx context.Context) (context.Context, error) { q.CreatedAt = time.Now() + q.UpdatedAt = time.Now() return ctx, nil } type QuestionInput struct { - Content *string `json:"content" xml:"content" gqlgen:"content"` - From *string `json:"from" xml:"from" gqlgen:"from"` - Explanation *string `json:"explanation" xml:"explanation" gqlgen:"explanation"` - CorrectAnswer *Answer `json:"correctAnswer" xml:"correctAnswer" gqlgen:"correctAnswer"` - AnswerA *Answer `gqlgen:"answerA" json:"answerA" xml:"answerA"` - AnswerB *Answer `gqlgen:"answerB" json:"answerB" xml:"answerB"` - AnswerC *Answer `gqlgen:"answerC" json:"answerC" xml:"answerC"` - AnswerD *Answer `gqlgen:"answerD" json:"answerD" xml:"answerD"` - QualificationID *int `gqlgen:"qualificationID" json:"qualificationID" xml:"qualificationID"` - Image *graphql.Upload `json:"image" xml:"image" gqlgen:"image"` - AnswerAImage *graphql.Upload `json:"answerAImage" gqlgen:"answerAImage" xml:"answerAImage"` - AnswerAImagePath *string `json:"answerAImagePath" gqlgen:"answerAImagePath" xml:"answerAImagePath"` - AnswerBImage *graphql.Upload `json:"answerBImage" gqlgen:"answerBImage" xml:"answerBImage"` - AnswerBImagePath *string `json:"answerBImagePath" gqlgen:"answerBImagePath" xml:"answerBImagePath"` - AnswerCImage *graphql.Upload `json:"answerCImage" gqlgen:"answerCImage" xml:"answerCImage"` - AnswerCImagePath *string `json:"answerCImagePath" gqlgen:"answerCImagePath" xml:"answerCImagePath"` - AnswerDImage *graphql.Upload `json:"answerDImage" gqlgen:"answerDImage" xml:"answerDImage"` - AnswerDImagePath *string `json:"answerDImagePath" gqlgen:"answerDImagePath" xml:"answerDImagePath"` + Content *string `json:"content" xml:"content" gqlgen:"content"` + From *string `json:"from" xml:"from" gqlgen:"from"` + Explanation *string `json:"explanation" xml:"explanation" gqlgen:"explanation"` + CorrectAnswer *Answer `json:"correctAnswer" xml:"correctAnswer" gqlgen:"correctAnswer"` + AnswerA *Answer `gqlgen:"answerA" json:"answerA" xml:"answerA"` + AnswerB *Answer `gqlgen:"answerB" json:"answerB" xml:"answerB"` + AnswerC *Answer `gqlgen:"answerC" json:"answerC" xml:"answerC"` + AnswerD *Answer `gqlgen:"answerD" json:"answerD" xml:"answerD"` + QualificationID *int `gqlgen:"qualificationID" json:"qualificationID" xml:"qualificationID"` + Image *graphql.Upload `json:"image" xml:"image" gqlgen:"image"` + DeleteImage *bool `json:"deleteImage" xml:"deleteImage" gqlgen:"deleteImage"` + AnswerAImage *graphql.Upload `json:"answerAImage" gqlgen:"answerAImage" xml:"answerAImage"` + DeleteAnswerAImage *bool `json:"deleteAnswerAImage" xml:"deleteAnswerAImage" gqlgen:"deleteAnswerAImage"` + AnswerBImage *graphql.Upload `json:"answerBImage" gqlgen:"answerBImage" xml:"answerBImage"` + DeleteAnswerBImage *bool `json:"deleteAnswerBImage" xml:"deleteAnswerBImage" gqlgen:"deleteAnswerBImage"` + AnswerCImage *graphql.Upload `json:"answerCImage" gqlgen:"answerCImage" xml:"answerCImage"` + DeleteAnswerCImage *bool `json:"deleteAnswerCImage" xml:"deleteAnswerCImage" gqlgen:"deleteAnswerCImage"` + AnswerDImage *graphql.Upload `json:"answerDImage" gqlgen:"answerDImage" xml:"answerDImage"` + DeleteAnswerDImage *bool `json:"deleteAnswerDImage" xml:"deleteAnswerDImage" gqlgen:"deleteAnswerDImage"` +} + +func (input *QuestionInput) IsEmpty() bool { + return input == nil && + input.Content == nil && + input.From == nil && + input.Explanation == nil && + input.CorrectAnswer == nil && + input.AnswerA == nil && + input.AnswerAImage == nil && + input.AnswerB == nil && + input.AnswerBImage == nil && + input.AnswerC == nil && + input.AnswerCImage == nil && + input.AnswerD == nil && + input.AnswerDImage == nil && + input.Image == nil && + input.QualificationID == nil } func (input *QuestionInput) ToQuestion() *Question { @@ -76,33 +97,55 @@ func (input *QuestionInput) ToQuestion() *Question { if input.AnswerA != nil { q.AnswerA = *input.AnswerA } - if input.AnswerAImagePath != nil { - q.AnswerAImage = *input.AnswerAImagePath - } if input.AnswerB != nil { q.AnswerB = *input.AnswerB } - if input.AnswerBImagePath != nil { - q.AnswerBImage = *input.AnswerBImagePath - } if input.AnswerC != nil { q.AnswerC = *input.AnswerC } - if input.AnswerCImagePath != nil { - q.AnswerCImage = *input.AnswerCImagePath - } if input.AnswerD != nil { q.AnswerD = *input.AnswerD } - if input.AnswerDImagePath != nil { - q.AnswerDImage = *input.AnswerDImagePath - } if input.QualificationID != nil { q.QualificationID = *input.QualificationID } return q } +func (input *QuestionInput) ApplyUpdate(q *orm.Query) (*orm.Query, error) { + if !input.IsEmpty() { + if input.Content != nil { + q.Set("content = ?", *input.Content) + } + if input.From != nil { + q.Set("from = ?", *input.From) + } + if input.Explanation != nil { + q.Set("explanation = ?", *input.Explanation) + } + if input.CorrectAnswer != nil { + q.Set("correct_answer = ?", *input.CorrectAnswer) + } + if input.AnswerA != nil { + q.Set("answer_a = ?", *input.AnswerA) + } + if input.AnswerB != nil { + q.Set("answer_b = ?", *input.AnswerB) + } + if input.AnswerC != nil { + q.Set("answer_c = ?", *input.AnswerC) + } + if input.AnswerD != nil { + q.Set("answer_d = ?", *input.AnswerD) + } + if input.QualificationID != nil { + q.Set("qualification_id = ?", *input.QualificationID) + } + } + + return q, nil +} + type QuestionFilter struct { ID []int `gqlgen:"id" json:"id" xml:"id"` IDNEQ []int `gqlgen:"idNEQ" json:"idNEQ" xml:"idNEQ"` diff --git a/internal/question/constants.go b/internal/question/constants.go new file mode 100644 index 0000000..71e3013 --- /dev/null +++ b/internal/question/constants.go @@ -0,0 +1,5 @@ +package question + +const ( + DefaultLimit = 100 +) diff --git a/internal/question/repository.go b/internal/question/repository.go new file mode 100644 index 0000000..f415461 --- /dev/null +++ b/internal/question/repository.go @@ -0,0 +1,28 @@ +package question + +import ( + "context" + + "github.com/zdam-egzamin-zawodowy/backend/internal/models" +) + +type FetchConfig struct { + Filter *models.QuestionFilter + Offset int + Limit int + Sort []string + Count bool +} + +type GenerateTestConfig struct { + Qualifications []int + Limit int +} + +type Repository interface { + Store(ctx context.Context, input *models.QuestionInput) (*models.Question, error) + FindByIDAndUpdate(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) + GenerateTest(ctx context.Context, cfg *GenerateTestConfig) ([]*models.Question, error) +} diff --git a/internal/question/repository/helpers.go b/internal/question/repository/helpers.go new file mode 100644 index 0000000..62fa4d4 --- /dev/null +++ b/internal/question/repository/helpers.go @@ -0,0 +1,123 @@ +package repository + +import ( + "path/filepath" + + "github.com/99designs/gqlgen/graphql" + "github.com/zdam-egzamin-zawodowy/backend/internal/models" + "github.com/zdam-egzamin-zawodowy/backend/pkg/filestorage" + "github.com/zdam-egzamin-zawodowy/backend/pkg/utils" +) + +func saveImages(storage filestorage.FileStorage, destination *models.Question, input *models.QuestionInput) { + images := [...]*graphql.Upload{ + input.Image, + input.AnswerAImage, + input.AnswerBImage, + input.AnswerCImage, + input.AnswerDImage, + } + filenames := [...]string{ + destination.Image, + destination.AnswerAImage, + destination.AnswerBImage, + destination.AnswerCImage, + destination.AnswerDImage, + } + + for index, file := range images { + if file != nil { + generated := false + if filenames[index] == "" { + generated = true + filenames[index] = utils.GenerateFilename(filepath.Ext(file.Filename)) + } + err := storage.Put(file.File, filenames[index]) + if err != nil && generated { + filenames[index] = "" + } + } + } + + destination.Image = filenames[0] + destination.AnswerAImage = filenames[1] + destination.AnswerBImage = filenames[2] + destination.AnswerCImage = filenames[3] + destination.AnswerDImage = filenames[4] +} + +func deleteImages(storage filestorage.FileStorage, filenames []string) { + for _, filename := range filenames { + storage.Remove(filename) + } +} + +func deleteImagesBasedOnInput(storage filestorage.FileStorage, question *models.Question, input *models.QuestionInput) { + filenames := []string{} + + if input.DeleteImage != nil && + *input.DeleteImage && + input.Image == nil && + question.Image != "" { + filenames = append(filenames, question.Image) + question.Image = "" + } + + if input.DeleteAnswerAImage != nil && + *input.DeleteAnswerAImage && + input.AnswerAImage == nil && + question.AnswerAImage != "" { + filenames = append(filenames, question.AnswerAImage) + question.AnswerAImage = "" + } + + if input.DeleteAnswerBImage != nil && + *input.DeleteAnswerBImage && + input.AnswerBImage == nil && + question.AnswerBImage != "" { + filenames = append(filenames, question.AnswerBImage) + question.AnswerBImage = "" + } + + if input.DeleteAnswerCImage != nil && + *input.DeleteAnswerCImage && + input.AnswerCImage == nil && + question.AnswerCImage != "" { + filenames = append(filenames, question.AnswerCImage) + question.AnswerCImage = "" + } + + if input.DeleteAnswerDImage != nil && + *input.DeleteAnswerDImage && + input.AnswerDImage == nil && + question.AnswerDImage != "" { + filenames = append(filenames, question.AnswerDImage) + question.AnswerDImage = "" + } + + deleteImages(storage, filenames) +} + +func getAllFilenamesAndDeleteImages(storage filestorage.FileStorage, questions []*models.Question) { + filenames := []string{} + + for _, question := range questions { + if question.Image != "" { + filenames = append(filenames, question.Image) + } + if question.AnswerAImage != "" { + filenames = append(filenames, question.AnswerAImage) + } + if question.AnswerBImage != "" { + filenames = append(filenames, question.AnswerBImage) + } + if question.AnswerCImage != "" { + filenames = append(filenames, question.AnswerCImage) + } + if question.AnswerDImage != "" { + filenames = append(filenames, question.AnswerDImage) + } + } + + deleteImages(storage, filenames) +} diff --git a/internal/question/repository/message.go b/internal/question/repository/message.go new file mode 100644 index 0000000..63a303b --- /dev/null +++ b/internal/question/repository/message.go @@ -0,0 +1,9 @@ +package repository + +const ( + messageSimilarRecordExists = "Istnieje już podobne pytanie." + messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania pytania, prosimy spróbować później." + messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania pytania, prosimy spróbować później." + messageFailedToFetchModel = "Wystąpił błąd podczas pobierania pytań, prosimy spróbować później." + messageItemNotFound = "Nie znaleziono pytania." +) diff --git a/internal/question/repository/pg_repository.go b/internal/question/repository/pg_repository.go new file mode 100644 index 0000000..194b651 --- /dev/null +++ b/internal/question/repository/pg_repository.go @@ -0,0 +1,129 @@ +package repository + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/zdam-egzamin-zawodowy/backend/pkg/filestorage" + errorutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/error" + sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql" + + "github.com/go-pg/pg/v10" + "github.com/zdam-egzamin-zawodowy/backend/internal/models" + "github.com/zdam-egzamin-zawodowy/backend/internal/question" +) + +type pgRepository struct { + *pg.DB + fileStorage filestorage.FileStorage +} + +type PGRepositoryConfig struct { + DB *pg.DB + FileStorage filestorage.FileStorage +} + +func NewPGRepository(cfg *PGRepositoryConfig) (question.Repository, error) { + if cfg == nil || cfg.DB == nil || cfg.FileStorage == nil { + return nil, fmt.Errorf("question/pg_repository: *pg.DB and filestorage.FileStorage are required") + } + return &pgRepository{ + cfg.DB, + cfg.FileStorage, + }, nil +} + +func (repo *pgRepository) Store(ctx context.Context, input *models.QuestionInput) (*models.Question, error) { + item := input.ToQuestion() + if _, err := repo. + Model(item). + Context(ctx). + Returning("*"). + Insert(); err != nil { + if strings.Contains(err.Error(), "questions_from_content_correct_answer_qualification_id_key") { + return nil, errorutils.Wrap(err, messageSimilarRecordExists) + } + return nil, errorutils.Wrap(err, messageFailedToSaveModel) + } + + saveImages(repo.fileStorage, item, input) + + return item, nil +} + +func (repo *pgRepository) FindByIDAndUpdate(ctx context.Context, id int, input *models.QuestionInput) (*models.Question, error) { + item := &models.Question{} + baseQuery := repo. + Model(item). + Context(ctx). + Returning("*"). + Where(sqlutils.BuildConditionEquals(sqlutils.AddAliasToColumnName("id", "question")), id). + Set("updated_at = ?", time.Now()) + + if _, err := baseQuery. + Clone(). + Apply(input.ApplyUpdate). + Update(); err != nil && err != pg.ErrNoRows { + if strings.Contains(err.Error(), "questions_from_content_correct_answer_qualification_id_key") { + return nil, errorutils.Wrap(err, messageSimilarRecordExists) + } + return nil, errorutils.Wrap(err, messageFailedToSaveModel) + } + + saveImages(repo.fileStorage, item, input) + deleteImagesBasedOnInput(repo.fileStorage, item, input) + + if _, err := baseQuery. + Clone(). + Set("image = ?", item.Image). + Set("answer_a_image = ?", item.AnswerAImage). + Set("answer_b_image = ?", item.AnswerBImage). + Set("answer_c_image = ?", item.AnswerCImage). + Set("answer_d_image = ?", item.AnswerDImage). + Update(); err != nil && err != pg.ErrNoRows { + + return nil, errorutils.Wrap(err, messageFailedToSaveModel) + } + + return item, nil +} + +func (repo *pgRepository) Delete(ctx context.Context, f *models.QuestionFilter) ([]*models.Question, error) { + items := []*models.Question{} + if _, err := repo. + Model(&items). + Context(ctx). + Returning("*"). + Apply(f.Where). + Delete(); err != nil && err != pg.ErrNoRows { + return nil, errorutils.Wrap(err, messageFailedToDeleteModel) + } + + go getAllFilenamesAndDeleteImages(repo.fileStorage, items) + + return items, nil +} + +func (repo *pgRepository) Fetch(ctx context.Context, cfg *question.FetchConfig) ([]*models.Question, int, error) { + var err error + items := []*models.Question{} + 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 +} diff --git a/internal/question/usecase.go b/internal/question/usecase.go new file mode 100644 index 0000000..d6de18f --- /dev/null +++ b/internal/question/usecase.go @@ -0,0 +1,15 @@ +package question + +import ( + "context" + + "github.com/zdam-egzamin-zawodowy/backend/internal/models" +) + +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) + 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) +} diff --git a/internal/question/usecase/message.go b/internal/question/usecase/message.go new file mode 100644 index 0000000..5932601 --- /dev/null +++ b/internal/question/usecase/message.go @@ -0,0 +1,9 @@ +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." +) diff --git a/internal/question/usecase/usecase.go b/internal/question/usecase/usecase.go new file mode 100644 index 0000000..c0437f0 --- /dev/null +++ b/internal/question/usecase/usecase.go @@ -0,0 +1,129 @@ +package usecase + +import ( + "context" + "fmt" + "strings" + + "github.com/zdam-egzamin-zawodowy/backend/internal/models" + "github.com/zdam-egzamin-zawodowy/backend/internal/profession" + sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql" +) + +type usecase struct { + professionRepository profession.Repository +} + +type Config struct { + ProfessionRepository profession.Repository +} + +func New(cfg *Config) (profession.Usecase, error) { + if cfg == nil || cfg.ProfessionRepository == nil { + return nil, fmt.Errorf("profession/usecase: ProfessionRepository is required") + } + return &usecase{ + cfg.ProfessionRepository, + }, nil +} + +func (ucase *usecase) Store(ctx context.Context, input *models.ProfessionInput) (*models.Profession, error) { + if err := ucase.validateInput(input, validateOptions{false}); err != nil { + return nil, err + } + return ucase.professionRepository.Store(ctx, input) +} + +func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.ProfessionInput) (*models.Profession, 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}, + }, + 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.ProfessionFilter) ([]*models.Profession, error) { + return ucase.professionRepository.Delete(ctx, f) +} + +func (ucase *usecase) Fetch(ctx context.Context, cfg *profession.FetchConfig) ([]*models.Profession, int, error) { + if cfg == nil { + cfg = &profession.FetchConfig{ + Limit: profession.DefaultLimit, + Count: true, + } + } + cfg.Sort = sqlutils.SanitizeSortExpressions(cfg.Sort) + return ucase.professionRepository.Fetch(ctx, cfg) +} + +func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.Profession, error) { + items, _, err := ucase.Fetch(ctx, &profession.FetchConfig{ + Limit: 1, + Count: false, + Filter: &models.ProfessionFilter{ + 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.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 + } + if len(items) == 0 { + return nil, fmt.Errorf(messageItemNotFound) + } + return items[0], nil +} + +type validateOptions struct { + nameCanBeNil bool +} + +func (ucase *usecase) validateInput(input *models.ProfessionInput, 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) + } + } else if !opts.nameCanBeNil { + return fmt.Errorf(messageNameIsRequired) + } + + return nil +} diff --git a/pkg/filestorage/config.go b/pkg/filestorage/config.go index 55af6b7..c3d960a 100644 --- a/pkg/filestorage/config.go +++ b/pkg/filestorage/config.go @@ -1,11 +1,14 @@ package filestorage +import "os" + type Config struct { BasePath string } func getDefaultConfig() *Config { + path, _ := os.Getwd() return &Config{ - BasePath: "./", + BasePath: path, } } diff --git a/pkg/filestorage/filestorage.go b/pkg/filestorage/filestorage.go index 8d272ca..be23cf9 100644 --- a/pkg/filestorage/filestorage.go +++ b/pkg/filestorage/filestorage.go @@ -36,11 +36,11 @@ func (storage *fileStorage) Put(file io.Reader, filename string) error { return nil } -func (storage *fileStorage) Delete(filename string) error { +func (storage *fileStorage) Remove(filename string) error { fullPath := path.Join(storage.basePath, filename) err := os.Remove(fullPath) if err != nil { - return errors.Wrap(err, "FileStorage.Delete") + return errors.Wrap(err, "FileStorage.Remove") } return nil } diff --git a/pkg/filestorage/interface.go b/pkg/filestorage/interface.go index e077efd..1f2a563 100644 --- a/pkg/filestorage/interface.go +++ b/pkg/filestorage/interface.go @@ -4,5 +4,5 @@ import "io" type FileStorage interface { Put(file io.Reader, filename string) error - Delete(filename string) error + Remove(filename string) error } diff --git a/pkg/utils/generate_filename.go b/pkg/utils/generate_filename.go new file mode 100644 index 0000000..1d202a7 --- /dev/null +++ b/pkg/utils/generate_filename.go @@ -0,0 +1,14 @@ +package utils + +import ( + "strings" + + "github.com/google/uuid" +) + +func GenerateFilename(ext string) string { + if !strings.HasPrefix(ext, ".") { + ext = "." + ext + } + return uuid.New().String() + ext +} diff --git a/pkg/utils/sql/sql.go b/pkg/utils/sql/sql.go index 31dbcd3..1875ace 100644 --- a/pkg/utils/sql/sql.go +++ b/pkg/utils/sql/sql.go @@ -5,9 +5,9 @@ import ( "strings" ) -func AddAliasToColumnName(column, prefix string) string { - if prefix != "" && !strings.HasPrefix(column, prefix+".") { - column = WrapStringInDoubleQuotes(prefix) + "." + WrapStringInDoubleQuotes(column) +func AddAliasToColumnName(column, alias string) string { + if alias != "" && !strings.HasPrefix(column, alias+".") { + column = WrapStringInDoubleQuotes(alias) + "." + WrapStringInDoubleQuotes(column) } else { column = WrapStringInDoubleQuotes(column) }