DDD (Domain Design) partially implemented, added comments everywhere.

This commit is contained in:
Stanislav Nikitin 2018-04-30 22:31:48 +05:00
parent 3456ecd312
commit 242fb3d361
20 changed files with 85 additions and 17 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
) )
// Logs Echo requests.
func echoReqLog(ec echo.Context, next echo.HandlerFunc) error { func echoReqLog(ec echo.Context, next echo.HandlerFunc) error {
c.Logger.Info(). c.Logger.Info().
Str("IP", ec.RealIP()). Str("IP", ec.RealIP()).
@ -18,6 +19,7 @@ func echoReqLog(ec echo.Context, next echo.HandlerFunc) error {
return nil return nil
} }
// Wrapper around previous function.
func echoReqLogger() echo.MiddlewareFunc { func echoReqLogger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {

View File

@ -16,10 +16,12 @@ var (
e *echo.Echo e *echo.Echo
) )
// New initializes variables for api package.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
} }
// InitializeAPI initializes HTTP API and starts web server.
func InitializeAPI() { func InitializeAPI() {
c.Logger.Info().Msg("Initializing HTTP server...") c.Logger.Info().Msg("Initializing HTTP server...")

View File

@ -13,6 +13,8 @@ var (
c *context.Context c *context.Context
) )
// New initializes basic HTTP API, which shows only index page and serves
// static files.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
c.Logger.Info().Msg("Initializing HTTP API...") c.Logger.Info().Msg("Initializing HTTP API...")
@ -22,14 +24,4 @@ func New(cc *context.Context) {
// Index. // Index.
c.Echo.GET("/", indexGet) 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)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
) )
// Index of this site.
func indexGet(ec echo.Context) error { func indexGet(ec echo.Context) error {
html, err := static.ReadFile("index.html") html, err := static.ReadFile("index.html")
if err != nil { if err != nil {

View File

@ -9,6 +9,7 @@ var (
c *context.Context c *context.Context
) )
// New initializes basic JSON API.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
c.Logger.Info().Msg("Initializing JSON API...") c.Logger.Info().Msg("Initializing JSON API...")

View File

@ -11,6 +11,7 @@ import (
"github.com/pztrn/fastpastebin/context" "github.com/pztrn/fastpastebin/context"
"github.com/pztrn/fastpastebin/database" "github.com/pztrn/fastpastebin/database"
"github.com/pztrn/fastpastebin/database/migrations" "github.com/pztrn/fastpastebin/database/migrations"
"github.com/pztrn/fastpastebin/pastes"
) )
func main() { func main() {
@ -34,6 +35,8 @@ func main() {
api.New(c) api.New(c)
api.InitializeAPI() api.InitializeAPI()
pastes.New(c)
// CTRL+C handler. // CTRL+C handler.
signalHandler := make(chan os.Signal, 1) signalHandler := make(chan os.Signal, 1)
shutdownDone := make(chan bool, 1) shutdownDone := make(chan bool, 1)

View File

@ -1,5 +1,6 @@
package config package config
// ConfigDatabase describes database configuration.
type ConfigDatabase struct { type ConfigDatabase struct {
Address string `yaml:"address"` Address string `yaml:"address"`
Port string `yaml:"port"` Port string `yaml:"port"`

View File

@ -1,5 +1,6 @@
package config package config
// ConfigHTTP describes HTTP server configuration.
type ConfigHTTP struct { type ConfigHTTP struct {
Address string `yaml:"address"` Address string `yaml:"address"`
Port string `yaml:"port"` Port string `yaml:"port"`

View File

@ -1,5 +1,6 @@
package config package config
// ConfigLogging describes logger configuration.
type ConfigLogging struct { type ConfigLogging struct {
LogToFile bool `yaml:"log_to_file"` LogToFile bool `yaml:"log_to_file"`
FileName string `yaml:"filename"` FileName string `yaml:"filename"`

View File

@ -1,5 +1,6 @@
package config package config
// ConfigStruct describes whole configuration.
type ConfigStruct struct { type ConfigStruct struct {
Database ConfigDatabase `yaml:"database"` Database ConfigDatabase `yaml:"database"`
Logging ConfigLogging `yaml:"logging"` Logging ConfigLogging `yaml:"logging"`

View File

@ -17,6 +17,10 @@ import (
"gopkg.in/yaml.v2" "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 { type Context struct {
Config *config.ConfigStruct Config *config.ConfigStruct
Database databaseinterface.Interface Database databaseinterface.Interface
@ -25,6 +29,7 @@ type Context struct {
Logger zerolog.Logger Logger zerolog.Logger
} }
// Initialize initializes context.
func (c *Context) Initialize() { func (c *Context) Initialize() {
c.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Caller().Logger() 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() { func (c *Context) LoadConfiguration() {
c.Logger.Info().Msg("Loading configuration...") c.Logger.Info().Msg("Loading configuration...")
var configPath = "" 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") configPathFromCLI, err := c.Flagger.GetStringValue("config")
configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG") configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG")
@ -65,27 +76,33 @@ func (c *Context) LoadConfiguration() {
c.Config = &config.ConfigStruct{} c.Config = &config.ConfigStruct{}
// Read configuration file.
fileData, err2 := ioutil.ReadFile(normalizedConfigPath) fileData, err2 := ioutil.ReadFile(normalizedConfigPath)
if err2 != nil { if err2 != nil {
c.Logger.Panic().Msgf("Failed to read configuration file: %s", err2.Error()) c.Logger.Panic().Msgf("Failed to read configuration file: %s", err2.Error())
} }
// Parse it into structure.
err3 := yaml.Unmarshal(fileData, c.Config) err3 := yaml.Unmarshal(fileData, c.Config)
if err3 != nil { if err3 != nil {
c.Logger.Panic().Msgf("Failed to parse configuration file: %s", err3.Error()) 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) c.Logger.Debug().Msgf("Parsed configuration: %+v", c.Config)
} }
// RegisterDatabaseInterface registers database interface for later use.
func (c *Context) RegisterDatabaseInterface(di databaseinterface.Interface) { func (c *Context) RegisterDatabaseInterface(di databaseinterface.Interface) {
c.Database = di c.Database = di
} }
// RegisterEcho registers Echo instance for later usage.
func (c *Context) RegisterEcho(e *echo.Echo) { func (c *Context) RegisterEcho(e *echo.Echo) {
c.Echo = e c.Echo = e
} }
// Shutdown shutdowns entire application.
func (c *Context) Shutdown() { func (c *Context) Shutdown() {
c.Logger.Info().Msg("Shutting down Fast Pastebin...") c.Logger.Info().Msg("Shutting down Fast Pastebin...")
} }

View File

@ -1,5 +1,6 @@
package context package context
// New creates new context.
func New() *Context { func New() *Context {
return &Context{} return &Context{}
} }

View File

@ -9,17 +9,22 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// Database represents control structure for database connection.
type Database struct { type Database struct {
db *sqlx.DB db *sqlx.DB
} }
// GetDatabaseConnection returns current database connection.
func (db *Database) GetDatabaseConnection() *sqlx.DB { func (db *Database) GetDatabaseConnection() *sqlx.DB {
return db.db return db.db
} }
// Initialize initializes connection to database.
func (db *Database) Initialize() { func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...") 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 = "" var userpass = ""
if c.Config.Database.Password == "" { if c.Config.Database.Password == "" {
userpass = c.Config.Database.Username userpass = c.Config.Database.Username

View File

@ -11,6 +11,7 @@ var (
d *Database d *Database
) )
// New initializes database structure.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
d = &Database{} d = &Database{}

View File

@ -5,12 +5,16 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// Handler is an interfaceable structure that proxifies calls from anyone
// to Database structure.
type Handler struct{} type Handler struct{}
// GetDatabaseConnection returns current database connection.
func (dbh Handler) GetDatabaseConnection() *sqlx.DB { func (dbh Handler) GetDatabaseConnection() *sqlx.DB {
return d.GetDatabaseConnection() return d.GetDatabaseConnection()
} }
// Initialize initializes connection to database.
func (dbh Handler) Initialize() { func (dbh Handler) Initialize() {
d.Initialize() d.Initialize()
} }

View File

@ -5,6 +5,8 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
// Interface represents database interface which is available to all
// parts of application and registers with context.Context.
type Interface interface { type Interface interface {
GetDatabaseConnection() *sqlx.DB GetDatabaseConnection() *sqlx.DB
Initialize() Initialize()

View File

@ -13,15 +13,18 @@ var (
c *context.Context c *context.Context
) )
// New initializes migrations.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
} }
// Migrate launching migrations.
func Migrate() { func Migrate() {
c.Logger.Info().Msg("Migrating database...") c.Logger.Info().Msg("Migrating database...")
goose.SetDialect("mysql") goose.SetDialect("mysql")
goose.AddNamedMigration("1_initial.go", InitialUp, nil) goose.AddNamedMigration("1_initial.go", InitialUp, nil)
// Add new migrations BEFORE this message.
dbConn := c.Database.GetDatabaseConnection() dbConn := c.Database.GetDatabaseConnection()
err := goose.Up(dbConn.DB, ".") err := goose.Up(dbConn.DB, ".")

View File

@ -1,4 +1,4 @@
package http package pastes
import ( import (
// stdlib // stdlib
@ -10,7 +10,6 @@ import (
// local // local
"github.com/pztrn/fastpastebin/api/http/static" "github.com/pztrn/fastpastebin/api/http/static"
"github.com/pztrn/fastpastebin/models"
// other // other
"github.com/labstack/echo" "github.com/labstack/echo"
@ -20,6 +19,7 @@ var (
regexInts = regexp.MustCompile("[0-9]+") regexInts = regexp.MustCompile("[0-9]+")
) )
// GET for "/paste/PASTE_ID".
func pasteGET(ec echo.Context) error { func pasteGET(ec echo.Context) error {
errhtml, err := static.ReadFile("error.html") errhtml, err := static.ReadFile("error.html")
if err != nil { if err != nil {
@ -33,7 +33,7 @@ func pasteGET(ec echo.Context) error {
c.Logger.Debug().Msgf("Requesting paste #%+v", pasteID) c.Logger.Debug().Msgf("Requesting paste #%+v", pasteID)
// Get paste. // Get paste.
paste := &models.Paste{ID: pasteID} paste := &Paste{ID: pasteID}
err1 := paste.GetByID(c.Database.GetDatabaseConnection()) err1 := paste.GetByID(c.Database.GetDatabaseConnection())
if err1 != nil { if err1 != nil {
c.Logger.Error().Msgf("Failed to get paste #%d from database: %s", pasteID, err1.Error()) 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)) 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 { func pastePOST(ec echo.Context) error {
errhtml, err := static.ReadFile("error.html") errhtml, err := static.ReadFile("error.html")
if err != nil { if err != nil {
@ -68,7 +70,7 @@ func pastePOST(ec echo.Context) error {
return ec.HTML(http.StatusBadRequest, string(errhtml)) return ec.HTML(http.StatusBadRequest, string(errhtml))
} }
paste := &models.Paste{ paste := &Paste{
Title: params["paste-title"][0], Title: params["paste-title"][0],
Data: params["paste-contents"][0], Data: params["paste-contents"][0],
} }
@ -91,7 +93,7 @@ func pastePOST(ec echo.Context) error {
paste.KeepFor = keepFor paste.KeepFor = keepFor
keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0] keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0]
keepForUnit := models.PASTE_KEEPS_CORELLATION[keepForUnitRaw] keepForUnit := PASTE_KEEPS_CORELLATION[keepForUnitRaw]
paste.KeepForUnitType = keepForUnit paste.KeepForUnitType = keepForUnit
id, err2 := paste.Save(c.Database.GetDatabaseConnection()) id, err2 := paste.Save(c.Database.GetDatabaseConnection())
@ -105,6 +107,7 @@ func pastePOST(ec echo.Context) error {
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString) return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
} }
// GET for "/pastes/", a list of publicly available pastes.
func pastesGET(ec echo.Context) error { func pastesGET(ec echo.Context) error {
pasteListHTML, err1 := static.ReadFile("pastelist_list.html") pasteListHTML, err1 := static.ReadFile("pastelist_list.html")
if err1 != nil { if err1 != nil {
@ -125,7 +128,7 @@ func pastesGET(ec echo.Context) error {
c.Logger.Debug().Msgf("Requested page #%d", page) c.Logger.Debug().Msgf("Requested page #%d", page)
p := &models.Paste{} p := &Paste{}
// Get pastes IDs. // Get pastes IDs.
pastes, err3 := p.GetPagedPastes(c.Database.GetDatabaseConnection(), page) pastes, err3 := p.GetPagedPastes(c.Database.GetDatabaseConnection(), page)
c.Logger.Debug().Msgf("Got %d pastes", len(pastes)) c.Logger.Debug().Msgf("Got %d pastes", len(pastes))

26
pastes/exported.go Normal file
View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package models package pastes
import ( import (
// stdlib // stdlib
@ -24,6 +24,7 @@ var (
} }
) )
// Paste represents paste itself.
type Paste struct { type Paste struct {
ID int `db:"id"` ID int `db:"id"`
Title string `db:"title"` Title string `db:"title"`