Fastfix for previous two commits (sorry) and chroma update.

Well, doing "git add ." not from repository's root is a bad idea.

Updated chroma dependency, added more languages to syntax highlighting.
This commit is contained in:
2019-03-07 08:43:28 +05:00
parent 3fe51fc6c5
commit 19b5ef3d9f
155 changed files with 6765 additions and 258 deletions

View File

@@ -0,0 +1,62 @@
// 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 captcha
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
// other
"github.com/dchest/captcha"
"github.com/labstack/echo"
"github.com/rs/zerolog"
)
var (
c *context.Context
log zerolog.Logger
)
// New initializes captcha package and adds neccessary HTTP and API
// endpoints.
func New(cc *context.Context) {
c = cc
log = c.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
// New paste.
c.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
}
// NewCaptcha creates new captcha string.
func NewCaptcha() string {
s := captcha.New()
log.Debug().Str("captcha string", s).Msg("Created new captcha string")
return s
}
// Verify verifies captchas.
func Verify(captchaString string, captchaSolution string) bool {
return captcha.VerifyString(captchaString, captchaSolution)
}

View File

@@ -0,0 +1,36 @@
// 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 config
// 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"`
}

32
internal/config/http.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 config
// ConfigHTTP describes HTTP server configuration.
type ConfigHTTP struct {
Address string `yaml:"address"`
Port string `yaml:"port"`
AllowInsecure bool `yaml:"allow_insecure"`
}

View File

@@ -0,0 +1,32 @@
// 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 config
// ConfigLogging describes logger configuration.
type ConfigLogging struct {
LogToFile bool `yaml:"log_to_file"`
FileName string `yaml:"filename"`
LogLevel string `yaml:"loglevel"`
}

30
internal/config/pastes.go Normal file
View File

@@ -0,0 +1,30 @@
// 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 config
// ConfigPastes describes pastes subsystem configuration.
type ConfigPastes struct {
Pagination int `yaml:"pagination"`
}

33
internal/config/struct.go Normal file
View File

@@ -0,0 +1,33 @@
// 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 config
// ConfigStruct describes whole configuration.
type ConfigStruct struct {
Database ConfigDatabase `yaml:"database"`
Logging ConfigLogging `yaml:"logging"`
HTTP ConfigHTTP `yaml:"http"`
Pastes ConfigPastes `yaml:"pastes"`
}

140
internal/context/context.go Normal file
View File

@@ -0,0 +1,140 @@
// 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 (
// stdlib
"io/ioutil"
"os"
"path/filepath"
// local
"gitlab.com/pztrn/fastpastebin/internal/config"
"gitlab.com/pztrn/fastpastebin/internal/database/interface"
// other
"github.com/labstack/echo"
"github.com/rs/zerolog"
"gitlab.com/pztrn/flagger"
"gopkg.in/yaml.v2"
)
// Context is a some sort of singleton. Basically it's a structure that
// initialized once and then passed to all parts of application. It
// contains everything every part of application need, like configuration
// access, logger, etc.
type Context struct {
Config *config.ConfigStruct
Database databaseinterface.Interface
Echo *echo.Echo
Flagger *flagger.Flagger
Logger zerolog.Logger
}
// Initialize initializes context.
func (c *Context) Initialize() {
c.initializeLogger()
c.Flagger = flagger.New("fastpastebin", nil)
c.Flagger.Initialize()
c.Flagger.AddFlag(&flagger.Flag{
Name: "config",
Description: "Configuration file path. Can be overridded with FASTPASTEBIN_CONFIG environment variable (this is what used in tests).",
Type: "string",
DefaultValue: "NO_CONFIG",
})
}
// 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...")
var configPath = ""
// We're accepting configuration path from "-config" CLI parameter
// and FASTPASTEBIN_CONFIG environment variable. Later have higher
// weight and can override "-config" value.
configPathFromCLI, err := c.Flagger.GetStringValue("config")
configPathFromEnv, configPathFromEnvFound := os.LookupEnv("FASTPASTEBIN_CONFIG")
if err != nil && configPathFromEnvFound || err == nil && configPathFromEnvFound {
configPath = configPathFromEnv
} else if err != nil && !configPathFromEnvFound || err == nil && configPathFromCLI == "NO_CONFIG" {
c.Logger.Panic().Msg("Configuration file path wasn't passed via '-config' or 'FASTPASTEBIN_CONFIG' environment variable. Cannot continue.")
} else if err == nil && !configPathFromEnvFound {
configPath = configPathFromCLI
}
// Normalize file path.
normalizedConfigPath, err1 := filepath.Abs(configPath)
if err1 != nil {
c.Logger.Fatal().Msgf("Failed to normalize path to configuration file: %s", err1.Error())
}
c.Logger.Debug().Msgf("Configuration file path: %s", configPath)
c.Config = &config.ConfigStruct{}
// Read configuration file.
fileData, err2 := ioutil.ReadFile(normalizedConfigPath)
if err2 != nil {
c.Logger.Panic().Msgf("Failed to read configuration file: %s", err2.Error())
}
// Parse it into structure.
err3 := yaml.Unmarshal(fileData, c.Config)
if err3 != nil {
c.Logger.Panic().Msgf("Failed to parse configuration file: %s", err3.Error())
}
// Yay! See what it gets!
c.Logger.Debug().Msgf("Parsed configuration: %+v", c.Config)
}
// RegisterDatabaseInterface registers database interface for later use.
func (c *Context) RegisterDatabaseInterface(di databaseinterface.Interface) {
c.Database = di
}
// RegisterEcho registers Echo instance for later usage.
func (c *Context) RegisterEcho(e *echo.Echo) {
c.Echo = e
}
// Shutdown shutdowns entire application.
func (c *Context) Shutdown() {
c.Logger.Info().Msg("Shutting down Fast Pastebin...")
c.Database.Shutdown()
}

