Application superstructure (or supersingleton, if you want).
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			Got rid of context thing which misleads due to existance of stdlib's context package. Also fixed golangci-lint configuration. Fixes #20.
This commit is contained in:
		| @@ -15,8 +15,14 @@ linters: | |||||||
|     # This linter MIGHT BE good, but who decided that I want keepFor in |     # This linter MIGHT BE good, but who decided that I want keepFor in | ||||||
|     # JSON instead of keep_for for KeepFor field? |     # JSON instead of keep_for for KeepFor field? | ||||||
|     - tagliatelle |     - tagliatelle | ||||||
|  |     # Why it is bad to return an interface? No one knows, except authors of this linter. | ||||||
|  |     - ireturn | ||||||
|  |     # Some structs will contain context, and it's cancelFunc. | ||||||
|  |     - containedctx | ||||||
|     # Deprecated. |     # Deprecated. | ||||||
|     - exhaustivestruct |     - exhaustivestruct | ||||||
|  |     - golint | ||||||
|  |     - wrapcheck | ||||||
| linters-settings: | linters-settings: | ||||||
|   lll: |   lll: | ||||||
|     line-length: 420 |     line-length: 420 | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ services: | |||||||
|  |  | ||||||
| Take a look at [example configuration file](examples/fastpastebin.yaml.dist) which contains all supported options and their descriptions. | Take a look at [example configuration file](examples/fastpastebin.yaml.dist) which contains all supported options and their descriptions. | ||||||
|  |  | ||||||
| Configuration file position is irrelevant, there is no hardcoded paths where Fast Paste Bin looking for it's configuration. Use ``-config`` CLI parameter or ``FASTPASTEBIN_CONFIG`` environment variable to specify path. | Configuration file position is irrelevant, there is no hardcoded paths where Fast Paste Bin looking for it's configuration. Use ``FASTPASTEBIN_CONFIG`` environment variable to specify path. | ||||||
|  |  | ||||||
| ## Developing | ## Developing | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,6 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"flag" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| @@ -33,46 +32,39 @@ import ( | |||||||
| 	"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable" | 	"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/domains/indexpage" | 	"go.dev.pztrn.name/fastpastebin/domains/indexpage" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/domains/pastes" | 	"go.dev.pztrn.name/fastpastebin/domains/pastes" | ||||||
|  | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/captcha" | 	"go.dev.pztrn.name/fastpastebin/internal/captcha" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" |  | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/database" | 	"go.dev.pztrn.name/fastpastebin/internal/database" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/templater" | 	"go.dev.pztrn.name/fastpastebin/internal/templater" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	appCtx := context.New() |  | ||||||
| 	appCtx.Initialize() |  | ||||||
|  |  | ||||||
| 	appCtx.Logger.Info().Msg("Starting Fast Pastebin...") |  | ||||||
|  |  | ||||||
| 	// Here goes initial initialization for packages that want CLI flags |  | ||||||
| 	// to be added. |  | ||||||
|  |  | ||||||
| 	// Parse flags. |  | ||||||
| 	flag.Parse() |  | ||||||
|  |  | ||||||
| 	// Continue loading. |  | ||||||
| 	appCtx.LoadConfiguration() |  | ||||||
| 	appCtx.InitializePost() |  | ||||||
| 	database.New(appCtx) |  | ||||||
| 	appCtx.Database.Initialize() |  | ||||||
| 	templater.Initialize(appCtx) |  | ||||||
|  |  | ||||||
| 	captcha.New(appCtx) |  | ||||||
|  |  | ||||||
| 	dbnotavailable.New(appCtx) |  | ||||||
| 	indexpage.New(appCtx) |  | ||||||
| 	pastes.New(appCtx) |  | ||||||
|  |  | ||||||
| 	// 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) | ||||||
|  |  | ||||||
| 	signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM) | 	signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM) | ||||||
|  |  | ||||||
|  | 	app := application.New() | ||||||
|  | 	app.Log.Info().Msg("Starting Fast Pastebin...") | ||||||
|  |  | ||||||
|  | 	database.New(app) | ||||||
|  | 	app.Database.Initialize() | ||||||
|  | 	templater.Initialize(app) | ||||||
|  |  | ||||||
|  | 	captcha.New(app) | ||||||
|  |  | ||||||
|  | 	dbnotavailable.New(app) | ||||||
|  | 	indexpage.New(app) | ||||||
|  | 	pastes.New(app) | ||||||
|  |  | ||||||
|  | 	app.Start() | ||||||
|  |  | ||||||
| 	go func() { | 	go func() { | ||||||
| 		<-signalHandler | 		<-signalHandler | ||||||
| 		appCtx.Shutdown() | 		if err := app.Shutdown(); err != nil { | ||||||
|  | 			app.Log.Error().Err(err).Msg("Fast Pastebin failed to shutdown!") | ||||||
|  | 		} | ||||||
| 		shutdownDone <- true | 		shutdownDone <- true | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,16 +25,16 @@ | |||||||
| package dbnotavailable | package dbnotavailable | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ctx *context.Context | var app *application.Application | ||||||
|  |  | ||||||
| // New initializes pastes package and adds necessary HTTP and API | // New initializes pastes package and adds necessary HTTP and API | ||||||
| // endpoints. | // endpoints. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
|  |  | ||||||
| 	ctx.Echo.GET("/database_not_available", dbNotAvailableGet) | 	app.Echo.GET("/database_not_available", dbNotAvailableGet) | ||||||
| 	ctx.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet) | 	app.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,15 +25,15 @@ | |||||||
| package indexpage | package indexpage | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ctx *context.Context | var app *application.Application | ||||||
|  |  | ||||||
| // New initializes pastes package and adds necessary HTTP and API | // New initializes pastes package and adds necessary HTTP and API | ||||||
| // endpoints. | // endpoints. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
|  |  | ||||||
| 	ctx.Echo.GET("/", indexGet) | 	app.Echo.GET("/", indexGet) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,8 +37,8 @@ import ( | |||||||
| // Index of this site. | // Index of this site. | ||||||
| func indexGet(ectx echo.Context) error { | func indexGet(ectx echo.Context) error { | ||||||
| 	// We should check if database connection available. | 	// We should check if database connection available. | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | 	if app.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusFound, "/database_not_available") | 		return ectx.Redirect(http.StatusFound, "/database_not_available") | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -27,35 +27,35 @@ package pastes | |||||||
| import ( | import ( | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  |  | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var regexInts = regexp.MustCompile("[0-9]+") | var regexInts = regexp.MustCompile("[0-9]+") | ||||||
|  |  | ||||||
| var ctx *context.Context | var app *application.Application | ||||||
|  |  | ||||||
| // New initializes pastes package and adds necessary HTTP and API | // New initializes pastes package and adds necessary HTTP and API | ||||||
| // endpoints. | // endpoints. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
|  |  | ||||||
| 	//////////////////////////////////////////////////////////// | 	//////////////////////////////////////////////////////////// | ||||||
| 	// HTTP endpoints. | 	// HTTP endpoints. | ||||||
| 	//////////////////////////////////////////////////////////// | 	//////////////////////////////////////////////////////////// | ||||||
| 	// New paste. | 	// New paste. | ||||||
| 	ctx.Echo.POST("/paste/", pastePOSTWebInterface) | 	app.Echo.POST("/paste/", pastePOSTWebInterface) | ||||||
| 	// Show public paste. | 	// Show public paste. | ||||||
| 	ctx.Echo.GET("/paste/:id", pasteGETWebInterface) | 	app.Echo.GET("/paste/:id", pasteGETWebInterface) | ||||||
| 	// Show RAW representation of public paste. | 	// Show RAW representation of public paste. | ||||||
| 	ctx.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface) | 	app.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface) | ||||||
| 	// Show private paste. | 	// Show private paste. | ||||||
| 	ctx.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface) | 	app.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface) | ||||||
| 	// Show RAW representation of private paste. | 	// Show RAW representation of private paste. | ||||||
| 	ctx.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface) | 	app.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface) | ||||||
| 	// Verify access to passworded paste. | 	// Verify access to passworded paste. | ||||||
| 	ctx.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet) | 	app.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet) | ||||||
| 	ctx.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost) | 	app.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost) | ||||||
| 	// Pastes list. | 	// Pastes list. | ||||||
| 	ctx.Echo.GET("/pastes/", pastesGET) | 	app.Echo.GET("/pastes/", pastesGET) | ||||||
| 	ctx.Echo.GET("/pastes/:page", pastesGET) | 	app.Echo.GET("/pastes/:page", pastesGET) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -30,16 +30,16 @@ const ( | |||||||
| // value (they both will be ignored), but private will. | // value (they both will be ignored), but private will. | ||||||
| func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) { | func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) { | ||||||
| 	// Get paste. | 	// Get paste. | ||||||
| 	paste, err1 := ctx.Database.GetPaste(pasteID) | 	paste, err1 := app.Database.GetPaste(pasteID) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") | 		app.Log.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") | ||||||
|  |  | ||||||
| 		return nil, pasteNotFound | 		return nil, pasteNotFound | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Check if paste is expired. | 	// Check if paste is expired. | ||||||
| 	if paste.IsExpired() { | 	if paste.IsExpired() { | ||||||
| 		ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired") | 		app.Log.Error().Int("paste ID", pasteID).Msg("Paste is expired") | ||||||
|  |  | ||||||
| 		return nil, pasteExpired | 		return nil, pasteExpired | ||||||
| 	} | 	} | ||||||
| @@ -48,7 +48,7 @@ func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Pa | |||||||
| 	if paste.Private { | 	if paste.Private { | ||||||
| 		pasteTS := paste.CreatedAt.Unix() | 		pasteTS := paste.CreatedAt.Unix() | ||||||
| 		if timestamp != pasteTS { | 		if timestamp != pasteTS { | ||||||
| 			ctx.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTS).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste") | 			app.Log.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTS).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste") | ||||||
|  |  | ||||||
| 			return nil, pasteTimestampInvalid | 			return nil, pasteTimestampInvalid | ||||||
| 		} | 		} | ||||||
| @@ -77,7 +77,7 @@ func pasteGETWebInterface(ectx echo.Context) error { | |||||||
| 	// error. | 	// error. | ||||||
| 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | ||||||
| 	pasteIDStr := strconv.Itoa(pasteID) | 	pasteIDStr := strconv.Itoa(pasteID) | ||||||
| 	ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data") | 	app.Log.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data") | ||||||
|  |  | ||||||
| 	// Check if we have timestamp passed. | 	// Check if we have timestamp passed. | ||||||
| 	// If passed timestamp is invalid (isn't a real UNIX timestamp) we | 	// If passed timestamp is invalid (isn't a real UNIX timestamp) we | ||||||
| @@ -88,7 +88,7 @@ func pasteGETWebInterface(ectx echo.Context) error { | |||||||
| 	if tsProvidedStr != "" { | 	if tsProvidedStr != "" { | ||||||
| 		tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64) | 		tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste") | 			app.Log.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste") | ||||||
|  |  | ||||||
| 			errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDStr+" not found") | 			errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDStr+" not found") | ||||||
|  |  | ||||||
| @@ -121,7 +121,7 @@ func pasteGETWebInterface(ectx echo.Context) error { | |||||||
| 	// If passed cookie value was invalid - go to paste authorization | 	// If passed cookie value was invalid - go to paste authorization | ||||||
| 	// page. | 	// page. | ||||||
| 	if err == pasteCookieInvalid { | 	if err == pasteCookieInvalid { | ||||||
| 		ctx.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page") | 		app.Log.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ectx.Param("timestamp")+"/verify") | 		return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ectx.Param("timestamp")+"/verify") | ||||||
| @@ -158,7 +158,7 @@ func pasteGETWebInterface(ectx echo.Context) error { | |||||||
| 	// Tokenize paste data. | 	// Tokenize paste data. | ||||||
| 	lexered, err3 := lexer.Tokenise(nil, paste.Data) | 	lexered, err3 := lexer.Tokenise(nil, paste.Data) | ||||||
| 	if err3 != nil { | 	if err3 != nil { | ||||||
| 		ctx.Logger.Error().Err(err3).Msg("Failed to tokenize paste data") | 		app.Log.Error().Err(err3).Msg("Failed to tokenize paste data") | ||||||
| 	} | 	} | ||||||
| 	// Get style for HTML output. | 	// Get style for HTML output. | ||||||
| 	style := styles.Get("monokai") | 	style := styles.Get("monokai") | ||||||
| @@ -173,7 +173,7 @@ func pasteGETWebInterface(ectx echo.Context) error { | |||||||
|  |  | ||||||
| 	err4 := formatter.Format(buf, style, lexered) | 	err4 := formatter.Format(buf, style, lexered) | ||||||
| 	if err4 != nil { | 	if err4 != nil { | ||||||
| 		ctx.Logger.Error().Err(err4).Msg("Failed to format paste data") | 		app.Log.Error().Err(err4).Msg("Failed to format paste data") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pasteData["pastedata"] = buf.String() | 	pasteData["pastedata"] = buf.String() | ||||||
| @@ -194,9 +194,9 @@ func pastePasswordedVerifyGet(ectx echo.Context) error { | |||||||
| 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | ||||||
|  |  | ||||||
| 	// Get paste. | 	// Get paste. | ||||||
| 	paste, err1 := ctx.Database.GetPaste(pasteID) | 	paste, err1 := app.Database.GetPaste(pasteID) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data") | 		app.Log.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found") | 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found") | ||||||
|  |  | ||||||
| @@ -208,19 +208,19 @@ func pastePasswordedVerifyGet(ectx echo.Context) error { | |||||||
| 	cookie, err := ectx.Cookie("PASTE-" + strconv.Itoa(pasteID)) | 	cookie, err := ectx.Cookie("PASTE-" + strconv.Itoa(pasteID)) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		// No cookie, redirect to auth page. | 		// No cookie, redirect to auth page. | ||||||
| 		ctx.Logger.Debug().Msg("Paste cookie found, checking it...") | 		app.Log.Debug().Msg("Paste cookie found, checking it...") | ||||||
|  |  | ||||||
| 		// Generate cookie value to check. | 		// Generate cookie value to check. | ||||||
| 		cookieValue := paste.GenerateCryptedCookieValue() | 		cookieValue := paste.GenerateCryptedCookieValue() | ||||||
|  |  | ||||||
| 		if cookieValue == cookie.Value { | 		if cookieValue == cookie.Value { | ||||||
| 			ctx.Logger.Info().Msg("Valid cookie, redirecting to paste page...") | 			app.Log.Info().Msg("Valid cookie, redirecting to paste page...") | ||||||
|  |  | ||||||
| 			//nolint:wrapcheck | 			//nolint:wrapcheck | ||||||
| 			return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ectx.Param("timestamp")) | 			return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ectx.Param("timestamp")) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ctx.Logger.Debug().Msg("Invalid cookie, showing auth page") | 		app.Log.Debug().Msg("Invalid cookie, showing auth page") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// HTML data. | 	// HTML data. | ||||||
| @@ -237,9 +237,9 @@ func pastePasswordedVerifyGet(ectx echo.Context) error { | |||||||
| // POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page. | // POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page. | ||||||
| func pastePasswordedVerifyPost(ectx echo.Context) error { | func pastePasswordedVerifyPost(ectx echo.Context) error { | ||||||
| 	// We should check if database connection available. | 	// We should check if database connection available. | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
|  |  | ||||||
| 	if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | 	if app.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusFound, "/database_not_available") | 		return ectx.Redirect(http.StatusFound, "/database_not_available") | ||||||
| 	} | 	} | ||||||
| @@ -249,12 +249,12 @@ func pastePasswordedVerifyPost(ectx echo.Context) error { | |||||||
| 	// We already get numbers from string, so we will not check strconv.Atoi() | 	// We already get numbers from string, so we will not check strconv.Atoi() | ||||||
| 	// error. | 	// error. | ||||||
| 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | ||||||
| 	ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste") | 	app.Log.Debug().Int("paste ID", pasteID).Msg("Requesting paste") | ||||||
|  |  | ||||||
| 	// Get paste. | 	// Get paste. | ||||||
| 	paste, err1 := ctx.Database.GetPaste(pasteID) | 	paste, err1 := app.Database.GetPaste(pasteID) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") | 		app.Log.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found") | 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| @@ -263,7 +263,7 @@ func pastePasswordedVerifyPost(ectx echo.Context) error { | |||||||
|  |  | ||||||
| 	params, err2 := ectx.FormParams() | 	params, err2 := ectx.FormParams() | ||||||
| 	if err2 != nil { | 	if err2 != nil { | ||||||
| 		ctx.Logger.Debug().Msg("No form parameters passed") | 		app.Log.Debug().Msg("No form parameters passed") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found") | 		errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found") | ||||||
|  |  | ||||||
| @@ -294,8 +294,8 @@ func pastePasswordedVerifyPost(ectx echo.Context) error { | |||||||
| // Web interface version. | // Web interface version. | ||||||
| func pasteRawGETWebInterface(ectx echo.Context) error { | func pasteRawGETWebInterface(ectx echo.Context) error { | ||||||
| 	// We should check if database connection available. | 	// We should check if database connection available. | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | 	if app.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusFound, "/database_not_available/raw") | 		return ectx.Redirect(http.StatusFound, "/database_not_available/raw") | ||||||
| 	} | 	} | ||||||
| @@ -304,19 +304,19 @@ func pasteRawGETWebInterface(ectx echo.Context) error { | |||||||
| 	// We already get numbers from string, so we will not check strconv.Atoi() | 	// We already get numbers from string, so we will not check strconv.Atoi() | ||||||
| 	// error. | 	// error. | ||||||
| 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | 	pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0]) | ||||||
| 	ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data") | 	app.Log.Debug().Int("paste ID", pasteID).Msg("Requesting paste data") | ||||||
|  |  | ||||||
| 	// Get paste. | 	// Get paste. | ||||||
| 	paste, err1 := ctx.Database.GetPaste(pasteID) | 	paste, err1 := app.Database.GetPaste(pasteID) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database") | 		app.Log.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.") | 		return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if paste.IsExpired() { | 	if paste.IsExpired() { | ||||||
| 		ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired") | 		app.Log.Error().Int("paste ID", pasteID).Msg("Paste is expired") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.") | 		return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.") | ||||||
| @@ -328,7 +328,7 @@ func pasteRawGETWebInterface(ectx echo.Context) error { | |||||||
|  |  | ||||||
| 		tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64) | 		tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64) | ||||||
| 		if err2 != nil { | 		if err2 != nil { | ||||||
| 			ctx.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste") | 			app.Log.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste") | ||||||
|  |  | ||||||
| 			//nolint:wrapcheck | 			//nolint:wrapcheck | ||||||
| 			return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | 			return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | ||||||
| @@ -336,7 +336,7 @@ func pasteRawGETWebInterface(ectx echo.Context) error { | |||||||
|  |  | ||||||
| 		pasteTS := paste.CreatedAt.Unix() | 		pasteTS := paste.CreatedAt.Unix() | ||||||
| 		if tsProvided != pasteTS { | 		if tsProvided != pasteTS { | ||||||
| 			ctx.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTS).Msg("Incorrect timestamp provided for private paste") | 			app.Log.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTS).Msg("Incorrect timestamp provided for private paste") | ||||||
|  |  | ||||||
| 			//nolint:wrapcheck | 			//nolint:wrapcheck | ||||||
| 			return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | 			return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | ||||||
| @@ -347,7 +347,7 @@ func pasteRawGETWebInterface(ectx echo.Context) error { | |||||||
| 	// ToDo: figure out how to handle passworded pastes here. | 	// ToDo: figure out how to handle passworded pastes here. | ||||||
| 	// Return error for now. | 	// Return error for now. | ||||||
| 	if paste.Password != "" { | 	if paste.Password != "" { | ||||||
| 		ctx.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!") | 		app.Log.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!") | ||||||
| 		return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | 		return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,15 +22,15 @@ const KeepPastesForever = "forever" | |||||||
| // requests comes from browsers via web interface. | // requests comes from browsers via web interface. | ||||||
| func pastePOSTWebInterface(ectx echo.Context) error { | func pastePOSTWebInterface(ectx echo.Context) error { | ||||||
| 	// We should check if database connection available. | 	// We should check if database connection available. | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | 	if app.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusFound, "/database_not_available") | 		return ectx.Redirect(http.StatusFound, "/database_not_available") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	params, err := ectx.FormParams() | 	params, err := ectx.FormParams() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Error().Msg("Passed paste form is empty") | 		app.Log.Error().Msg("Passed paste form is empty") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Cannot create empty paste") | 		errtpl := templater.GetErrorTemplate(ectx, "Cannot create empty paste") | ||||||
|  |  | ||||||
| @@ -38,11 +38,11 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
| 		return ectx.HTML(http.StatusBadRequest, errtpl) | 		return ectx.HTML(http.StatusBadRequest, errtpl) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Logger.Debug().Msgf("Received parameters: %+v", params) | 	app.Log.Debug().Msgf("Received parameters: %+v", params) | ||||||
|  |  | ||||||
| 	// Do nothing if paste contents is empty. | 	// Do nothing if paste contents is empty. | ||||||
| 	if len(params["paste-contents"][0]) == 0 { | 	if len(params["paste-contents"][0]) == 0 { | ||||||
| 		ctx.Logger.Debug().Msg("Empty paste submitted, ignoring") | 		app.Log.Debug().Msg("Empty paste submitted, ignoring") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Empty pastes aren't allowed.") | 		errtpl := templater.GetErrorTemplate(ectx, "Empty pastes aren't allowed.") | ||||||
|  |  | ||||||
| @@ -51,7 +51,7 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != KeepPastesForever { | 	if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != KeepPastesForever { | ||||||
| 		ctx.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value") | 		app.Log.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") | 		errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") | ||||||
|  |  | ||||||
| @@ -61,7 +61,7 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
|  |  | ||||||
| 	// Verify captcha. | 	// Verify captcha. | ||||||
| 	if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) { | 	if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) { | ||||||
| 		ctx.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution") | 		app.Log.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Invalid captcha solution.") | 		errtpl := templater.GetErrorTemplate(ectx, "Invalid captcha solution.") | ||||||
|  |  | ||||||
| @@ -95,11 +95,11 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
| 		keepFor, err = strconv.Atoi(keepForRaw) | 		keepFor, err = strconv.Atoi(keepForRaw) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if params["paste-keep-for"][0] == KeepPastesForever { | 			if params["paste-keep-for"][0] == KeepPastesForever { | ||||||
| 				ctx.Logger.Debug().Msg("Keeping paste forever!") | 				app.Log.Debug().Msg("Keeping paste forever!") | ||||||
|  |  | ||||||
| 				keepFor = 0 | 				keepFor = 0 | ||||||
| 			} else { | 			} else { | ||||||
| 				ctx.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer") | 				app.Log.Debug().Err(err).Msg("Failed to parse 'Keep for' integer") | ||||||
|  |  | ||||||
| 				errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") | 				errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") | ||||||
|  |  | ||||||
| @@ -138,9 +138,9 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
| 		_ = paste.CreatePassword(pastePassword[0]) | 		_ = paste.CreatePassword(pastePassword[0]) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pasteID, err2 := ctx.Database.SavePaste(paste) | 	pasteID, err2 := app.Database.SavePaste(paste) | ||||||
| 	if err2 != nil { | 	if err2 != nil { | ||||||
| 		ctx.Logger.Error().Err(err2).Msg("Failed to save paste") | 		app.Log.Error().Err(err2).Msg("Failed to save paste") | ||||||
|  |  | ||||||
| 		errtpl := templater.GetErrorTemplate(ectx, "Failed to save paste. Please, try again later.") | 		errtpl := templater.GetErrorTemplate(ectx, "Failed to save paste. Please, try again later.") | ||||||
|  |  | ||||||
| @@ -149,7 +149,7 @@ func pastePOSTWebInterface(ectx echo.Context) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	newPasteIDAsString := strconv.FormatInt(pasteID, 10) | 	newPasteIDAsString := strconv.FormatInt(pasteID, 10) | ||||||
| 	ctx.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString) | 	app.Log.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString) | ||||||
|  |  | ||||||
| 	// Private pastes have it's timestamp in URL. | 	// Private pastes have it's timestamp in URL. | ||||||
| 	if paste.Private { | 	if paste.Private { | ||||||
|   | |||||||
| @@ -39,8 +39,8 @@ import ( | |||||||
| // Web interface version. | // Web interface version. | ||||||
| func pastesGET(ectx echo.Context) error { | func pastesGET(ectx echo.Context) error { | ||||||
| 	// We should check if database connection available. | 	// We should check if database connection available. | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | 	if app.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return ectx.Redirect(http.StatusFound, "/database_not_available") | 		return ectx.Redirect(http.StatusFound, "/database_not_available") | ||||||
| 	} | 	} | ||||||
| @@ -54,17 +54,17 @@ func pastesGET(ectx echo.Context) error { | |||||||
| 		page, _ = strconv.Atoi(pageRaw) | 		page, _ = strconv.Atoi(pageRaw) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Logger.Debug().Int("page", page).Msg("Requested page") | 	app.Log.Debug().Int("page", page).Msg("Requested page") | ||||||
|  |  | ||||||
| 	// Get pastes IDs. | 	// Get pastes IDs. | ||||||
| 	pastes, err3 := ctx.Database.GetPagedPastes(page) | 	pastes, err3 := app.Database.GetPagedPastes(page) | ||||||
| 	ctx.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes") | 	app.Log.Debug().Int("count", len(pastes)).Msg("Got pastes") | ||||||
|  |  | ||||||
| 	pastesString := "No pastes to show." | 	pastesString := "No pastes to show." | ||||||
|  |  | ||||||
| 	// Show "No pastes to show" on any error for now. | 	// Show "No pastes to show" on any error for now. | ||||||
| 	if err3 != nil { | 	if err3 != nil { | ||||||
| 		ctx.Logger.Error().Err(err3).Msg("Failed to get pastes list from database") | 		app.Log.Error().Err(err3).Msg("Failed to get pastes list from database") | ||||||
|  |  | ||||||
| 		noPastesToShowTpl := templater.GetErrorTemplate(ectx, "No pastes to show.") | 		noPastesToShowTpl := templater.GetErrorTemplate(ectx, "No pastes to show.") | ||||||
|  |  | ||||||
| @@ -100,8 +100,8 @@ func pastesGET(ectx echo.Context) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Pagination. | 	// Pagination. | ||||||
| 	pages := ctx.Database.GetPastesPages() | 	pages := app.Database.GetPastesPages() | ||||||
| 	ctx.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data") | 	app.Log.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data") | ||||||
| 	paginationHTML := pagination.CreateHTML(page, pages, "/pastes/") | 	paginationHTML := pagination.CreateHTML(page, pages, "/pastes/") | ||||||
|  |  | ||||||
| 	pasteListTpl := templater.GetTemplate(ectx, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML}) | 	pasteListTpl := templater.GetTemplate(ectx, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML}) | ||||||
|   | |||||||
							
								
								
									
										124
									
								
								internal/application/application.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								internal/application/application.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/labstack/echo" | ||||||
|  | 	"github.com/rs/zerolog" | ||||||
|  | 	databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Application is a main application superstructure. It passes around all parts of application | ||||||
|  | // and serves as lifecycle management thing as well as kind-of-dependency-injection thing. | ||||||
|  | type Application struct { | ||||||
|  | 	Config        *Config | ||||||
|  | 	Database      databaseinterface.Interface | ||||||
|  | 	Echo          *echo.Echo | ||||||
|  | 	Log           zerolog.Logger | ||||||
|  | 	services      map[string]Service | ||||||
|  | 	servicesMutex sync.RWMutex | ||||||
|  | 	ctx           context.Context | ||||||
|  | 	cancelFunc    context.CancelFunc | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New creates new application superstructure. | ||||||
|  | func New() *Application { | ||||||
|  | 	//nolint:exhaustruct | ||||||
|  | 	appl := &Application{} | ||||||
|  | 	appl.initialize() | ||||||
|  |  | ||||||
|  | 	return appl | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetContext returns application-wide context. | ||||||
|  | func (a *Application) GetContext() context.Context { | ||||||
|  | 	return a.ctx | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetService returns interface{} with requested service or error if service wasn't registered. | ||||||
|  | func (a *Application) GetService(name string) (Service, error) { | ||||||
|  | 	a.servicesMutex.RLock() | ||||||
|  | 	srv, found := a.services[name] | ||||||
|  | 	a.servicesMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	if !found { | ||||||
|  | 		return nil, fmt.Errorf("%s: %w", ErrApplicationError, ErrApplicationServiceNotRegistered) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return srv, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Initializes internal state. | ||||||
|  | func (a *Application) initialize() { | ||||||
|  | 	a.Log = zerolog.New(os.Stdout).With().Timestamp().Logger() | ||||||
|  | 	a.Log.Info().Msg("Initializing Application...") | ||||||
|  |  | ||||||
|  | 	a.ctx, a.cancelFunc = context.WithCancel(context.Background()) | ||||||
|  |  | ||||||
|  | 	a.services = make(map[string]Service) | ||||||
|  |  | ||||||
|  | 	cfg, err := newConfig(a) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.Log.Fatal().Err(err).Msg("Failed to initialize configuration!") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	a.Config = cfg | ||||||
|  |  | ||||||
|  | 	a.initializeLogger() | ||||||
|  | 	a.initializeHTTPServer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RegisterService registers service for later re-use everywhere it's needed. | ||||||
|  | func (a *Application) RegisterService(srv Service) error { | ||||||
|  | 	a.servicesMutex.Lock() | ||||||
|  | 	_, found := a.services[srv.GetName()] | ||||||
|  | 	a.servicesMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	if found { | ||||||
|  | 		return fmt.Errorf("%s: %w", ErrApplicationError, ErrApplicationServiceAlreadyRegistered) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := srv.Initialize(); err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %s: %w", ErrApplicationError, ErrApplicationServiceRegister, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	a.services[srv.GetName()] = srv | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Shutdown shutdowns application. | ||||||
|  | func (a *Application) Shutdown() error { | ||||||
|  | 	a.cancelFunc() | ||||||
|  |  | ||||||
|  | 	a.servicesMutex.RLock() | ||||||
|  | 	defer a.servicesMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, service := range a.services { | ||||||
|  | 		if err := service.Shutdown(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start starts application. | ||||||
|  | func (a *Application) Start() error { | ||||||
|  | 	a.initializeLoggerPost() | ||||||
|  | 	a.startHTTPServer() | ||||||
|  |  | ||||||
|  | 	a.servicesMutex.RLock() | ||||||
|  | 	defer a.servicesMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, service := range a.services { | ||||||
|  | 		if err := service.Start(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								internal/application/application_errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								internal/application/application_errors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import "errors" | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	// ErrApplicationError indicates that error belongs to Application. | ||||||
|  | 	ErrApplicationError = errors.New("application") | ||||||
|  |  | ||||||
|  | 	// ErrApplicationServiceRegister appears when trying to register (and initialize) a service | ||||||
|  | 	// but something went wrong. | ||||||
|  | 	ErrApplicationServiceRegister = errors.New("service registering and initialization") | ||||||
|  |  | ||||||
|  | 	// ErrApplicationServiceAlreadyRegistered appears when trying to register service with already used name. | ||||||
|  | 	ErrApplicationServiceAlreadyRegistered = errors.New("service already registered") | ||||||
|  |  | ||||||
|  | 	// ErrApplicationServiceNotRegistered appears when trying to obtain a service that wasn't previously registered. | ||||||
|  | 	ErrApplicationServiceNotRegistered = errors.New("service not registered") | ||||||
|  | ) | ||||||
							
								
								
									
										48
									
								
								internal/application/application_http_server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								internal/application/application_http_server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/labstack/echo" | ||||||
|  | 	"github.com/labstack/echo/middleware" | ||||||
|  | 	"go.dev.pztrn.name/fastpastebin/assets" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Wrapper around previous function. | ||||||
|  | func (a *Application) echoReqLogger() echo.MiddlewareFunc { | ||||||
|  | 	return func(next echo.HandlerFunc) echo.HandlerFunc { | ||||||
|  | 		return func(ectx echo.Context) error { | ||||||
|  | 			a.Log.Info(). | ||||||
|  | 				Str("IP", ectx.RealIP()). | ||||||
|  | 				Str("Host", ectx.Request().Host). | ||||||
|  | 				Str("Method", ectx.Request().Method). | ||||||
|  | 				Str("Path", ectx.Request().URL.Path). | ||||||
|  | 				Str("UA", ectx.Request().UserAgent()). | ||||||
|  | 				Msg("HTTP request") | ||||||
|  |  | ||||||
|  | 			return next(ectx) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Application) initializeHTTPServer() { | ||||||
|  | 	a.Echo = echo.New() | ||||||
|  | 	a.Echo.Use(a.echoReqLogger()) | ||||||
|  | 	a.Echo.Use(middleware.Recover()) | ||||||
|  | 	a.Echo.Use(middleware.BodyLimit(a.Config.HTTP.MaxBodySizeMegabytes + "M")) | ||||||
|  | 	a.Echo.DisableHTTP2 = true | ||||||
|  | 	a.Echo.HideBanner = true | ||||||
|  | 	a.Echo.HidePort = true | ||||||
|  |  | ||||||
|  | 	// Static files. | ||||||
|  | 	a.Echo.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(assets.Data)))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Application) startHTTPServer() { | ||||||
|  | 	listenAddress := a.Config.HTTP.Address + ":" + a.Config.HTTP.Port | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		a.Echo.Logger.Fatal(a.Echo.Start(listenAddress)) | ||||||
|  | 	}() | ||||||
|  | 	a.Log.Info().Str("address", listenAddress).Msg("Started HTTP server") | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package context | package application | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @@ -11,7 +11,7 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Puts memory usage into log lines. | // Puts memory usage into log lines. | ||||||
| func (c *Context) getMemoryUsage(event *zerolog.Event, level zerolog.Level, message string) { | func (a *Application) getMemoryUsage(event *zerolog.Event, level zerolog.Level, message string) { | ||||||
| 	var memstats runtime.MemStats | 	var memstats runtime.MemStats | ||||||
| 
 | 
 | ||||||
| 	runtime.ReadMemStats(&memstats) | 	runtime.ReadMemStats(&memstats) | ||||||
| @@ -22,7 +22,7 @@ func (c *Context) getMemoryUsage(event *zerolog.Event, level zerolog.Level, mess | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Initializes logger. | // Initializes logger. | ||||||
| func (c *Context) initializeLogger() { | func (a *Application) initializeLogger() { | ||||||
| 	// Устанавливаем форматирование логгера. | 	// Устанавливаем форматирование логгера. | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| 	output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339} | 	output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339} | ||||||
| @@ -52,17 +52,17 @@ func (c *Context) initializeLogger() { | |||||||
| 		return fmt.Sprintf("| %s |", lvl) | 		return fmt.Sprintf("| %s |", lvl) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	c.Logger = zerolog.New(output).With().Timestamp().Logger() | 	a.Log = zerolog.New(output).With().Timestamp().Logger() | ||||||
| 
 | 
 | ||||||
| 	c.Logger = c.Logger.Hook(zerolog.HookFunc(c.getMemoryUsage)) | 	a.Log = a.Log.Hook(zerolog.HookFunc(a.getMemoryUsage)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Initialize logger after configuration parse. | // Initialize logger after configuration parse. | ||||||
| func (c *Context) initializeLoggerPost() { | func (a *Application) initializeLoggerPost() { | ||||||
| 	// Set log level. | 	// Set log level. | ||||||
| 	c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel) | 	a.Log.Info().Msgf("Setting logger level: %s", a.Config.Logging.LogLevel) | ||||||
| 
 | 
 | ||||||
| 	switch c.Config.Logging.LogLevel { | 	switch a.Config.Logging.LogLevel { | ||||||
| 	case "DEBUG": | 	case "DEBUG": | ||||||
| 		zerolog.SetGlobalLevel(zerolog.DebugLevel) | 		zerolog.SetGlobalLevel(zerolog.DebugLevel) | ||||||
| 	case "INFO": | 	case "INFO": | ||||||
							
								
								
									
										13
									
								
								internal/application/application_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								internal/application/application_service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | // Service is a generic interface for all services of application. | ||||||
|  | type Service interface { | ||||||
|  | 	// GetName returns service name for registering with application superstructure. | ||||||
|  | 	GetName() string | ||||||
|  | 	// Initialize initializes service. | ||||||
|  | 	Initialize() error | ||||||
|  | 	// Shutdown shuts service down if needed. Also should block is shutdown should be done in synchronous manner. | ||||||
|  | 	Shutdown() error | ||||||
|  | 	// Start starts service if needed. Should not block execution. | ||||||
|  | 	Start() error | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								internal/application/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								internal/application/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/rs/zerolog" | ||||||
|  | 	"go.dev.pztrn.name/fastpastebin/internal/helpers" | ||||||
|  | 	"gopkg.in/yaml.v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Config represents configuration structure. | ||||||
|  | type Config struct { | ||||||
|  | 	app *Application | ||||||
|  | 	log zerolog.Logger | ||||||
|  |  | ||||||
|  | 	Database ConfigDatabase `yaml:"database"` | ||||||
|  | 	Logging  ConfigLogging  `yaml:"logging"` | ||||||
|  | 	HTTP     ConfigHTTP     `yaml:"http"` | ||||||
|  | 	Pastes   ConfigPastes   `yaml:"pastes"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newConfig(app *Application) (*Config, error) { | ||||||
|  | 	//nolint:exhaustruct | ||||||
|  | 	cfg := &Config{ | ||||||
|  | 		app: app, | ||||||
|  | 		log: app.Log.With().Str("type", "core").Str("name", "configuration").Logger(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := cfg.initialize(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("%s: %w", ErrConfigurationError, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return cfg, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Config) initialize() error { | ||||||
|  | 	c.log.Info().Msg("Initializing configuration...") | ||||||
|  |  | ||||||
|  | 	configPathRaw, found := os.LookupEnv("FASTPASTEBIN_CONFIG") | ||||||
|  | 	if !found { | ||||||
|  | 		return fmt.Errorf("%s: %w", ErrConfigurationLoad, ErrConfigurationPathNotDefined) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	configPath, err := helpers.NormalizePath(configPathRaw) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %w", ErrConfigurationLoad, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.log.Info().Str("config path", configPath).Msg("Reading configuration file...") | ||||||
|  |  | ||||||
|  | 	fileData, err := os.ReadFile(configPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %w", ErrConfigurationLoad, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := yaml.Unmarshal(fileData, c); err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %w", ErrConfigurationLoad, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.log.Debug().Msgf("Configuration loaded: %+v", c) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								internal/application/config_errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								internal/application/config_errors.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | import "errors" | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	// ErrConfigurationError indicates that this error is related to configuration. | ||||||
|  | 	ErrConfigurationError = errors.New("configuration") | ||||||
|  |  | ||||||
|  | 	// ErrConfigurationLoad indicates that error appears when trying to load | ||||||
|  | 	// configuration data from file. | ||||||
|  | 	ErrConfigurationLoad = errors.New("loading configuration") | ||||||
|  |  | ||||||
|  | 	// ErrConfigurationPathNotDefined indicates that CONFIG_PATH environment variable is empty or not defined. | ||||||
|  | 	ErrConfigurationPathNotDefined = errors.New("configuration path (CONFIG_PATH) is empty or not defined") | ||||||
|  | ) | ||||||
							
								
								
									
										30
									
								
								internal/application/config_structs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								internal/application/config_structs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | // ConfigDatabase describes database configuration. | ||||||
|  | type ConfigDatabase struct { | ||||||
|  | 	Type     string `yaml:"type"` | ||||||
|  | 	Path     string `yaml:"path"` | ||||||
|  | 	Address  string `yaml:"address"` | ||||||
|  | 	Port     string `yaml:"port"` | ||||||
|  | 	Username string `yaml:"username"` | ||||||
|  | 	Password string `yaml:"password"` | ||||||
|  | 	Database string `yaml:"database"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ConfigHTTP describes HTTP server configuration. | ||||||
|  | type ConfigHTTP struct { | ||||||
|  | 	Address              string `yaml:"address"` | ||||||
|  | 	Port                 string `yaml:"port"` | ||||||
|  | 	MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"` | ||||||
|  | 	AllowInsecure        bool   `yaml:"allow_insecure"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ConfigLogging describes logger configuration. | ||||||
|  | type ConfigLogging struct { | ||||||
|  | 	LogLevel string `yaml:"loglevel"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ConfigPastes describes pastes subsystem configuration. | ||||||
|  | type ConfigPastes struct { | ||||||
|  | 	Pagination int `yaml:"pagination"` | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								internal/application/vars.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/application/vars.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | package application | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// Version . | ||||||
|  | 	Version = "0.4.1" | ||||||
|  | ) | ||||||
| @@ -28,22 +28,22 @@ import ( | |||||||
| 	"github.com/dchest/captcha" | 	"github.com/dchest/captcha" | ||||||
| 	"github.com/labstack/echo" | 	"github.com/labstack/echo" | ||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx *context.Context | 	app *application.Application | ||||||
| 	log zerolog.Logger | 	log zerolog.Logger | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // New initializes captcha package and adds necessary HTTP and API | // New initializes captcha package and adds necessary HTTP and API | ||||||
| // endpoints. | // endpoints. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	log = ctx.Logger.With().Str("type", "internal").Str("package", "captcha").Logger() | 	log = app.Log.With().Str("type", "internal").Str("package", "captcha").Logger() | ||||||
|  |  | ||||||
| 	// New paste. | 	// New paste. | ||||||
| 	ctx.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight))) | 	app.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight))) | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewCaptcha creates new captcha string. | // NewCaptcha creates new captcha string. | ||||||
|   | |||||||
| @@ -1,126 +0,0 @@ | |||||||
| // Fast Paste Bin - uberfast and easy-to-use pastebin. |  | ||||||
| // |  | ||||||
| // Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin |  | ||||||
| // developers. |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject |  | ||||||
| // to the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |  | ||||||
| // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |  | ||||||
| // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |  | ||||||
| // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |  | ||||||
| // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  |  | ||||||
| package context |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"flag" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/labstack/echo" |  | ||||||
| 	"github.com/rs/zerolog" |  | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/config" |  | ||||||
| 	databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface" |  | ||||||
| 	"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.Struct |  | ||||||
| 	Database          databaseinterface.Interface |  | ||||||
| 	Echo              *echo.Echo |  | ||||||
| 	Logger            zerolog.Logger |  | ||||||
| 	configPathFromCLI string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Initialize initializes context. |  | ||||||
| func (c *Context) Initialize() { |  | ||||||
| 	c.initializeLogger() |  | ||||||
|  |  | ||||||
| 	flag.StringVar(&c.configPathFromCLI, "config", "NO_CONFIG", "Configuration file path. Can be overridded with FASTPASTEBIN_CONFIG environment variable.") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InitializePost initializes everything that needs a configuration. |  | ||||||
| func (c *Context) InitializePost() { |  | ||||||
| 	c.initializeLoggerPost() |  | ||||||
| 	c.initializeHTTPServer() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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...") |  | ||||||
|  |  | ||||||
| 	configPath := c.configPathFromCLI |  | ||||||
|  |  | ||||||
| 	// We're accepting configuration path from "-config" CLI parameter |  | ||||||
| 	// and FASTPASTEBIN_CONFIG environment variable. Later have higher |  | ||||||
| 	// weight and can override "-config" value. |  | ||||||
| 	configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG") |  | ||||||
| 	if configPathFromEnvFound { |  | ||||||
| 		configPath = configPathFromEnv |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if configPath == "NO_CONFIG" { |  | ||||||
| 		c.Logger.Panic().Msg("Configuration file path wasn't passed via '-config' or 'FASTPASTEBIN_CONFIG' environment variable. Cannot continue.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Normalize file path. |  | ||||||
| 	normalizedConfigPath, err1 := filepath.Abs(configPath) |  | ||||||
| 	if err1 != nil { |  | ||||||
| 		c.Logger.Fatal().Err(err1).Msg("Failed to normalize path to configuration file") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.Logger.Debug().Str("path", configPath).Msg("Configuration file path") |  | ||||||
|  |  | ||||||
| 	//nolint:exhaustruct |  | ||||||
| 	c.Config = &config.Struct{} |  | ||||||
|  |  | ||||||
| 	// Read configuration file. |  | ||||||
| 	fileData, err2 := os.ReadFile(normalizedConfigPath) |  | ||||||
| 	if err2 != nil { |  | ||||||
| 		c.Logger.Panic().Err(err2).Msg("Failed to read configuration file") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Parse it into structure. |  | ||||||
| 	err3 := yaml.Unmarshal(fileData, c.Config) |  | ||||||
| 	if err3 != nil { |  | ||||||
| 		c.Logger.Panic().Err(err3).Msg("Failed to parse configuration file") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// 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...") |  | ||||||
|  |  | ||||||
| 	c.Database.Shutdown() |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| // Fast Paste Bin - uberfast and easy-to-use pastebin. |  | ||||||
| // |  | ||||||
| // Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin |  | ||||||
| // developers. |  | ||||||
| // |  | ||||||
| // Permission is hereby granted, free of charge, to any person obtaining |  | ||||||
| // a copy of this software and associated documentation files (the |  | ||||||
| // "Software"), to deal in the Software without restriction, including |  | ||||||
| // without limitation the rights to use, copy, modify, merge, publish, |  | ||||||
| // distribute, sublicense, and/or sell copies of the Software, and to |  | ||||||
| // permit persons to whom the Software is furnished to do so, subject |  | ||||||
| // to the following conditions: |  | ||||||
| // |  | ||||||
| // The above copyright notice and this permission notice shall be |  | ||||||
| // included in all copies or substantial portions of the Software. |  | ||||||
| // |  | ||||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
| // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |  | ||||||
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |  | ||||||
| // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |  | ||||||
| // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |  | ||||||
| // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |  | ||||||
| // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  |  | ||||||
| package context |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	// Version . |  | ||||||
| 	Version = "0.4.1" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // New creates new context. |  | ||||||
| func New() *Context { |  | ||||||
| 	//nolint:exhaustruct |  | ||||||
| 	return &Context{} |  | ||||||
| } |  | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| package context |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
|  |  | ||||||
| 	"github.com/labstack/echo" |  | ||||||
| 	"github.com/labstack/echo/middleware" |  | ||||||
| 	"go.dev.pztrn.name/fastpastebin/assets" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func (c *Context) initializeHTTPServer() { |  | ||||||
| 	c.Echo = echo.New() |  | ||||||
| 	c.Echo.Use(c.echoReqLogger()) |  | ||||||
| 	c.Echo.Use(middleware.Recover()) |  | ||||||
| 	c.Echo.Use(middleware.BodyLimit(c.Config.HTTP.MaxBodySizeMegabytes + "M")) |  | ||||||
| 	c.Echo.DisableHTTP2 = true |  | ||||||
| 	c.Echo.HideBanner = true |  | ||||||
| 	c.Echo.HidePort = true |  | ||||||
|  |  | ||||||
| 	// Static files. |  | ||||||
| 	c.Echo.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(assets.Data)))) |  | ||||||
|  |  | ||||||
| 	listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port |  | ||||||
|  |  | ||||||
| 	go func() { |  | ||||||
| 		c.Echo.Logger.Fatal(c.Echo.Start(listenAddress)) |  | ||||||
| 	}() |  | ||||||
| 	c.Logger.Info().Str("address", listenAddress).Msg("Started HTTP server") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Wrapper around previous function. |  | ||||||
| func (c *Context) echoReqLogger() echo.MiddlewareFunc { |  | ||||||
| 	return func(next echo.HandlerFunc) echo.HandlerFunc { |  | ||||||
| 		return func(ectx echo.Context) error { |  | ||||||
| 			c.Logger.Info(). |  | ||||||
| 				Str("IP", ectx.RealIP()). |  | ||||||
| 				Str("Host", ectx.Request().Host). |  | ||||||
| 				Str("Method", ectx.Request().Method). |  | ||||||
| 				Str("Path", ectx.Request().URL.Path). |  | ||||||
| 				Str("UA", ectx.Request().UserAgent()). |  | ||||||
| 				Msg("HTTP request") |  | ||||||
|  |  | ||||||
| 			return next(ectx) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -46,7 +46,7 @@ type Database struct { | |||||||
| // a subject of change in future. | // a subject of change in future. | ||||||
| func (db *Database) cleanup() { | func (db *Database) cleanup() { | ||||||
| 	for { | 	for { | ||||||
| 		ctx.Logger.Info().Msg("Starting pastes cleanup procedure...") | 		app.Log.Info().Msg("Starting pastes cleanup procedure...") | ||||||
|  |  | ||||||
| 		pages := db.db.GetPastesPages() | 		pages := db.db.GetPastesPages() | ||||||
|  |  | ||||||
| @@ -55,7 +55,7 @@ func (db *Database) cleanup() { | |||||||
| 		for i := 0; i < pages; i++ { | 		for i := 0; i < pages; i++ { | ||||||
| 			pastes, err := db.db.GetPagedPastes(i) | 			pastes, err := db.db.GetPagedPastes(i) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				ctx.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup") | 				app.Log.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, paste := range pastes { | 			for _, paste := range pastes { | ||||||
| @@ -68,11 +68,11 @@ func (db *Database) cleanup() { | |||||||
| 		for _, pasteID := range pasteIDsToRemove { | 		for _, pasteID := range pasteIDsToRemove { | ||||||
| 			err := db.DeletePaste(pasteID) | 			err := db.DeletePaste(pasteID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				ctx.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!") | 				app.Log.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ctx.Logger.Info().Msg("Pastes cleanup done.") | 		app.Log.Info().Msg("Pastes cleanup done.") | ||||||
|  |  | ||||||
| 		time.Sleep(time.Hour) | 		time.Sleep(time.Hour) | ||||||
| 	} | 	} | ||||||
| @@ -107,16 +107,16 @@ func (db *Database) GetPastesPages() int { | |||||||
|  |  | ||||||
| // Initialize initializes connection to database. | // Initialize initializes connection to database. | ||||||
| func (db *Database) Initialize() { | func (db *Database) Initialize() { | ||||||
| 	ctx.Logger.Info().Msg("Initializing database connection...") | 	app.Log.Info().Msg("Initializing database connection...") | ||||||
|  |  | ||||||
| 	if ctx.Config.Database.Type == "mysql" { | 	if app.Config.Database.Type == "mysql" { | ||||||
| 		mysql.New(ctx) | 		mysql.New(app) | ||||||
| 	} else if ctx.Config.Database.Type == flatfiles.FlatFileDialect { | 	} else if app.Config.Database.Type == flatfiles.FlatFileDialect { | ||||||
| 		flatfiles.New(ctx) | 		flatfiles.New(app) | ||||||
| 	} else if ctx.Config.Database.Type == "postgresql" { | 	} else if app.Config.Database.Type == "postgresql" { | ||||||
| 		postgresql.New(ctx) | 		postgresql.New(app) | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Logger.Fatal().Str("type", ctx.Config.Database.Type).Msg("Unknown database type") | 		app.Log.Fatal().Str("type", app.Config.Database.Type).Msg("Unknown database type") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	go db.cleanup() | 	go db.cleanup() | ||||||
|   | |||||||
| @@ -25,21 +25,21 @@ | |||||||
| package flatfiles | package flatfiles | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const FlatFileDialect = "flatfiles" | const FlatFileDialect = "flatfiles" | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx *context.Context | 	app *application.Application | ||||||
| 	flf *FlatFiles | 	flf *FlatFiles | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| 	flf = &FlatFiles{} | 	flf = &FlatFiles{} | ||||||
|  |  | ||||||
| 	ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | 	app.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -48,7 +48,7 @@ func (ff *FlatFiles) DeletePaste(pasteID int) error { | |||||||
| 	// Delete from disk. | 	// Delete from disk. | ||||||
| 	err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")) | 	err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Error().Err(err).Msg("Failed to delete paste!") | 		app.Log.Error().Err(err).Msg("Failed to delete paste!") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return err | 		return err | ||||||
| @@ -82,17 +82,17 @@ func (ff *FlatFiles) GetDatabaseConnection() *sql.DB { | |||||||
| func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) { | func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) { | ||||||
| 	ff.writeMutex.Lock() | 	ff.writeMutex.Lock() | ||||||
| 	pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json") | 	pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json") | ||||||
| 	ctx.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data") | 	app.Log.Debug().Str("path", pastePath).Msg("Trying to load paste data") | ||||||
|  |  | ||||||
| 	pasteInBytes, err := os.ReadFile(pastePath) | 	pasteInBytes, err := os.ReadFile(pastePath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Debug().Err(err).Msg("Failed to read paste from storage") | 		app.Log.Debug().Err(err).Msg("Failed to read paste from storage") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste") | 	app.Log.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste") | ||||||
| 	ff.writeMutex.Unlock() | 	ff.writeMutex.Unlock() | ||||||
|  |  | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| @@ -100,7 +100,7 @@ func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) { | |||||||
|  |  | ||||||
| 	err1 := json.Unmarshal(pasteInBytes, paste) | 	err1 := json.Unmarshal(pasteInBytes, paste) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Msgf("Failed to parse paste") | 		app.Log.Error().Err(err1).Msgf("Failed to parse paste") | ||||||
|  |  | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return nil, err1 | 		return nil, err1 | ||||||
| @@ -113,7 +113,7 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { | |||||||
| 	// Pagination. | 	// Pagination. | ||||||
| 	startPagination := 0 | 	startPagination := 0 | ||||||
| 	if page > 1 { | 	if page > 1 { | ||||||
| 		startPagination = (page - 1) * ctx.Config.Pastes.Pagination | 		startPagination = (page - 1) * app.Config.Pastes.Pagination | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Iteration one - get only public pastes. | 	// Iteration one - get only public pastes. | ||||||
| @@ -129,23 +129,23 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { | |||||||
| 	pastesData := make([]structs.Paste, 0) | 	pastesData := make([]structs.Paste, 0) | ||||||
|  |  | ||||||
| 	for idx, paste := range publicPastes { | 	for idx, paste := range publicPastes { | ||||||
| 		if len(pastesData) == ctx.Config.Pastes.Pagination { | 		if len(pastesData) == app.Config.Pastes.Pagination { | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if idx < startPagination { | 		if idx < startPagination { | ||||||
| 			ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index") | 			app.Log.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index") | ||||||
|  |  | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*ctx.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*ctx.Config.Pastes.Pagination)) { | 		if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*app.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*app.Config.Pastes.Pagination)) { | ||||||
| 			ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index") | 			app.Log.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index") | ||||||
|  |  | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ctx.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data") | 		app.Log.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data") | ||||||
|  |  | ||||||
| 		// Get paste data. | 		// Get paste data. | ||||||
| 		//nolint:exhaustruct | 		//nolint:exhaustruct | ||||||
| @@ -153,14 +153,14 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { | |||||||
|  |  | ||||||
| 		pasteRawData, err := os.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json")) | 		pasteRawData, err := os.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json")) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Error().Err(err).Msg("Failed to read paste data") | 			app.Log.Error().Err(err).Msg("Failed to read paste data") | ||||||
|  |  | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err1 := json.Unmarshal(pasteRawData, pasteData) | 		err1 := json.Unmarshal(pasteRawData, pasteData) | ||||||
| 		if err1 != nil { | 		if err1 != nil { | ||||||
| 			ctx.Logger.Error().Err(err1).Msg("Failed to parse paste data") | 			app.Log.Error().Err(err1).Msg("Failed to parse paste data") | ||||||
|  |  | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -184,9 +184,9 @@ func (ff *FlatFiles) GetPastesPages() int { | |||||||
| 	ff.writeMutex.Unlock() | 	ff.writeMutex.Unlock() | ||||||
|  |  | ||||||
| 	// Calculate pages. | 	// Calculate pages. | ||||||
| 	pages := len(publicPastes) / ctx.Config.Pastes.Pagination | 	pages := len(publicPastes) / app.Config.Pastes.Pagination | ||||||
| 	// Check if we have any remainder. Add 1 to pages count if so. | 	// Check if we have any remainder. Add 1 to pages count if so. | ||||||
| 	if len(publicPastes)%ctx.Config.Pastes.Pagination > 0 { | 	if len(publicPastes)%app.Config.Pastes.Pagination > 0 { | ||||||
| 		pages++ | 		pages++ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -194,14 +194,14 @@ func (ff *FlatFiles) GetPastesPages() int { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ff *FlatFiles) Initialize() { | func (ff *FlatFiles) Initialize() { | ||||||
| 	ctx.Logger.Info().Msg("Initializing flatfiles storage...") | 	app.Log.Info().Msg("Initializing flatfiles storage...") | ||||||
|  |  | ||||||
| 	path := ctx.Config.Database.Path | 	path := app.Config.Database.Path | ||||||
| 	// Get proper paste file path. | 	// Get proper paste file path. | ||||||
| 	if strings.Contains(ctx.Config.Database.Path, "~") { | 	if strings.Contains(app.Config.Database.Path, "~") { | ||||||
| 		curUser, err := user.Current() | 		curUser, err := user.Current() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!") | 			app.Log.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!") | ||||||
|  |  | ||||||
| 			path = strings.Replace(path, "~", "/", -1) | 			path = strings.Replace(path, "~", "/", -1) | ||||||
| 		} | 		} | ||||||
| @@ -212,40 +212,40 @@ func (ff *FlatFiles) Initialize() { | |||||||
| 	path, _ = filepath.Abs(path) | 	path, _ = filepath.Abs(path) | ||||||
| 	ff.path = path | 	ff.path = path | ||||||
|  |  | ||||||
| 	ctx.Logger.Debug().Msgf("Storage path is now: %s", ff.path) | 	app.Log.Debug().Msgf("Storage path is now: %s", ff.path) | ||||||
|  |  | ||||||
| 	// Create directory if necessary. | 	// Create directory if necessary. | ||||||
| 	if _, err := os.Stat(ff.path); err != nil { | 	if _, err := os.Stat(ff.path); err != nil { | ||||||
| 		ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...") | 		app.Log.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...") | ||||||
| 		_ = os.MkdirAll(ff.path, os.ModePerm) | 		_ = os.MkdirAll(ff.path, os.ModePerm) | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists") | 		app.Log.Debug().Str("directory", ff.path).Msg("Directory already exists") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Create directory for pastes. | 	// Create directory for pastes. | ||||||
| 	if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil { | 	if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil { | ||||||
| 		ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...") | 		app.Log.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...") | ||||||
| 		_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm) | 		_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm) | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists") | 		app.Log.Debug().Str("directory", ff.path).Msg("Directory already exists") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Load pastes index. | 	// Load pastes index. | ||||||
| 	ff.pastesIndex = []Index{} | 	ff.pastesIndex = []Index{} | ||||||
| 	if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil { | 	if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil { | ||||||
| 		ctx.Logger.Warn().Msg("Pastes index file does not exist, will create new one") | 		app.Log.Warn().Msg("Pastes index file does not exist, will create new one") | ||||||
| 	} else { | 	} else { | ||||||
| 		indexData, err := os.ReadFile(filepath.Join(ff.path, "pastes", "index.json")) | 		indexData, err := os.ReadFile(filepath.Join(ff.path, "pastes", "index.json")) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Fatal().Msg("Failed to read contents of index file!") | 			app.Log.Fatal().Msg("Failed to read contents of index file!") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err1 := json.Unmarshal(indexData, &ff.pastesIndex) | 		err1 := json.Unmarshal(indexData, &ff.pastesIndex) | ||||||
| 		if err1 != nil { | 		if err1 != nil { | ||||||
| 			ctx.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.") | 			app.Log.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ctx.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index") | 		app.Log.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -256,7 +256,7 @@ func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) { | |||||||
| 	pasteID := len(filesOnDisk) + 1 | 	pasteID := len(filesOnDisk) + 1 | ||||||
| 	paste.ID = pasteID | 	paste.ID = pasteID | ||||||
|  |  | ||||||
| 	ctx.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk") | 	app.Log.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk") | ||||||
|  |  | ||||||
| 	data, err := json.Marshal(paste) | 	data, err := json.Marshal(paste) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -286,18 +286,18 @@ func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ff *FlatFiles) Shutdown() { | func (ff *FlatFiles) Shutdown() { | ||||||
| 	ctx.Logger.Info().Msg("Saving indexes...") | 	app.Log.Info().Msg("Saving indexes...") | ||||||
|  |  | ||||||
| 	indexData, err := json.Marshal(ff.pastesIndex) | 	indexData, err := json.Marshal(ff.pastesIndex) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Error().Err(err).Msg("Failed to encode index data into JSON") | 		app.Log.Error().Err(err).Msg("Failed to encode index data into JSON") | ||||||
|  |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err1 := os.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0o600) | 	err1 := os.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0o600) | ||||||
| 	if err1 != nil { | 	if err1 != nil { | ||||||
| 		ctx.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.") | 		app.Log.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.") | ||||||
|  |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -25,19 +25,19 @@ | |||||||
| package mysql | package mysql | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx       *context.Context | 	app       *application.Application | ||||||
| 	dbAdapter *Database | 	dbAdapter *Database | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| 	dbAdapter = &Database{} | 	dbAdapter = &Database{} | ||||||
|  |  | ||||||
| 	ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | 	app.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,19 +26,19 @@ package migrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/pressly/goose" | 	"github.com/pressly/goose" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ctx *context.Context | var app *application.Application | ||||||
|  |  | ||||||
| // New initializes migrations. | // New initializes migrations. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| } | } | ||||||
|  |  | ||||||
| // Migrate launching migrations. | // Migrate launching migrations. | ||||||
| func Migrate() { | func Migrate() { | ||||||
| 	ctx.Logger.Info().Msg("Migrating database...") | 	app.Log.Info().Msg("Migrating database...") | ||||||
|  |  | ||||||
| 	_ = goose.SetDialect("mysql") | 	_ = goose.SetDialect("mysql") | ||||||
| 	goose.AddNamedMigration("1_initial.go", InitialUp, nil) | 	goose.AddNamedMigration("1_initial.go", InitialUp, nil) | ||||||
| @@ -47,13 +47,13 @@ func Migrate() { | |||||||
| 	goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown) | 	goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown) | ||||||
| 	// Add new migrations BEFORE this message. | 	// Add new migrations BEFORE this message. | ||||||
|  |  | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if dbConn != nil { | 	if dbConn != nil { | ||||||
| 		err := goose.Up(dbConn, ".") | 		err := goose.Up(dbConn, ".") | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error()) | 			app.Log.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error()) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping") | 		app.Log.Warn().Msg("Current database dialect isn't supporting migrations, skipping") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -103,10 +103,10 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { | |||||||
| 	// Pagination. | 	// Pagination. | ||||||
| 	startPagination := 0 | 	startPagination := 0 | ||||||
| 	if page > 1 { | 	if page > 1 { | ||||||
| 		startPagination = (page - 1) * ctx.Config.Pastes.Pagination | 		startPagination = (page - 1) * app.Config.Pastes.Pagination | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), ctx.Config.Pastes.Pagination, startPagination) | 	err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), app.Config.Pastes.Pagination, startPagination) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -142,9 +142,9 @@ func (db *Database) GetPastesPages() int { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Calculate pages. | 	// Calculate pages. | ||||||
| 	pages := len(pastes) / ctx.Config.Pastes.Pagination | 	pages := len(pastes) / app.Config.Pastes.Pagination | ||||||
| 	// Check if we have any remainder. Add 1 to pages count if so. | 	// Check if we have any remainder. Add 1 to pages count if so. | ||||||
| 	if len(pastes)%ctx.Config.Pastes.Pagination > 0 { | 	if len(pastes)%app.Config.Pastes.Pagination > 0 { | ||||||
| 		pages++ | 		pages++ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -153,23 +153,23 @@ func (db *Database) GetPastesPages() int { | |||||||
|  |  | ||||||
| // Initialize initializes MySQL/MariaDB connection. | // Initialize initializes MySQL/MariaDB connection. | ||||||
| func (db *Database) Initialize() { | func (db *Database) Initialize() { | ||||||
| 	ctx.Logger.Info().Msg("Initializing database connection...") | 	app.Log.Info().Msg("Initializing database connection...") | ||||||
|  |  | ||||||
| 	// There might be only user, without password. MySQL/MariaDB driver | 	// There might be only user, without password. MySQL/MariaDB driver | ||||||
| 	// in DSN wants "user" or "user:password", "user:" is invalid. | 	// in DSN wants "user" or "user:password", "user:" is invalid. | ||||||
| 	var userpass string | 	var userpass string | ||||||
| 	if ctx.Config.Database.Password == "" { | 	if app.Config.Database.Password == "" { | ||||||
| 		userpass = ctx.Config.Database.Username | 		userpass = app.Config.Database.Username | ||||||
| 	} else { | 	} else { | ||||||
| 		userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password | 		userpass = app.Config.Database.Username + ":" + app.Config.Database.Password | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, ctx.Config.Database.Address, ctx.Config.Database.Port, ctx.Config.Database.Database) | 	dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, app.Config.Database.Address, app.Config.Database.Port, app.Config.Database.Database) | ||||||
| 	ctx.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed") | 	app.Log.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed") | ||||||
|  |  | ||||||
| 	dbConn, err := sqlx.Connect("mysql", dbConnString) | 	dbConn, err := sqlx.Connect("mysql", dbConnString) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Error().Err(err).Msg("Failed to connect to database") | 		app.Log.Error().Err(err).Msg("Failed to connect to database") | ||||||
|  |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -177,12 +177,12 @@ func (db *Database) Initialize() { | |||||||
| 	// Force UTC for current connection. | 	// Force UTC for current connection. | ||||||
| 	_ = dbConn.MustExec("SET @@session.time_zone='+00:00';") | 	_ = dbConn.MustExec("SET @@session.time_zone='+00:00';") | ||||||
|  |  | ||||||
| 	ctx.Logger.Info().Msg("Database connection established") | 	app.Log.Info().Msg("Database connection established") | ||||||
|  |  | ||||||
| 	db.db = dbConn | 	db.db = dbConn | ||||||
|  |  | ||||||
| 	// Perform migrations. | 	// Perform migrations. | ||||||
| 	migrations.New(ctx) | 	migrations.New(app) | ||||||
| 	migrations.Migrate() | 	migrations.Migrate() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -208,7 +208,7 @@ func (db *Database) Shutdown() { | |||||||
| 	if db.db != nil { | 	if db.db != nil { | ||||||
| 		err := db.db.Close() | 		err := db.db.Close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Error().Err(err).Msg("Failed to close database connection") | 			app.Log.Error().Err(err).Msg("Failed to close database connection") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,19 +25,19 @@ | |||||||
| package postgresql | package postgresql | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | 	dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx       *context.Context | 	app       *application.Application | ||||||
| 	dbAdapter *Database | 	dbAdapter *Database | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| 	dbAdapter = &Database{} | 	dbAdapter = &Database{} | ||||||
|  |  | ||||||
| 	ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | 	app.Database.RegisterDialect(dialectinterface.Interface(Handler{})) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,19 +26,19 @@ package migrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/pressly/goose" | 	"github.com/pressly/goose" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ctx *context.Context | var app *application.Application | ||||||
|  |  | ||||||
| // New initializes migrations. | // New initializes migrations. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| } | } | ||||||
|  |  | ||||||
| // Migrate launching migrations. | // Migrate launching migrations. | ||||||
| func Migrate() { | func Migrate() { | ||||||
| 	ctx.Logger.Info().Msg("Migrating database...") | 	app.Log.Info().Msg("Migrating database...") | ||||||
|  |  | ||||||
| 	_ = goose.SetDialect("postgres") | 	_ = goose.SetDialect("postgres") | ||||||
| 	goose.AddNamedMigration("1_initial.go", InitialUp, nil) | 	goose.AddNamedMigration("1_initial.go", InitialUp, nil) | ||||||
| @@ -47,14 +47,14 @@ func Migrate() { | |||||||
| 	goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown) | 	goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown) | ||||||
| 	// Add new migrations BEFORE this message. | 	// Add new migrations BEFORE this message. | ||||||
|  |  | ||||||
| 	dbConn := ctx.Database.GetDatabaseConnection() | 	dbConn := app.Database.GetDatabaseConnection() | ||||||
| 	if dbConn != nil { | 	if dbConn != nil { | ||||||
| 		err := goose.Up(dbConn, ".") | 		err := goose.Up(dbConn, ".") | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Info().Msgf("%+v", err) | 			app.Log.Info().Msgf("%+v", err) | ||||||
| 			ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error()) | 			app.Log.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error()) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping") | 		app.Log.Warn().Msg("Current database dialect isn't supporting migrations, skipping") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -114,10 +114,10 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { | |||||||
| 	// Pagination. | 	// Pagination. | ||||||
| 	startPagination := 0 | 	startPagination := 0 | ||||||
| 	if page > 1 { | 	if page > 1 { | ||||||
| 		startPagination = (page - 1) * ctx.Config.Pastes.Pagination | 		startPagination = (page - 1) * app.Config.Pastes.Pagination | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), ctx.Config.Pastes.Pagination, startPagination) | 	err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), app.Config.Pastes.Pagination, startPagination) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		//nolint:wrapcheck | 		//nolint:wrapcheck | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -159,9 +159,9 @@ func (db *Database) GetPastesPages() int { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Calculate pages. | 	// Calculate pages. | ||||||
| 	pages := len(pastes) / ctx.Config.Pastes.Pagination | 	pages := len(pastes) / app.Config.Pastes.Pagination | ||||||
| 	// Check if we have any remainder. Add 1 to pages count if so. | 	// Check if we have any remainder. Add 1 to pages count if so. | ||||||
| 	if len(pastes)%ctx.Config.Pastes.Pagination > 0 { | 	if len(pastes)%app.Config.Pastes.Pagination > 0 { | ||||||
| 		pages++ | 		pages++ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -170,31 +170,31 @@ func (db *Database) GetPastesPages() int { | |||||||
|  |  | ||||||
| // Initialize initializes MySQL/MariaDB connection. | // Initialize initializes MySQL/MariaDB connection. | ||||||
| func (db *Database) Initialize() { | func (db *Database) Initialize() { | ||||||
| 	ctx.Logger.Info().Msg("Initializing database connection...") | 	app.Log.Info().Msg("Initializing database connection...") | ||||||
|  |  | ||||||
| 	var userpass string | 	var userpass string | ||||||
| 	if ctx.Config.Database.Password == "" { | 	if app.Config.Database.Password == "" { | ||||||
| 		userpass = ctx.Config.Database.Username | 		userpass = app.Config.Database.Username | ||||||
| 	} else { | 	} else { | ||||||
| 		userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password | 		userpass = app.Config.Database.Username + ":" + app.Config.Database.Password | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dbConnString := fmt.Sprintf("postgres://%s@%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, net.JoinHostPort(ctx.Config.Database.Address, ctx.Config.Database.Port), ctx.Config.Database.Database) | 	dbConnString := fmt.Sprintf("postgres://%s@%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, net.JoinHostPort(app.Config.Database.Address, app.Config.Database.Port), app.Config.Database.Database) | ||||||
| 	ctx.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed") | 	app.Log.Debug().Str("DSN", dbConnString).Msg("Database connection string composed") | ||||||
|  |  | ||||||
| 	dbConn, err := sqlx.Connect("postgres", dbConnString) | 	dbConn, err := sqlx.Connect("postgres", dbConnString) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Logger.Error().Err(err).Msg("Failed to connect to database") | 		app.Log.Error().Err(err).Msg("Failed to connect to database") | ||||||
|  |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx.Logger.Info().Msg("Database connection established") | 	app.Log.Info().Msg("Database connection established") | ||||||
|  |  | ||||||
| 	db.db = dbConn | 	db.db = dbConn | ||||||
|  |  | ||||||
| 	// Perform migrations. | 	// Perform migrations. | ||||||
| 	migrations.New(ctx) | 	migrations.New(app) | ||||||
| 	migrations.Migrate() | 	migrations.Migrate() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -222,7 +222,7 @@ func (db *Database) Shutdown() { | |||||||
| 	if db.db != nil { | 	if db.db != nil { | ||||||
| 		err := db.db.Close() | 		err := db.db.Close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Logger.Error().Err(err).Msg("Failed to close database connection") | 			app.Log.Error().Err(err).Msg("Failed to close database connection") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,20 +25,20 @@ | |||||||
| package database | package database | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| 	databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface" | 	databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx       *context.Context | 	app       *application.Application | ||||||
| 	dbAdapter *Database | 	dbAdapter *Database | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // New initializes database structure. | // New initializes database structure. | ||||||
| func New(cc *context.Context) { | func New(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	//nolint:exhaustruct | 	//nolint:exhaustruct | ||||||
| 	dbAdapter = &Database{} | 	dbAdapter = &Database{} | ||||||
|  |  | ||||||
| 	ctx.RegisterDatabaseInterface(databaseinterface.Interface(Handler{})) | 	app.Database = databaseinterface.Interface(Handler{}) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								internal/helpers/path.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								internal/helpers/path.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | package helpers | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	// ErrHelperError indicates that error came from helper. | ||||||
|  | 	ErrHelperError = errors.New("helper") | ||||||
|  |  | ||||||
|  | 	// ErrHelperPathNormalizationError indicates that error came from path normalization helper. | ||||||
|  | 	ErrHelperPathNormalizationError = errors.New("path normalization") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NormalizePath normalizes passed path: | ||||||
|  | // * Path will be absolute. | ||||||
|  | // * Symlinks will be resolved. | ||||||
|  | // * Support for tilde (~) for home path. | ||||||
|  | func NormalizePath(path string) (string, error) { | ||||||
|  | 	// Replace possible tilde in the beginning (and only beginning!) of data path. | ||||||
|  | 	if strings.HasPrefix(path, "~") { | ||||||
|  | 		homeDir, err := os.UserHomeDir() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return "", fmt.Errorf("%s: %s: %w", ErrHelperError, ErrHelperPathNormalizationError, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		path = strings.Replace(path, "~", homeDir, 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Normalize path. | ||||||
|  | 	absPath, err := filepath.Abs(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("%s: %s: %w", ErrHelperError, ErrHelperPathNormalizationError, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return absPath, nil | ||||||
|  | } | ||||||
| @@ -31,11 +31,11 @@ import ( | |||||||
| 	"github.com/labstack/echo" | 	"github.com/labstack/echo" | ||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/assets" | 	"go.dev.pztrn.name/fastpastebin/assets" | ||||||
| 	"go.dev.pztrn.name/fastpastebin/internal/context" | 	"go.dev.pztrn.name/fastpastebin/internal/application" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ctx *context.Context | 	app *application.Application | ||||||
| 	log zerolog.Logger | 	log zerolog.Logger | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -100,7 +100,7 @@ func GetTemplate(ectx echo.Context, name string, data map[string]string) string | |||||||
| 	tpl := strings.Replace(string(mainhtml), "{navigation}", string(navhtml), 1) | 	tpl := strings.Replace(string(mainhtml), "{navigation}", string(navhtml), 1) | ||||||
| 	tpl = strings.Replace(tpl, "{footer}", string(footerhtml), 1) | 	tpl = strings.Replace(tpl, "{footer}", string(footerhtml), 1) | ||||||
| 	// Version. | 	// Version. | ||||||
| 	tpl = strings.Replace(tpl, "{version}", context.Version, 1) | 	tpl = strings.Replace(tpl, "{version}", application.Version, 1) | ||||||
|  |  | ||||||
| 	// Get requested template. | 	// Get requested template. | ||||||
| 	reqhtml, err3 := assets.Data.ReadFile(name) | 	reqhtml, err3 := assets.Data.ReadFile(name) | ||||||
| @@ -122,7 +122,7 @@ func GetTemplate(ectx echo.Context, name string, data map[string]string) string | |||||||
| } | } | ||||||
|  |  | ||||||
| // Initialize initializes package. | // Initialize initializes package. | ||||||
| func Initialize(cc *context.Context) { | func Initialize(cc *application.Application) { | ||||||
| 	ctx = cc | 	app = cc | ||||||
| 	log = ctx.Logger.With().Str("type", "internal").Str("package", "templater").Logger() | 	log = app.Log.With().Str("type", "internal").Str("package", "templater").Logger() | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user