[WIP]: initial version of question repository
This commit is contained in:
parent
17c0d6c4e5
commit
ff9e6289ee
1
go.mod
1
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package question
|
||||
|
||||
const (
|
||||
DefaultLimit = 100
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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."
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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."
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
package filestorage
|
||||
|
||||
import "os"
|
||||
|
||||
type Config struct {
|
||||
BasePath string
|
||||
}
|
||||
|
||||
func getDefaultConfig() *Config {
|
||||
path, _ := os.Getwd()
|
||||
return &Config{
|
||||
BasePath: "./",
|
||||
BasePath: path,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ import "io"
|
|||
|
||||
type FileStorage interface {
|
||||
Put(file io.Reader, filename string) error
|
||||
Delete(filename string) error
|
||||
Remove(filename string) error
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Reference in New Issue