View File

@@ -0,0 +1,35 @@
// 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.3.0"
)
// New creates new context.
func New() *Context {
return &Context{}
}

View File

@@ -0,0 +1,47 @@
package context
import (
// local
"gitlab.com/pztrn/fastpastebin/assets/static"
// other
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func (c *Context) initializeHTTPServer() {
c.Echo = echo.New()
c.Echo.Use(c.echoReqLogger())
c.Echo.Use(middleware.Recover())
c.Echo.DisableHTTP2 = true
c.Echo.HideBanner = true
c.Echo.HidePort = true
// Static files.
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
go func() {
c.Echo.Logger.Fatal(c.Echo.Start(listenAddress))
}()
c.Logger.Info().Msgf("Started HTTP server at %s", listenAddress)
}
// Wrapper around previous function.
func (c *Context) echoReqLogger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ec echo.Context) error {
c.Logger.Info().
Str("IP", ec.RealIP()).
Str("Host", ec.Request().Host).
Str("Method", ec.Request().Method).
Str("Path", ec.Request().URL.Path).
Str("UA", ec.Request().UserAgent()).
Msg("HTTP request")
next(ec)
return nil
}
}
}

View File

@@ -0,0 +1,76 @@
package context
import (
// stdlib
"fmt"
"os"
"runtime"
"strings"
"time"
// other
"github.com/rs/zerolog"
)
// Puts memory usage into log lines.
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024))
e.Str("memsys", fmt.Sprintf("%dMB", m.Sys/1024/1024))
e.Str("numgc", fmt.Sprintf("%d", m.NumGC))
}
// Initializes logger.
func (c *Context) initializeLogger() {
// Устанавливаем форматирование логгера.
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
output.FormatLevel = func(i interface{}) string {
var v string
if ii, ok := i.(string); ok {
ii = strings.ToUpper(ii)
switch ii {
case "DEBUG":
v = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", ii)
case "ERROR":
v = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", ii)
case "FATAL":
v = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", ii)
case "INFO":
v = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", ii)
case "PANIC":
v = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", ii)
case "WARN":
v = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", ii)
default:
v = ii
}
}
return fmt.Sprintf("| %s |", v)
}
c.Logger = zerolog.New(output).With().Timestamp().Logger()
c.Logger = c.Logger.Hook(zerolog.HookFunc(c.getMemoryUsage))
}
// Initialize logger after configuration parse.
func (c *Context) initializeLoggerPost() {
// Set log level.
c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel)
switch c.Config.Logging.LogLevel {
case "DEBUG":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "INFO":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "WARN":
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case "ERROR":
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
case "FATAL":
zerolog.SetGlobalLevel(zerolog.FatalLevel)
case "PANIC":
zerolog.SetGlobalLevel(zerolog.PanicLevel)
}
}

View File

@@ -0,0 +1,93 @@
// 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 database
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/flatfiles"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/mysql"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/postgresql"
"gitlab.com/pztrn/fastpastebin/internal/structs"
// other
_ "github.com/go-sql-driver/mysql"
)
// Database represents control structure for database connection.
type Database struct {
db dialectinterface.Interface
}
func (db *Database) GetDatabaseConnection() *sql.DB {
if db.db != nil {
return db.db.GetDatabaseConnection()
}
return nil
}
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
return db.db.GetPaste(pasteID)
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
return db.db.GetPagedPastes(page)
}
func (db *Database) GetPastesPages() int {
return db.db.GetPastesPages()
}
// Initialize initializes connection to database.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
if c.Config.Database.Type == "mysql" {
mysql.New(c)
} else if c.Config.Database.Type == "flatfiles" {
flatfiles.New(c)
} else if c.Config.Database.Type == "postgresql" {
postgresql.New(c)
} else {
c.Logger.Fatal().Msgf("Unknown database type: %s", c.Config.Database.Type)
}
}
func (db *Database) RegisterDialect(di dialectinterface.Interface) {
db.db = di
db.db.Initialize()
}
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
return db.db.SavePaste(p)
}
func (db *Database) Shutdown() {
db.db.Shutdown()
}

View File

