From 67eadd66989c1fcd34f3e004004239a50f56097e Mon Sep 17 00:00:00 2001
From: Kevin Lyda <kevin@lyda.ie>
Date: Sat, 17 May 2025 17:44:41 +0100
Subject: [PATCH] Get SHOW USER to work

Still need to make it show last read message, not the most recent
message.  But I think all the rest is there.  The latter should
only take a SQL change - hopefully.
---
 batch/batch.go               |   8 +--
 dclish/dclish.go             |   9 ++-
 repl/command.go              |  78 +--------------------
 repl/show.go                 | 132 ++++++++++++++++++++++++++++++-----
 storage/display.go           |   2 +-
 storage/messages.sql.go      |  98 ++++++++++++++++++++++++++
 storage/queries/messages.sql |  17 +++++
 storage/queries/users.sql    |  16 ++++-
 storage/users.sql.go         | 110 ++++++++++++++++++++++++++---
 this/this.go                 |   2 +
 10 files changed, 359 insertions(+), 113 deletions(-)

diff --git a/batch/batch.go b/batch/batch.go
index e22082b..f17eb5c 100644
--- a/batch/batch.go
+++ b/batch/batch.go
@@ -79,8 +79,7 @@ the following files:
 	* ~/.ssh/authorized_keys - key lines to let users connect to BULLETIN
 
 To complete the installation, please enter the login, name and ssh key for
-the first user.
-`)
+the first user.`)
 	login, err := ask.GetLine("Enter login of initial user: ")
 	login = strings.ToUpper(login)
 	ask.CheckErr(err)
