Skip to content
Snippets Groups Projects
Unverified Commit 61cac96e authored by Kevin Lyda's avatar Kevin Lyda
Browse files

A bunch of sql changes

Creates the users, folders and co_owners tables.  Move user preferences
inside the main database.

Makes use of sqlite triggers and foreign key constraints.  Not a huge
fan of these, but they do fit the use case and this isn't a case where
scaling is a concern.
parent fcc3fb91
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,8 @@ Look up how to run migrations on the db from embedded files. ...@@ -19,6 +19,8 @@ Look up how to run migrations on the db from embedded files.
Figure out readline completion. Case sensitive - issue? Building from Figure out readline completion. Case sensitive - issue? Building from
repl.commands? repl.commands?
sqlite trigger tracing: `.trace stdout --row --profile --stmt --expanded --plain --close`
## Things to do ## Things to do
* Implement a better dclish parser. * Implement a better dclish parser.
......
...@@ -2,15 +2,11 @@ ...@@ -2,15 +2,11 @@
package accounts package accounts
import ( import (
"database/sql"
"errors" "errors"
"fmt" "fmt"
"os"
"path"
"strings" "strings"
"git.lyda.ie/kevin/bulletin/folders" "git.lyda.ie/kevin/bulletin/folders"
"github.com/adrg/xdg"
_ "modernc.org/sqlite" // Loads sqlite driver. _ "modernc.org/sqlite" // Loads sqlite driver.
) )
...@@ -19,7 +15,6 @@ import ( ...@@ -19,7 +15,6 @@ import (
type UserData struct { type UserData struct {
Account string Account string
FullName string FullName string
pref *sql.DB
Folders *folders.Store Folders *folders.Store
CurrentFolder string CurrentFolder string
CurrentMessage int CurrentMessage int
...@@ -49,18 +44,7 @@ func Open(acc string) error { ...@@ -49,18 +44,7 @@ func Open(acc string) error {
Account: acc, Account: acc,
} }
prefdir := path.Join(xdg.ConfigHome, "BULLETIN") User.Folders, err = folders.Open(acc)
err = os.MkdirAll(prefdir, 0700)
if err != nil {
return errors.New("account preference directory problem")
}
User.pref, err = sql.Open("sqlite", path.Join(prefdir, fmt.Sprintf("%s.%s", acc, ".db")))
if err != nil {
return errors.New("account preference database problem")
}
// TODO: run prefs migration - move this to a storage module.
User.Folders, err = folders.Open()
if err != nil { if err != nil {
return err return err
} }
...@@ -70,7 +54,6 @@ func Open(acc string) error { ...@@ -70,7 +54,6 @@ func Open(acc string) error {
// Close closes the resources open for the account. // Close closes the resources open for the account.
func (u *UserData) Close() { func (u *UserData) Close() {
u.pref.Close()
u.Folders.Close() u.Folders.Close()
} }
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"database/sql" "database/sql"
"embed" "embed"
"errors" "errors"
"log"
"os" "os"
"path" "path"
...@@ -23,11 +22,12 @@ var fs embed.FS ...@@ -23,11 +22,12 @@ var fs embed.FS
// Store is the store for folders. // Store is the store for folders.
type Store struct { type Store struct {
user string
db *sql.DB db *sql.DB
} }
// Open opens the folders database. // Open opens the folders database.
func Open() (*Store, error) { func Open(user string) (*Store, error) {
fdir := path.Join(xdg.DataHome, "BULLETIN") fdir := path.Join(xdg.DataHome, "BULLETIN")
err := os.MkdirAll(fdir, 0700) err := os.MkdirAll(fdir, 0700)
if err != nil { if err != nil {
...@@ -39,18 +39,18 @@ func Open() (*Store, error) { ...@@ -39,18 +39,18 @@ func Open() (*Store, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
m, err := migrate.NewWithSourceInstance("iofs", sqldir, "sqlite://"+fdb) m, err := migrate.NewWithSourceInstance("iofs", sqldir, "sqlite://"+fdb+"?_pragma=foreign_keys(1)")
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
err = m.Up() err = m.Up()
if err != nil { if err != nil && err != migrate.ErrNoChange {
return nil, err return nil, err
} }
m.Close() m.Close()
store := &Store{} store := &Store{user: user}
store.db, err = sql.Open("sqlite", fdb) store.db, err = sql.Open("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)")
if err != nil { if err != nil {
return nil, errors.New("bulletin database problem") return nil, errors.New("bulletin database problem")
} }
......
--- Dropped in reverse order to deal with foreign keys.
DROP TRIGGER co_owners_after_update_update_at;
DROP TABLE co_owners;
DROP TRIGGER folders_before_delete_protect;
DROP TRIGGER folders_after_update_update_at;
DROP TRIGGER folders_before_update_validate;
DROP TRIGGER folders_before_insert_validate;
DROP TABLE folders; DROP TABLE folders;
DROP TRIGGER users_before_delete_protect;
DROP TRIGGER users_before_update_protect;
DROP TRIGGER users_after_update_update_at;
DROP TABLE users;
CREATE TABLE users (
login VARCHAR(25) NOT NULL PRIMARY KEY,
name VARCHAR(53) NOT NULL,
admin INT DEFAULT 0,
create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
) WITHOUT ROWID;
CREATE TRIGGER users_after_update_update_at
AFTER UPDATE ON users FOR EACH ROW
WHEN NEW.update_at = OLD.update_at --- avoid infinite loop
BEGIN
UPDATE users SET update_at=CURRENT_TIMESTAMP WHERE login=NEW.login;
END;
INSERT INTO users (login, name, admin)
VALUES ('SYSTEM', 'System User', 1);
CREATE TRIGGER users_before_update_protect
AFTER UPDATE ON users FOR EACH ROW
WHEN OLD.login = 'SYSTEM' AND (NEW.login != OLD.login OR NEW.admin != 1)
BEGIN
SELECT RAISE (ABORT, 'SYSTEM user is protected');
END;
CREATE TRIGGER users_before_delete_protect
BEFORE DELETE on users FOR EACH ROW
WHEN OLD.login = 'SYSTEM'
BEGIN
SELECT RAISE (ABORT, 'SYSTEM user is protected');
END;
CREATE TABLE folders ( CREATE TABLE folders (
name VARCHAR(25) NOT NULL UNIQUE, name VARCHAR(25) NOT NULL PRIMARY KEY,
always INT DEFAULT 0, always INT DEFAULT 0,
brief INT DEFAULT 0, brief INT DEFAULT 0,
description VARCHAR(53), description VARCHAR(53) NOT NULL,
co_owners TEXT,
notify INT DEFAULT 0, notify INT DEFAULT 0,
owner TEXT, owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE,
readnew INT DEFAULT 0, readnew INT DEFAULT 0,
shownew INT DEFAULT 0, shownew INT DEFAULT 0,
system INT DEFAULT 0, system INT DEFAULT 0,
expire INT DEFAULT 14, expire INT DEFAULT 14,
visibility TEXT DEFAULT "public" visibility TEXT DEFAULT 'public',
); create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
) WITHOUT ROWID;
CREATE TRIGGER folders_before_insert_validate
BEFORE INSERT on folders
BEGIN
SELECT
CASE
WHEN NEW.name != UPPER(NEW.name) OR NEW.name GLOB '*[^A-Z0-9_-]*' THEN
RAISE (ABORT, 'Invalid folder name')
END;
END;
CREATE TRIGGER folders_before_update_validate
BEFORE UPDATE on folders
BEGIN
SELECT
CASE
WHEN NEW.name != UPPER(NEW.name) OR NEW.name GLOB '*[^A-Z0-9_-]*' THEN
RAISE (ABORT, 'Invalid folder name')
WHEN OLD.name = 'GENERAL' AND OLD.name != NEW.name THEN
RAISE (ABORT, 'GENERAL folder is protected')
END;
END;
CREATE TRIGGER folders_after_update_update_at
AFTER UPDATE ON folders FOR EACH ROW
WHEN NEW.update_at = OLD.update_at --- avoid infinite loop
BEGIN
UPDATE folders SET update_at=CURRENT_TIMESTAMP WHERE name=NEW.name;
END;
CREATE TRIGGER folders_before_delete_protect
BEFORE DELETE on folders FOR EACH ROW
WHEN OLD.name = 'GENERAL'
BEGIN
SELECT RAISE (ABORT, 'GENERAL folder is protected');
END;
INSERT INTO folders (name, description, system, shownew, owner) INSERT INTO folders (name, description, system, shownew, owner)
VALUES ("GENERAL", "Default general bulletin folder.", 1, 1, "SYSTEM"); VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1, 'SYSTEM');
CREATE TABLE co_owners (
folder VARCHAR(25) REFERENCES folders(name) ON UPDATE CASCADE,
owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE,
create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (folder, owner)
) WITHOUT ROWID;
CREATE TRIGGER co_owners_after_update_update_at
AFTER UPDATE ON co_owners FOR EACH ROW
WHEN NEW.update_at = OLD.update_at --- avoid infinite loop
BEGIN
UPDATE co_owners SET update_at=CURRENT_TIMESTAMP WHERE folder=NEW.folder AND owner=NEW.owner;
END;
...@@ -678,7 +678,9 @@ file exists, the file would be appended to that file.`, ...@@ -678,7 +678,9 @@ file exists, the file would be appended to that file.`,
"HELP": { "HELP": {
Description: `To obtain help on any topic, type: Description: `To obtain help on any topic, type:
HELP topic`, HELP topic
Type "HELP Topics" to get a list of topics.`,
MaxArgs: 1, MaxArgs: 1,
Action: ActionHelp, Action: ActionHelp,
}, },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment