diff --git a/.cd/cd.yaml b/.cd/cd.yaml new file mode 100644 index 0000000..e99a976 --- /dev/null +++ b/.cd/cd.yaml @@ -0,0 +1,42 @@ +variables: + DOCKER_HOST: tcp://docker:2375/ + +stages: + - build + - test + - package + - prepare-test-artifacts + - deploy-to-test-env + - pact-provider-verify + - prepare-prod-artifacts + - deploy-to-prod-env + +build: + stage: build + image: golang:1.17.0-alpine3.14 + before_script: + - go mod tidy -go=1.16 && go mod tidy -go=1.17 + script: + - go build -o bin/main main.go + +test: + stage: test + image: golang:1.17 + variables: + CI : $CI + before_script: + - go mod tidy -go=1.16 && go mod tidy -go=1.17 + script: + - go test + +package: + stage: package + image: docker:18-git + services: + - name: docker:18-dind + variables: + DOCKER_REGISTRY_NAME: $MY_CI_REGISTRY_NAME:$CI_PIPELINE_ID + script: + - docker login -u $MY_CI_DOCKER_USER --password $MY_CI_DOCKER_PASS + - docker build -t $DOCKER_REGISTRY_NAME $CI_PROJECT_DIR + - docker push $DOCKER_REGISTRY_NAME \ No newline at end of file diff --git a/.cd/deployment-artifacts/deployment.yaml b/.cd/deployment-artifacts/deployment.yaml new file mode 100644 index 0000000..f47e180 --- /dev/null +++ b/.cd/deployment-artifacts/deployment.yaml @@ -0,0 +1,3 @@ +--- +apiVersion: apps/v1 +kind: Deployment \ No newline at end of file diff --git a/.cd/deployment-artifacts/pv-claim.yaml b/.cd/deployment-artifacts/pv-claim.yaml new file mode 100644 index 0000000..66953eb --- /dev/null +++ b/.cd/deployment-artifacts/pv-claim.yaml @@ -0,0 +1,3 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim \ No newline at end of file diff --git a/.cd/deployment-artifacts/pv.yaml b/.cd/deployment-artifacts/pv.yaml new file mode 100644 index 0000000..afd47b9 --- /dev/null +++ b/.cd/deployment-artifacts/pv.yaml @@ -0,0 +1,3 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass \ No newline at end of file diff --git a/.cd/deployment-artifacts/service.yaml b/.cd/deployment-artifacts/service.yaml new file mode 100644 index 0000000..13d15c3 --- /dev/null +++ b/.cd/deployment-artifacts/service.yaml @@ -0,0 +1,3 @@ +--- +apiVersion: v1 +kind: Service \ No newline at end of file diff --git a/.dev/dev.yaml b/.dev/dev.yaml new file mode 100644 index 0000000..df051ab --- /dev/null +++ b/.dev/dev.yaml @@ -0,0 +1,3 @@ +--- +technology: + config_name: value \ No newline at end of file diff --git a/.dev/local.yaml b/.dev/local.yaml new file mode 100644 index 0000000..df051ab --- /dev/null +++ b/.dev/local.yaml @@ -0,0 +1,3 @@ +--- +technology: + config_name: value \ No newline at end of file diff --git a/.dev/prod.yaml b/.dev/prod.yaml new file mode 100644 index 0000000..df051ab --- /dev/null +++ b/.dev/prod.yaml @@ -0,0 +1,3 @@ +--- +technology: + config_name: value \ No newline at end of file diff --git a/.dev/qa.yaml b/.dev/qa.yaml new file mode 100644 index 0000000..df051ab --- /dev/null +++ b/.dev/qa.yaml @@ -0,0 +1,3 @@ +--- +technology: + config_name: value \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ee8a161 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} \ No newline at end of file diff --git a/goarch/cobra/nlw.go b/goarch/cobra/nlw.go index 975dabb..56dce5a 100644 --- a/goarch/cobra/nlw.go +++ b/goarch/cobra/nlw.go @@ -6,7 +6,7 @@ Copyright © 2022 NAME HERE package cobra import ( - nlayered_backend "github.com/eneskzlcn/goarch/goarch/nlayered-backend" + nlayered_web "github.com/eneskzlcn/goarch/goarch/nlayered-web" "github.com/spf13/cobra" ) @@ -21,12 +21,12 @@ Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, RunE: func(cmd *cobra.Command, args []string) error { - nlb := nlayered_backend.New(".") - return nlb.Create() + nlw := nlayered_web.New(".") + return nlw.Create() }, } func init() { - rootCmd.AddCommand(nlbCmd) + rootCmd.AddCommand(nlwCmd) } diff --git a/goarch/cobra/root.go b/goarch/cobra/root.go index e742196..ca0f9f8 100644 --- a/goarch/cobra/root.go +++ b/goarch/cobra/root.go @@ -41,13 +41,5 @@ func Execute() { } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.goarch.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/goarch/nlayered-web/nlayered_web.go b/goarch/nlayered-web/nlayered_web.go index b1316e8..39f9046 100644 --- a/goarch/nlayered-web/nlayered_web.go +++ b/goarch/nlayered-web/nlayered_web.go @@ -11,9 +11,12 @@ type NLayeredWeb struct { func New(basePath string) *NLayeredWeb { arch := arch.New(basePath) - nlw := NLayeredWeb{arch: arch} + nlw := &NLayeredWeb{arch: arch} nlw.initializeArchitecture() - return &nlw + return nlw +} +func (nlw *NLayeredWeb) Create() error { + return nlw.arch.Create() } func (nlw *NLayeredWeb) initializeArchitecture() { nlw.addInitializedCommonRootDirectories() diff --git a/goarch/nlayered-web/template.go b/goarch/nlayered-web/template.go index e0e90e7..0b47e27 100644 --- a/goarch/nlayered-web/template.go +++ b/goarch/nlayered-web/template.go @@ -1,6 +1,7 @@ package nlayered_web import ( + _ "embed" "github.com/eneskzlcn/goarch/goarch/common" nlayered_common "github.com/eneskzlcn/goarch/goarch/common/nlayered-common" "github.com/eneskzlcn/goarch/goarch/directory" @@ -21,6 +22,7 @@ var coreDirectory = directory.Directory{ "mail": common.MailDirectory, }, } + var internalDirectory = directory.Directory{ SubDirs: directory.Directories{ "client": common.ClientDirectory, @@ -33,27 +35,55 @@ var internalDirectory = directory.Directory{ }, } +//go:embed templates/web/template/include/README.md +var webTemplateIncludeMarkdownFileContent string + +//go:embed templates/web/template/include/header.gohtml +var webTemplateIncludeHeaderFileContent string + +//go:embed templates/web/template/include/layout.gohtml +var webTemplateIncludeLayoutFileContent string + +//go:embed templates/web/template/home.gohtml +var webTemplateHomeFileContent string + var templateDirectory = directory.Directory{ SubDirs: directory.Directories{ "include": directory.Directory{ Files: file.Files{ - "layout": file.NewGoHtmlFile(""), - "header": file.NewGoHtmlFile(""), + "layout": file.NewGoHtmlFile(webTemplateIncludeLayoutFileContent), + "header": file.NewGoHtmlFile(webTemplateIncludeHeaderFileContent), + "README": file.NewMarkdownFile(webTemplateIncludeMarkdownFileContent), }, }, }, Files: file.Files{ - "home": file.NewGoHtmlFile(""), + "home": file.NewGoHtmlFile(webTemplateHomeFileContent), }, } + +//MARK: web directory initialization. + +//go:embed templates/web/template_go.arch +var webTemplateGoFileContent string + +//go:embed templates/web/handler_go.arch +var webHandlerGoFileContent string + +//go:embed templates/web/home_go.arch +var webHomeGoFileContent string + +//go:embed templates/web/web_go.arch +var webGoFileContent string + var webDirectory = directory.Directory{ SubDirs: directory.Directories{ "template": templateDirectory, }, Files: file.Files{ - //TODO: content will be taken by embed, - "template": file.NewGoFile(""), - "handler": file.NewGoFile(""), - "home": file.NewGoFile(""), + "template": file.NewGoFile(webTemplateGoFileContent), + "handler": file.NewGoFile(webHandlerGoFileContent), + "home": file.NewGoFile(webHomeGoFileContent), + "web": file.NewGoFile(webGoFileContent), }, } diff --git a/goarch/nlayered-web/templates/web/handler.go b/goarch/nlayered-web/templates/web/handler.go deleted file mode 100644 index efb3895..0000000 --- a/goarch/nlayered-web/templates/web/handler.go +++ /dev/null @@ -1 +0,0 @@ -package web diff --git a/goarch/nlayered-web/templates/web/handler_go.arch b/goarch/nlayered-web/templates/web/handler_go.arch new file mode 100644 index 0000000..c0fcf99 --- /dev/null +++ b/goarch/nlayered-web/templates/web/handler_go.arch @@ -0,0 +1,77 @@ +package web + +import ( + logger "/logger" + middleware "/middleware" + router "/router" + "context" + "encoding/gob" + "errors" + "html/template" + "net/http" + "net/url" +) + +type Service interface { + GetUsers(ctx context.Context, limit int) +} +type Renderer interface { + Render(w http.ResponseWriter, tmpl *template.Template, data any, statusCode int) +} + +type Handler struct { + handler http.Handler + logger logger.Logger + renderer Renderer + service Service + templates PageTemplates + urlParamExtractor func(ctx context.Context, key string) string +} + +func NewHandler(logger logger.Logger, service Service, renderer Renderer) *Handler { + if logger == nil { + return nil + } + if session == nil || service == nil || renderer == nil { + logger.Error("invalid arguments")) + return nil + } + handler := Handler{logger: logger, service: service, renderer: renderer} + + handler.init() + + return &handler +} +func (h *Handler) init() { + muxRouter := router.NewMuxRouterAdapter() + h.urlParamExtractor = muxRouter.ExtractURLParam + h.handler = muxRouter + h.RegisterHandlers(muxRouter) + + h.applyMiddlewares() + + gob.Register(url.Values{}) + + templates := make(PageTemplates, 0) + templates["home"] = ParseTemplate("home.gohtml") + h.templates = templates +} +func (h *Handler) applyMiddlewares() { + //apply method overriding middleware + h.handler = middleware.OverrideFormMethods(h.handler) +} + +func (h *Handler) RenderPage(page string, w http.ResponseWriter, data any, statusCode int) { + h.renderer.Render(w, h.templates[page], data, statusCode) +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + h.handler.ServeHTTP(w, req) +} + +func (h *Handler) RegisterHandlers(muxRouter router.Router) { + muxRouter.Handle("/", router.MethodHandlers{ + http.MethodGet: h.ShowHome, + }) + +} diff --git a/goarch/nlayered-web/templates/web/home_go.arch b/goarch/nlayered-web/templates/web/home_go.arch new file mode 100644 index 0000000..455a4cd --- /dev/null +++ b/goarch/nlayered-web/templates/web/home_go.arch @@ -0,0 +1,17 @@ +package web + +import ( + "net/http" +) + +type homeData struct { + Header string +} + +func (h *Handler) ShowHome(w http.ResponseWriter, r *http.Request) { + h.RenderHome(w, homeData{Header: "Welcome"}, http.StatusFound) +} + +func (h *Handler) RenderHome(w http.ResponseWriter, data homeData, status int) { + h.RenderPage("home", w, data, status) +} diff --git a/goarch/nlayered-web/templates/web/template/home.gohtml b/goarch/nlayered-web/templates/web/template/home.gohtml index e69de29..0835e29 100644 --- a/goarch/nlayered-web/templates/web/template/home.gohtml +++ b/goarch/nlayered-web/templates/web/template/home.gohtml @@ -0,0 +1,3 @@ +
+

