diff --git a/repl/command.go b/repl/command.go index 206c523dfb76908fe172852dc85bbeb395fab97c..e6c448805cbe187606b2efe4a712b843a36ff329 100644 --- a/repl/command.go +++ b/repl/command.go @@ -1254,6 +1254,7 @@ no default expiration date will be present. The latter should never be specified for a folder or else the messages will disappear.`, MinArgs: 1, MaxArgs: 1, + Action: ActionSetDefaultExpire, }, "EXPIRE_LIMIT": { Description: `Specifies expiration limit that is allowed for messages. Non-privileged @@ -1268,6 +1269,7 @@ The command SHOW FOLDER/FULL will show the expiration limit, if one exists. (NOTE: SHOW FOLDER/FULL is a privileged command.)`, MinArgs: 1, MaxArgs: 1, + Action: ActionSetExpireLimit, }, "FOLDER": { Description: `Select a folder of messages. Identical to the SELECT command. See help diff --git a/repl/repl.go b/repl/repl.go index 6f3e2363e3670d5c5c7ea924000b303d011f84dd..f7ea79e5fd323821fe00b28b5ee28389c95b9e5f 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -57,6 +57,7 @@ func Loop() error { // TODO: END for { + this.ShowBroadcast() line, err := rl.Readline() if err != nil { if err.Error() == "Interrupt" { diff --git a/repl/set.go b/repl/set.go index fc7bc80f483f1e912d8348e92057597929b85a45..ae87712fd86a922f1a036c3a758d832a0d96cdd7 100644 --- a/repl/set.go +++ b/repl/set.go @@ -4,6 +4,7 @@ package repl import ( "errors" "fmt" + "strconv" "strings" "git.lyda.ie/kevin/bulletin/dclish" @@ -67,15 +68,24 @@ func ActionSetNobrief(_ *dclish.Command) error { } // ActionSetDefaultExpire handles the `SET DEFAULT_EXPIRE` command. -func ActionSetDefaultExpire(_ *dclish.Command) error { - fmt.Println("TODO: implement ActionSetDefaultExpire.") - return nil +func ActionSetDefaultExpire(cmd *dclish.Command) error { + value, err := strconv.ParseInt(cmd.Args[0], 10, 64) + if err != nil { + return err + } + ctx := storage.Context() + return this.Q.UpdateDefaultExpire(ctx, value) } // ActionSetExpireLimit handles the `SET EXPIRE_LIMIT` command. -func ActionSetExpireLimit(_ *dclish.Command) error { +func ActionSetExpireLimit(cmd *dclish.Command) error { fmt.Println("TODO: implement ActionSetExpireLimit.") - return nil + value, err := strconv.ParseInt(cmd.Args[0], 10, 64) + if err != nil { + return err + } + ctx := storage.Context() + return this.Q.UpdateExpireLimit(ctx, value) } // ActionSetFolder handles the `SET FOLDER` command. This selects a folder. diff --git a/storage/broadcast.sql.go b/storage/broadcast.sql.go new file mode 100644 index 0000000000000000000000000000000000000000..550d63e3d621d72e87b1eaac3afa615b4c8b880f --- /dev/null +++ b/storage/broadcast.sql.go @@ -0,0 +1,93 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: broadcast.sql + +package storage + +import ( + "context" + "time" +) + +const clearBroadcasts = `-- name: ClearBroadcasts :exec +DELETE FROM broadcast +` + +func (q *Queries) ClearBroadcasts(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, clearBroadcasts) + return err +} + +const createBroadcast = `-- name: CreateBroadcast :exec +INSERT INTO broadcast + (author, bell, message) + VALUES + (?, ?, ?) +` + +type CreateBroadcastParams struct { + Author string + Bell int64 + Message string +} + +func (q *Queries) CreateBroadcast(ctx context.Context, arg CreateBroadcastParams) error { + _, err := q.db.ExecContext(ctx, createBroadcast, arg.Author, arg.Bell, arg.Message) + return err +} + +const getBroadcasts = `-- name: GetBroadcasts :many +SELECT author, bell, message, create_at FROM broadcast WHERE create_at > ? ORDER BY create_at +` + +func (q *Queries) GetBroadcasts(ctx context.Context, createAt time.Time) ([]Broadcast, error) { + rows, err := q.db.QueryContext(ctx, getBroadcasts, createAt) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Broadcast + for rows.Next() { + var i Broadcast + if err := rows.Scan( + &i.Author, + &i.Bell, + &i.Message, + &i.CreateAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const reapBroadcasts = `-- name: ReapBroadcasts :exec +DELETE FROM broadcast WHERE julianday(create_at) > 3 +` + +func (q *Queries) ReapBroadcasts(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, reapBroadcasts) + return err +} + +const updateLastBroadcast = `-- name: UpdateLastBroadcast :exec +UPDATE users SET last_broadcast = ? WHERE login = ? +` + +type UpdateLastBroadcastParams struct { + LastBroadcast time.Time + Login string +} + +func (q *Queries) UpdateLastBroadcast(ctx context.Context, arg UpdateLastBroadcastParams) error { + _, err := q.db.ExecContext(ctx, updateLastBroadcast, arg.LastBroadcast, arg.Login) + return err +} diff --git a/storage/display.go b/storage/display.go index 05f904d86292d0fb42cb3e8c51bacafde6510413..b995426bb3c58a04078b96ca9a57dbeab80ae0b6 100644 --- a/storage/display.go +++ b/storage/display.go @@ -61,3 +61,16 @@ func (f Folder) String() string { f.Expire, f.Visibility) } + +// String displays a folder (mainly used for debugging). +func (b Broadcast) String() string { + bell := "" + if b.Bell == 1 { + bell = "\a\a" + } + return fmt.Sprintf("%sFrom: %s %s\n%s\n", + bell, + b.Author, + b.CreateAt.Format("06-01-02 15:04:05"), + b.Message) +} diff --git a/storage/migrations/1_create_table.up.sql b/storage/migrations/1_create_table.up.sql index acec6f82f018420ea63ef35cbb93fce6db95f451..2f066f83b26ac6ddc2f873895a4a028304860c42 100644 --- a/storage/migrations/1_create_table.up.sql +++ b/storage/migrations/1_create_table.up.sql @@ -1,13 +1,14 @@ CREATE TABLE users ( - login VARCHAR(12) PRIMARY KEY NOT NULL, - name VARCHAR(53) NOT NULL, - admin INT DEFAULT 0 NOT NULL, - moderator INT DEFAULT 0 NOT NULL, - alert INT DEFAULT 0 NOT NULL, --- 0=no, 1=brief, 2=readnew - disabled INT DEFAULT 0 NOT NULL, - last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + login VARCHAR(12) PRIMARY KEY NOT NULL, + name VARCHAR(53) NOT NULL, + admin INT DEFAULT 0 NOT NULL, + moderator INT DEFAULT 0 NOT NULL, + alert INT DEFAULT 0 NOT NULL, --- 0=no, 1=brief, 2=readnew + disabled INT DEFAULT 0 NOT NULL, + last_broadcast TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + 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 @@ -182,3 +183,11 @@ CREATE TABLE system ( default_expire INT DEFAULT -1 NOT NULL, expire_limit INT DEFAULT -1 NOT NULL ); + +--- Broadcast messages. +CREATE TABLE broadcast ( + author VARCHAR(12) PRIMARY KEY NOT NULL, + bell INT DEFAULT 0 NOT NULL, + message TEXT NOT NULL, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +); diff --git a/storage/models.go b/storage/models.go index d0089adc1fe00551e8cf670913831e6452707e33..12928857ab096356a0680802cd8e5d923c2db2bb 100644 --- a/storage/models.go +++ b/storage/models.go @@ -8,6 +8,13 @@ import ( "time" ) +type Broadcast struct { + Author string + Bell int64 + Message string + CreateAt time.Time +} + type Folder struct { Name string Always int64 @@ -75,13 +82,14 @@ type System struct { } type User struct { - Login string - Name string - Admin int64 - Moderator int64 - Alert int64 - Disabled int64 - LastLogin time.Time - CreateAt time.Time - UpdateAt time.Time + Login string + Name string + Admin int64 + Moderator int64 + Alert int64 + Disabled int64 + LastBroadcast time.Time + LastLogin time.Time + CreateAt time.Time + UpdateAt time.Time } diff --git a/storage/queries/broadcast.sql b/storage/queries/broadcast.sql new file mode 100644 index 0000000000000000000000000000000000000000..4b5dceb74399d223c5194cd18bbe4b0cb0c68118 --- /dev/null +++ b/storage/queries/broadcast.sql @@ -0,0 +1,17 @@ +-- name: CreateBroadcast :exec +INSERT INTO broadcast + (author, bell, message) + VALUES + (?, ?, ?); + +-- name: GetBroadcasts :many +SELECT * FROM broadcast WHERE create_at > ? ORDER BY create_at; + +-- name: UpdateLastBroadcast :exec +UPDATE users SET last_broadcast = ? WHERE login = ?; + +-- name: ClearBroadcasts :exec +DELETE FROM broadcast; + +-- name: ReapBroadcasts :exec +DELETE FROM broadcast WHERE julianday(create_at) > 3; diff --git a/storage/queries/system.sql b/storage/queries/system.sql new file mode 100644 index 0000000000000000000000000000000000000000..1d901a6fa72ab63553c1de1da58b3ffcb0e3a5fb --- /dev/null +++ b/storage/queries/system.sql @@ -0,0 +1,5 @@ +-- name: UpdateDefaultExpire :exec +UPDATE system SET default_expire = ? WHERE rowid = 1; + +-- name: UpdateExpireLimit :exec +UPDATE system SET expire_limit = ? WHERE rowid = 1; diff --git a/storage/standard.sql.go b/storage/standard.sql.go index f6c4615c285fbb883b1ae91d779314aa26cc9bb4..01d0e147ccf889ed01e7d8d8e2620132607e0beb 100644 --- a/storage/standard.sql.go +++ b/storage/standard.sql.go @@ -660,7 +660,7 @@ func (q *Queries) ListSeen(ctx context.Context) ([]Seen, error) { } const listUsers = `-- name: ListUsers :many -SELECT login, name, admin, moderator, alert, disabled, last_login, create_at, update_at FROM users +SELECT login, name, admin, moderator, alert, disabled, last_broadcast, last_login, create_at, update_at FROM users ` func (q *Queries) ListUsers(ctx context.Context) ([]User, error) { @@ -679,6 +679,7 @@ func (q *Queries) ListUsers(ctx context.Context) ([]User, error) { &i.Moderator, &i.Alert, &i.Disabled, + &i.LastBroadcast, &i.LastLogin, &i.CreateAt, &i.UpdateAt, diff --git a/storage/system.sql.go b/storage/system.sql.go new file mode 100644 index 0000000000000000000000000000000000000000..920642d6685d8fc631d5f32ac404d471648d1256 --- /dev/null +++ b/storage/system.sql.go @@ -0,0 +1,28 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: system.sql + +package storage + +import ( + "context" +) + +const updateDefaultExpire = `-- name: UpdateDefaultExpire :exec +UPDATE system SET default_expire = ? WHERE rowid = 1 +` + +func (q *Queries) UpdateDefaultExpire(ctx context.Context, defaultExpire int64) error { + _, err := q.db.ExecContext(ctx, updateDefaultExpire, defaultExpire) + return err +} + +const updateExpireLimit = `-- name: UpdateExpireLimit :exec +UPDATE system SET expire_limit = ? WHERE rowid = 1 +` + +func (q *Queries) UpdateExpireLimit(ctx context.Context, expireLimit int64) error { + _, err := q.db.ExecContext(ctx, updateExpireLimit, expireLimit) + return err +} diff --git a/storage/users.sql.go b/storage/users.sql.go index 75f923ca32b1cc8c1cb0f3cfd66a085b02eaacfd..613b4c13abc8ecd3ccfa6ad4efe0c7b1ca8a7996 100644 --- a/storage/users.sql.go +++ b/storage/users.sql.go @@ -12,7 +12,7 @@ import ( const addUser = `-- name: AddUser :one INSERT INTO users (login, name, admin, disabled, last_login) VALUES (?, ?, ?, ?, ?) -RETURNING login, name, admin, moderator, alert, disabled, last_login, create_at, update_at +RETURNING login, name, admin, moderator, alert, disabled, last_broadcast, last_login, create_at, update_at ` type AddUserParams struct { @@ -39,6 +39,7 @@ func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (User, error) &i.Moderator, &i.Alert, &i.Disabled, + &i.LastBroadcast, &i.LastLogin, &i.CreateAt, &i.UpdateAt, @@ -127,7 +128,7 @@ func (q *Queries) GetLastLoginByLogin(ctx context.Context, login string) (GetLas } const getUser = `-- name: GetUser :one -SELECT login, name, admin, moderator, alert, disabled, last_login, create_at, update_at FROM users WHERE login = ? +SELECT login, name, admin, moderator, alert, disabled, last_broadcast, last_login, create_at, update_at FROM users WHERE login = ? ` func (q *Queries) GetUser(ctx context.Context, login string) (User, error) { @@ -140,6 +141,7 @@ func (q *Queries) GetUser(ctx context.Context, login string) (User, error) { &i.Moderator, &i.Alert, &i.Disabled, + &i.LastBroadcast, &i.LastLogin, &i.CreateAt, &i.UpdateAt, diff --git a/this/broadcast.go b/this/broadcast.go new file mode 100644 index 0000000000000000000000000000000000000000..5682ab74fdcb5c373378c3181e00dcb7efc6d326 --- /dev/null +++ b/this/broadcast.go @@ -0,0 +1,26 @@ +package this + +import ( + "fmt" + "time" + + "git.lyda.ie/kevin/bulletin/storage" +) + +// ShowBroadcast print broadcast messages. +func ShowBroadcast() { + ctx := storage.Context() + User.LastBroadcast = time.Now() + msgs, _ := Q.GetBroadcasts(ctx, User.LastBroadcast) + if len(msgs) > 0 { + fmt.Println("BROADCAST MSG START") + for i := range msgs { + fmt.Printf("\n%s\n", msgs[i]) + } + fmt.Println("BROADCAST MSG END") + Q.UpdateLastBroadcast(ctx, storage.UpdateLastBroadcastParams{ + LastBroadcast: User.LastBroadcast, + Login: User.Login, + }) + } +} diff --git a/this/this.go b/this/this.go index 20f44b22b58685f3a26157b2b382cc75852e83a2..7cc73fabb4cd89c503224c413cc8820e57e6b041 100644 --- a/this/this.go +++ b/this/this.go @@ -100,10 +100,12 @@ func StartThis(login string) error { if User.Disabled == 1 { return errors.New("User is disabled") } + Folder, err = Q.GetFolder(ctx, "GENERAL") if err != nil { return err } + ReadFirstCall = true MsgID, err = Q.NextMsgid(ctx, storage.NextMsgidParams{ Folder: Folder.Name,