Skip to content

Commit

Permalink
Merge pull request #1551 from memphisdev/RND-356-add-last-login-indic…
Browse files Browse the repository at this point in the history
…ation

RND-356-add-last-login-indication
  • Loading branch information
shohamroditimemphis authored Dec 24, 2023
2 parents 3058fd2 + b2b464d commit efbcece
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 35 deletions.
52 changes: 23 additions & 29 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func createTables(MetadataDbClient MetadataStorage) error {
ALTER TABLE users ADD COLUMN IF NOT EXISTS position VARCHAR NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS owner VARCHAR NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS description VARCHAR NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login TIMESTAMPTZ NOT NULL DEFAULT NOW();
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_username_key;
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_username_tenant_name_key;
ALTER TABLE users ADD CONSTRAINT users_username_tenant_name_key UNIQUE(username, tenant_name);
Expand All @@ -157,6 +158,7 @@ func createTables(MetadataDbClient MetadataStorage) error {
position VARCHAR NOT NULL DEFAULT '',
owner VARCHAR NOT NULL DEFAULT '',
description VARCHAR NOT NULL DEFAULT '',
last_login TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (id),
CONSTRAINT fk_tenant_name
FOREIGN KEY(tenant_name)
Expand Down Expand Up @@ -4680,34 +4682,6 @@ func GetUserForLoginByUsernameAndTenant(username, tenantname string) (bool, mode
return true, users[0], nil
}

func GetUserByUserId(userId int) (bool, models.User, error) {
ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second)
defer cancelfunc()
conn, err := MetadataDbClient.Client.Acquire(ctx)
if err != nil {
return false, models.User{}, err
}
defer conn.Release()
query := `SELECT * FROM users WHERE id = $1 LIMIT 1`
stmt, err := conn.Conn().Prepare(ctx, "get_user_by_id", query)
if err != nil {
return false, models.User{}, err
}
rows, err := conn.Conn().Query(ctx, stmt.Name, userId)
if err != nil {
return false, models.User{}, err
}
defer rows.Close()
users, err := pgx.CollectRows(rows, pgx.RowToStructByPos[models.User])
if err != nil {
return false, models.User{}, err
}
if len(users) == 0 {
return false, models.User{}, nil
}
return true, users[0], nil
}

func GetAllUsers(tenantName string) ([]models.FilteredGenericUser, error) {
ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second)
defer cancelfunc()
Expand All @@ -4716,7 +4690,7 @@ func GetAllUsers(tenantName string) ([]models.FilteredGenericUser, error) {
return []models.FilteredGenericUser{}, err
}
defer conn.Release()
query := `SELECT s.id, s.username, s.type, s.created_at, s.avatar_id, s.full_name, s.pending, s.position, s.team, s.owner, s.description FROM users AS s WHERE tenant_name=$1 AND username NOT LIKE '$%'` // filter memphis internal users
query := `SELECT s.id, s.username, s.type, s.created_at, s.avatar_id, s.full_name, s.pending, s.position, s.team, s.owner, s.description, s.last_login FROM users AS s WHERE tenant_name=$1 AND username NOT LIKE '$%'` // filter memphis internal users
stmt, err := conn.Conn().Prepare(ctx, "get_all_users", query)
if err != nil {
return []models.FilteredGenericUser{}, err
Expand Down Expand Up @@ -4896,6 +4870,26 @@ func UpdateUserAlreadyLoggedIn(userId int) error {
return nil
}

func UpdateLastLoginUser(userId int) (time.Time, error) {
ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second)
defer cancelfunc()
conn, err := MetadataDbClient.Client.Acquire(ctx)
if err != nil {
return time.Time{}, err
}
defer conn.Release()
query := `UPDATE users SET last_login = NOW() WHERE id = $1 RETURNING last_login`
stmt, err := conn.Conn().Prepare(ctx, "update_last_login_in_user", query)
if err != nil {
return time.Time{}, err
}
var lastLogin time.Time
if err := conn.Conn().QueryRow(ctx, stmt.Name, userId).Scan(&lastLogin); err != nil {
return time.Time{}, err
}
return lastLogin, nil
}

func UpdateSkipGetStarted(username string, tenantName string) error {
ctx, cancelfunc := context.WithTimeout(context.Background(), DbOperationTimeout*time.Second)
defer cancelfunc()
Expand Down
16 changes: 16 additions & 0 deletions models/user_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type User struct {
Team string `json:"team"`
Owner string `json:"owner"`
Description string `json:"description"`
LastLogin time.Time `json:"last_login"`
}

type Image struct {
Expand Down Expand Up @@ -101,6 +102,21 @@ type FilteredGenericUser struct {
Team string `json:"team"`
Owner string `json:"owner"`
Description string `json:"description"`
LastLogin time.Time `json:"last_login"`
}

type FilteredAppUser struct {
ID int `json:"id"`
Username string `json:"username"`
UserType string `json:"user_type"`
CreatedAt time.Time `json:"created_at"`
AvatarId int `json:"avatar_id"`
FullName string `json:"full_name"`
Pending bool `json:"pending"`
Position string `json:"position"`
Team string `json:"team"`
Owner string `json:"owner"`
Description string `json:"description"`
}

type FilteredApplicationUser struct {
Expand Down
20 changes: 19 additions & 1 deletion server/memphis_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,14 @@ func (umh UserMgmtHandler) Login(c *gin.Context) {
}

serv.Noticef("[tenant: %v][user: %v] has logged in", user.TenantName, user.Username)

lastLogin, err := db.UpdateLastLoginUser(user.ID)
if err != nil {
serv.Errorf("[tenant: %v][user: %v]Login at UpdateLastLoginUser: User %v: %v", user.TenantName, user.Username, user.Username, err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}

shouldSendAnalytics, _ := shouldSendAnalytics()
if shouldSendAnalytics {
analyticsParams := make(map[string]interface{})
Expand All @@ -1290,6 +1298,7 @@ func (umh UserMgmtHandler) Login(c *gin.Context) {
"created_at": user.CreatedAt,
"already_logged_in": user.AlreadyLoggedIn,
"avatar_id": user.AvatarId,
"last_login": lastLogin,
"send_analytics": shouldSendAnalytics,
"env": env,
"full_name": user.FullName,
Expand Down Expand Up @@ -1369,7 +1378,7 @@ func (umh UserMgmtHandler) AddUser(c *gin.Context) {
}
exist, _, err := memphis_cache.GetUser(username, user.TenantName, true)
if err != nil {
serv.Errorf("[tenant: %v][user: %v]AddUser at GetUserByUsername: User %v: %v", user.TenantName, user.Username, body.Username, err.Error())
serv.Errorf("[tenant: %v][user: %v]AddUser at GetUser: User %v: %v", user.TenantName, user.Username, body.Username, err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}
Expand Down Expand Up @@ -1801,6 +1810,14 @@ func (umh UserMgmtHandler) RefreshToken(c *gin.Context) {

domain := ""
secure := true

lastLogin, err := db.UpdateLastLoginUser(user.ID)
if err != nil {
serv.Errorf("[tenant: %v][user: %v]RefreshToken at UpdateLastLoginUser: User %v: %v", user.TenantName, user.Username, user.Username, err.Error())
c.AbortWithStatusJSON(500, gin.H{"message": "Server error"})
return
}

c.SetCookie("memphis-jwt-refresh-token", refreshToken, REFRESH_JWT_EXPIRES_IN_MINUTES*60*1000, "/", domain, secure, true)
c.IndentedJSON(200, gin.H{
"jwt": token,
Expand All @@ -1816,6 +1833,7 @@ func (umh UserMgmtHandler) RefreshToken(c *gin.Context) {
"namespace": serv.opts.K8sNamespace,
"full_name": user.FullName,
"skip_get_started": user.SkipGetStarted,
"last_login": lastLogin,
"broker_host": serv.opts.BrokerHost,
"rest_gw_host": serv.opts.RestGwHost,
"ui_host": serv.opts.UiHost,
Expand Down
2 changes: 1 addition & 1 deletion server/memphis_handlers_consumers.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ func (s *Server) destroyCGFromNats(c *client, reply, userName, tenantName string
_, user, err := memphis_cache.GetUser(username, consumer.TenantName, false)
if err != nil && !IsNatsErr(err, JSConsumerNotFoundErr) && !IsNatsErr(err, JSStreamNotFoundErr) {
errMsg := fmt.Sprintf("[tenant: %v]Consumer group %v at station %v: %v", tenantName, consumer.ConsumersGroup, station.Name, err.Error())
serv.Errorf("destroyCGFromNats at GetUserByUsername: " + errMsg)
serv.Errorf("destroyCGFromNats at GetUser: " + errMsg)
respondWithErr(serv.MemphisGlobalAccountString(), s, reply, err)
return
}
Expand Down
4 changes: 2 additions & 2 deletions server/memphis_handlers_producers.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ func (s *Server) destroyProducerDirect(c *client, reply string, msg []byte) {
}
_, user, err := memphis_cache.GetUser(username, dpr.TenantName, false)
if err != nil {
serv.Errorf("[tenant: %v][user: %v]destroyProducerDirect at GetUserByUsername: Producer %v at station %v: %v", dpr.TenantName, dpr.Username, name, dpr.StationName, err.Error())
serv.Errorf("[tenant: %v][user: %v]destroyProducerDirect at GetUser: Producer %v at station %v: %v", dpr.TenantName, dpr.Username, name, dpr.StationName, err.Error())
}
message := "Producer " + name + " has been destroyed"
serv.Noticef("[tenant: %v][user: %v]: %v", tenantName, username, message)
Expand Down Expand Up @@ -446,7 +446,7 @@ func (s *Server) destroyProducerDirectV0(c *client, reply string, dpr destroyPro
}
_, user, err := memphis_cache.GetUser(username, dpr.TenantName, false)
if err != nil {
serv.Errorf("[tenant: %v][user: %v]destroyProducerDirectV0 at GetUserByUsername: Producer %v at station %v: %v", dpr.TenantName, dpr.Username, name, dpr.StationName, err.Error())
serv.Errorf("[tenant: %v][user: %v]destroyProducerDirectV0 at GetUser: Producer %v at station %v: %v", dpr.TenantName, dpr.Username, name, dpr.StationName, err.Error())
}
message := "Producer " + name + " has been destroyed"
serv.Noticef("[tenant: %v][user: %v]: %v", dpr.TenantName, username, message)
Expand Down
17 changes: 15 additions & 2 deletions server/memphis_handlers_user_mgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,25 @@ func (umh UserMgmtHandler) GetAllUsers(c *gin.Context) {
analytics.SendEvent(user.TenantName, user.Username, analyticsParams, "user-enter-users-page")
}

applicationUsers := []models.FilteredGenericUser{}
applicationUsers := []models.FilteredAppUser{}
managementUsers := []models.FilteredGenericUser{}

for _, user := range users {
if user.UserType == "application" {
applicationUsers = append(applicationUsers, user)
applicationUser := models.FilteredAppUser{
ID: user.ID,
Username: user.Username,
UserType: user.UserType,
CreatedAt: user.CreatedAt,
AvatarId: user.AvatarId,
FullName: user.FullName,
Pending: user.Pending,
Position: user.Position,
Team: user.Team,
Owner: user.Owner,
Description: user.Description,
}
applicationUsers = append(applicationUsers, applicationUser)
} else if user.UserType == "management" || user.UserType == "root" {
managementUsers = append(managementUsers, user)
}
Expand Down

0 comments on commit efbcece

Please sign in to comment.