diff --git a/accounts/accounts.go b/accounts/accounts.go
index def5a4b2f724e36a5f9766d7f9d20ce862c0abcb..e69a617b817dfa6a7a501f12884cea1fa46b47d0 100644
--- a/accounts/accounts.go
+++ b/accounts/accounts.go
@@ -9,6 +9,7 @@ import (
"path"
"strings"
+ "git.lyda.ie/kevin/bulletin/folders"
"github.com/adrg/xdg"
_ "modernc.org/sqlite" // Loads sqlite driver.
)
@@ -19,7 +20,7 @@ type UserData struct {
Account string
FullName string
pref *sql.DB
- bull *sql.DB
+ Folders *folders.Store
CurrentFolder string
CurrentMessage int
}
@@ -59,16 +60,10 @@ func Open(acc string) error {
}
// TODO: run prefs migration - move this to a storage module.
- bulldir := path.Join(xdg.DataHome, "BULLETIN")
- err = os.MkdirAll(bulldir, 0700)
+ User.Folders, err = folders.Open()
if err != nil {
- return errors.New("bulletin directory problem")
- }
- User.bull, err = sql.Open("sqlite", path.Join(bulldir, "bboard.db"))
- if err != nil {
- return errors.New("bulletin database problem")
+ return err
}
- // TODO: run prefs migration - move this to a storage module.
return nil
}
@@ -76,7 +71,7 @@ func Open(acc string) error {
// Close closes the resources open for the account.
func (u *UserData) Close() {
u.pref.Close()
- u.bull.Close()
+ u.Folders.Close()
}
// IsAdmin returns true if the user is an admin
diff --git a/folders/folders.go b/folders/folders.go
new file mode 100644
index 0000000000000000000000000000000000000000..9f4c8fa4303e78c65412b1efedbb3e67f425191c
--- /dev/null
+++ b/folders/folders.go
@@ -0,0 +1,63 @@
+// Package folders are all the routines and sql for managing folders.
+package folders
+
+import (
+ "database/sql"
+ "embed"
+ "errors"
+ "log"
+ "os"
+ "path"
+
+ "github.com/adrg/xdg"
+ "github.com/golang-migrate/migrate/v4"
+ "github.com/golang-migrate/migrate/v4/source/iofs"
+
+ // Included to connect to sqlite.
+ _ "github.com/golang-migrate/migrate/v4/database/sqlite"
+ _ "modernc.org/sqlite"
+)
+
+//go:embed sql/*.sql
+var fs embed.FS
+
+// Store is the store for folders.
+type Store struct {
+ db *sql.DB
+}
+
+// Open opens the folders database.
+func Open() (*Store, error) {
+ fdir := path.Join(xdg.DataHome, "BULLETIN")
+ err := os.MkdirAll(fdir, 0700)
+ if err != nil {
+ return nil, errors.New("bulletin directory problem")
+ }
+ fdb := path.Join(fdir, "bboard.db")
+
+ sqldir, err := iofs.New(fs, "sql")
+ if err != nil {
+ return nil, err
+ }
+ m, err := migrate.NewWithSourceInstance("iofs", sqldir, "sqlite://"+fdb)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = m.Up()
+ if err != nil {
+ return nil, err
+ }
+ m.Close()
+
+ store := &Store{}
+ store.db, err = sql.Open("sqlite", fdb)
+ if err != nil {
+ return nil, errors.New("bulletin database problem")
+ }
+ return store, nil
+}
+
+// Close closes the db backing the store.
+func (fstore *Store) Close() {
+ fstore.db.Close()
+}
diff --git a/folders/sql/1_create_table.down.sql b/folders/sql/1_create_table.down.sql
new file mode 100644
index 0000000000000000000000000000000000000000..df3837bed721d8c7b22e2eaf5ded9a401202c71f
--- /dev/null
+++ b/folders/sql/1_create_table.down.sql
@@ -0,0 +1 @@
+DROP TABLE folders;
diff --git a/folders/sql/1_create_table.up.sql b/folders/sql/1_create_table.up.sql
new file mode 100644
index 0000000000000000000000000000000000000000..232fd99f8a09a6d1edd860f6aecd5e56ec7d6d55
--- /dev/null
+++ b/folders/sql/1_create_table.up.sql
@@ -0,0 +1,18 @@
+CREATE TABLE folders (
+ name VARCHAR(25) NOT NULL UNIQUE,
+ always INT DEFAULT 0,
+ brief INT DEFAULT 0,
+ description VARCHAR(53),
+ co_owners TEXT,
+ notify INT DEFAULT 0,
+ owner TEXT,
+ readnew INT DEFAULT 0,
+ shownew INT DEFAULT 0,
+ system INT DEFAULT 0,
+ expire INT DEFAULT 14,
+ visibility TEXT DEFAULT "public"
+);
+
+INSERT INTO folders (name, description, system, shownew, owner)
+ VALUES ("GENERAL", "Default general bulletin folder.", 1, 1, "SYSTEM");
+
diff --git a/go.mod b/go.mod
index cba97c6269574d2e17ffc6883bf08332a627ff27..4f17e6ab7216c567010304d9a3b4881b16543ed8 100644
--- a/go.mod
+++ b/go.mod
@@ -5,17 +5,22 @@ go 1.24.2
require (
github.com/adrg/xdg v0.5.3
github.com/chzyer/readline v1.5.1
+ github.com/golang-migrate/migrate/v4 v4.18.3
github.com/urfave/cli/v3 v3.3.2
modernc.org/sqlite v1.37.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/sys v0.33.0 // indirect
modernc.org/libc v1.65.0 // indirect
diff --git a/go.sum b/go.sum
index a9657c7c3bc75d75b15d8748e53c114b7e9a6d5e..95baed5b57cce81c2990d3cd0c5a588e636f8cf9 100644
--- a/go.sum
+++ b/go.sum
@@ -4,20 +4,33 @@ github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwys
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
+github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
+github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
+github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o=
github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
diff --git a/repl/command.go b/repl/command.go
index a963b64f9c5a703ce95c89df9450a82cb2f8e163..378d46a922cf2ea6a93dd799b97ff66bb3f67696 100644
--- a/repl/command.go
+++ b/repl/command.go
@@ -311,6 +311,7 @@ folder is stored in a file name created with the folder name).
NOTE: Creation of folders may be a restricted command if the installer
has elected to install it as such. This is done by modifying
BULLCOM.CLD.`,
+ Action: ActionCreate,
MinArgs: 1,
MaxArgs: 1,
Flags: dclish.Flags{
@@ -849,6 +850,7 @@ owner of the folder or a user with privileges can use this command.
Format:
MODIFY`,
+ Action: ActionModify,
Flags: dclish.Flags{
"/DESCRIPTION": {
Description: `Specifies a new description for the folder. You will be prompted for
@@ -1295,6 +1297,8 @@ BULLCP process running (invoked by BULLETIN/STARTUP command.)
After selecting a folder, the user will notified of the number of unread
messages, and the message pointer will be placed at the first unread
message.`,
+ Action: ActionSelect,
+ MaxArgs: 1,
Flags: dclish.Flags{
"/MARKED": {
Description: `Selects only messages that have been marked (indicated by an asterisk).
diff --git a/repl/folders.go b/repl/folders.go
index 3e0db73736c2adf72ef36da6de7ec22d336e2661..7611b81c7305ffd4cd0d33ffa2bdad60b10fe2fd 100644
--- a/repl/folders.go
+++ b/repl/folders.go
@@ -27,3 +27,21 @@ func ActionIndex(cmd *dclish.Command) error {
fmt.Printf("TODO: implement INDEX:\n%s\n\n", cmd.Description)
return nil
}
+
+// ActionCreate handles the `CREATE` command. This creates a folder.
+func ActionCreate(cmd *dclish.Command) error {
+ fmt.Printf("TODO: implement CREATE:\n%s\n\n", cmd.Description)
+ return nil
+}
+
+// ActionSelect handles the `SELECT` command. This selects a folder.
+func ActionSelect(cmd *dclish.Command) error {
+ fmt.Printf("TODO: implement SELECT:\n%s\n\n", cmd.Description)
+ return nil
+}
+
+// ActionModify handles the `MODIFY` command. This modifies a folder.
+func ActionModify(cmd *dclish.Command) error {
+ fmt.Printf("TODO: implement MODIFY:\n%s\n\n", cmd.Description)
+ return nil
+}