{{.Header}}

+
\ No newline at end of file diff --git a/goarch/nlayered-web/templates/web/template/include/README.md b/goarch/nlayered-web/templates/web/template/include/README.md new file mode 100644 index 0000000..56b4af5 --- /dev/null +++ b/goarch/nlayered-web/templates/web/template/include/README.md @@ -0,0 +1,9 @@ +This directory contains common ui elements +like reusable components and layout elements which +are common for every page. + +Suppose that you want to render home.gohtml page +then actually the html content of home.gohtml +taken by template engine and get put into layout.gohtml +file which expects a template "content". Every page +you want to parse and render, the same thing continually happens. diff --git a/goarch/nlayered-web/templates/web/template/include/header.gohtml b/goarch/nlayered-web/templates/web/template/include/header.gohtml index e69de29..cae408b 100644 --- a/goarch/nlayered-web/templates/web/template/include/header.gohtml +++ b/goarch/nlayered-web/templates/web/template/include/header.gohtml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/goarch/nlayered-web/templates/web/template/include/layout.gohtml b/goarch/nlayered-web/templates/web/template/include/layout.gohtml index e69de29..fbe5916 100644 --- a/goarch/nlayered-web/templates/web/template/include/layout.gohtml +++ b/goarch/nlayered-web/templates/web/template/include/layout.gohtml @@ -0,0 +1,14 @@ + + + + + + Softdare + + + + {{template "header.gohtml" .}} + {{template "content" .}} + + + \ No newline at end of file diff --git a/goarch/nlayered-web/templates/web/template_go.arch b/goarch/nlayered-web/templates/web/template_go.arch new file mode 100644 index 0000000..5fe4e5d --- /dev/null +++ b/goarch/nlayered-web/templates/web/template_go.arch @@ -0,0 +1,20 @@ +package web + +import ( + coreTemplate "/html/template" + "embed" + "html/template" +) + +//go:embed template/include/*.gohtml template/*.gohtml + +var templateFS embed.FS + +var templateFuncs = template.FuncMap{ + "linkify": coreTemplate.Linkify, +} + +func ParseTemplate(name string) *template.Template { + return coreTemplate.Parse(name, templateFuncs, templateFS, + "template/include/*.gohtml", "template/") +} diff --git a/goarch/nlayered-web/templates/web/web_go.arch b/goarch/nlayered-web/templates/web/web_go.arch new file mode 100644 index 0000000..0910096 --- /dev/null +++ b/goarch/nlayered-web/templates/web/web_go.arch @@ -0,0 +1,5 @@ +package web + +import "html/template" + +type PageTemplates map[string]*template.Template diff --git a/internal/client/httpclient/httpclient.go b/internal/client/httpclient/httpclient.go new file mode 100644 index 0000000..8bdac7f --- /dev/null +++ b/internal/client/httpclient/httpclient.go @@ -0,0 +1 @@ +package httpclient \ No newline at end of file diff --git a/internal/client/httpclient/httpclient_test_test.go b/internal/client/httpclient/httpclient_test_test.go new file mode 100644 index 0000000..62b0625 --- /dev/null +++ b/internal/client/httpclient/httpclient_test_test.go @@ -0,0 +1 @@ +package httpclient_test \ No newline at end of file diff --git a/internal/client/rabbitclient/rabbitclient.go b/internal/client/rabbitclient/rabbitclient.go new file mode 100644 index 0000000..08ee0ed --- /dev/null +++ b/internal/client/rabbitclient/rabbitclient.go @@ -0,0 +1 @@ +package rabbitclient \ No newline at end of file diff --git a/internal/client/rabbitclient/rabbitclient_test_test.go b/internal/client/rabbitclient/rabbitclient_test_test.go new file mode 100644 index 0000000..84a9a11 --- /dev/null +++ b/internal/client/rabbitclient/rabbitclient_test_test.go @@ -0,0 +1 @@ +package rabbitclient_test \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..d62d8df --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "github.com/spf13/viper" +) + +type Config struct { + Db DB `mapstructure:"db"` +} + +/*LoadConfig is a generic function that takes a config structure as a type, +path of the config, and the name of the file in that path, then the format of the file. +Ex: +Suppose that you have a config type like; + +type SomeConfig struct { + url string `mapstructure:"url"` +} + +and a file in the .dev/ directory named local.yaml with the following content: + + +url: "someurl" + +Then all you need to call the function to get the configs from the file by: + +someConfig , err := LoadConfig[SomeConfig](".dev/", "local", "yaml") + +*/ +func LoadConfig[T any](path, name, configType string) (config T, err error) { + viper.AddConfigPath(path) + viper.SetConfigName(name) + viper.SetConfigType(configType) + err = viper.ReadInConfig() + if err != nil { + return + } + err = viper.Unmarshal(&config) + return +} \ No newline at end of file diff --git a/internal/config/config_test_test.go b/internal/config/config_test_test.go new file mode 100644 index 0000000..5f6eed6 --- /dev/null +++ b/internal/config/config_test_test.go @@ -0,0 +1,14 @@ +package config_test + +import ( + config "" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLoadConfig(t *testing.T) { + environment := "test" + config, err := config.LoadConfig[config.Config]("", environment, "yaml") + assert.Nil(t, err) + assert.Equal(t, " >", config.Db.Username) +} diff --git a/internal/config/db.go b/internal/config/db.go new file mode 100644 index 0000000..0365294 --- /dev/null +++ b/internal/config/db.go @@ -0,0 +1,5 @@ +package config + +type DB struct { + Username string `mapstructure:"username"` +} diff --git a/internal/core/cache/cache.go b/internal/core/cache/cache.go new file mode 100644 index 0000000..098bef4 --- /dev/null +++ b/internal/core/cache/cache.go @@ -0,0 +1,13 @@ +package cache + +import "time" + +/*Cache provides in-memory caching. You can +set the cache content with the expiration duration +which ensures that your data put in to the cache +will be removed after the expiration duration completes. +*/ +type Cache interface { + SetWithExpire(key interface{}, value interface{}, expiration time.Duration) error + Get(key interface{}) (interface{}, error) +} diff --git a/internal/core/cache/gcache_adapter.go b/internal/core/cache/gcache_adapter.go new file mode 100644 index 0000000..06751a9 --- /dev/null +++ b/internal/core/cache/gcache_adapter.go @@ -0,0 +1,21 @@ +package cache + +import ( + "github.com/bluele/gcache" + "time" +) + +type GCacheAdapter struct { + cache gcache.Cache +} + +func NewGCacheAdapter(size int) Cache { + cache := gcache.New(size).Build() + return &GCacheAdapter{cache: cache} +} +func (g *GCacheAdapter) SetWithExpire(key interface{}, value interface{}, expiration time.Duration) error { + return g.cache.SetWithExpire(key, value, expiration) +} +func (g *GCacheAdapter) Get(key interface{}) (interface{}, error) { + return g.cache.Get(key) +} diff --git a/internal/core/client/httpclient/httpclient.go b/internal/core/client/httpclient/httpclient.go new file mode 100644 index 0000000..8bdac7f --- /dev/null +++ b/internal/core/client/httpclient/httpclient.go @@ -0,0 +1 @@ +package httpclient \ No newline at end of file diff --git a/internal/core/client/httpclient/httpclient_test_test.go b/internal/core/client/httpclient/httpclient_test_test.go new file mode 100644 index 0000000..62b0625 --- /dev/null +++ b/internal/core/client/httpclient/httpclient_test_test.go @@ -0,0 +1 @@ +package httpclient_test \ No newline at end of file diff --git a/internal/core/client/rabbitclient/rabbitclient.go b/internal/core/client/rabbitclient/rabbitclient.go new file mode 100644 index 0000000..08ee0ed --- /dev/null +++ b/internal/core/client/rabbitclient/rabbitclient.go @@ -0,0 +1 @@ +package rabbitclient \ No newline at end of file diff --git a/internal/core/client/rabbitclient/rabbitclient_test_test.go b/internal/core/client/rabbitclient/rabbitclient_test_test.go new file mode 100644 index 0000000..84a9a11 --- /dev/null +++ b/internal/core/client/rabbitclient/rabbitclient_test_test.go @@ -0,0 +1 @@ +package rabbitclient_test \ No newline at end of file diff --git a/internal/core/logger/logger.go b/internal/core/logger/logger.go new file mode 100644 index 0000000..78c904b --- /dev/null +++ b/internal/core/logger/logger.go @@ -0,0 +1,16 @@ +package logger + +type Logger interface { + Error(args ...interface{}) + Info(args ...interface{}) + Debug(args ...interface{}) + Fatal(args ...interface{}) + Errorf(template string, args ...interface{}) + Infof(template string, args ...interface{}) + Debugf(template string, args ...interface{}) + Fatalf(template string, args ...interface{}) + Sync() error + StringModifier(key, value string) interface{} + ErrorModifier(err error) interface{} + AnyModifier(key string, value any) interface{} +} diff --git a/internal/core/logger/zap_logger_adapter.go b/internal/core/logger/zap_logger_adapter.go new file mode 100644 index 0000000..b309f34 --- /dev/null +++ b/internal/core/logger/zap_logger_adapter.go @@ -0,0 +1,93 @@ +package logger + +import ( + "errors" + "fmt" + "go.uber.org/zap" +) + +type ZapLogger interface { + Error(args ...interface{}) + Info(args ...interface{}) + Debug(args ...interface{}) + Fatal(args ...interface{}) + Errorf(template string, args ...interface{}) + Infof(template string, args ...interface{}) + Debugf(template string, args ...interface{}) + Fatalf(template string, args ...interface{}) + Sync() error +} + +type ZapLoggerAdapter struct { + logger ZapLogger +} + +func newZapLoggerForEnv(env string, callerSkip int) (ZapLogger, error) { + if env == "" || env == "local" || env == "test" || env == "qa" || env == "dev" { + logger, err := zap.NewDevelopment(zap.AddCallerSkip(1)) + return logger.Sugar(), err + } else if env == "prod" { + logger, err := zap.NewProduction(zap.AddCallerSkip(callerSkip), zap.AddStacktrace(zap.ErrorLevel)) + return logger.Sugar(), err + } + return nil, errors.New("not valid") +} + +func NewZapLoggerAdapter(env string, callerSkip int) *ZapLoggerAdapter { + logger, err := newZapLoggerForEnv(env, callerSkip) + + if err != nil { + fmt.Println("error logger is nil") + return nil + } + zapLoggerAdapter := ZapLoggerAdapter{logger: logger} + return &zapLoggerAdapter +} + +func (z *ZapLoggerAdapter) StringModifier(key, value string) interface{} { + return zap.String(key, value) +} + +func (z *ZapLoggerAdapter) ErrorModifier(err error) interface{} { + return zap.Error(err) +} + +func (z *ZapLoggerAdapter) AnyModifier(key string, value any) interface{} { + return zap.Any(key, value) +} + +func (z *ZapLoggerAdapter) Info(args ...interface{}) { + z.logger.Info(args) +} + +func (z *ZapLoggerAdapter) Infof(template string, args ...interface{}) { + z.logger.Infof(template, args) +} + +func (z *ZapLoggerAdapter) Error(args ...interface{}) { + z.logger.Error(args) +} + +func (z *ZapLoggerAdapter) Errorf(template string, args ...interface{}) { + z.logger.Errorf(template, args) +} + +func (z *ZapLoggerAdapter) Debug(args ...interface{}) { + z.logger.Debug(args) +} + +func (z *ZapLoggerAdapter) Debugf(template string, args ...interface{}) { + z.logger.Debugf(template, args) +} + +func (z *ZapLoggerAdapter) Fatal(args ...interface{}) { + z.logger.Fatal(args) +} + +func (z *ZapLoggerAdapter) Fatalf(template string, args ...interface{}) { + z.logger.Fatalf(template, args) +} + +func (z *ZapLoggerAdapter) Sync() error { + return z.logger.Sync() +} diff --git a/internal/core/logger/zap_logger_adapter_test.go b/internal/core/logger/zap_logger_adapter_test.go new file mode 100644 index 0000000..87427cc --- /dev/null +++ b/internal/core/logger/zap_logger_adapter_test.go @@ -0,0 +1,21 @@ +package logger_test + +import ( + logger "/..//logger" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewZapLoggerAdapter(t *testing.T) { + bestCases := []string{"", "qa", "local", "test", "dev", "prod"} + worstCases := []string{",", "afasfaf", "any", "123", "afsaf"} + + for _, bestCase := range bestCases { + zapLogger := logger.NewZapLoggerAdapter(bestCase, 1) + assert.NotNil(t, zapLogger) + } + for _, worstCase := range worstCases { + zapLogger := logger.NewZapLoggerAdapter(worstCase, 1) + assert.Nil(t, zapLogger) + } +} diff --git a/internal/core/mail/gomail_adapter.go b/internal/core/mail/gomail_adapter.go new file mode 100644 index 0000000..2efddfb --- /dev/null +++ b/internal/core/mail/gomail_adapter.go @@ -0,0 +1,54 @@ +package mail + +import ( + "crypto/tls" + "fmt" + "github.com/eneskzlcn/softdare/internal/config" + "github.com/eneskzlcn/softdare/internal/core/logger" + customerror "github.com/eneskzlcn/softdare/internal/error" + gomail "gopkg.in/mail.v2" +) + +type GomailServiceAdapter struct { + config config.MailService + logger logger.Logger +} + +func NewGomailServiceAdapter(config config.MailService, logger logger.Logger) *GomailServiceAdapter { + if logger == nil { + fmt.Println(customerror.NilLogger) + return nil + } + return &GomailServiceAdapter{config: config, logger: logger} +} +func (g *GomailServiceAdapter) SendTextMail(to, subject, content string) error { + m := gomail.NewMessage() + + // Set E-Mail sender + m.SetHeader("From", g.config.SenderMail) + + // Set E-Mail receivers + m.SetHeader("To", to) + + // Set E-Mail subject + m.SetHeader("Subject", subject) + + // Set E-Mail body. You can set plain text or html with text/html + // Set E-Mail body. You can set plain text or html with text/html + m.SetBody("text/plain", content) + + // Settings for SMTP server + + d := gomail.NewDialer(g.config.Host, g.config.Port, g.config.SenderMail, g.config.SenderPassword) + + // This is only needed when SSL/TLS certificate is not valid on server. + // In production this should be set to false. + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + // Now send E-Mail + if err := d.DialAndSend(m); err != nil { + g.logger.Error("error when sending the email.") + return err + } + return nil +} diff --git a/internal/core/mail/mail.go b/internal/core/mail/mail.go new file mode 100644 index 0000000..4bddcf6 --- /dev/null +++ b/internal/core/mail/mail.go @@ -0,0 +1,8 @@ +package mail + +/*Service is a mail service that provides you +to send mails with smtp protocol. +*/ +type Service interface { + SendTextMail(to, subject, content string) error +} diff --git a/internal/core/postgres/mock_postgres.go b/internal/core/postgres/mock_postgres.go new file mode 100644 index 0000000..3f861b7 --- /dev/null +++ b/internal/core/postgres/mock_postgres.go @@ -0,0 +1,14 @@ +package postgres + +import ( + "database/sql" + "github.com/DATA-DOG/go-sqlmock" +) + +func NewMockPostgres() (*sql.DB, sqlmock.Sqlmock) { + db, mock, err := sqlmock.New() + if err != nil { + return nil, nil + } + return db, mock +} \ No newline at end of file diff --git a/internal/core/postgres/postgres.go b/internal/core/postgres/postgres.go new file mode 100644 index 0000000..9b5aa66 --- /dev/null +++ b/internal/core/postgres/postgres.go @@ -0,0 +1,33 @@ +package postgres + +import ( + "context" + "database/sql" + "errors" + "fmt" + config "/internal/config" + _ "github.com/lib/pq" +) + +type DB interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(config config.DB) (DB, error) { + db, err := sql.Open("postgres", createDSN(config)) + if err != nil { + return nil, err + } + if err = db.Ping(); err != nil { + return nil, err + } + return db, nil +} + +func createDSN(config config.DB) string { + return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + config.Host, config.Port, config.Username, config.Password, config.DBName) +} \ No newline at end of file diff --git a/internal/core/postgres/postgres_test_test.go b/internal/core/postgres/postgres_test_test.go new file mode 100644 index 0000000..97032fe --- /dev/null +++ b/internal/core/postgres/postgres_test_test.go @@ -0,0 +1 @@ +package postgres_test diff --git a/internal/core/rabbitmq/config.go b/internal/core/rabbitmq/config.go new file mode 100644 index 0000000..d62d8df --- /dev/null +++ b/internal/core/rabbitmq/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "github.com/spf13/viper" +) + +type Config struct { + Db DB `mapstructure:"db"` +} + +/*LoadConfig is a generic function that takes a config structure as a type, +path of the config, and the name of the file in that path, then the format of the file. +Ex: +Suppose that you have a config type like; + +type SomeConfig struct { + url string `mapstructure:"url"` +} + +and a file in the .dev/ directory named local.yaml with the following content: + + +url: "someurl" + +Then all you need to call the function to get the configs from the file by: + +someConfig , err := LoadConfig[SomeConfig](".dev/", "local", "yaml") + +*/ +func LoadConfig[T any](path, name, configType string) (config T, err error) { + viper.AddConfigPath(path) + viper.SetConfigName(name) + viper.SetConfigType(configType) + err = viper.ReadInConfig() + if err != nil { + return + } + err = viper.Unmarshal(&config) + return +} \ No newline at end of file diff --git a/internal/core/rabbitmq/config_test_test.go b/internal/core/rabbitmq/config_test_test.go new file mode 100644 index 0000000..5f6eed6 --- /dev/null +++ b/internal/core/rabbitmq/config_test_test.go @@ -0,0 +1,14 @@ +package config_test + +import ( + config "" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLoadConfig(t *testing.T) { + environment := "test" + config, err := config.LoadConfig[config.Config]("", environment, "yaml") + assert.Nil(t, err) + assert.Equal(t, " >", config.Db.Username) +} diff --git a/internal/core/rabbitmq/db.go b/internal/core/rabbitmq/db.go new file mode 100644 index 0000000..0365294 --- /dev/null +++ b/internal/core/rabbitmq/db.go @@ -0,0 +1,5 @@ +package config + +type DB struct { + Username string `mapstructure:"username"` +} diff --git a/internal/core/router/mux_router_adapter.go b/internal/core/router/mux_router_adapter.go new file mode 100644 index 0000000..b47708e --- /dev/null +++ b/internal/core/router/mux_router_adapter.go @@ -0,0 +1,29 @@ +package router + +import ( + "context" + "github.com/nicolasparada/go-mux" + "net/http" +) + +type MuxRouterAdapter struct { + router *mux.Router +} + +func (m *MuxRouterAdapter) ExtractURLParam(ctx context.Context, key string) string { + return mux.URLParam(ctx, key) +} + +func NewMuxRouterAdapter() *MuxRouterAdapter { + router := mux.NewRouter() + return &MuxRouterAdapter{router: router} +} + +func (m *MuxRouterAdapter) Handle(pattern string, handlers MethodHandlers) { + muxMethodHandlers := (mux.MethodHandler)(handlers) + m.router.Handle(pattern, muxMethodHandlers) +} + +func (m *MuxRouterAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + m.router.ServeHTTP(w, r) +} diff --git a/internal/core/router/router.go b/internal/core/router/router.go new file mode 100644 index 0000000..07c361e --- /dev/null +++ b/internal/core/router/router.go @@ -0,0 +1,14 @@ +package router + +import ( + "context" + "net/http" +) + +type MethodHandlers map[string]http.HandlerFunc + +type Router interface { + Handle(pattern string, methodHandlers MethodHandlers) + ExtractURLParam(ctx context.Context, key string) string + ServeHTTP(w http.ResponseWriter, r *http.Request) +} diff --git a/internal/core/server/server.go b/internal/core/server/server.go new file mode 100644 index 0000000..7f92fe3 --- /dev/null +++ b/internal/core/server/server.go @@ -0,0 +1,58 @@ +package server + +import ( + "fmt" + config "/internal/config" + "github.com/gofiber/fiber/v2/middleware/cors" + "os" + "os/signal" + "syscall" +) + +type Handler interface { + RegisterRoutes(app *fiber.App) +} + +type Logger interface { + Error(args ...interface{}) +} + +type Server struct { + app *fiber.App + config config.Server + logger Logger +} + +func New(handlers []Handler, config config.Server, logger Logger) *Server { + app := fiber.New() + app.Use(cors.New()) + for _, handler := range handlers { + handler.RegisterRoutes(app) + } + server := &Server{ + app: app, + config: config, + logger: logger, + } + server.AddRoutes() + return server +} +func (s *Server) AddRoutes() { + s.app.Get("/health", s.healthCheck) +} +func (s *Server) healthCheck(ctx *fiber.Ctx) error { + return ctx.SendStatus(fiber.StatusOK) +} + +func (s *Server) Start() error { + address := fmt.Sprintf(":%s", s.config.Port) + shutDownChan := make(chan os.Signal, 1) + signal.Notify(shutDownChan, os.Interrupt, syscall.SIGTERM, os.Kill) + go func() { + <-shutDownChan + if err := s.app.Shutdown(); err != nil { + s.logger.Error(err) + } + }() + return s.app.Listen(address) +} \ No newline at end of file diff --git a/internal/core/session/college_session_adapter.go b/internal/core/session/college_session_adapter.go new file mode 100644 index 0000000..0b14603 --- /dev/null +++ b/internal/core/session/college_session_adapter.go @@ -0,0 +1,70 @@ +package session + +import ( + "errors" + "github.com/eneskzlcn/softdare/internal/config" + "github.com/eneskzlcn/softdare/internal/core/logger" + "github.com/golangcollege/sessions" + "net/http" +) + +type CollegeSessionAdapter struct { + logger logger.Logger + session *sessions.Session + sessionKey []byte +} + +func NewCollegeSessionAdapter(logger logger.Logger, config config.Session) *CollegeSessionAdapter { + if logger == nil { + return nil + } + if err := validateSessionKey(config.Key); err != nil { + logger.Errorf("Err validating session key", err.Error()) + return nil + } + sessionKeyByte := []byte(config.Key) + session := sessions.New(sessionKeyByte) + return &CollegeSessionAdapter{session: session, logger: logger} +} + +func (s *CollegeSessionAdapter) Exists(r *http.Request, key string) bool { + exists := s.session.Exists(r, key) + return exists +} + +func (s *CollegeSessionAdapter) Get(r *http.Request, key string) any { + return s.session.Get(r, key) +} + +func (s *CollegeSessionAdapter) Put(r *http.Request, key string, data interface{}) { + s.session.Put(r, key, data) +} + +func (s *CollegeSessionAdapter) Remove(r *http.Request, key string) { + s.session.Remove(r, key) +} + +func (s *CollegeSessionAdapter) GetString(r *http.Request, key string) string { + return s.session.GetString(r, key) +} + +func (s *CollegeSessionAdapter) PopString(r *http.Request, key string) string { + return s.session.PopString(r, key) +} + +func (s *CollegeSessionAdapter) Pop(r *http.Request, key string) any { + return s.session.Pop(r, key) +} + +/*PopError extracts a string that known as oops from session and converts it to oops*/ +func (s *CollegeSessionAdapter) PopError(r *http.Request, key string) error { + str := s.PopString(r, key) + if str != "" { + return errors.New(str) + } + return nil +} +func (s *CollegeSessionAdapter) Enable(handler http.Handler) http.Handler { + s.logger.Debug("session enabled for given handler") + return s.session.Enable(handler) +} diff --git a/internal/core/session/session.go b/internal/core/session/session.go new file mode 100644 index 0000000..a67a563 --- /dev/null +++ b/internal/core/session/session.go @@ -0,0 +1,24 @@ +package session + +import ( + "errors" + "net/http" +) + +type Session interface { + Exists(r *http.Request, key string) bool + Get(r *http.Request, key string) any + Enable(handler http.Handler) http.Handler + GetString(r *http.Request, key string) string + PopError(r *http.Request, key string) error + Pop(r *http.Request, key string) any + Put(r *http.Request, key string, data any) + Remove(r *http.Request, key string) +} + +func validateSessionKey(s string) error { + if len(s) != 32 { + return errors.New("length of session key must be in length 32") + } + return nil +} diff --git a/internal/core/util/ctxutil/ctxutil.go b/internal/core/util/ctxutil/ctxutil.go new file mode 100644 index 0000000..5085467 --- /dev/null +++ b/internal/core/util/ctxutil/ctxutil.go @@ -0,0 +1,8 @@ +package ctxutil + +import "context" + +func FromContext[T any](ctx context.Context, key string) (T, bool) { + val, exists := ctx.Value(key).(T) + return val, exists +} diff --git a/internal/entity/user.go b/internal/entity/user.go new file mode 100644 index 0000000..930792d --- /dev/null +++ b/internal/entity/user.go @@ -0,0 +1,5 @@ +package entity + +type User struct { + Name string +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..8cab449 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,15 @@ +package repository + +import logger "/logger" + +type DB interface { +} + +type Repository struct { + logger logger.Logger + db DB +} + +func New(db DB, logger logger.Logger) *Repository { + return &Repository{logger: logger, db: db} +} diff --git a/internal/repository/repository_test_test.go b/internal/repository/repository_test_test.go new file mode 100644 index 0000000..6c2168a --- /dev/null +++ b/internal/repository/repository_test_test.go @@ -0,0 +1 @@ +package repository_test \ No newline at end of file diff --git a/internal/service/repository.go b/internal/service/repository.go new file mode 100644 index 0000000..e255e1c --- /dev/null +++ b/internal/service/repository.go @@ -0,0 +1,15 @@ +package service + +import logger "/logger" + +type Repository interface { +} + +type Service struct { + logger logger.Logger + repository Repository +} + +func New(repository Repository, logger logger.Logger) *Service { + return &Service{repository: repository, logger: logger} +} diff --git a/internal/service/repository_test_test.go b/internal/service/repository_test_test.go new file mode 100644 index 0000000..b818d08 --- /dev/null +++ b/internal/service/repository_test_test.go @@ -0,0 +1 @@ +package service_test \ No newline at end of file diff --git a/internal/web/handler.go b/internal/web/handler.go new file mode 100644 index 0000000..c0fcf99 --- /dev/null +++ b/internal/web/handler.go @@ -0,0 +1,77 @@ +package web + +import ( + logger "/logger" + middleware "/middleware" + router "/router" + "context" + "encoding/gob" + "errors" + "html/template" + "net/http" + "net/url" +) + +type Service interface { + GetUsers(ctx context.Context, limit int) +} +type Renderer interface { + Render(w http.ResponseWriter, tmpl *template.Template, data any, statusCode int) +} + +type Handler struct { + handler http.Handler + logger logger.Logger + renderer Renderer + service Service + templates PageTemplates + urlParamExtractor func(ctx context.Context, key string) string +} + +func NewHandler(logger logger.Logger, service Service, renderer Renderer) *Handler { + if logger == nil { + return nil + } + if session == nil || service == nil || renderer == nil { + logger.Error("invalid arguments")) + return nil + } + handler := Handler{logger: logger, service: service, renderer: renderer} + + handler.init() + + return &handler +} +func (h *Handler) init() { + muxRouter := router.NewMuxRouterAdapter() + h.urlParamExtractor = muxRouter.ExtractURLParam + h.handler = muxRouter + h.RegisterHandlers(muxRouter) + + h.applyMiddlewares() + + gob.Register(url.Values{}) + + templates := make(PageTemplates, 0) + templates["home"] = ParseTemplate("home.gohtml") + h.templates = templates +} +func (h *Handler) applyMiddlewares() { + //apply method overriding middleware + h.handler = middleware.OverrideFormMethods(h.handler) +} + +func (h *Handler) RenderPage(page string, w http.ResponseWriter, data any, statusCode int) { + h.renderer.Render(w, h.templates[page], data, statusCode) +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + h.handler.ServeHTTP(w, req) +} + +func (h *Handler) RegisterHandlers(muxRouter router.Router) { + muxRouter.Handle("/", router.MethodHandlers{ + http.MethodGet: h.ShowHome, + }) + +} diff --git a/internal/web/home.go b/internal/web/home.go new file mode 100644 index 0000000..455a4cd --- /dev/null +++ b/internal/web/home.go @@ -0,0 +1,17 @@ +package web + +import ( + "net/http" +) + +type homeData struct { + Header string +} + +func (h *Handler) ShowHome(w http.ResponseWriter, r *http.Request) { + h.RenderHome(w, homeData{Header: "Welcome"}, http.StatusFound) +} + +func (h *Handler) RenderHome(w http.ResponseWriter, data homeData, status int) { + h.RenderPage("home", w, data, status) +} diff --git a/internal/web/template.go b/internal/web/template.go new file mode 100644 index 0000000..5fe4e5d --- /dev/null +++ b/internal/web/template.go @@ -0,0 +1,20 @@ +package web + +import ( + coreTemplate "/html/template" + "embed" + "html/template" +) + +//go:embed template/include/*.gohtml template/*.gohtml + +var templateFS embed.FS + +var templateFuncs = template.FuncMap{ + "linkify": coreTemplate.Linkify, +} + +func ParseTemplate(name string) *template.Template { + return coreTemplate.Parse(name, templateFuncs, templateFS, + "template/include/*.gohtml", "template/") +} diff --git a/internal/web/template/home.gohtml b/internal/web/template/home.gohtml new file mode 100644 index 0000000..0835e29 --- /dev/null +++ b/internal/web/template/home.gohtml @@ -0,0 +1,3 @@ +
+