@@ -0,0 +1,42 @@
// 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 flatfiles
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
)
var (
c *context.Context
f *FlatFiles
)
func New(cc *context.Context) {
c = cc
f = &FlatFiles{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -0,0 +1,246 @@
// 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 flatfiles
import (
// stdlib
"database/sql"
"encoding/json"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"sync"
// local
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
type FlatFiles struct {
pastesIndex []*Index
path string
writeMutex sync.Mutex
}
func (ff *FlatFiles) GetDatabaseConnection() *sql.DB {
return nil
}
func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
ff.writeMutex.Lock()
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
c.Logger.Debug().Msgf("Trying to load paste data from '%s'...", pastePath)
pasteInBytes, err := ioutil.ReadFile(pastePath)
if err != nil {
c.Logger.Debug().Msgf("Failed to read paste from storage: %s", err.Error())
return nil, err
}
c.Logger.Debug().Msgf("Loaded %d bytes: %s", len(pasteInBytes), string(pasteInBytes))
ff.writeMutex.Unlock()
paste := &structs.Paste{}
err = json.Unmarshal(pasteInBytes, paste)
if err != nil {
c.Logger.Error().Msgf("Failed to parse paste: %s", err.Error())
return nil, err
}
return paste, nil
}
func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
// Pagination.
var startPagination = 0
if page > 1 {
startPagination = (page - 1) * c.Config.Pastes.Pagination
}
c.Logger.Debug().Msgf("Pastes index: %+v", ff.pastesIndex)
// Iteration one - get only public pastes.
var publicPastes []*Index
for _, paste := range ff.pastesIndex {
if !paste.Private {
publicPastes = append(publicPastes, paste)
}
}
c.Logger.Debug().Msgf("%+v", publicPastes)
// Iteration two - get paginated pastes.
var pastesData []structs.Paste
for idx, paste := range publicPastes {
if len(pastesData) == c.Config.Pastes.Pagination {
break
}
if idx < startPagination {
c.Logger.Debug().Msgf("Paste with index %d isn't in pagination query: too low index", idx)
continue
}
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*c.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*c.Config.Pastes.Pagination)) {
c.Logger.Debug().Msgf("Paste with index %d isn't in pagination query: too high index", idx)
break
}
c.Logger.Debug().Msgf("Getting paste data (ID: %d, index: %d)", paste.ID, idx)
// Get paste data.
pasteData := &structs.Paste{}
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
if err != nil {
c.Logger.Error().Msgf("Failed to read paste data: %s", err.Error())
continue
}
err = json.Unmarshal(pasteRawData, pasteData)
if err != nil {
c.Logger.Error().Msgf("Failed to parse paste data: %s", err.Error())
continue
}
pastesData = append(pastesData, (*pasteData))
}
return pastesData, nil
}
func (ff *FlatFiles) GetPastesPages() int {
// Get public pastes count.
var publicPastes []*Index
ff.writeMutex.Lock()
for _, paste := range ff.pastesIndex {
if !paste.Private {
publicPastes = append(publicPastes, paste)
}
}
ff.writeMutex.Unlock()
// Calculate pages.
pages := len(publicPastes) / c.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(publicPastes)%c.Config.Pastes.Pagination > 0 {
pages++
}
return pages
}
func (ff *FlatFiles) Initialize() {
c.Logger.Info().Msg("Initializing flatfiles storage...")
path := c.Config.Database.Path
// Get proper paste file path.
if strings.Contains(c.Config.Database.Path, "~") {
curUser, err := user.Current()
if err != nil {
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
path = strings.Replace(path, "~", "/", -1)
}
path = strings.Replace(path, "~", curUser.HomeDir, -1)
}
path, _ = filepath.Abs(path)
ff.path = path
c.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
// Create directory if neccessary.
if _, err := os.Stat(ff.path); err != nil {
c.Logger.Debug().Msgf("Directory '%s' does not exist, creating...", ff.path)
os.MkdirAll(ff.path, os.ModePerm)
} else {
c.Logger.Debug().Msgf("Directory '%s' already exists", ff.path)
}
// Create directory for pastes.
if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil {
c.Logger.Debug().Msgf("Directory '%s' does not exist, creating...", filepath.Join(ff.path, "pastes"))
os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm)
} else {
c.Logger.Debug().Msgf("Directory '%s' already exists", filepath.Join(ff.path, "pastes"))
}
// Load pastes index.
ff.pastesIndex = []*Index{}
if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil {
c.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
} else {
indexData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
if err != nil {
c.Logger.Fatal().Msg("Failed to read contents of index file!")
}
err = json.Unmarshal(indexData, &ff.pastesIndex)
if err != nil {
c.Logger.Error().Msgf("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable. Error was: %s", err.Error())
}
c.Logger.Debug().Msgf("Parsed pastes index: %+v", ff.pastesIndex)
}
}
func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
ff.writeMutex.Lock()
// Write paste data on disk.
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
pasteID := len(filesOnDisk) + 1
c.Logger.Debug().Msgf("Writing paste to disk, ID will be " + strconv.Itoa(pasteID))
p.ID = pasteID
data, err := json.Marshal(p)
if err != nil {
ff.writeMutex.Unlock()
return 0, err
}
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
if err != nil {
ff.writeMutex.Unlock()
return 0, err
}
// Add it to cache.
indexData := &Index{}
indexData.ID = pasteID
indexData.Private = p.Private
ff.pastesIndex = append(ff.pastesIndex, indexData)
ff.writeMutex.Unlock()
return int64(pasteID), nil
}
func (ff *FlatFiles) Shutdown() {
c.Logger.Info().Msg("Saving indexes...")
indexData, err := json.Marshal(ff.pastesIndex)
if err != nil {
c.Logger.Error().Msgf("Failed to encode index data into JSON: %s", err.Error())
return
}
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0644)
if err != nil {
c.Logger.Error().Msgf("Failed to write index data to file. Pretty sure that you've lost your pastes.")
return
}
}

