package main import ( "context" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/cmd/internal" "github.com/Kichiyaki/appmode" "github.com/Kichiyaki/chilogrus" "github.com/Kichiyaki/goutil/envutil" "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/go-chi/chi/v5" "github.com/go-pg/pg/v10" "log" "net/http" "os" "os/signal" "time" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/auth" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/chi/middleware" graphqlhttpdelivery "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/graphql/delivery/httpdelivery" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/graphql/directive" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/graphql/resolvers" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/profession" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/qualification" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/question" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/user" "github.com/pkg/errors" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/auth/jwt" authusecase "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/auth/usecase" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/graphql/dataloader" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/postgres" professionrepository "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/profession/repository" professionusecase "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/profession/usecase" qualificationrepository "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/qualification/repository" qualificationusecase "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/qualification/usecase" questionrepository "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/question/repository" questionusecase "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/question/usecase" userrepository "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/user/repository" userusecase "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/internal/user/usecase" "github.com/sirupsen/logrus" "gitea.dwysokinski.me/zdam-egzamin-zawodowy/backend/fstorage" chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) var ( Version = "development" ) func main() { if err := internal.LoadENVFiles(); err != nil { logrus.Fatal("internal.LoadENVFiles", err) } prepareLogger() if err := internal.InitSentry(Version); err != nil { logrus.Fatal("internal.InitSentry", err) } defer sentry.Flush(2 * time.Second) fileStorage := fstorage.New(&fstorage.Config{ BasePath: envutil.GetenvString("FILE_STORAGE_PATH"), }) dbConn, err := postgres.Connect(&postgres.Config{ LogQueries: envutil.GetenvBool("LOG_DB_QUERIES"), }) if err != nil { logrus.Fatal(errors.Wrap(err, "Couldn't connect to the db")) } repos, err := prepareRepositories(dbConn, fileStorage) if err != nil { logrus.Fatal(err) } ucases, err := prepareUsecases(repos) if err != nil { logrus.Fatal(err) } srv := &http.Server{ Addr: ":8080", Handler: prepareRouter(repos, ucases), } go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logrus.Fatalln("listen:", err) } }() logrus.Info("Server is listening on the port 8080") quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) <-quit logrus.Info("Shutdown signal received, exiting...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { logrus.Fatalln("Server Shutdown:", err) } } func prepareLogger() { if appmode.Equals(appmode.DevelopmentMode) { logrus.SetLevel(logrus.DebugLevel) } timestampFormat := "2006-01-02 15:04:05" if appmode.Equals(appmode.ProductionMode) { customFormatter := new(logrus.JSONFormatter) customFormatter.TimestampFormat = timestampFormat logrus.SetFormatter(customFormatter) } else { customFormatter := new(logrus.TextFormatter) customFormatter.TimestampFormat = timestampFormat customFormatter.FullTimestamp = true logrus.SetFormatter(customFormatter) } } type repositories struct { userRepository user.Repository professionRepository profession.Repository qualificationRepository qualification.Repository questionRepository question.Repository } func prepareRepositories(dbConn *pg.DB, fileStorage fstorage.FileStorage) (*repositories, error) { var err error repos := &repositories{} repos.userRepository, err = userrepository.NewPGRepository(&userrepository.PGRepositoryConfig{ DB: dbConn, }) if err != nil { return nil, errors.Wrap(err, "userRepository") } repos.professionRepository, err = professionrepository.NewPGRepository(&professionrepository.PGRepositoryConfig{ DB: dbConn, }) if err != nil { return nil, errors.Wrap(err, "professionRepository") } repos.qualificationRepository, err = qualificationrepository.NewPGRepository(&qualificationrepository.PGRepositoryConfig{ DB: dbConn, }) if err != nil { return nil, errors.Wrap(err, "qualificationRepository") } repos.questionRepository, err = questionrepository.NewPGRepository(&questionrepository.PGRepositoryConfig{ DB: dbConn, FileStorage: fileStorage, }) if err != nil { return nil, errors.Wrap(err, "questionRepository") } return repos, nil } type usecases struct { authUsecase auth.Usecase userUsecase user.Usecase professionUsecase profession.Usecase qualificationUsecase qualification.Usecase questionUsecase question.Usecase } func prepareUsecases(repos *repositories) (*usecases, error) { var err error ucases := &usecases{} ucases.authUsecase, err = authusecase.New(&authusecase.Config{ UserRepository: repos.userRepository, TokenGenerator: jwt.NewTokenGenerator(envutil.GetenvString("ACCESS_SECRET")), }) if err != nil { return nil, errors.Wrap(err, "authUsecase") } ucases.userUsecase, err = userusecase.New(&userusecase.Config{ UserRepository: repos.userRepository, }) if err != nil { return nil, errors.Wrap(err, "userUsecase") } ucases.professionUsecase, err = professionusecase.New(&professionusecase.Config{ ProfessionRepository: repos.professionRepository, }) if err != nil { return nil, errors.Wrap(err, "professionUsecase") } ucases.qualificationUsecase, err = qualificationusecase.New(&qualificationusecase.Config{ QualificationRepository: repos.qualificationRepository, }) if err != nil { return nil, errors.Wrap(err, "qualificationUsecase") } ucases.questionUsecase, err = questionusecase.New(&questionusecase.Config{ QuestionRepository: repos.questionRepository, }) if err != nil { return nil, errors.Wrap(err, "questionUsecase") } return ucases, nil } func prepareRouter(repos *repositories, ucases *usecases) *chi.Mux { r := chi.NewRouter() sentryHandler := sentryhttp.New(sentryhttp.Options{ Repanic: true, WaitForDelivery: false, Timeout: 2 * time.Second, }) r.Use(chimiddleware.RealIP) if envutil.GetenvBool("ENABLE_ACCESS_LOG") { r.Use(chilogrus.Logger(logrus.StandardLogger())) } r.Use(chimiddleware.Recoverer) r.Use(sentryHandler.Handle) if appmode.Equals(appmode.DevelopmentMode) { r.Use(cors.Handler(cors.Options{ AllowOriginFunc: func(*http.Request, string) bool { return true }, AllowCredentials: true, ExposedHeaders: []string{"Authorization"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"}, AllowedHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, MaxAge: 300, })) } r.Group(func(r chi.Router) { r.Use( middleware.DataLoaderToContext(dataloader.Config{ ProfessionRepo: repos.professionRepository, QualificationRepo: repos.qualificationRepository, }), middleware.Authenticate(ucases.authUsecase), ) err := graphqlhttpdelivery.Attach(r, graphqlhttpdelivery.Config{ Resolver: &resolvers.Resolver{ AuthUsecase: ucases.authUsecase, UserUsecase: ucases.userUsecase, ProfessionUsecase: ucases.professionUsecase, QualificationUsecase: ucases.qualificationUsecase, QuestionUsecase: ucases.questionUsecase, }, Directive: &directive.Directive{}, }) if err != nil { log.Fatalln(err) } }) return r }