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"
)
// 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 {

View File

@ -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...")

View File

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

View File

@ -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 {

View File

@ -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...")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, ".")

View File

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

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 (
// stdlib
@ -24,6 +24,7 @@ var (
}
)
// Paste represents paste itself.
type Paste struct {
ID int `db:"id"`
Title string `db:"title"`