View File

@@ -0,0 +1,63 @@
// 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 flatfiles
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return f.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return f.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return f.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return f.GetPastesPages()
}
func (dbh Handler) Initialize() {
f.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return f.SavePaste(p)
}
func (dbh Handler) Shutdown() {
f.Shutdown()
}

View File

@@ -0,0 +1,31 @@
// 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 flatfiles
// Index describes paste index structure for flatfiles data storage.
type Index struct {
ID int `yaml:"id"`
Private bool `json:"private"`
}

View File

@@ -0,0 +1,43 @@
// 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 dialectinterface
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
type Interface interface {
GetDatabaseConnection() *sql.DB
GetPaste(pasteID int) (*structs.Paste, error)
GetPagedPastes(page int) ([]structs.Paste, error)
GetPastesPages() int
Initialize()
SavePaste(p *structs.Paste) (int64, error)
Shutdown()
}

View File

@@ -0,0 +1,42 @@
// 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 mysql
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
)
var (
c *context.Context
d *Database
)
func New(cc *context.Context) {
c = cc
d = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -0,0 +1,63 @@
// 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 mysql
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
}
func (dbh Handler) Initialize() {
d.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
}

View File

@@ -0,0 +1,39 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func InitialUp(tx *sql.Tx) error {
_, err := tx.Exec("CREATE TABLE `pastes` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Paste ID', `title` text NOT NULL COMMENT 'Paste title', `data` longtext NOT NULL COMMENT 'Paste data', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Paste creation timestamp', `keep_for` int(4) NOT NULL DEFAULT 1 COMMENT 'Keep for integer. 0 - forever.', `keep_for_unit_type` int(1) NOT NULL DEFAULT 1 COMMENT 'Keep for unit type. 1 - minutes, 2 - hours, 3 - days, 4 - months.', PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Pastes';")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,48 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PasteLangUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `language` VARCHAR(191) NOT NULL DEFAULT 'text' COMMENT 'Paste language'")
if err != nil {
return err
}
return nil
}
func PasteLangDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `language`")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,48 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PrivatePastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `private` BOOL NOT NULL DEFAULT false COMMENT 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.'")
if err != nil {
return err
}
return nil
}
func PrivatePastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `private`")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,58 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PasswordedPastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
if err != nil {
return err
}
_, err1 := tx.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
if err1 != nil {
return err1
}
return nil
}
func PasswordedPastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
if err != nil {
return err
}
_, err1 := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
if err1 != nil {
return err1
}
return nil
}

View File

@@ -0,0 +1,65 @@
// 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 migrations
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
// other
//"gitlab.com/jmoiron/sqlx"
"github.com/pressly/goose"
)
var (
c *context.Context
)
// New initializes migrations.
func New(cc *context.Context) {
c = cc
}
// Migrate launching migrations.
func Migrate() {
c.Logger.Info().Msg("Migrating database...")
goose.SetDialect("mysql")
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
goose.AddNamedMigration("2_paste_lang.go", PasteLangUp, PasteLangDown)
goose.AddNamedMigration("3_private_pastes.go", PrivatePastesUp, PrivatePastesDown)
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
// Add new migrations BEFORE this message.
dbConn := c.Database.GetDatabaseConnection()
if dbConn != nil {
err := goose.Up(dbConn, ".")
if err != nil {
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
}
} else {
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
}
}

View File