{{.Header}}

+
\ No newline at end of file diff --git a/internal/web/template/include/README.md b/internal/web/template/include/README.md new file mode 100644 index 0000000..56b4af5 --- /dev/null +++ b/internal/web/template/include/README.md @@ -0,0 +1,9 @@ +This directory contains common ui elements +like reusable components and layout elements which +are common for every page. + +Suppose that you want to render home.gohtml page +then actually the html content of home.gohtml +taken by template engine and get put into layout.gohtml +file which expects a template "content". Every page +you want to parse and render, the same thing continually happens. diff --git a/internal/web/template/include/header.gohtml b/internal/web/template/include/header.gohtml new file mode 100644 index 0000000..cae408b --- /dev/null +++ b/internal/web/template/include/header.gohtml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/internal/web/template/include/layout.gohtml b/internal/web/template/include/layout.gohtml new file mode 100644 index 0000000..fbe5916 --- /dev/null +++ b/internal/web/template/include/layout.gohtml @@ -0,0 +1,14 @@ + + + + + + Softdare + + + + {{template "header.gohtml" .}} + {{template "content" .}} + + + \ No newline at end of file diff --git a/internal/web/web.go b/internal/web/web.go new file mode 100644 index 0000000..0910096 --- /dev/null +++ b/internal/web/web.go @@ -0,0 +1,5 @@ +package web + +import "html/template" + +type PageTemplates map[string]*template.Template diff --git a/seed/cmd/main.go b/seed/cmd/main.go new file mode 100644 index 0000000..8d6933d --- /dev/null +++ b/seed/cmd/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "database/sql" + config "" + "os" +) + +func main() { + if err := run(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} +func run() error { + conf, err := config.LoadConfig[config.Config](".dev/", "local", "yaml") + if err != nil { + fmt.Println(err.Error()) + return err + } + dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + conf.Host, conf.Port, conf.Username, conf.Password, conf.DBName) + + db, err := sql.Open("postgres", dsn) + if err != nil { + fmt.Println("error when initializing database", err.Error()) + return err + } + + var ddlType string + + fs := flag.NewFlagSet("", flag.ExitOnError) + fs.StringVar(&ddlType, "type", "migrate", ` + Enter the type of the operation you want to perform. + migrate: will create all the tables + drop: will drop all the tables`) + if err = fs.Parse(os.Args[1:]); err != nil { + return fmt.Errorf("parse flags error") + } + switch ddlType { + case "migrate": + if err = seed.MigrateTables(context.Background(), db); err != nil { + return fmt.Errorf("migration error") + } + break + case "drop": + if err = seed.DropTables(context.Background(), db); err != nil { + return fmt.Errorf("drop error") + } + break + default: + return errors.New("not valid flag type for action") + } + return nil +} \ No newline at end of file diff --git a/seed/create_seed.sql b/seed/create_seed.sql new file mode 100644 index 0000000..b735b3b --- /dev/null +++ b/seed/create_seed.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS users( + id VARCHAR NOT NULL PRIMARY KEY, + email VARCHAR NOT NULL UNIQUE, + username VARCHAR NOT NULL UNIQUE, + post_count INTEGER NOT NULL DEFAULT 0 CHECK ( post_count >= 0 ), + follower_count INTEGER NOT NULL DEFAULT 0 CHECK (follower_count >= 0), + followed_count INTEGER NOT NULL DEFAULT 0 CHECK ( followed_count >= 0 ), + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS posts( + id VARCHAR NOT NULL PRIMARY KEY, + user_id VARCHAR NOT NULL REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE, + content TEXT NOT NULL, + comment_count INT DEFAULT 0 CHECK ( comment_count >= 0 ), + like_count INT DEFAULT 0 CHECK ( like_count >= 0 ), + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now() +); \ No newline at end of file diff --git a/seed/drop_seed.sql b/seed/drop_seed.sql new file mode 100644 index 0000000..d12c178 --- /dev/null +++ b/seed/drop_seed.sql @@ -0,0 +1,2 @@ +DROP TABLE users; +DROP TABLE posts; \ No newline at end of file diff --git a/seed/readme.md b/seed/readme.md new file mode 100644 index 0000000..17e4eee --- /dev/null +++ b/seed/readme.md @@ -0,0 +1,13 @@ + +### How To Use That Command + +You can put below commands to your projects Makefile like; + +- Migration: + `go build -o migrate ./seed/cmd/postgres && ./migrate -type=migrate && rm -rf migrate` +- Drop: + `go build -o drop ./seed/cmd/postgres && ./drop -type=drop && rm -rf drop` + +The Makefile command I use for that commands are `make migrate` and `make drop` + +Do not forget the set -type flag for your use purpose. diff --git a/seed/seed.go b/seed/seed.go new file mode 100644 index 0000000..ce65c35 --- /dev/null +++ b/seed/seed.go @@ -0,0 +1,29 @@ +package seed + +import ( + "context" + "database/sql" + _ "embed" +) + +//go:embed create_seed.sql +var createSeedSql string + +//go:embed drop_seed.sql +var dropSeedSql string + +type DB interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func MigrateTables(ctx context.Context, db DB) error { + _, err := db.ExecContext(ctx, createSeedSql) + return err +} +func DropTables(ctx context.Context, db DB) error { + _, err := db.ExecContext(ctx, dropSeedSql) + return err +} diff --git a/seed/seed_test_test.go b/seed/seed_test_test.go new file mode 100644 index 0000000..04a6921 --- /dev/null +++ b/seed/seed_test_test.go @@ -0,0 +1,34 @@ +package seed_test + +import ( + "context" + "github.com/DATA-DOG/go-sqlmock" + seed "../seed" + "github.com/stretchr/testify/assert" + "io/ioutil" + "regexp" + "testing" +) + +func TestMigrateTables(t *testing.T) { + db, mock, _ := sqlmock.New() + fileBytes, err := ioutil.ReadFile("./create_seed.sql") + assert.Nil(t, err) + mock.ExpectExec(regexp.QuoteMeta(string(fileBytes))).WillReturnResult(sqlmock.NewResult(1, 1)) + err = seed.MigrateTables(context.Background(), db) + assert.Nil(t, err) + err = mock.ExpectationsWereMet() + assert.Nil(t, err) +} + +func TestDropTables(t *testing.T) { + db, mock, _ := sqlmock.New() + fileBytes, err := ioutil.ReadFile("./drop_seed.sql") + assert.Nil(t, err) + mock.ExpectExec(regexp.QuoteMeta(string(fileBytes))). + WillReturnResult(sqlmock.NewResult(1, 1)) + err = seed.DropTables(context.Background(), db) + assert.Nil(t, err) + err = mock.ExpectationsWereMet() + assert.Nil(t, err) +}