diff --git a/internal/database/database.go b/internal/database/database.go index ef4a5d5..4de4ef4 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -27,6 +27,7 @@ package database import ( // stdlib "database/sql" + "time" // local "go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles" @@ -44,6 +45,46 @@ type Database struct { db dialectinterface.Interface } +// Database cleanup function. Executes once per hour, hardcoded for now and is +// a subject of change in future. +func (db *Database) cleanup() { + for { + c.Logger.Info().Msg("Starting pastes cleanup procedure...") + + pages := db.db.GetPastesPages() + + var pasteIDsToRemove []int + + for i := 0; i < pages; i++ { + pastes, err := db.db.GetPagedPastes(i) + if err != nil { + c.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup") + } + + for _, paste := range pastes { + if paste.IsExpired() { + pasteIDsToRemove = append(pasteIDsToRemove, paste.ID) + } + } + } + + for _, pasteID := range pasteIDsToRemove { + err := db.DeletePaste(pasteID) + if err != nil { + c.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!") + } + } + + c.Logger.Info().Msg("Pastes cleanup done.") + + time.Sleep(time.Hour) + } +} + +func (db *Database) DeletePaste(pasteID int) error { + return db.db.DeletePaste(pasteID) +} + func (db *Database) GetDatabaseConnection() *sql.DB { if db.db != nil { return db.db.GetDatabaseConnection() @@ -77,6 +118,8 @@ func (db *Database) Initialize() { } else { c.Logger.Fatal().Str("type", c.Config.Database.Type).Msg("Unknown database type") } + + go db.cleanup() } func (db *Database) RegisterDialect(di dialectinterface.Interface) { diff --git a/internal/database/dialects/flatfiles/flatfiles.go b/internal/database/dialects/flatfiles/flatfiles.go index b2abbeb..9c03c19 100644 --- a/internal/database/dialects/flatfiles/flatfiles.go +++ b/internal/database/dialects/flatfiles/flatfiles.go @@ -41,11 +41,42 @@ import ( ) type FlatFiles struct { - pastesIndex []*Index path string + pastesIndex []Index writeMutex sync.Mutex } +// DeletePaste deletes paste from disk and index. +func (ff *FlatFiles) DeletePaste(pasteID int) error { + // Delete from disk. + err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")) + if err != nil { + c.Logger.Error().Err(err).Msg("Failed to delete paste!") + + return err + } + + // Delete from index. + ff.writeMutex.Lock() + defer ff.writeMutex.Unlock() + + pasteIndex := -1 + + for idx, paste := range ff.pastesIndex { + if paste.ID == pasteID { + pasteIndex = idx + + break + } + } + + if pasteIndex != -1 { + ff.pastesIndex = append(ff.pastesIndex[:pasteIndex], ff.pastesIndex[pasteIndex+1:]...) + } + + return nil +} + func (ff *FlatFiles) GetDatabaseConnection() *sql.DB { return nil } @@ -77,13 +108,13 @@ func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) { func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { // Pagination. - var startPagination = 0 + startPagination := 0 if page > 1 { startPagination = (page - 1) * c.Config.Pastes.Pagination } // Iteration one - get only public pastes. - var publicPastes []*Index + var publicPastes []Index for _, paste := range ff.pastesIndex { if !paste.Private { @@ -134,7 +165,7 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { func (ff *FlatFiles) GetPastesPages() int { // Get public pastes count. - var publicPastes []*Index + var publicPastes []Index ff.writeMutex.Lock() for _, paste := range ff.pastesIndex { @@ -192,7 +223,7 @@ func (ff *FlatFiles) Initialize() { } // Load pastes index. - ff.pastesIndex = []*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 { @@ -232,7 +263,7 @@ func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) { } // Add it to cache. - indexData := &Index{} + indexData := Index{} indexData.ID = pasteID indexData.Private = p.Private ff.pastesIndex = append(ff.pastesIndex, indexData) diff --git a/internal/database/dialects/flatfiles/handler.go b/internal/database/dialects/flatfiles/handler.go index 4a772ca..dcb26d3 100644 --- a/internal/database/dialects/flatfiles/handler.go +++ b/internal/database/dialects/flatfiles/handler.go @@ -34,6 +34,10 @@ import ( type Handler struct{} +func (dbh Handler) DeletePaste(pasteID int) error { + return f.DeletePaste(pasteID) +} + func (dbh Handler) GetDatabaseConnection() *sql.DB { return f.GetDatabaseConnection() } diff --git a/internal/database/dialects/interface/dialectinterface.go b/internal/database/dialects/interface/dialectinterface.go index c01e174..711fa3f 100644 --- a/internal/database/dialects/interface/dialectinterface.go +++ b/internal/database/dialects/interface/dialectinterface.go @@ -33,6 +33,7 @@ import ( ) type Interface interface { + DeletePaste(int) error GetDatabaseConnection() *sql.DB GetPaste(pasteID int) (*structs.Paste, error) GetPagedPastes(page int) ([]structs.Paste, error) diff --git a/internal/database/dialects/mysql/handler.go b/internal/database/dialects/mysql/handler.go index a94df9b..c272b72 100644 --- a/internal/database/dialects/mysql/handler.go +++ b/internal/database/dialects/mysql/handler.go @@ -34,6 +34,10 @@ import ( type Handler struct{} +func (dbh Handler) DeletePaste(pasteID int) error { + return d.DeletePaste(pasteID) +} + func (dbh Handler) GetDatabaseConnection() *sql.DB { return d.GetDatabaseConnection() } diff --git a/internal/database/dialects/mysql/mysqldatabase.go b/internal/database/dialects/mysql/mysqldatabase.go index 9523010..01a9dbb 100644 --- a/internal/database/dialects/mysql/mysqldatabase.go +++ b/internal/database/dialects/mysql/mysqldatabase.go @@ -56,6 +56,18 @@ func (db *Database) check() { } } +// DeletePaste deletes paste from database. +func (db *Database) DeletePaste(pasteID int) error { + db.check() + + _, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID) + if err != nil { + return err + } + + return nil +} + func (db *Database) GetDatabaseConnection() *sql.DB { db.check() @@ -89,7 +101,7 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { ) // Pagination. - var startPagination = 0 + startPagination := 0 if page > 1 { startPagination = (page - 1) * c.Config.Pastes.Pagination } diff --git a/internal/database/dialects/postgresql/handler.go b/internal/database/dialects/postgresql/handler.go index 39c023b..7ffac3c 100644 --- a/internal/database/dialects/postgresql/handler.go +++ b/internal/database/dialects/postgresql/handler.go @@ -34,6 +34,10 @@ import ( type Handler struct{} +func (dbh Handler) DeletePaste(pasteID int) error { + return d.DeletePaste(pasteID) +} + func (dbh Handler) GetDatabaseConnection() *sql.DB { return d.GetDatabaseConnection() } diff --git a/internal/database/dialects/postgresql/postgresqldatabase.go b/internal/database/dialects/postgresql/postgresqldatabase.go index a2e2909..b61c709 100644 --- a/internal/database/dialects/postgresql/postgresqldatabase.go +++ b/internal/database/dialects/postgresql/postgresqldatabase.go @@ -58,6 +58,18 @@ func (db *Database) check() { } } +// DeletePaste deletes paste from database. +func (db *Database) DeletePaste(pasteID int) error { + db.check() + + _, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID) + if err != nil { + return err + } + + return nil +} + func (db *Database) GetDatabaseConnection() *sql.DB { db.check() @@ -98,7 +110,7 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { ) // Pagination. - var startPagination = 0 + startPagination := 0 if page > 1 { startPagination = (page - 1) * c.Config.Pastes.Pagination } diff --git a/internal/database/handler.go b/internal/database/handler.go index 62f76b6..e2a3791 100644 --- a/internal/database/handler.go +++ b/internal/database/handler.go @@ -38,6 +38,10 @@ import ( // to Database structure. type Handler struct{} +func (dbh Handler) DeletePaste(pasteID int) error { + return d.DeletePaste(pasteID) +} + func (dbh Handler) GetDatabaseConnection() *sql.DB { return d.GetDatabaseConnection() } diff --git a/internal/database/interface/databaseinterface.go b/internal/database/interface/databaseinterface.go index dfadcaf..a1afd7a 100644 --- a/internal/database/interface/databaseinterface.go +++ b/internal/database/interface/databaseinterface.go @@ -36,6 +36,7 @@ import ( // Interface represents database interface which is available to all // parts of application and registers with context.Context. type Interface interface { + DeletePaste(int) error GetDatabaseConnection() *sql.DB GetPaste(pasteID int) (*structs.Paste, error) GetPagedPastes(page int) ([]structs.Paste, error)