@@ -0,0 +1,187 @@
// 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 mysql
import (
// stdlib
"database/sql"
"fmt"
// local
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/mysql/migrations"
"gitlab.com/pztrn/fastpastebin/internal/structs"
// other
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
// Database is a MySQL/MariaDB connection controlling structure.
type Database struct {
db *sqlx.DB
}
// Checks if queries can be run on known connection and reestablish it
// if not.
func (db *Database) check() {
if db.db != nil {
_, err := db.db.Exec("SELECT 1")
if err != nil {
db.Initialize()
}
} else {
db.Initialize()
}
}
func (db *Database) GetDatabaseConnection() *sql.DB {
db.check()
if db.db != nil {
return db.db.DB
}
return nil
}
// GetPaste returns a single paste by ID.
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check()
p := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
if err != nil {
return nil, err
}
return p, nil
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste
// Pagination.
var startPagination = 0
if page > 1 {
startPagination = (page - 1) * c.Config.Pastes.Pagination
}
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), c.Config.Pastes.Pagination, startPagination)
if err != nil {
return nil, err
}
for i := range pastesRaw {
if !pastesRaw[i].IsExpired() {
pastes = append(pastes, pastesRaw[i])
}
}
return pastes, nil
}
func (db *Database) GetPastesPages() int {
db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste
err := db.db.Get(&pastesRaw, "SELECT * FROM `pastes` WHERE private != true")
if err != nil {
return 1
}
// Check if pastes isn't expired.
for i := range pastesRaw {
if !pastesRaw[i].IsExpired() {
pastes = append(pastes, pastesRaw[i])
}
}
// Calculate pages.
pages := len(pastes) / c.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(pastes)%c.Config.Pastes.Pagination > 0 {
pages++
}
return pages
}
// Initialize initializes MySQL/MariaDB connection.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
// There might be only user, without password. MySQL/MariaDB driver
// in DSN wants "user" or "user:password", "user:" is invalid.
var userpass = ""
if c.Config.Database.Password == "" {
userpass = c.Config.Database.Username
} else {
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
}
dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
c.Logger.Debug().Msgf("Database connection string: %s", dbConnString)
dbConn, err := sqlx.Connect("mysql", dbConnString)
if err != nil {
c.Logger.Error().Msgf("Failed to connect to database: %s", err.Error())
return
}
// Force UTC for current connection.
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
c.Logger.Info().Msg("Database connection established")
db.db = dbConn
// Perform migrations.
migrations.New(c)
migrations.Migrate()
}
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
db.check()
result, err := db.db.NamedExec("INSERT INTO `pastes` (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt)", p)
if err != nil {
return 0, err
}
ID, err1 := result.LastInsertId()
if err1 != nil {
return 0, err
}
return ID, nil
}
func (db *Database) Shutdown() {
if db.db != nil {
err := db.db.Close()
if err != nil {
c.Logger.Error().Msgf("Failed to close database connection: %s", err.Error())
}
}
}

View File

@@ -0,0 +1,42 @@
// 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 postgresql
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
)
var (
c *context.Context
d *Database
)
func New(cc *context.Context) {
c = cc
d = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -0,0 +1,63 @@
// 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 postgresql
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
}
func (dbh Handler) Initialize() {
d.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
}

View File

@@ -0,0 +1,56 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func InitialUp(tx *sql.Tx) error {
_, err := tx.Exec(`
CREATE TABLE pastes
(
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
data TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
keep_for INTEGER NOT NULL DEFAULT 1,
keep_for_unit_type SMALLINT NOT NULL DEFAULT 1
);
COMMENT ON COLUMN pastes.id IS 'Paste ID';
COMMENT ON COLUMN pastes.title IS 'Paste title';
COMMENT ON COLUMN pastes.data IS 'Paste data';
COMMENT ON COLUMN pastes.created_at IS 'Paste creation timestamp';
COMMENT ON COLUMN pastes.keep_for IS 'Keep for integer. 0 - forever.';
COMMENT ON COLUMN pastes.keep_for_unit_type IS 'Keep for unit type. 0 - forever, 1 - minutes, 2 - hours, 3 - days, 4 - months.';
`)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,48 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PasteLangUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN language VARCHAR(191) NOT NULL DEFAULT 'text'; COMMENT ON COLUMN pastes.language IS 'Paste language';")
if err != nil {
return err
}
return nil
}
func PasteLangDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN language")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,48 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PrivatePastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN private BOOLEAN NOT NULL DEFAULT false; COMMENT ON COLUMN pastes.private IS 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.';")
if err != nil {
return err
}
return nil
}
func PrivatePastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN private")
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,58 @@
// 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 migrations
import (
// stdlib
"database/sql"
)
func PasswordedPastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
if err != nil {
return err
}
_, err1 := tx.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
if err1 != nil {
return err1
}
return nil
}
func PasswordedPastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN password")
if err != nil {
return err
}
_, err1 := tx.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
if err1 != nil {
return err1
}
return nil
}

View File

@@ -0,0 +1,66 @@
// 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 migrations
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
// other
//"gitlab.com/jmoiron/sqlx"
"github.com/pressly/goose"
)
var (
c *context.Context
)
// New initializes migrations.
func New(cc *context.Context) {
c = cc
}
// Migrate launching migrations.
func Migrate() {
c.Logger.Info().Msg("Migrating database...")
goose.SetDialect("postgres")
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
goose.AddNamedMigration("2_paste_lang.go", PasteLangUp, PasteLangDown)
goose.AddNamedMigration("3_private_pastes.go", PrivatePastesUp, PrivatePastesDown)
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
// Add new migrations BEFORE this message.
dbConn := c.Database.GetDatabaseConnection()
if dbConn != nil {
err := goose.Up(dbConn, ".")
if err != nil {
c.Logger.Info().Msgf("%+v", err)
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
}
} else {
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
}
}

