diff --git a/hw12_13_14_15_calendar/Makefile b/hw12_13_14_15_calendar/Makefile index 733d4d1..93a3436 100644 --- a/hw12_13_14_15_calendar/Makefile +++ b/hw12_13_14_15_calendar/Makefile @@ -1,13 +1,16 @@ build: - go build -o ./bin/calendar ./cmd/calendar + go build -o ./bin/calendar ./cmd/calendar/main.go + +run: + go run ./cmd/calendar/main.go -config ./configs/config.toml test: - go test -race ./internal/... ./pkg/... - -install-lint-deps: - (which golangci-lint > /dev/null) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0 + go test -race ./internal/... lint: install-lint-deps - golangci-lint run ./... + golangci-lint run .cmd/... ./internal/... -.PHONY: build test lint +install-lint-deps: + (which golangci-lint > /dev/null) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.30.0 + +.PHONY: build run test lint \ No newline at end of file diff --git a/hw12_13_14_15_calendar/cmd/calendar/config.go b/hw12_13_14_15_calendar/cmd/calendar/config.go deleted file mode 100644 index 1d35a47..0000000 --- a/hw12_13_14_15_calendar/cmd/calendar/config.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -// При желании конфигурацию можно вынести в internal/config. -// Организация конфига в main принуждает нас сужать API компонентов, использовать -// при их конструировании только необходимые параметры, а также уменьшает вероятность циклической зависимости. -type Config struct { - // TODO -} - -func NewConfig() Config { - return Config{} -} - -// TODO diff --git a/hw12_13_14_15_calendar/cmd/calendar/main.go b/hw12_13_14_15_calendar/cmd/calendar/main.go index ceaa900..ecef853 100644 --- a/hw12_13_14_15_calendar/cmd/calendar/main.go +++ b/hw12_13_14_15_calendar/cmd/calendar/main.go @@ -2,29 +2,47 @@ package main import ( "flag" + oslog "log" "os" "os/signal" - "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/app" - "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/logger" - internalhttp "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/server/http" - memorystorage "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/storage/memory" + _ "github.com/go-sql-driver/mysql" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/app" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/config" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/logger" + internalhttp "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/server/http" + store "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage" ) var configFile string func init() { flag.StringVar(&configFile, "config", "/etc/calendar/config.toml", "Path to configuration file") + flag.Parse() } func main() { - config := NewConfig() - logg := logger.New(config.Logger.Level) + conf, err := config.NewConfig(configFile) + if err != nil { + oslog.Fatal("не удалось открыть файл конфигурации:", err.Error()) + } + log, err := logger.New(conf) + if err != nil { + oslog.Fatal("не удалось запустить логер:", err.Error()) + } + storeConf := store.Config{ + InMemory: conf.Storage.InMemory, + SQLHost: conf.Storage.SQLHost, + SQLPort: conf.Storage.SQLPort, + SQLDbase: conf.Storage.SQLDbase, + SQLUser: conf.Storage.SQLUser, + SQLPass: conf.Storage.SQLPass, + } + st := store.NewStore(storeConf) - storage := memorystorage.New() - calendar := app.New(logg, storage) + calendar := app.New(log, st) - server := internalhttp.NewServer(calendar) + server := internalhttp.NewServer(calendar, conf.Server.Address, conf.Server.Port) go func() { signals := make(chan os.Signal, 1) @@ -34,12 +52,12 @@ func main() { signal.Stop(signals) if err := server.Stop(); err != nil { - logger.Error("failed to stop http server: " + err.String()) + log.Errorf("failed to stop http server: " + err.Error()) } }() if err := server.Start(); err != nil { - logger.Error("failed to start http server: " + err.String()) + log.Errorf("failed to start http server: " + err.Error()) os.Exit(1) } } diff --git a/hw12_13_14_15_calendar/configs/config.toml b/hw12_13_14_15_calendar/configs/config.toml index 5b33eb0..af65fa9 100644 --- a/hw12_13_14_15_calendar/configs/config.toml +++ b/hw12_13_14_15_calendar/configs/config.toml @@ -1,5 +1,15 @@ -[logger] -level = "INFO" +[Server] +Address = "localhost" +Port = "8080" -# TODO -# ... +[Logger] +File = "./calendar.log" +Level = "INFO" + +[Storage] +inMemory = false +SQLHost = "localhost" +SQLPort = "5432" +SQLDbase = "calendar" +SQLUser = "calendar" +SQLPass = "12345678" \ No newline at end of file diff --git a/hw12_13_14_15_calendar/go.mod b/hw12_13_14_15_calendar/go.mod index 3eb18a3..60e7d9b 100644 --- a/hw12_13_14_15_calendar/go.mod +++ b/hw12_13_14_15_calendar/go.mod @@ -1,3 +1,13 @@ -module github.com/fixme_my_friend/hw12_13_14_15_calendar +module github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar go 1.14 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/amitrai48/logger v0.0.0-20190214092904-448001c055ec + github.com/go-sql-driver/mysql v1.5.0 + github.com/mattn/go-shellwords v1.0.10 // indirect + github.com/stretchr/testify v1.4.0 + go.uber.org/zap v1.15.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) diff --git a/hw12_13_14_15_calendar/internal/app/app.go b/hw12_13_14_15_calendar/internal/app/app.go index bbbdaea..ffed7f0 100644 --- a/hw12_13_14_15_calendar/internal/app/app.go +++ b/hw12_13_14_15_calendar/internal/app/app.go @@ -2,28 +2,57 @@ package app import ( "context" + "net/http" + "time" - "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/storage" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/logger" + store "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/event" ) +type Interface interface { + CreateEvent(ctx context.Context, title string) (err error) + Handler(w http.ResponseWriter, r *http.Request) + LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc +} + type App struct { - // TODO + Storage store.StorageInterface + Logger logger.Interface } -type Logger interface { - // TODO +func New(logger logger.Interface, storage store.StorageInterface) *App { + return &App{Logger: logger, Storage: storage} } -type Storage interface { - // TODO +func (a *App) CreateEvent(ctx context.Context, title string) (err error) { + _, err = a.Storage.Create(event.Event{Title: title}) + if err != nil { + a.Logger.Errorf("can not create event") + } + a.Logger.Infof("event created") + return err } -func New(logger Logger, storage Storage) *App { - return &App{} +func (a *App) Handler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + _, _ = w.Write([]byte("Hello! I'm calendar app!")) } -func (a *App) CreateEvent(ctx context.Context, id string, title string) error { - return a.storage.CreateEvent(storage.Event{ID: id, Title: title}) +func (a *App) LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + defer func() { + var path, useragent string + if r.URL != nil { + path = r.URL.Path + } + if len(r.UserAgent()) > 0 { + useragent = r.UserAgent() + } + latency := time.Since(start) + a.Logger.Infof("receive %s request from IP: %s on path: %s, duration: %s useragent: %s ", r.Method, r.RemoteAddr, path, latency, useragent) + }() + next.ServeHTTP(w, r) + }) } - -// TODO diff --git a/hw12_13_14_15_calendar/internal/config/config.go b/hw12_13_14_15_calendar/internal/config/config.go new file mode 100644 index 0000000..fbf34fd --- /dev/null +++ b/hw12_13_14_15_calendar/internal/config/config.go @@ -0,0 +1,44 @@ +package config + +import ( + "io/ioutil" + "os" + + "github.com/BurntSushi/toml" +) + +type Config struct { + Server struct { + Address string + Port string + } + Logger struct { + File string + Level string + MuteStdout bool + } + Storage struct { + InMemory bool + SQLHost string + SQLPort string + SQLDbase string + SQLUser string + SQLPass string + } +} + +// Confita может быти и хороша, но она не возвращает ошибки, если не может распарсить файл в структуру. Мне не нравится такая "молчаливость". +func NewConfig(configFile string) (Config, error) { + f, err := os.Open(configFile) + if err != nil { + return Config{}, err + } + defer f.Close() + s, err := ioutil.ReadAll(f) + if err != nil { + return Config{}, err + } + var config Config + _, err = toml.Decode(string(s), &config) + return config, err +} diff --git a/hw12_13_14_15_calendar/internal/config/config_test.go b/hw12_13_14_15_calendar/internal/config/config_test.go new file mode 100644 index 0000000..3fdd126 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/config/config_test.go @@ -0,0 +1,58 @@ +package config + +import ( + "github.com/stretchr/testify/require" + "io/ioutil" + oslog "log" + "os" + "testing" +) + +var _ = func() bool { + testing.Init() + return true +}() + +func TestNewConfig(t *testing.T) { + + badfile, err := ioutil.TempFile("", "conf.") + if err != nil { + oslog.Fatal(err) + } + defer os.Remove(badfile.Name()) + badfile.WriteString(`aefSD +sadfg +RFABND FYGUMG +V`) + badfile.Sync() + + goodfile, err := ioutil.TempFile("", "conf.") + if err != nil { + oslog.Fatal(err) + } + defer os.Remove(goodfile.Name()) + goodfile.WriteString(`[storage] +inMemory = true +SQLHost = "localhost"`) + goodfile.Sync() + + t.Run("No such file", func(t *testing.T) { + c, e := NewConfig("adfergdth") + require.Equal(t, Config{}, c) + require.Error(t, e) + }) + + t.Run("Bad file", func(t *testing.T) { + c, e := NewConfig(badfile.Name()) + require.Equal(t, Config{}, c) + require.Error(t, e) + }) + + t.Run("TOML reading", func(t *testing.T) { + c, e := NewConfig(goodfile.Name()) + require.Equal(t, true, c.Storage.InMemory) + require.Equal(t, "localhost", c.Storage.SQLHost) + require.NoError(t, e) + }) + +} diff --git a/hw12_13_14_15_calendar/internal/logger/logger.go b/hw12_13_14_15_calendar/internal/logger/logger.go index bdba951..b2482dc 100644 --- a/hw12_13_14_15_calendar/internal/logger/logger.go +++ b/hw12_13_14_15_calendar/internal/logger/logger.go @@ -1,11 +1,71 @@ package logger +import ( + "errors" + "log" + "os" + "strings" + + amitralog "github.com/amitrai48/logger" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/config" +) + +type Interface interface { + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) +} + type Logger struct { - // TODO + Logger amitralog.Logger } -func New(level string) *Logger { - return &Logger{} +func New(conf config.Config) (Interface, error) { + if conf.Logger.File == "" || !validLevel(conf.Logger.Level) { + return nil, errors.New("invalid logger config") + } + + c := amitralog.Configuration{ + EnableConsole: !conf.Logger.MuteStdout, + ConsoleLevel: amitralog.Fatal, + ConsoleJSONFormat: false, + EnableFile: true, + FileLevel: strings.ToLower(conf.Logger.Level), + FileJSONFormat: true, + FileLocation: conf.Logger.File, + } + + if err := amitralog.NewLogger(c, amitralog.InstanceZapLogger); err != nil { + log.Fatalf("Could not instantiate log %s", err.Error()) + } + l := amitralog.WithFields(amitralog.Fields{"hw": "12"}) + return l, nil } -// TODO +func (l *Logger) Debugf(format string, args ...interface{}) { + l.Logger.Debugf(format, args) +} + +func (l *Logger) Infof(format string, args ...interface{}) { + l.Logger.Infof(format, args) +} + +func (l *Logger) Warnf(format string, args ...interface{}) { + l.Logger.Warnf(format, args) +} + +func (l *Logger) Errorf(format string, args ...interface{}) { + l.Logger.Errorf(format, args) +} + +func (l *Logger) Fatalf(format string, args ...interface{}) { + l.Logger.Fatalf(format, args) + os.Exit(2) +} + +func validLevel(level string) bool { + var l = map[string]int{"debug": 1, "info": 1, "warn": 1, "error": 1, "fatal": 1} + return l[strings.ToLower(level)] == 1 +} diff --git a/hw12_13_14_15_calendar/internal/logger/logger_test.go b/hw12_13_14_15_calendar/internal/logger/logger_test.go index 34bb72c..3efbd7d 100644 --- a/hw12_13_14_15_calendar/internal/logger/logger_test.go +++ b/hw12_13_14_15_calendar/internal/logger/logger_test.go @@ -1,7 +1,63 @@ package logger -import "testing" +import ( + "github.com/stretchr/testify/require" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/config" + "io/ioutil" + oslog "log" + "os" + "strings" + "testing" +) -func TestLogger(t *testing.T) { - // TODO +func TestLoggerLogic(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "log.") + if err != nil { + oslog.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + conf := config.Config{Logger: struct { + File string + Level string + MuteStdout bool + }{File: tmpfile.Name(), Level: "warn", MuteStdout: false}} + log, err := New(conf) + if err != nil { + oslog.Fatal(err) + } + + t.Run("Messages arround the level", func(t *testing.T) { + log.Debugf("debug message") + log.Errorf("error message") + + res, err := ioutil.ReadAll(tmpfile) + if err != nil { + oslog.Fatal(err) + } + require.Less(t, strings.Index(string(res), "debug message"), 0) + require.Greater(t, strings.Index(string(res), "error message"), 0) + }) +} + +func TestLoggerNegative(t *testing.T) { + t.Run("Bad file name", func(t *testing.T) { + conf := config.Config{Logger: struct { + File string + Level string + MuteStdout bool + }{File: "", Level: "debug", MuteStdout: true}} + _, err := New(conf) + require.Error(t, err, "invalid logger config") + }) + + t.Run("Bad level", func(t *testing.T) { + conf := config.Config{Logger: struct { + File string + Level string + MuteStdout bool + }{File: "asdafad", Level: "wegretryjt", MuteStdout: true}} + _, err := New(conf) + require.Error(t, err, "invalid logger config") + }) } diff --git a/hw12_13_14_15_calendar/internal/server/http/middleware.go b/hw12_13_14_15_calendar/internal/server/http/middleware.go deleted file mode 100644 index 803cade..0000000 --- a/hw12_13_14_15_calendar/internal/server/http/middleware.go +++ /dev/null @@ -1,11 +0,0 @@ -package internalhttp - -import ( - "net/http" -) - -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO - }) -} diff --git a/hw12_13_14_15_calendar/internal/server/http/server.go b/hw12_13_14_15_calendar/internal/server/http/server.go index cdbefb8..3ae6cd2 100644 --- a/hw12_13_14_15_calendar/internal/server/http/server.go +++ b/hw12_13_14_15_calendar/internal/server/http/server.go @@ -1,26 +1,33 @@ -package internalhttp +package http -import "context" +import ( + "net" + "net/http" + + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/app" +) type Server struct { - // TODO + server *http.Server + app app.App } -type Application interface { - // TODO -} - -func NewServer(app Application) *Server { - return &Server{} +func NewServer(app *app.App, address string, port string) *Server { + return &Server{server: &http.Server{Addr: net.JoinHostPort(address, port), Handler: app.LoggingMiddleware(app.Handler)}, app: *app} } func (s *Server) Start() error { - // TODO + if err := s.server.ListenAndServe(); err != nil { + return err + } + s.app.Logger.Infof("Server starting") + return nil } func (s *Server) Stop() error { - ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) - // TODO + if err := s.server.Close(); err != nil { + return err + } + s.app.Logger.Infof("Server stoped") + return nil } - -// TODO diff --git a/hw12_13_14_15_calendar/internal/storage/event.go b/hw12_13_14_15_calendar/internal/storage/event.go deleted file mode 100644 index 39b2d1a..0000000 --- a/hw12_13_14_15_calendar/internal/storage/event.go +++ /dev/null @@ -1,7 +0,0 @@ -package storage - -type Event struct { - ID string - Title string - // TODO -} diff --git a/hw12_13_14_15_calendar/internal/storage/event/event.go b/hw12_13_14_15_calendar/internal/storage/event/event.go new file mode 100644 index 0000000..1c4b4a8 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/event/event.go @@ -0,0 +1,16 @@ +package event + +import ( + "time" +) + +type ID int64 + +type Event struct { + Title string + Date time.Time + Latency time.Duration + Note string + UserID int64 + NotifyTime time.Duration +} diff --git a/hw12_13_14_15_calendar/internal/storage/memory/memory.go b/hw12_13_14_15_calendar/internal/storage/memory/memory.go new file mode 100644 index 0000000..48ad73a --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/memory/memory.go @@ -0,0 +1,50 @@ +package memory + +import ( + "sync" + + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/event" +) + +type Storage struct { + Events map[event.ID]event.Event + lastID event.ID + Mu sync.RWMutex +} + +func New() *Storage { + return &Storage{Events: make(map[event.ID]event.Event)} +} + +func (s *Storage) Create(event event.Event) (event.ID, error) { + s.Mu.Lock() + s.lastID++ + s.Events[s.lastID] = event + s.Mu.Unlock() + return s.lastID, nil +} + +func (s *Storage) Update(id event.ID, event event.Event) error { + s.Mu.Lock() + s.Events[id] = event + s.Mu.Unlock() + return nil +} + +func (s *Storage) Delete(id event.ID) error { + s.Mu.Lock() + delete(s.Events, id) + s.Mu.Unlock() + return nil +} + +func (s *Storage) List() (map[event.ID]event.Event, error) { + return s.Events, nil +} + +func (s *Storage) GetByID(id event.ID) (event.Event, bool) { + if s.Events[id].Title == "" { + return event.Event{}, false + } + return s.Events[id], true +} diff --git a/hw12_13_14_15_calendar/internal/storage/memory/memory_test.go b/hw12_13_14_15_calendar/internal/storage/memory/memory_test.go new file mode 100644 index 0000000..5735453 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/memory/memory_test.go @@ -0,0 +1,60 @@ +package memory + +import ( + "github.com/stretchr/testify/require" + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/event" + "testing" + "time" +) + +const dateTimeLayout = "2006-01-02 15:04:00 -0700" + +func TestMemoryStorage(t *testing.T) { + s := New() + dateParced1, _ := time.Parse(dateTimeLayout, "11.11.1111") + dateParced2, _ := time.Parse(dateTimeLayout, "22.11.22222") + + t.Run("Empty storage", func(t *testing.T) { + require.Equal(t, 0, len(s.Events)) + }) + id, err := s.Create(event.Event{Title: "event1", Date: dateParced1}) + + t.Run("Create events", func(t *testing.T) { + require.NoError(t, err) + require.Equal(t, 1, len(s.Events)) + require.Equal(t, event.Event{Title: "event1", Date: dateParced1}, s.Events[id]) + }) + + t.Run("Update event", func(t *testing.T) { + err := s.Update(id, event.Event{Title: "event1_modifyed", Date: dateParced2}) + require.NoError(t, err) + require.Equal(t, 1, len(s.Events)) + require.Equal(t, event.Event{Title: "event1_modifyed", Date: dateParced2}, s.Events[id]) + }) + + t.Run("List event", func(t *testing.T) { + res, err := s.List() + require.NoError(t, err) + require.Equal(t, 1, len(res)) + require.Equal(t, event.Event{Title: "event1_modifyed", Date: dateParced2}, res[id]) + }) + + t.Run("Get event by ID", func(t *testing.T) { + res, ok := s.GetByID(id) + require.Equal(t, ok, true) + require.Equal(t, event.Event{Title: "event1_modifyed", Date: dateParced2}, res) + }) + + t.Run("Get event by fake ID", func(t *testing.T) { + res, ok := s.GetByID(53663) + require.Equal(t, ok, false) + require.Equal(t, event.Event{}, res) + }) + + t.Run("Delete event", func(t *testing.T) { + err := s.Delete(id) + require.NoError(t, err) + require.Equal(t, 0, len(s.Events)) + }) + +} diff --git a/hw12_13_14_15_calendar/internal/storage/memory/storage.go b/hw12_13_14_15_calendar/internal/storage/memory/storage.go deleted file mode 100644 index bbc033d..0000000 --- a/hw12_13_14_15_calendar/internal/storage/memory/storage.go +++ /dev/null @@ -1,14 +0,0 @@ -package memorystorage - -import "sync" - -type Storage struct { - // TODO - mu sync.RWMutex -} - -func New() *Storage { - return &Storage{} -} - -// TODO diff --git a/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go b/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go deleted file mode 100644 index 7e303bc..0000000 --- a/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package memorystorage - -import "testing" - -func TestStorage(t *testing.T) { - // TODO -} diff --git a/hw12_13_14_15_calendar/internal/storage/sql/sql.go b/hw12_13_14_15_calendar/internal/storage/sql/sql.go new file mode 100644 index 0000000..4476f30 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/sql/sql.go @@ -0,0 +1,126 @@ +package sql + +import ( + "database/sql" + "time" + + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/event" +) + +const dateTimeLayout = "2006-01-02 15:04:00 -0700" + +type Config struct { + User string + Pass string + Host string + Port string + Dbase string +} + +type Storage struct { + db *sql.DB +} + +func New(conf Config) *Storage { + return &Storage{} +} + +func (s *Storage) Connect(config Config) error { + var err error + s.db, err = sql.Open("mysql", config.User+":"+config.Pass+"@tcp("+config.Host+":"+config.Port+")/"+config.Dbase) + if err != nil { + return err + } + return nil +} + +func (s *Storage) Close() error { + return s.db.Close() +} + +func (s *Storage) Create(ev event.Event) (event.ID, error) { + res, err := s.db.Exec( + `INSERT INTO events + (title, date, latency, note, userID, notifyTime) VALUES + ($1, $2, $3, $4, $5, $6)`, + ev.Title, + ev.Date.Format(dateTimeLayout), + ev.Latency, + ev.Note, + ev.UserID, + ev.NotifyTime, + ) + if err != nil { + return -1, err + } + idint64, err := res.LastInsertId() + return event.ID(idint64), err +} + +func (s *Storage) Update(id event.ID, event event.Event) error { + _, err := s.db.Exec( + `UPDATE events set + title=$1, date=$2, latency=$3, note=$4, userID=$5, notifyTime=$6 + where id=$7`, + event.Title, + event.Date.Format(dateTimeLayout), + event.Latency, + event.Note, + event.UserID, + event.NotifyTime, + id, + ) + return err +} + +func (s *Storage) Delete(id event.ID) error { + _, err := s.db.Exec( + `DELETE from events where id=$1`, + id, + ) + return err +} + +func (s *Storage) List() (map[event.ID]event.Event, error) { + res := make(map[event.ID]event.Event) + results, err := s.db.Query( + `SELECT (id,title,date,latency,note,userID,notifyTime) from events ORDER BY id`) + if err != nil { + return map[event.ID]event.Event{}, err + } + defer results.Close() + for results.Next() { + var id event.ID + var evt event.Event + var dateRaw string + err = results.Scan(&id, &evt.Title, &dateRaw, &evt.Latency, &evt.Note, &evt.UserID, &evt.NotifyTime) + if err != nil { + return map[event.ID]event.Event{}, err + } + evt.Date, err = time.Parse(dateTimeLayout, dateRaw) + if err != nil { + return map[event.ID]event.Event{}, err + } + res[id] = evt + } + if results.Err() != nil { + return map[event.ID]event.Event{}, results.Err() + } + return res, nil +} + +func (s *Storage) GetByID(id event.ID) (event.Event, bool) { + var res event.Event + var dateRaw string + err := s.db.QueryRow( + `SELECT (id,title,date,latency,note,userID,notifyTime) from events where id=$1`, id).Scan(res.Title, dateRaw, res.Latency, res.Note, res.UserID, res.NotifyTime) + if err != nil { + return res, false + } + dateParced, err := time.Parse(dateTimeLayout, dateRaw) + if err != nil { + return res, false + } + res.Date = dateParced + return res, true +} diff --git a/hw12_13_14_15_calendar/internal/storage/sql/storage.go b/hw12_13_14_15_calendar/internal/storage/sql/storage.go deleted file mode 100644 index 5845045..0000000 --- a/hw12_13_14_15_calendar/internal/storage/sql/storage.go +++ /dev/null @@ -1,19 +0,0 @@ -package sqlstorage - -import "context" - -type Storage struct { - // TODO -} - -func New() *Storage { - return &Storage{} -} - -func (s *Storage) Connect(ctx context.Context) error { - // TODO -} - -func (s *Storage) Close(ctx context.Context) error { - // TODO -} diff --git a/hw12_13_14_15_calendar/internal/storage/store.go b/hw12_13_14_15_calendar/internal/storage/store.go new file mode 100644 index 0000000..5341c20 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/store.go @@ -0,0 +1,39 @@ +package store + +import ( + "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/event" + memorystorage "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/memory" + sqlstorage "github.com/tiburon-777/HW_OTUS/hw12_13_14_15_calendar/internal/storage/sql" +) + +type Config struct { + InMemory bool + SQLHost string + SQLPort string + SQLDbase string + SQLUser string + SQLPass string +} + +type StorageInterface interface { + Create(event event.Event) (event.ID, error) + Update(id event.ID, event event.Event) error + Delete(id event.ID) error + List() (map[event.ID]event.Event, error) + GetByID(id event.ID) (event.Event, bool) +} + +func NewStore(conf Config) StorageInterface { + if conf.InMemory { + st := memorystorage.New() + return st + } + sqlConf := sqlstorage.Config{ + Host: conf.SQLHost, + Port: conf.SQLPort, + Dbase: conf.SQLDbase, + User: conf.SQLUser, + Pass: conf.SQLPass, + } + return sqlstorage.New(sqlConf) +} diff --git a/hw12_13_14_15_calendar/migrations/20200924000000_init.sql b/hw12_13_14_15_calendar/migrations/20200924000000_init.sql new file mode 100644 index 0000000..c048c95 --- /dev/null +++ b/hw12_13_14_15_calendar/migrations/20200924000000_init.sql @@ -0,0 +1,18 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE events ( + id int(16) NOT NULL AUTO_INCREMENT, + title varchar(255) NOT NULL, + date datetime NOT NULL, + latency int(16) NOT NULL, + note text, + userID int(16), + notifyTime int(16) + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8;; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE events; +-- +goose StatementEnd