From 242fb3d36108ca2de56a3f9d0339afe3c3c9233b Mon Sep 17 00:00:00 2001 From: "Stanislav N. aka pztrn" Date: Mon, 30 Apr 2018 22:31:48 +0500 Subject: [PATCH] DDD (Domain Design) partially implemented, added comments everywhere. --- api/echo_logger.go | 2 ++ api/exported.go | 2 ++ api/http/exported.go | 12 ++---------- api/http/index.go | 1 + api/json/exported.go | 1 + cmd/fastpastebin/fastpastebin.go | 3 +++ config/database.go | 1 + config/http.go | 1 + config/logging.go | 1 + config/struct.go | 1 + context/context.go | 17 ++++++++++++++++ context/exported.go | 1 + database/database.go | 5 +++++ database/exported.go | 1 + database/handler.go | 4 ++++ database/interface/databaseinterface.go | 2 ++ database/migrations/exported.go | 3 +++ api/http/paste.go => pastes/api_http.go | 15 ++++++++------ pastes/exported.go | 26 +++++++++++++++++++++++++ models/paste.go => pastes/model.go | 3 ++- 20 files changed, 85 insertions(+), 17 deletions(-) rename api/http/paste.go => pastes/api_http.go (93%) create mode 100644 pastes/exported.go rename models/paste.go => pastes/model.go (96%) diff --git a/api/echo_logger.go b/api/echo_logger.go index 3887692..e1f6c52 100644 --- a/api/echo_logger.go +++ b/api/echo_logger.go @@ -5,6 +5,7 @@ import ( "github.com/labstack/echo" ) +// Logs Echo requests. func echoReqLog(ec echo.Context, next echo.HandlerFunc) error { c.Logger.Info(). Str("IP", ec.RealIP()). @@ -18,6 +19,7 @@ func echoReqLog(ec echo.Context, next echo.HandlerFunc) error { return nil } +// Wrapper around previous function. func echoReqLogger() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { diff --git a/api/exported.go b/api/exported.go index 24a5d94..054017b 100644 --- a/api/exported.go +++ b/api/exported.go @@ -16,10 +16,12 @@ var ( e *echo.Echo ) +// New initializes variables for api package. func New(cc *context.Context) { c = cc } +// InitializeAPI initializes HTTP API and starts web server. func InitializeAPI() { c.Logger.Info().Msg("Initializing HTTP server...") diff --git a/api/http/exported.go b/api/http/exported.go index 29fa70c..c609874 100644 --- a/api/http/exported.go +++ b/api/http/exported.go @@ -13,6 +13,8 @@ var ( c *context.Context ) +// New initializes basic HTTP API, which shows only index page and serves +// static files. func New(cc *context.Context) { c = cc c.Logger.Info().Msg("Initializing HTTP API...") @@ -22,14 +24,4 @@ func New(cc *context.Context) { // Index. c.Echo.GET("/", indexGet) - - // New paste. - c.Echo.POST("/paste/", pastePOST) - - // Show paste. - c.Echo.GET("/paste/:id", pasteGET) - - // Pastes list. - c.Echo.GET("/pastes/", pastesGET) - c.Echo.GET("/pastes/:page", pastesGET) } diff --git a/api/http/index.go b/api/http/index.go index 6aee24c..b36d6c0 100644 --- a/api/http/index.go +++ b/api/http/index.go @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo" ) +// Index of this site. func indexGet(ec echo.Context) error { html, err := static.ReadFile("index.html") if err != nil { diff --git a/api/json/exported.go b/api/json/exported.go index 5b5bd29..ca6ee9e 100644 --- a/api/json/exported.go +++ b/api/json/exported.go @@ -9,6 +9,7 @@ var ( c *context.Context ) +// New initializes basic JSON API. func New(cc *context.Context) { c = cc c.Logger.Info().Msg("Initializing JSON API...") diff --git a/cmd/fastpastebin/fastpastebin.go b/cmd/fastpastebin/fastpastebin.go index d13527f..9b75ffb 100644 --- a/cmd/fastpastebin/fastpastebin.go +++ b/cmd/fastpastebin/fastpastebin.go @@ -11,6 +11,7 @@ import ( "github.com/pztrn/fastpastebin/context" "github.com/pztrn/fastpastebin/database" "github.com/pztrn/fastpastebin/database/migrations" + "github.com/pztrn/fastpastebin/pastes" ) func main() { @@ -34,6 +35,8 @@ func main() { api.New(c) api.InitializeAPI() + pastes.New(c) + // CTRL+C handler. signalHandler := make(chan os.Signal, 1) shutdownDone := make(chan bool, 1) diff --git a/config/database.go b/config/database.go index 7102678..43d6762 100644 --- a/config/database.go +++ b/config/database.go @@ -1,5 +1,6 @@ package config +// ConfigDatabase describes database configuration. type ConfigDatabase struct { Address string `yaml:"address"` Port string `yaml:"port"` diff --git a/config/http.go b/config/http.go index 8b3960d..24dd461 100644 --- a/config/http.go +++ b/config/http.go @@ -1,5 +1,6 @@ package config +// ConfigHTTP describes HTTP server configuration. type ConfigHTTP struct { Address string `yaml:"address"` Port string `yaml:"port"` diff --git a/config/logging.go b/config/logging.go index e4cfa5d..f82bc92 100644 --- a/config/logging.go +++ b/config/logging.go @@ -1,5 +1,6 @@ package config +// ConfigLogging describes logger configuration. type ConfigLogging struct { LogToFile bool `yaml:"log_to_file"` FileName string `yaml:"filename"` diff --git a/config/struct.go b/config/struct.go index 4c62116..be5f594 100644 --- a/config/struct.go +++ b/config/struct.go @@ -1,5 +1,6 @@ package config +// ConfigStruct describes whole configuration. type ConfigStruct struct { Database ConfigDatabase `yaml:"database"` Logging ConfigLogging `yaml:"logging"` diff --git a/context/context.go b/context/context.go index 1410cb8..ad94823 100644 --- a/context/context.go +++ b/context/context.go @@ -17,6 +17,10 @@ import ( "gopkg.in/yaml.v2" ) +// Context is a some sort of singleton. Basically it's a structure that +// initialized once and then passed to all parts of application. It +// contains everything every part of application need, like configuration +// access, logger, etc. type Context struct { Config *config.ConfigStruct Database databaseinterface.Interface @@ -25,6 +29,7 @@ type Context struct { Logger zerolog.Logger } +// Initialize initializes context. func (c *Context) Initialize() { c.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Caller().Logger() @@ -39,11 +44,17 @@ func (c *Context) Initialize() { }) } +// LoadConfiguration loads configuration and executes right after Flagger +// have parsed CLI flags, because it depends on "-config" defined in +// Initialize(). func (c *Context) LoadConfiguration() { c.Logger.Info().Msg("Loading configuration...") var configPath = "" + // We're accepting configuration path from "-config" CLI parameter + // and FASTPASTEBIN_CONFIG environment variable. Later have higher + // weight and can override "-config" value. configPathFromCLI, err := c.Flagger.GetStringValue("config") configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG") @@ -65,27 +76,33 @@ func (c *Context) LoadConfiguration() { c.Config = &config.ConfigStruct{} + // Read configuration file. fileData, err2 := ioutil.ReadFile(normalizedConfigPath) if err2 != nil { c.Logger.Panic().Msgf("Failed to read configuration file: %s", err2.Error()) } + // Parse it into structure. err3 := yaml.Unmarshal(fileData, c.Config) if err3 != nil { c.Logger.Panic().Msgf("Failed to parse configuration file: %s", err3.Error()) } + // Yay! See what it gets! c.Logger.Debug().Msgf("Parsed configuration: %+v", c.Config) } +// RegisterDatabaseInterface registers database interface for later use. func (c *Context) RegisterDatabaseInterface(di databaseinterface.Interface) { c.Database = di } +// RegisterEcho registers Echo instance for later usage. func (c *Context) RegisterEcho(e *echo.Echo) { c.Echo = e } +// Shutdown shutdowns entire application. func (c *Context) Shutdown() { c.Logger.Info().Msg("Shutting down Fast Pastebin...") } diff --git a/context/exported.go b/context/exported.go index 9847d19..e58d87b 100644 --- a/context/exported.go +++ b/context/exported.go @@ -1,5 +1,6 @@ package context +// New creates new context. func New() *Context { return &Context{} } diff --git a/database/database.go b/database/database.go index 3b66975..d8254d4 100644 --- a/database/database.go +++ b/database/database.go @@ -9,17 +9,22 @@ import ( "github.com/jmoiron/sqlx" ) +// Database represents control structure for database connection. type Database struct { db *sqlx.DB } +// GetDatabaseConnection returns current database connection. func (db *Database) GetDatabaseConnection() *sqlx.DB { return db.db } +// Initialize initializes connection to database. func (db *Database) Initialize() { c.Logger.Info().Msg("Initializing database connection...") + // There might be only user, without password. MySQL/MariaDB driver + // in DSN wants "user" or "user:password", "user:" is invalid. var userpass = "" if c.Config.Database.Password == "" { userpass = c.Config.Database.Username diff --git a/database/exported.go b/database/exported.go index 318a3f5..e60579e 100644 --- a/database/exported.go +++ b/database/exported.go @@ -11,6 +11,7 @@ var ( d *Database ) +// New initializes database structure. func New(cc *context.Context) { c = cc d = &Database{} diff --git a/database/handler.go b/database/handler.go index d778b3c..a3be8ab 100644 --- a/database/handler.go +++ b/database/handler.go @@ -5,12 +5,16 @@ import ( "github.com/jmoiron/sqlx" ) +// Handler is an interfaceable structure that proxifies calls from anyone +// to Database structure. type Handler struct{} +// GetDatabaseConnection returns current database connection. func (dbh Handler) GetDatabaseConnection() *sqlx.DB { return d.GetDatabaseConnection() } +// Initialize initializes connection to database. func (dbh Handler) Initialize() { d.Initialize() } diff --git a/database/interface/databaseinterface.go b/database/interface/databaseinterface.go index 3e39823..e96554c 100644 --- a/database/interface/databaseinterface.go +++ b/database/interface/databaseinterface.go @@ -5,6 +5,8 @@ import ( "github.com/jmoiron/sqlx" ) +// Interface represents database interface which is available to all +// parts of application and registers with context.Context. type Interface interface { GetDatabaseConnection() *sqlx.DB Initialize() diff --git a/database/migrations/exported.go b/database/migrations/exported.go index 5d7cc1e..f575b8d 100644 --- a/database/migrations/exported.go +++ b/database/migrations/exported.go @@ -13,15 +13,18 @@ var ( c *context.Context ) +// New initializes migrations. func New(cc *context.Context) { c = cc } +// Migrate launching migrations. func Migrate() { c.Logger.Info().Msg("Migrating database...") goose.SetDialect("mysql") goose.AddNamedMigration("1_initial.go", InitialUp, nil) + // Add new migrations BEFORE this message. dbConn := c.Database.GetDatabaseConnection() err := goose.Up(dbConn.DB, ".") diff --git a/api/http/paste.go b/pastes/api_http.go similarity index 93% rename from api/http/paste.go rename to pastes/api_http.go index d6818ba..4ddeee0 100644 --- a/api/http/paste.go +++ b/pastes/api_http.go @@ -1,4 +1,4 @@ -package http +package pastes import ( // stdlib @@ -10,7 +10,6 @@ import ( // local "github.com/pztrn/fastpastebin/api/http/static" - "github.com/pztrn/fastpastebin/models" // other "github.com/labstack/echo" @@ -20,6 +19,7 @@ var ( regexInts = regexp.MustCompile("[0-9]+") ) +// GET for "/paste/PASTE_ID". func pasteGET(ec echo.Context) error { errhtml, err := static.ReadFile("error.html") if err != nil { @@ -33,7 +33,7 @@ func pasteGET(ec echo.Context) error { c.Logger.Debug().Msgf("Requesting paste #%+v", pasteID) // Get paste. - paste := &models.Paste{ID: pasteID} + paste := &Paste{ID: pasteID} err1 := paste.GetByID(c.Database.GetDatabaseConnection()) if err1 != nil { c.Logger.Error().Msgf("Failed to get paste #%d from database: %s", pasteID, err1.Error()) @@ -50,6 +50,8 @@ func pasteGET(ec echo.Context) error { return ec.HTML(http.StatusOK, string(pasteHTMLAsString)) } +// POST for "/paste/" which will create new paste and redirect to +// "/pastes/CREATED_PASTE_ID". func pastePOST(ec echo.Context) error { errhtml, err := static.ReadFile("error.html") if err != nil { @@ -68,7 +70,7 @@ func pastePOST(ec echo.Context) error { return ec.HTML(http.StatusBadRequest, string(errhtml)) } - paste := &models.Paste{ + paste := &Paste{ Title: params["paste-title"][0], Data: params["paste-contents"][0], } @@ -91,7 +93,7 @@ func pastePOST(ec echo.Context) error { paste.KeepFor = keepFor keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0] - keepForUnit := models.PASTE_KEEPS_CORELLATION[keepForUnitRaw] + keepForUnit := PASTE_KEEPS_CORELLATION[keepForUnitRaw] paste.KeepForUnitType = keepForUnit id, err2 := paste.Save(c.Database.GetDatabaseConnection()) @@ -105,6 +107,7 @@ func pastePOST(ec echo.Context) error { return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString) } +// GET for "/pastes/", a list of publicly available pastes. func pastesGET(ec echo.Context) error { pasteListHTML, err1 := static.ReadFile("pastelist_list.html") if err1 != nil { @@ -125,7 +128,7 @@ func pastesGET(ec echo.Context) error { c.Logger.Debug().Msgf("Requested page #%d", page) - p := &models.Paste{} + p := &Paste{} // Get pastes IDs. pastes, err3 := p.GetPagedPastes(c.Database.GetDatabaseConnection(), page) c.Logger.Debug().Msgf("Got %d pastes", len(pastes)) diff --git a/pastes/exported.go b/pastes/exported.go new file mode 100644 index 0000000..3148a32 --- /dev/null +++ b/pastes/exported.go @@ -0,0 +1,26 @@ +package pastes + +import ( + // local + "github.com/pztrn/fastpastebin/context" +) + +var ( + c *context.Context +) + +// New initializes pastes package and adds neccessary HTTP and API +// endpoints. +func New(cc *context.Context) { + c = cc + + // New paste. + c.Echo.POST("/paste/", pastePOST) + + // Show paste. + c.Echo.GET("/paste/:id", pasteGET) + + // Pastes list. + c.Echo.GET("/pastes/", pastesGET) + c.Echo.GET("/pastes/:page", pastesGET) +} diff --git a/models/paste.go b/pastes/model.go similarity index 96% rename from models/paste.go rename to pastes/model.go index ee8efc4..7871765 100644 --- a/models/paste.go +++ b/pastes/model.go @@ -1,4 +1,4 @@ -package models +package pastes import ( // stdlib @@ -24,6 +24,7 @@ var ( } ) +// Paste represents paste itself. type Paste struct { ID int `db:"id"` Title string `db:"title"`