diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a8fe5e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env.local \ No newline at end of file diff --git a/internal/db/db.go b/internal/db/db.go new file mode 100644 index 0000000..73d70f2 --- /dev/null +++ b/internal/db/db.go @@ -0,0 +1,82 @@ +package db + +import ( + "context" + "os" + + "github.com/sirupsen/logrus" + envutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/env" + + "github.com/go-pg/pg/v10" + "github.com/go-pg/pg/v10/orm" + "github.com/pkg/errors" + "github.com/zdam-egzamin-zawodowy/backend/internal/models" +) + +const ( + extensions = ` + CREATE EXTENSION IF NOT EXISTS tsm_system_rows; + ` +) + +func init() { + orm.RegisterTable((*models.QualificationToProfession)(nil)) +} + +type Config struct { + DebugHook bool +} + +func New(cfg *Config) (*pg.DB, error) { + db := pg.Connect(prepareOptions()) + if err := createSchema(db); err != nil { + return nil, err + } + + if cfg != nil { + if cfg.DebugHook { + db.AddQueryHook(DebugHook{ + Entry: logrus.WithField("package", "internal/db"), + }) + } + } + + return db, nil +} + +func prepareOptions() *pg.Options { + return &pg.Options{ + User: os.Getenv("DB_USER"), + Password: os.Getenv("DB_PASSWORD"), + Database: os.Getenv("DB_NAME"), + Addr: os.Getenv("DB_HOST") + ":" + os.Getenv("DB_PORT"), + PoolSize: envutils.GetenvInt("DB_POOL_SIZE"), + } +} + +func createSchema(db *pg.DB) error { + return db.RunInTransaction(context.Background(), func(tx *pg.Tx) error { + if _, err := tx.Exec(extensions); err != nil { + return errors.Wrap(err, "createSchema") + } + + models := []interface{}{ + (*models.User)(nil), + (*models.Profession)(nil), + (*models.Qualification)(nil), + (*models.QualificationToProfession)(nil), + (*models.Question)(nil), + } + + for _, model := range models { + err := tx.Model(model).CreateTable(&orm.CreateTableOptions{ + IfNotExists: true, + }) + if err != nil { + return errors.Wrap(err, "createSchema") + } + } + + return nil + }) +} diff --git a/internal/db/debug_hook.go b/internal/db/debug_hook.go new file mode 100644 index 0000000..a81f251 --- /dev/null +++ b/internal/db/debug_hook.go @@ -0,0 +1,33 @@ +package db + +import ( + "context" + + "github.com/go-pg/pg/v10" + "github.com/sirupsen/logrus" +) + +type DebugHook struct { + Entry *logrus.Entry +} + +var _ pg.QueryHook = (*DebugHook)(nil) + +func (logger DebugHook) BeforeQuery(ctx context.Context, evt *pg.QueryEvent) (context.Context, error) { + q, err := evt.FormattedQuery() + if err != nil { + return nil, err + } + + if evt.Err != nil { + logger.Entry.Errorf("%s executing a query:\n%s\n", evt.Err, q) + } else { + logger.Entry.Info(string(q)) + } + + return ctx, nil +} + +func (DebugHook) AfterQuery(context.Context, *pg.QueryEvent) error { + return nil +} diff --git a/internal/graphql/generate.sh b/internal/graphql/generate.sh index f315666..0a9f580 100644 --- a/internal/graphql/generate.sh +++ b/internal/graphql/generate.sh @@ -1,4 +1,5 @@ #!/bin/sh cd ./internal/graphql go run github.com/99designs/gqlgen -go mod tidy \ No newline at end of file +go mod tidy +cd ../.. \ No newline at end of file diff --git a/internal/models/question.go b/internal/models/question.go index c88f617..117192b 100644 --- a/internal/models/question.go +++ b/internal/models/question.go @@ -11,19 +11,21 @@ import ( ) type Question struct { + tableName struct{} `pg:"alias:question"` + ID int `json:"id" xml:"id" gqlgen:"id"` From string `pg:",unique:group_1" json:"from" xml:"from" gqlgen:"from"` Content string `pg:",unique:group_1,notnull" json:"content" xml:"content" gqlgen:"content"` Explanation string `json:"explanation" xml:"explanation" gqlgen:"explanation"` CorrectAnswer Answer `pg:",unique:group_1,notnull" json:"correctAnswer" xml:"correctAnswer" gqlgen:"correctAnswer"` Image string `json:"image" xml:"image" gqlgen:"image"` - AnswerA Answer `json:"answerA" xml:"answerA" gqlgen:"answerA"` + AnswerA Answer `pg:"answer_a" json:"answerA" xml:"answerA" gqlgen:"answerA"` AnswerAImage string `pg:"answer_a_image" json:"answerAImage" xml:"answerAImage" gqlgen:"answerAImage"` - AnswerB Answer `json:"answerB" xml:"answerB" gqlgen:"answerB"` + AnswerB Answer `pg:"answer_b" json:"answerB" xml:"answerB" gqlgen:"answerB"` AnswerBImage string `pg:"answer_b_image" json:"answerBImage" xml:"answerBImage" gqlgen:"answerBImage"` - AnswerC Answer `json:"answerC" xml:"answerC" gqlgen:"answerC"` + AnswerC Answer `pg:"answer_c" json:"answerC" xml:"answerC" gqlgen:"answerC"` AnswerCImage string `pg:"answer_c_image" json:"answerCImage" xml:"answerCImage" gqlgen:"answerCImage"` - AnswerD Answer `json:"answerD" xml:"answerD" gqlgen:"answerD"` + AnswerD Answer `pg:"answer_d" json:"answerD" xml:"answerD" gqlgen:"answerD"` AnswerDImage string `pg:"answer_d_image" json:"answerDImage" xml:"answerDImage" gqlgen:"answerDImage"` 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"` diff --git a/main.go b/main.go index 7722824..f52c1e7 100644 --- a/main.go +++ b/main.go @@ -7,11 +7,15 @@ import ( "os/signal" "time" + "github.com/pkg/errors" + "github.com/zdam-egzamin-zawodowy/backend/internal/db" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/joho/godotenv" "github.com/sirupsen/logrus" "github.com/zdam-egzamin-zawodowy/backend/pkg/mode" + envutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/env" ) func init() { @@ -25,6 +29,14 @@ func init() { } func main() { + _, err := db.New(&db.Config{ + DebugHook: envutils.GetenvBool("LOG_DB_QUERIES"), + }) + if err != nil { + logrus.Fatal(errors.Wrap(err, "Error establishing a database connection")) + } + logrus.Info("Database connection established") + router := gin.Default() if mode.Get() == mode.DevelopmentMode { router.Use(cors.New(cors.Config{ @@ -35,7 +47,7 @@ func main() { ExposeHeaders: []string{"X-Access-Token", "X-Refresh-Token"}, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"}, AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, - AllowWebSockets: true, + AllowWebSockets: false, })) } diff --git a/pkg/utils/env/env.go b/pkg/utils/env/env.go new file mode 100644 index 0000000..f07d168 --- /dev/null +++ b/pkg/utils/env/env.go @@ -0,0 +1,26 @@ +package envutils + +import ( + "os" + "strconv" +) + +func GetenvInt(key string) int { + str := os.Getenv(key) + if str == "" { + return 0 + } + i, err := strconv.Atoi(str) + if err != nil { + return 0 + } + return i +} + +func GetenvBool(key string) bool { + str := os.Getenv(key) + if str == "" { + return false + } + return str == "true" || str == "1" +}