Database dialects, proper database shutdown, pagination in configuration.

Added possibility to use different database (storage) backends. Currently
supported: flatfiles and mysql. Fixes #2.

Added database shutdown call. This call will properly shutdown database
connections (in case of RDBMS) or flush pastes index cache on disk (in
case of flatfiles).

De-hardcoded pagination count. Fixes #12.
This commit is contained in:
2018-05-27 12:25:01 +05:00
parent 296e2771d6
commit 0cca0f453f
22 changed files with 854 additions and 175 deletions

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
"github.com/pztrn/fastpastebin/context"
"github.com/pztrn/fastpastebin/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
"github.com/pztrn/fastpastebin/pastes/model"
)
type FlatFiles struct {
pastesIndex []*Index
path string
writeMutex sync.Mutex
}
func (ff *FlatFiles) GetDatabaseConnection() *sql.DB {
return nil
}
func (ff *FlatFiles) GetPaste(pasteID int) (*pastesmodel.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 := &pastesmodel.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) ([]pastesmodel.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 []pastesmodel.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 := &pastesmodel.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 *pastesmodel.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
"github.com/pztrn/fastpastebin/pastes/model"
)
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return f.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*pastesmodel.Paste, error) {
return f.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]pastesmodel.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 *pastesmodel.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
"github.com/pztrn/fastpastebin/pastes/model"
)
type Interface interface {
GetDatabaseConnection() *sql.DB
GetPaste(pasteID int) (*pastesmodel.Paste, error)
GetPagedPastes(page int) ([]pastesmodel.Paste, error)
GetPastesPages() int
Initialize()
SavePaste(p *pastesmodel.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
"github.com/pztrn/fastpastebin/context"
"github.com/pztrn/fastpastebin/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
"github.com/pztrn/fastpastebin/pastes/model"
)
type Handler struct{}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*pastesmodel.Paste, error) {
return d.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]pastesmodel.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 *pastesmodel.Paste) (int64, error) {
return d.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
}

View File

@@ -0,0 +1,156 @@
// 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
"github.com/pztrn/fastpastebin/pastes/model"
// 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
}
func (db *Database) GetDatabaseConnection() *sql.DB {
return db.db.DB
}
// GetPaste returns a single paste by ID.
func (db *Database) GetPaste(pasteID int) (*pastesmodel.Paste, error) {
p := &pastesmodel.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) ([]pastesmodel.Paste, error) {
var pastesRaw []pastesmodel.Paste
var pastes []pastesmodel.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 {
var pastesRaw []pastesmodel.Paste
var pastes []pastesmodel.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.Panic().Msgf("Failed to connect to database: %s", err.Error())
}
// Force UTC for current connection.
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
c.Logger.Info().Msg("Database connection established")
db.db = dbConn
}
func (db *Database) SavePaste(p *pastesmodel.Paste) (int64, error) {
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() {
err := db.db.Close()
if err != nil {
c.Logger.Error().Msgf("Failed to close database connection: %s", err.Error())
}
}