View File

@@ -0,0 +1,197 @@
// 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 postgresql
import (
// stdlib
"database/sql"
"fmt"
"time"
// local
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/postgresql/migrations"
"gitlab.com/pztrn/fastpastebin/internal/structs"
// other
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
// Database is a PostgreSQL connection controlling structure.
type Database struct {
db *sqlx.DB
}
// Checks if queries can be run on known connection and reestablish it
// if not.
func (db *Database) check() {
if db.db != nil {
_, err := db.db.Exec("SELECT 1")
if err != nil {
db.Initialize()
}
} else {
db.Initialize()
}
}
func (db *Database) GetDatabaseConnection() *sql.DB {
db.check()
if db.db != nil {
return db.db.DB
}
return nil
}
// GetPaste returns a single paste by ID.
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check()
p := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
if err != nil {
return nil, err
}
// We're aware of timezone in PostgreSQL, so SELECT will return
// timestamps in server's local timezone. We should convert them.
loc, _ := time.LoadLocation("UTC")
utcCreatedAt := p.CreatedAt.In(loc)
p.CreatedAt = &utcCreatedAt
return p, nil
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste
// Pagination.
var startPagination = 0
if page > 1 {
startPagination = (page - 1) * c.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"), c.Config.Pastes.Pagination, startPagination)
if err != nil {
return nil, err
}
// We're aware of timezone in PostgreSQL, so SELECT will return
// timestamps in server's local timezone. We should convert them.
loc, _ := time.LoadLocation("UTC")
for _, paste := range pastesRaw {
if !paste.IsExpired() {
utcCreatedAt := paste.CreatedAt.In(loc)
paste.CreatedAt = &utcCreatedAt
pastes = append(pastes, paste)
}
}
return pastes, nil
}
func (db *Database) GetPastesPages() int {
db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste
err := db.db.Get(&pastesRaw, "SELECT * FROM pastes WHERE private != true")
if err != nil {
return 1
}
// Check if pastes isn't expired.
for _, paste := range pastesRaw {
if !paste.IsExpired() {
pastes = append(pastes, paste)
}
}
// Calculate pages.
pages := len(pastes) / c.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(pastes)%c.Config.Pastes.Pagination > 0 {
pages++
}
return pages
}
// Initialize initializes MySQL/MariaDB connection.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
var userpass = ""
if c.Config.Database.Password == "" {
userpass = c.Config.Database.Username
} else {
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
}
dbConnString := fmt.Sprintf("postgres://%s@%s:%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
c.Logger.Debug().Msgf("Database connection string: %s", dbConnString)
dbConn, err := sqlx.Connect("postgres", dbConnString)
if err != nil {
c.Logger.Error().Msgf("Failed to connect to database: %s", err.Error())
return
}
c.Logger.Info().Msg("Database connection established")
db.db = dbConn
// Perform migrations.
migrations.New(c)
migrations.Migrate()
}
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
db.check()
stmt, err := db.db.PrepareNamed("INSERT INTO pastes (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt) RETURNING id")
if err != nil {
return 0, err
}
var id int64
err = stmt.Get(&id, p)
if err != nil {
return 0, err
}
return id, nil
}
func (db *Database) Shutdown() {
if db.db != nil {
err := db.db.Close()
if err != nil {
c.Logger.Error().Msgf("Failed to close database connection: %s", err.Error())
}
}
}

View File

@@ -0,0 +1,43 @@
// 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 database
import (
// local
"gitlab.com/pztrn/fastpastebin/internal/context"
"gitlab.com/pztrn/fastpastebin/internal/database/interface"
)
var (
c *context.Context
d *Database
)
// New initializes database structure.
func New(cc *context.Context) {
c = cc
d = &Database{}
c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
}

View File

@@ -0,0 +1,71 @@
// 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 database
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
// Handler is an interfaceable structure that proxifies calls from anyone
// to Database structure.
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
}
// Initialize initializes connection to database.
func (dbh Handler) Initialize() {
d.Initialize()
}
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
d.RegisterDialect(di)
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
}

View File

@@ -0,0 +1,47 @@
// 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 databaseinterface
import (
// stdlib
"database/sql"
// local
"gitlab.com/pztrn/fastpastebin/internal/database/dialects/interface"
"gitlab.com/pztrn/fastpastebin/internal/structs"
)
// Interface represents database interface which is available to all
// parts of application and registers with context.Context.
type Interface interface {
GetDatabaseConnection() *sql.DB
GetPaste(pasteID int) (*structs.Paste, error)
GetPagedPastes(page int) ([]structs.Paste, error)
GetPastesPages() int
Initialize()
RegisterDialect(dialectinterface.Interface)
SavePaste(p *structs.Paste) (int64, error)
Shutdown()
}

View File

