2021-02-09 11:53:33 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
2021-03-02 16:14:02 +00:00
|
|
|
// Package sqlite implements the SQLite backend of the admindb interfaces.
|
|
|
|
//
|
|
|
|
// It uses sql-migrate (github.com/rubenv/sql-migrate) for it's schema definition and maintainace.
|
|
|
|
// 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:
|
|
|
|
//
|
|
|
|
// 1. Make changes to the interfaces in package admindb
|
|
|
|
// 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"
|
2021-02-09 15:49:48 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-03-02 16:14:02 +00:00
|
|
|
"time"
|
2021-02-09 15:49:48 +00:00
|
|
|
|
|
|
|
migrate "github.com/rubenv/sql-migrate"
|
2021-02-08 16:47:42 +00:00
|
|
|
|
2021-02-09 16:38:51 +00:00
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
2021-03-11 16:57:55 +00:00
|
|
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
2021-02-08 16:47:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Database struct {
|
|
|
|
db *sql.DB
|
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
AuthWithSSB roomdb.AuthWithSSBService
|
|
|
|
AuthFallback roomdb.AuthFallbackService
|
2021-02-08 16:47:42 +00:00
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
AllowList roomdb.AllowListService
|
|
|
|
Aliases roomdb.AliasService
|
2021-02-22 16:55:12 +00:00
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
PinnedNotices roomdb.PinnedNoticesService
|
|
|
|
Notices roomdb.NoticesService
|
2021-03-02 16:14:02 +00:00
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
Invites roomdb.InviteService
|
2021-02-08 16:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Open looks for a database file 'fname'
|
2021-02-09 15:49:48 +00:00
|
|
|
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) {
|
|
|
|
return nil, fmt.Errorf("admindb: failed to create folder for database (%q): %w", dir, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 16:14:02 +00:00
|
|
|
// 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-02-09 15:49:48 +00:00
|
|
|
return nil, fmt.Errorf("admindb: failed to open sqlite database: %w", err)
|
2021-02-08 16:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := db.Ping(); err != nil {
|
2021-02-09 15:49:48 +00:00
|
|
|
return nil, fmt.Errorf("admindb: sqlite ping failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := migrate.Exec(db, "sqlite3", migrationSource, migrate.Up)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("admindb: failed to apply database mirations: %w", err)
|
|
|
|
}
|
|
|
|
if n > 0 {
|
|
|
|
// TODO: hook up logging
|
|
|
|
log.Printf("admindb: applied %d migrations", n)
|
2021-02-08 16:47:42 +00:00
|
|
|
}
|
|
|
|
|
2021-03-02 16:14:02 +00:00
|
|
|
if err := deleteConsumedInvites(db); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() { // server might not restart as often
|
|
|
|
threeDays := 5 * 24 * time.Hour
|
|
|
|
ticker := time.NewTicker(threeDays)
|
|
|
|
for range ticker.C {
|
|
|
|
err := transact(db, func(tx *sql.Tx) error {
|
|
|
|
return deleteConsumedInvites(tx)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
// TODO: hook up logging
|
|
|
|
log.Printf("admindb: failed to clean up old invites: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
al := &AllowList{db}
|
2021-02-08 16:47:42 +00:00
|
|
|
admindb := &Database{
|
2021-02-22 16:55:12 +00:00
|
|
|
db: db,
|
|
|
|
AuthWithSSB: AuthWithSSB{db},
|
|
|
|
AuthFallback: AuthFallback{db},
|
2021-03-02 16:14:02 +00:00
|
|
|
AllowList: al,
|
2021-02-22 16:55:12 +00:00
|
|
|
Aliases: Aliases{db},
|
|
|
|
PinnedNotices: PinnedNotices{db},
|
|
|
|
Notices: Notices{db},
|
2021-03-02 16:14:02 +00:00
|
|
|
|
|
|
|
Invites: Invites{
|
|
|
|
db: db,
|
|
|
|
allowList: al,
|
|
|
|
},
|
2021-02-08 16:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return admindb, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|