@@ -107,8 +106,9 @@ the first user.
 	for i := range seedMsgs {
 		if !userCreated[seedMsgs[i].Login] {
 			_, err = q.AddUser(ctx, storage.AddUserParams{
-				Login: seedMsgs[i].Login,
-				Name:  seedMsgs[i].Name,
+				Login:    seedMsgs[i].Login,
+				Name:     seedMsgs[i].Name,
+				Disabled: 1,
 			})
 			ask.CheckErr(err)
 			userCreated[seedMsgs[i].Login] = true
diff --git a/dclish/dclish.go b/dclish/dclish.go
index 76a38a2..7736360 100644
--- a/dclish/dclish.go
+++ b/dclish/dclish.go
@@ -16,6 +16,7 @@ type Flag struct {
 	Value       string
 	Default     string
 	Description string
+	Set         bool
 }
 
 // Flags is the list of flags.
@@ -177,6 +178,7 @@ func (c Commands) run(words []string) error {
 	}
 	for flg := range cmd.Flags {
 		cmd.Flags[flg].Value = cmd.Flags[flg].Default
+		cmd.Flags[flg].Set = false
 	}
 	cmd.Args = []string{}
 
@@ -196,7 +198,7 @@ func (c Commands) run(words []string) error {
 			if assigned {
 				wordup = strings.ToUpper(flag)
 			} else {
-				wordup = args[i]
+				wordup = strings.ToUpper(args[i])
 			}
 			toggleValue := "true"
 			flg, ok := cmd.Flags[wordup]
@@ -213,6 +215,11 @@ func (c Commands) run(words []string) error {
 				fmt.Printf("ERROR: Flag '%s' is a toggle.\n", args[i])
 				return nil
 			}
+			if flg.Set {
+				fmt.Printf("ERROR: Flag '%s' is already set.\n", args[i])
+				return nil
+			}
+			flg.Set = true
 			if flg.OptArg {
 				if assigned {
 					flg.Value = strings.Trim(val, "\"'")
diff --git a/repl/command.go b/repl/command.go
index 493a12b..0dc244e 100644
--- a/repl/command.go
+++ b/repl/command.go
@@ -1205,83 +1205,6 @@ characteristics of the BULLETIN Utility.
     SET option`,
 		Action: ActionSet,
 		Commands: dclish.Commands{
-			"ACCESS": {
-				Description: `Controls  access  to  a  private  folder.   A private folder can only be
-selected by users who have been granted access.  Only the owner of  that
-folder is allowed to grant access.
-
-  Format:
-    SET [NO]ACCESS id-name [folder-name]
-
-The id-name can be one or more ids from the system Rights  Database  for
-which  access  is  being  modified.   It  can  also be a file name which
-contains a list of  ids.   For  more  information  concerning  usage  of
-private  folders, see HELP CREATE /PRIVATE.  NOTE: Access is created via
-ACLs.  If a user's process privileges are set  to  override  ACLs,  that
-user  will  be  able  to  access  the folder even if access has not been
-granted.
-
-It  is suggested that if you plan on granting access to many users, that
-you create an id using the AUTHORIZE utility and then use the SET ACCESS
-command  to  grant  access  to  that id.  Then, you can use the GRANT/ID
-command in AUTHORIZE to grant the id to users, and this will give  those
-users  access to the folder.  This is preferred because of problems with
-running into system quota when checking for acls on a file with a  large
-amount  of  acls.   It  is also means that you don't have to remember to
-remove the access for that user from a folder if that  user  is  removed
-from the system.
-
-A user with BULLETIN privileges (see HELP SET  PRIV)  will  be  able  to
-select a protected folder regardless of the access settings.  However, a
-user without explicit access will not receive login notifications of new
-messages,  and thus will not be able to set any login flags.  (NOTE:  If
-such a user selects such a folder and then uses SET ACCESS to grant  him
-or  herself  access,  the user must reselect the folder in order for the
-new access to take affect in order to be able to set login flags.)
-
-The id-name can be one or  more  ids  contained  in  the  system  Rights
-Database.   This  includes  usernames  and  UICs.  A UIC that contains a
-comma must be enclosed in quotes.   UICs  can  contain  wildcards,  i.e.
-"[130,*]".   Note that by default, a process is given the process rights
-id SYS$NODE_nodename, where nodename is the decnet nodename.   Thus,  by
-specifing  this id, a folder can be restricted to a specific node, which
-is useful when the folder is shared among nodes in a cluster.
-
-Alternatively,  the  id-name  can be a filename which contains a list of
-ids.  The filename should be preceeded by a "@".  If the suffix  is  not
-specified, it will be assumed that the suffix is ".DIS" .
-
-Warning
-
-  If  a  user  logs  in after a private folder has been created but before
-  being given access, and then is given  access,  any  defaults  that  the
-  folder  has,  i.e. /BRIEF, /READNEW, & /NOTIFY, will not be set for that
-  user. This is because if the  id  is  not  a  username,  it  becomes  an
-  extremely  lengthy  operation  to check each user to see if have that id
-  assigned to them.  The alternative is to set the defaults for all  users
-  after  every  SET  ACCESS,  but that might cause problems with users who
-  have manually reset those defaults.  The  correct  solution  requires  a
-  large programming modification, which will be done in a later version.`,
-				MinArgs: 1,
-				MaxArgs: 1,
-				Flags: dclish.Flags{
-					"/ALL": {
-						Description: `  Specifies that access to the folder is granted to all users.   If  /READ
-  is  not  specified,  the  folder will no longer be private.  If /READ is
-  specified, all users will have read access, but  only  privileged  users
-  will  have  write access (of course non-privileged users can gain access
-  via a later SET ACCESS command.)
-
-  Format:
-    SET ACCESS /ALL[=folder-name]`,
-						OptArg: true,
-					},
-					"/READ": {
-						Description: `  Specifies that access to the folder will be limited to being able to
-  read the messages.`,
-					},
-				},
-			},
 			"ALWAYS": {
 				Description: `Specifies  that  the  selected  folder  has  the ALWAYS attribute.  This
 causes messages in the folder to be displayed differently  when  logging
@@ -1665,6 +1588,7 @@ have done this.`,
   Specifies to display  the latest message that was read  by the user(s)
   for the specified foldername.  If the foldername is not specified, the
 	selected folder will be used.`,
+						OptArg: true,
 					},
 					"/SINCE": {
 						Description: `/SINCE=[date]
diff --git a/repl/show.go b/repl/show.go
index a583496..a0ce307 100644
--- a/repl/show.go
+++ b/repl/show.go
@@ -3,6 +3,7 @@ package repl
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/carlmjohnson/versioninfo"
 
@@ -127,33 +128,128 @@ func ActionShowPrivileges(_ *dclish.Command) error {
 
 // ActionShowUser handles the `SHOW USER` command.
 func ActionShowUser(cmd *dclish.Command) error {
-	showAll := false
-	if cmd.Flags["/ALL"].Value == "true" {
-		showAll = true
-		// TODO: Check permissions.
-		fmt.Println("ERROR: No privs to use command.")
-		return nil
+	// Parse the options.
+	login := this.User.Login
+	if len(cmd.Args) == 1 {
+		if this.User.Admin == 0 {
+			fmt.Println("ERROR: You are not an admin.")
+			return nil
+		}
+		login = strings.ToUpper(cmd.Args[0])
+	}
+	if cmd.Flags["/ALL"].Set {
+		if this.User.Admin == 0 {
+			fmt.Println("ERROR: You are not an admin.")
+			return nil
+		}
+		if len(cmd.Args) == 1 {
+			fmt.Println("ERROR: You can't specify a user with /ALL.")
+			return nil
+		}
 	}
-	showLogin := false
+	var showLogin int64
 	if cmd.Flags["/LOGIN"].Value == "true" {
-		// TODO: Check permissions.
-		showLogin = true
+		if this.User.Admin == 0 {
+			fmt.Println("ERROR: You are not an admin.")
+			return nil
+		}
+		showLogin = 1
 	}
 	folder := this.Folder
-	if cmd.Flags["/FOLDER"].Value != "" {
-		// TODO: Check permissions.
-		folder = folders.FindFolder(cmd.Flags["/FOLDER"].Value)
+	if cmd.Flags["/FOLDER"].Set {
+		fmt.Printf("%v\n", cmd.Flags["/FOLDER"].Value)
+		if cmd.Flags["/FOLDER"].Value != "" {
+			folder = folders.FindFolder(cmd.Flags["/FOLDER"].Value)
+			if folder.Name == "" {
+				fmt.Println("ERROR: Folder does not exist.")
+				return nil
+			}
+		}
 	}
-	if cmd.Flags["/FOLDER"].Value != "" {
-		since, err := ParseDate(cmd.Flags["/FOLDER"].Value)
+	/*
+		TODO: need to add this.
+		var since time.Time
+		var err error
+		if cmd.Flags["/SINCE"].Value != "" {
+			if cmd.Flags["/LOGIN"].Set || cmd.Flags["/FOLDER"].Set {
+				fmt.Println("ERROR: Must set /[NO]LOGIN or /FOLDER.")
+				return nil
+			}
+			since, err = ParseDate(cmd.Flags["/SINCE"].Value)
+			if err != nil {
+				fmt.Println("ERROR: Invalid date specified.")
+				return nil
+			}
+		}
+	*/
+
+	// Actually do the thing.
+	ctx := storage.Context()
+	if cmd.Flags["/LOGIN"].Set && cmd.Flags["/FOLDER"].Set {
+		rows, err := this.Q.GetLastReadByEnabled(ctx,
+			storage.GetLastReadByEnabledParams{
+				Folder:   folder.Name,
+				Disabled: showLogin,
+			})
+		if err != nil {
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
+			return nil
+		}
+		fmt.Println("User          Folder                     Message #")
+		for _, r := range rows {
+			fmt.Printf("%-12s  %-25s  % 4d\n", r.Author, folder.Name, r.ID)
+		}
+	} else if cmd.Flags["/ALL"].Set && cmd.Flags["/FOLDER"].Set {
+		rows, err := this.Q.GetLastRead(ctx, folder.Name)
+		if err != nil {
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
+			return nil
+		}
+		fmt.Println("User          Folder                     Message #")
+		for _, r := range rows {
+			fmt.Printf("%-12s  %-25s  % 4d\n", r.Author, folder.Name, r.ID)
+		}
+	} else if cmd.Flags["/FOLDER"].Set {
+		r, err := this.Q.GetLastReadByUser(ctx,
+			storage.GetLastReadByUserParams{
+				Folder: folder.Name,
+				Author: login,
+			})
+		if err != nil {
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
+			return nil
+		}
+		fmt.Println("User          Folder                     Message #")
+		fmt.Printf("%-12s  %-25s  % 4d\n", r.Author, folder.Name, r.ID)
+	} else if cmd.Flags["/LOGIN"].Set {
+		rows, err := this.Q.GetLastLoginByEnabled(ctx, showLogin)
+		if err != nil {
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
+			return nil
+		}
+		fmt.Println("User          Last Login")
+		for _, r := range rows {
+			fmt.Printf("%-12s  %s\n", r.Login, r.LastLogin.Format("2006-01-02 15:04:05"))
+		}
+	} else if cmd.Flags["/ALL"].Set {
+		rows, err := this.Q.GetLastLogin(ctx)
+		if err != nil {
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
+			return nil
+		}
+		fmt.Println("User          Last Login")
+		for _, r := range rows {
+			fmt.Printf("%-12s  %s\n", r.Login, r.LastLogin.Format("2006-01-02 15:04:05"))
+		}
+	} else {
+		r, err := this.Q.GetLastLoginByLogin(ctx, login)
 		if err != nil {
-			fmt.Println("ERROR: Invalid date specified.")
+			fmt.Printf("ERROR: Failed to get list (%s).\n", err)
 			return nil
 		}
-		fmt.Printf("TODO: select messages since %s.\n", since.Format("2006-05-04"))
+		fmt.Println("User          Last Login")
+		fmt.Printf("%-12s  %s\n", r.Login, r.LastLogin.Format("2006-01-02 15:04:05"))
 	}
-	fmt.Println("TODO: implement ActionShowUser.")
-	fmt.Printf("TODO: %t %t %s.\n", showAll, showLogin, folder.Name)
 	return nil
 }
 
diff --git a/storage/display.go b/storage/display.go
index 1ac380f..7972da1 100644
--- a/storage/display.go
+++ b/storage/display.go
@@ -43,7 +43,7 @@ func (u User) String() string {
 		u.Moderator,
 		u.Alert,
 		u.Disabled,
-		u.LastLogin.Format("06-05-04 15:02:01"))
+		u.LastLogin.Format("06-01-02 15:04:05"))
 }
 
 // String displays a folder (mainly used for debugging).
diff --git a/storage/messages.sql.go b/storage/messages.sql.go
index bdec2bb..a916720 100644
--- a/storage/messages.sql.go
+++ b/storage/messages.sql.go
@@ -50,6 +50,104 @@ func (q *Queries) DeleteAllMessages(ctx context.Context, folder string) error {
 	return err
 }
 
+const getLastRead = `-- name: GetLastRead :many
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author
+GROUP BY m.author
+ORDER BY m.author
+`
+
+type GetLastReadRow struct {
+	ID     int64
+	Author string
+}
+
+// - TODO: These get the max message written, not read.  Leaving for now; easier to test.
+func (q *Queries) GetLastRead(ctx context.Context, folder string) ([]GetLastReadRow, error) {
+	rows, err := q.db.QueryContext(ctx, getLastRead, folder)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []GetLastReadRow
+	for rows.Next() {
+		var i GetLastReadRow
+		if err := rows.Scan(&i.ID, &i.Author); 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 getLastReadByEnabled = `-- name: GetLastReadByEnabled :many
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author AND u.disabled = ?
+GROUP BY m.author
+ORDER BY m.author
+`
+
+type GetLastReadByEnabledParams struct {
+	Folder   string
+	Disabled int64
+}
+
+type GetLastReadByEnabledRow struct {
+	ID     int64
+	Author string
+}
+
+func (q *Queries) GetLastReadByEnabled(ctx context.Context, arg GetLastReadByEnabledParams) ([]GetLastReadByEnabledRow, error) {
+	rows, err := q.db.QueryContext(ctx, getLastReadByEnabled, arg.Folder, arg.Disabled)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []GetLastReadByEnabledRow
+	for rows.Next() {
+		var i GetLastReadByEnabledRow
+		if err := rows.Scan(&i.ID, &i.Author); 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 getLastReadByUser = `-- name: GetLastReadByUser :one
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author AND m.author = ?
+`
+
+type GetLastReadByUserParams struct {
+	Folder string
+	Author string
+}
+
+type GetLastReadByUserRow struct {
+	ID     int64
+	Author string
+}
+
+func (q *Queries) GetLastReadByUser(ctx context.Context, arg GetLastReadByUserParams) (GetLastReadByUserRow, error) {
+	row := q.db.QueryRowContext(ctx, getLastReadByUser, arg.Folder, arg.Author)
+	var i GetLastReadByUserRow
+	err := row.Scan(&i.ID, &i.Author)
+	return i, err
+}
+
 const lastMsgidIgnoringSeen = `-- name: LastMsgidIgnoringSeen :one
 SELECT CAST(MAX(id) AS INT) FROM messages AS m WHERE m.folder = ?1
 `
diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql
index e9bd74d..aaf975c 100644
--- a/storage/queries/messages.sql
+++ b/storage/queries/messages.sql
@@ -37,3 +37,20 @@ DELETE FROM messages WHERE folder = ?;
 
 -- name: LastMsgidIgnoringSeen :one
 SELECT CAST(MAX(id) AS INT) FROM messages AS m WHERE m.folder = ?1;
+
+--- TODO: These get the max message written, not read.  Leaving for now; easier to test.
+-- name: GetLastRead :many
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author
+GROUP BY m.author
+ORDER BY m.author;
+
+-- name: GetLastReadByEnabled :many
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author AND u.disabled = ?
+GROUP BY m.author
+ORDER BY m.author;
+
+-- name: GetLastReadByUser :one
+SELECT CAST(MAX(m.id) AS INT) AS id, m.author FROM messages AS m, users AS u
+WHERE folder = ? AND u.login == m.author AND m.author = ?;
diff --git a/storage/queries/users.sql b/storage/queries/users.sql
index 880ddcb..d87b015 100644
--- a/storage/queries/users.sql
+++ b/storage/queries/users.sql
@@ -2,7 +2,7 @@
 SELECT * FROM users WHERE login = ?;
 
 -- name: AddUser :one
-INSERT INTO users (login, name, admin) VALUES (?, ?, ?)
+INSERT INTO users (login, name, admin, disabled, last_login) VALUES (?, ?, ?, ?, ?)
 RETURNING *;
 
 -- name: IsUserAdmin :one
@@ -23,5 +23,15 @@ UPDATE users SET name = ? WHERE login = ? AND login != 'SYSTEM';
 -- name: UpdateUserMod :exec
 UPDATE users SET moderator = ? WHERE login = ? AND login != 'SYSTEM';
 
--- name: UpdateUserLastLogin :exec
-UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE login = ? AND login != 'SYSTEM';
+-- name: UpdateUserLastLogin :one
+UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE login = ? AND login != 'SYSTEM'
+RETURNING last_login;
+
+-- name: GetLastLogin :many
+SELECT login, last_login FROM users ORDER BY login;
+
+-- name: GetLastLoginByLogin :one
+SELECT login, last_login FROM users WHERE login = ? ORDER BY login;
+
+-- name: GetLastLoginByEnabled :many
+SELECT login, last_login FROM users WHERE disabled = ? ORDER BY login;
diff --git a/storage/users.sql.go b/storage/users.sql.go
index 79a138a..75f923c 100644
--- a/storage/users.sql.go
+++ b/storage/users.sql.go
@@ -7,21 +7,30 @@ package storage
 
 import (
 	"context"
+	"time"
 )
 
 const addUser = `-- name: AddUser :one
-INSERT INTO users (login, name, admin) VALUES (?, ?, ?)
+INSERT INTO users (login, name, admin, disabled, last_login) VALUES (?, ?, ?, ?, ?)
 RETURNING login, name, admin, moderator, alert, disabled, last_login, create_at, update_at
 `
 
 type AddUserParams struct {
-	Login string
-	Name  string
-	Admin int64
+	Login     string
+	Name      string
+	Admin     int64
+	Disabled  int64
+	LastLogin time.Time
 }
 
 func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (User, error) {
-	row := q.db.QueryRowContext(ctx, addUser, arg.Login, arg.Name, arg.Admin)
+	row := q.db.QueryRowContext(ctx, addUser,
+		arg.Login,
+		arg.Name,
+		arg.Admin,
+		arg.Disabled,
+		arg.LastLogin,
+	)
 	var i User
 	err := row.Scan(
 		&i.Login,
@@ -37,6 +46,86 @@ func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (User, error)
 	return i, err
 }
 
+const getLastLogin = `-- name: GetLastLogin :many
+SELECT login, last_login FROM users ORDER BY login
+`
+
+type GetLastLoginRow struct {
+	Login     string
+	LastLogin time.Time
+}
+
+func (q *Queries) GetLastLogin(ctx context.Context) ([]GetLastLoginRow, error) {
+	rows, err := q.db.QueryContext(ctx, getLastLogin)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []GetLastLoginRow
+	for rows.Next() {
+		var i GetLastLoginRow
+		if err := rows.Scan(&i.Login, &i.LastLogin); 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 getLastLoginByEnabled = `-- name: GetLastLoginByEnabled :many
+SELECT login, last_login FROM users WHERE disabled = ? ORDER BY login
+`
+
+type GetLastLoginByEnabledRow struct {
+	Login     string
+	LastLogin time.Time
+}
+
+func (q *Queries) GetLastLoginByEnabled(ctx context.Context, disabled int64) ([]GetLastLoginByEnabledRow, error) {
+	rows, err := q.db.QueryContext(ctx, getLastLoginByEnabled, disabled)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	var items []GetLastLoginByEnabledRow
+	for rows.Next() {
+		var i GetLastLoginByEnabledRow
+		if err := rows.Scan(&i.Login, &i.LastLogin); 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 getLastLoginByLogin = `-- name: GetLastLoginByLogin :one
+SELECT login, last_login FROM users WHERE login = ? ORDER BY login
+`
+
+type GetLastLoginByLoginRow struct {
+	Login     string
+	LastLogin time.Time
+}
+
+func (q *Queries) GetLastLoginByLogin(ctx context.Context, login string) (GetLastLoginByLoginRow, error) {
+	row := q.db.QueryRowContext(ctx, getLastLoginByLogin, login)
+	var i GetLastLoginByLoginRow
+	err := row.Scan(&i.Login, &i.LastLogin)
+	return i, err
+}
+
 const getUser = `-- name: GetUser :one
 SELECT login, name, admin, moderator, alert, disabled, last_login, create_at, update_at FROM users WHERE login = ?
 `
@@ -111,13 +200,16 @@ func (q *Queries) UpdateUserDisabled(ctx context.Context, arg UpdateUserDisabled
 	return err
 }
 
-const updateUserLastLogin = `-- name: UpdateUserLastLogin :exec
+const updateUserLastLogin = `-- name: UpdateUserLastLogin :one
 UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE login = ? AND login != 'SYSTEM'
+RETURNING last_login
 `
 
-func (q *Queries) UpdateUserLastLogin(ctx context.Context, login string) error {
-	_, err := q.db.ExecContext(ctx, updateUserLastLogin, login)
-	return err
+func (q *Queries) UpdateUserLastLogin(ctx context.Context, login string) (time.Time, error) {
+	row := q.db.QueryRowContext(ctx, updateUserLastLogin, login)
+	var last_login time.Time
+	err := row.Scan(&last_login)
+	return last_login, err
 }
 
 const updateUserMod = `-- name: UpdateUserMod :exec
diff --git a/this/this.go b/this/this.go
index 23cf5c3..0a14a07 100644
--- a/this/this.go
+++ b/this/this.go
@@ -67,6 +67,8 @@ func StartThis(login string) error {
 			return err
 		}
 		fmt.Println("User successfully created. Enjoy!")
+	} else {
+		User.LastLogin, _ = Q.UpdateUserLastLogin(ctx, User.Login)
 	}
 	if User.Disabled == 1 {
 		return errors.New("User is disabled")
-- 
GitLab