@@ -0,0 +1,93 @@
package pagination
import (
// stdlib
"strconv"
"strings"
// local
"gitlab.com/pztrn/fastpastebin/assets/static"
)
// CreateHTML creates pagination HTML based on passed parameters.
func CreateHTML(currentPage int, pages int, linksBase string) string {
// Load templates.
paginationHTMLRaw, err := static.ReadFile("pagination.html")
if err != nil {
return "Missing pagination.html"
}
paginationLinkRaw, err1 := static.ReadFile("pagination_link.html")
if err1 != nil {
return "Missing pagination_link.html"
}
paginationLinkCurrentRaw, err2 := static.ReadFile("pagination_link_current.html")
if err2 != nil {
return "Missing pagination_link_current.html"
}
paginationEllipsisRaw, err3 := static.ReadFile("pagination_ellipsis.html")
if err3 != nil {
return "Missing pagination_ellipsis.html"
}
// First page should always be visible.
var paginationString = ""
if currentPage == 1 {
paginationString = strings.Replace(string(paginationLinkCurrentRaw), "{pageNum}", strconv.Itoa(currentPage), -1)
} else {
paginationString = strings.Replace(string(paginationLinkRaw), "{pageNum}", "1", -1)
paginationString = strings.Replace(string(paginationString), "{paginationLink}", linksBase+"1", -1)
}
var ellipsisStartAdded = false
var ellipsisEndAdded = false
i := 2
for i <= pages {
if pages > 5 {
if currentPage-3 < i && currentPage+3 > i || i == pages {
var paginationItemRaw = string(paginationLinkRaw)
if i == currentPage {
paginationItemRaw = string(paginationLinkCurrentRaw)
}
paginationItem := strings.Replace(paginationItemRaw, "{pageNum}", strconv.Itoa(i), -1)
paginationItem = strings.Replace(paginationItem, "{paginationLink}", linksBase+strconv.Itoa(i), 1)
paginationString += paginationItem
} else {
if currentPage-3 < i && !ellipsisStartAdded {
paginationString += string(paginationEllipsisRaw)
ellipsisStartAdded = true
} else if currentPage+3 > i && !ellipsisEndAdded {
paginationString += string(paginationEllipsisRaw)
ellipsisEndAdded = true
}
}
} else {
var paginationItemRaw = string(paginationLinkRaw)
if i == currentPage {
paginationItemRaw = string(paginationLinkCurrentRaw)
}
paginationItem := strings.Replace(paginationItemRaw, "{pageNum}", strconv.Itoa(i), -1)
paginationItem = strings.Replace(paginationItem, "{paginationLink}", linksBase+strconv.Itoa(i), 1)
paginationString += paginationItem
}
i += 1
}
pagination := strings.Replace(string(paginationHTMLRaw), "{paginationLinks}", paginationString, 1)
if currentPage+1 <= pages {
pagination = strings.Replace(pagination, "{nextPageLink}", linksBase+strconv.Itoa(currentPage+1), 1)
} else {
pagination = strings.Replace(pagination, "{nextPageLink}", linksBase+strconv.Itoa(pages), 1)
}
if currentPage-1 > 1 {
pagination = strings.Replace(pagination, "{previousPageLink}", linksBase+strconv.Itoa(currentPage-1), 1)
} else {
pagination = strings.Replace(pagination, "{previousPageLink}", linksBase, 1)
}
return pagination
}

146
internal/structs/model.go Normal file
View File

