DDD (Domain Design) partially implemented, added comments everywhere.
This commit is contained in:
		| @@ -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 { | ||||
|   | ||||
| @@ -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...") | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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...") | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package config | ||||
|  | ||||
| // ConfigDatabase describes database configuration. | ||||
| type ConfigDatabase struct { | ||||
| 	Address  string `yaml:"address"` | ||||
| 	Port     string `yaml:"port"` | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package config | ||||
|  | ||||
| // ConfigHTTP describes HTTP server configuration. | ||||
| type ConfigHTTP struct { | ||||
| 	Address       string `yaml:"address"` | ||||
| 	Port          string `yaml:"port"` | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package config | ||||
|  | ||||
| // ConfigLogging describes logger configuration. | ||||
| type ConfigLogging struct { | ||||
| 	LogToFile bool   `yaml:"log_to_file"` | ||||
| 	FileName  string `yaml:"filename"` | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package config | ||||
|  | ||||
| // ConfigStruct describes whole configuration. | ||||
| type ConfigStruct struct { | ||||
| 	Database ConfigDatabase `yaml:"database"` | ||||
| 	Logging  ConfigLogging  `yaml:"logging"` | ||||
|   | ||||
| @@ -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...") | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package context | ||||
|  | ||||
| // New creates new context. | ||||
| func New() *Context { | ||||
| 	return &Context{} | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -11,6 +11,7 @@ var ( | ||||
| 	d *Database | ||||
| ) | ||||
|  | ||||
| // New initializes database structure. | ||||
| func New(cc *context.Context) { | ||||
| 	c = cc | ||||
| 	d = &Database{} | ||||
|   | ||||
| @@ -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() | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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, ".") | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										26
									
								
								pastes/exported.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -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"` | ||||
		Reference in New Issue
	
	Block a user