go-ssb-room/roomdb/sqlite/new.go

155 lines
3.8 KiB
Go
Raw Normal View History

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
2021-02-09 11:53:33 +00:00
// SPDX-License-Identifier: MIT
2021-03-31 08:55:14 +00:00
// Package sqlite implements the SQLite backend of the roomdb interfaces.
//
2021-03-31 08:55:14 +00:00
// It uses sql-migrate (github.com/rubenv/sql-migrate) for it's schema definition and maintenance.
// For query construction/ORM it uses SQLBoiler (https://github.com/volatiletech/sqlboiler).
//
// The process of updating the schema and ORM can be summarized as follows:
//
2021-03-31 08:55:14 +00:00
// 1. Make changes to the interfaces in package roomdb
// 2. Add a new migration to the 'migrations' folder
// 3. Run 'go test -run Simple', which applies all the migrations
// 4. Run sqlboiler to generate package models
// 5. Implement the interface as needed by using the models package
//
// For convenience step 3 and 4 are combined in the generate_models bash script.
2021-02-08 16:47:42 +00:00
package sqlite
import (
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"time"
migrate "github.com/rubenv/sql-migrate"
2021-02-08 16:47:42 +00:00
"github.com/ssbc/go-ssb-room/v2/internal/repo"
2021-02-08 16:47:42 +00:00
)
type Database struct {
db *sql.DB
AuthFallback AuthFallback
AuthWithSSB AuthWithSSB
2021-02-08 16:47:42 +00:00
Members Members
Aliases Aliases
2021-03-19 11:28:14 +00:00
Invites Invites
2021-03-31 09:29:36 +00:00
Config Config
2021-03-19 11:28:14 +00:00
DeniedKeys DeniedKeys
PinnedNotices PinnedNotices
Notices Notices
2021-02-08 16:47:42 +00:00
}
// Open looks for a database file 'fname'
func Open(r repo.Interface) (*Database, error) {
fname := r.GetPath("roomdb")
if dir := filepath.Dir(fname); dir != "" {
err := os.MkdirAll(dir, 0700)
if err != nil && !os.IsExist(err) {
2021-03-31 08:55:14 +00:00
return nil, fmt.Errorf("roomdb: failed to create folder for database (%q): %w", dir, err)
}
}
// enable constraint enforcment for relations
fname += "?_foreign_keys=on"
2021-02-08 16:47:42 +00:00
db, err := sql.Open("sqlite3", fname)
if err != nil {
2021-03-31 08:55:14 +00:00
return nil, fmt.Errorf("roomdb: failed to open sqlite database: %w", err)
2021-02-08 16:47:42 +00:00
}
if err := db.Ping(); err != nil {
2021-03-31 08:55:14 +00:00
return nil, fmt.Errorf("roomdb: sqlite ping failed: %w", err)
}
n, err := migrate.Exec(db, "sqlite3", migrationSource, migrate.Up)
if err != nil {
2021-03-31 08:55:14 +00:00
return nil, fmt.Errorf("roomdb: failed to apply database mirations: %w", err)
}
if n > 0 {
// TODO: hook up logging
2021-03-31 08:55:14 +00:00
log.Printf("roomdb: applied %d migrations", n)
2021-02-08 16:47:42 +00:00
}
if err := deleteConsumedInvites(db); err != nil {
return nil, err
}
if err := deleteConsumedResetTokens(db); err != nil {
return nil, err
}
// scrub old invites and reset tokens
go func() { // server might not restart as often
fiveDays := 5 * 24 * time.Hour
ticker := time.NewTicker(fiveDays)
for range ticker.C {
err := transact(db, func(tx *sql.Tx) error {
if err := deleteConsumedResetTokens(tx); err != nil {
return err
}
return deleteConsumedInvites(tx)
})
if err != nil {
// TODO: hook up logging
2021-03-31 08:55:14 +00:00
log.Printf("roomdb: failed to clean up old invites: %s", err.Error())
}
}
}()
ml := Members{db}
2021-03-31 08:55:14 +00:00
roomdb := &Database{
db: db,
2021-03-19 11:28:14 +00:00
Aliases: Aliases{db},
AuthFallback: AuthFallback{db},
AuthWithSSB: AuthWithSSB{db},
2021-03-31 09:29:36 +00:00
Config: Config{db},
2021-03-19 11:28:14 +00:00
DeniedKeys: DeniedKeys{db},
Invites: Invites{db: db, members: ml},
Notices: Notices{db},
2021-03-19 11:28:14 +00:00
Members: ml,
PinnedNotices: PinnedNotices{db},
2021-02-08 16:47:42 +00:00
}
2021-03-31 08:55:14 +00:00
return roomdb, nil
2021-02-08 16:47:42 +00:00
}
// Close closes the contained sql database object
func (t Database) Close() error {
return t.db.Close()
}
func transact(db *sql.DB, fn func(tx *sql.Tx) error) error {
var err error
var tx *sql.Tx
tx, err = db.Begin()
if err != nil {
return fmt.Errorf("transact: could not begin transaction: %w", err)
}
if err = fn(tx); err != nil {
if err2 := tx.Rollback(); err2 != nil {
err = fmt.Errorf("rollback failed after %s: %s", err.Error(), err2.Error())
} else {
err = fmt.Errorf("transaction failed, rolling back: %w", err)
}
return err
}
if err = tx.Commit(); err != nil {
return fmt.Errorf("transact: could not commit transaction: %w", err)
}
return nil
}