@@ -0,0 +1,146 @@
// 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 structs
import (
// stdlib
"crypto/sha256"
"fmt"
"math/rand"
"time"
// other
"golang.org/x/crypto/scrypt"
)
const (
PASTE_KEEP_FOREVER = 0
PASTE_KEEP_FOR_MINUTES = 1
PASTE_KEEP_FOR_HOURS = 2
PASTE_KEEP_FOR_DAYS = 3
PASTE_KEEP_FOR_MONTHS = 4
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
var (
PASTE_KEEPS_CORELLATION = map[string]int{
"M": PASTE_KEEP_FOR_MINUTES,
"h": PASTE_KEEP_FOR_HOURS,
"d": PASTE_KEEP_FOR_DAYS,
"m": PASTE_KEEP_FOR_MONTHS,
"forever": PASTE_KEEP_FOREVER,
}
)
// Paste represents paste itself.
type Paste struct {
ID int `db:"id" json:"id"`
Title string `db:"title" json:"title"`
Data string `db:"data" json:"data"`
CreatedAt *time.Time `db:"created_at" json:"created_at"`
KeepFor int `db:"keep_for" json:"keep_for"`
KeepForUnitType int `db:"keep_for_unit_type" json:"keep_for_unit_type"`
Language string `db:"language" json:"language"`
Private bool `db:"private" json:"private"`
Password string `db:"password" json:"password"`
PasswordSalt string `db:"password_salt" json:"password_salt"`
}
// CreatePassword creates password for current paste.
func (p *Paste) CreatePassword(password string) error {
// Create salt - random string.
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
saltBytes := make([]byte, 64)
for i := range saltBytes {
saltBytes[i] = charset[seededRand.Intn(len(charset))]
}
saltHashBytes := sha256.Sum256(saltBytes)
p.PasswordSalt = fmt.Sprintf("%x", saltHashBytes)
// Create crypted password and hash it.
passwordCrypted, err := scrypt.Key([]byte(password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
if err != nil {
return err
}
passwordHashBytes := sha256.Sum256(passwordCrypted)
p.Password = fmt.Sprintf("%x", passwordHashBytes)
return nil
}
// GenerateCryptedCookieValue generates crypted cookie value for paste.
func (p *Paste) GenerateCryptedCookieValue() string {
cookieValueCrypted, _ := scrypt.Key([]byte(p.Password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
return fmt.Sprintf("%x", sha256.Sum256(cookieValueCrypted))
}
func (p *Paste) GetExpirationTime() time.Time {
var expirationTime time.Time
switch p.KeepForUnitType {
case PASTE_KEEP_FOREVER:
expirationTime = time.Now().UTC().Add(time.Hour * 1)
case PASTE_KEEP_FOR_MINUTES:
expirationTime = p.CreatedAt.Add(time.Minute * time.Duration(p.KeepFor))
case PASTE_KEEP_FOR_HOURS:
expirationTime = p.CreatedAt.Add(time.Hour * time.Duration(p.KeepFor))
case PASTE_KEEP_FOR_DAYS:
expirationTime = p.CreatedAt.Add(time.Hour * 24 * time.Duration(p.KeepFor))
case PASTE_KEEP_FOR_MONTHS:
expirationTime = p.CreatedAt.Add(time.Hour * 24 * 30 * time.Duration(p.KeepFor))
}
return expirationTime
}
// IsExpired checks if paste is already expired (or not).
func (p *Paste) IsExpired() bool {
curTime := time.Now().UTC()
expirationTime := p.GetExpirationTime()
if curTime.Sub(expirationTime).Seconds() > 0 {
return true
}
return false
}
// VerifyPassword verifies that provided password is valid.
func (p *Paste) VerifyPassword(password string) bool {
// Create crypted password and hash it.
passwordCrypted, err := scrypt.Key([]byte(password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
if err != nil {
return false
}
passwordHashBytes := sha256.Sum256(passwordCrypted)
providedPassword := fmt.Sprintf("%x", passwordHashBytes)
if providedPassword == p.Password {
return true
}
return false
}

View File

@@ -0,0 +1,127 @@
// 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 templater
import (
// stdlib
"net/http"
"strings"
// local
"gitlab.com/pztrn/fastpastebin/assets/static"
"gitlab.com/pztrn/fastpastebin/internal/context"
// other
"github.com/labstack/echo"
"github.com/rs/zerolog"
)
var (
c *context.Context
log zerolog.Logger
)
// GetErrorTemplate returns formatted error template.
// If error.html wasn't found - it will return "error.html not found"
// message as simple string.
func GetErrorTemplate(ec echo.Context, errorText string) string {
// Getting main error template.
mainhtml := GetTemplate(ec, "error.html", map[string]string{"error": errorText})
return mainhtml
}
// GetRawTemplate returns only raw template data.
func GetRawTemplate(ec echo.Context, templateName string, data map[string]string) string {
// Getting main template.
tplRaw, err := static.ReadFile(templateName)
if err != nil {
ec.String(http.StatusBadRequest, templateName+" not found.")
return ""
}
tpl := string(tplRaw)
// Replace placeholders with data from data map.
for placeholder, value := range data {
tpl = strings.Replace(tpl, "{"+placeholder+"}", value, -1)
}
return tpl
}
// GetTemplate returns formatted template that can be outputted to client.
func GetTemplate(ec echo.Context, name string, data map[string]string) string {
log.Debug().Msgf("Requested template '%s'", name)
// Getting main template.
mainhtml, err := static.ReadFile("main.html")
if err != nil {
ec.String(http.StatusBadRequest, "main.html not found.")
return ""
}
// Getting navigation.
navhtml, err1 := static.ReadFile("navigation.html")
if err1 != nil {
ec.String(http.StatusBadRequest, "navigation.html not found.")
return ""
}
// Getting footer.
footerhtml, err2 := static.ReadFile("footer.html")
if err2 != nil {
ec.String(http.StatusBadRequest, "footer.html not found.")
return ""
}
// Format main template.
tpl := strings.Replace(string(mainhtml), "{navigation}", string(navhtml), 1)
tpl = strings.Replace(tpl, "{footer}", string(footerhtml), 1)
// Version.
tpl = strings.Replace(tpl, "{version}", context.Version, 1)
// Get requested template.
reqhtml, err3 := static.ReadFile(name)
if err3 != nil {
ec.String(http.StatusBadRequest, name+" not found.")
return ""
}
// Replace documentBody.
tpl = strings.Replace(tpl, "{documentBody}", string(reqhtml), 1)
// Replace placeholders with data from data map.
for placeholder, value := range data {
tpl = strings.Replace(tpl, "{"+placeholder+"}", value, -1)
}
return tpl
}
// Initialize initializes package.
func Initialize(cc *context.Context) {
c = cc
log = c.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
}