feat: persist user creation date (resolve #31)

This commit is contained in:
Ferdinand Mütsch 2020-05-30 22:19:05 +02:00
parent 1872bf4b4c
commit c1e6a3e265
7 changed files with 111 additions and 69 deletions

View File

@ -0,0 +1,20 @@
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
-- SQLite does not allow altering a table to add a new column with default of CURRENT_TIMESTAMP
-- See https://www.sqlite.org/lang_altertable.html
alter table users
add `created_at` timestamp default '2020-01-01T00:00:00.000' not null;
alter table users
add `last_logged_in_at` timestamp default '2020-01-01T00:00:00.000' not null;
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
alter table users
drop column `created_at`;
alter table users
drop column `last_logged_in_at`;

View File

@ -1,36 +1,31 @@
package models
import (
"database/sql/driver"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
type HeartbeatReqTime time.Time
type CustomTime time.Time
type Heartbeat struct {
ID uint `gorm:"primary_key"`
User *User `json:"-" gorm:"not null"`
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
Type string `json:"type"`
Category string `json:"category"`
Project string `json:"project"`
Branch string `json:"branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
Editor string `json:"editor"`
OperatingSystem string `json:"operating_system"`
Time HeartbeatReqTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"`
ID uint `gorm:"primary_key"`
User *User `json:"-" gorm:"not null"`
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
Type string `json:"type"`
Category string `json:"category"`
Project string `json:"project"`
Branch string `json:"branch"`
Language string `json:"language" gorm:"index:idx_language"`
IsWrite bool `json:"is_write"`
Editor string `json:"editor"`
OperatingSystem string `json:"operating_system"`
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"`
languageRegex *regexp.Regexp
}
func (h *Heartbeat) Valid() bool {
return h.User != nil && h.UserID != "" && h.Time != HeartbeatReqTime(time.Time{})
return h.User != nil && h.UserID != "" && h.Time != CustomTime(time.Time{})
}
func (h *Heartbeat) Augment(customLangs map[string]string) {
@ -49,47 +44,3 @@ func (h *Heartbeat) Augment(customLangs map[string]string) {
h.Language, _ = customLangs[ending]
}
}
func (j *HeartbeatReqTime) UnmarshalJSON(b []byte) error {
s := strings.Split(strings.Trim(string(b), "\""), ".")[0]
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
t := time.Unix(i, 0)
*j = HeartbeatReqTime(t)
return nil
}
func (j *HeartbeatReqTime) Scan(value interface{}) error {
switch value.(type) {
case string:
t, err := time.Parse("2006-01-02 15:04:05-07:00", value.(string))
if err != nil {
return errors.New(fmt.Sprintf("unsupported date time format: %s", value))
}
*j = HeartbeatReqTime(t)
case int64:
*j = HeartbeatReqTime(time.Unix(value.(int64), 0))
break
case time.Time:
*j = HeartbeatReqTime(value.(time.Time))
break
default:
return errors.New(fmt.Sprintf("unsupported type: %T", value))
}
return nil
}
func (j HeartbeatReqTime) Value() (driver.Value, error) {
return time.Time(j), nil
}
func (j HeartbeatReqTime) String() string {
t := time.Time(j)
return t.Format("2006-01-02 15:04:05")
}
func (j HeartbeatReqTime) Time() time.Time {
return time.Time(j)
}

View File

@ -1,6 +1,14 @@
package models
import "github.com/jinzhu/gorm"
import (
"database/sql/driver"
"errors"
"fmt"
"github.com/jinzhu/gorm"
"strconv"
"strings"
"time"
)
const (
UserKey = "user"
@ -14,3 +22,47 @@ type KeyStringValue struct {
Key string `gorm:"primary_key"`
Value string `gorm:"type:text"`
}
func (j *CustomTime) UnmarshalJSON(b []byte) error {
s := strings.Split(strings.Trim(string(b), "\""), ".")[0]
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
t := time.Unix(i, 0)
*j = CustomTime(t)
return nil
}
func (j *CustomTime) Scan(value interface{}) error {
switch value.(type) {
case string:
t, err := time.Parse("2006-01-02 15:04:05-07:00", value.(string))
if err != nil {
return errors.New(fmt.Sprintf("unsupported date time format: %s", value))
}
*j = CustomTime(t)
case int64:
*j = CustomTime(time.Unix(value.(int64), 0))
break
case time.Time:
*j = CustomTime(value.(time.Time))
break
default:
return errors.New(fmt.Sprintf("unsupported type: %T", value))
}
return nil
}
func (j CustomTime) Value() (driver.Value, error) {
return time.Time(j), nil
}
func (j CustomTime) String() string {
t := time.Time(j)
return t.Format("2006-01-02 15:04:05")
}
func (j CustomTime) Time() time.Time {
return time.Time(j)
}

View File

@ -1,9 +1,11 @@
package models
type User struct {
ID string `json:"id" gorm:"primary_key"`
ApiKey string `json:"api_key" gorm:"unique"`
Password string `json:"-"`
ID string `json:"id" gorm:"primary_key"`
ApiKey string `json:"api_key" gorm:"unique"`
Password string `json:"-"`
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"`
}
type Login struct {

View File

@ -9,6 +9,7 @@ import (
"github.com/muety/wakapi/utils"
"net/http"
"net/url"
"time"
)
type IndexHandler struct {
@ -106,6 +107,9 @@ func (h *IndexHandler) Login(w http.ResponseWriter, r *http.Request) {
return
}
user.LastLoggedInAt = models.CustomTime(time.Now())
h.userSrvc.Update(user)
cookie := &http.Cookie{
Name: models.AuthCookieKey,
Value: encoded,

View File

@ -69,6 +69,19 @@ func (srv *UserService) CreateOrGet(signup *models.Signup) (*models.User, bool,
return u, false, nil
}
func (srv *UserService) Update(user *models.User) (*models.User, error) {
result := srv.Db.Model(&models.User{}).Updates(user)
if err := result.Error; err != nil {
return nil, err
}
if result.RowsAffected != 1 {
return nil, errors.New("nothing updated")
}
return user, nil
}
func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Login) (*models.User, error) {
user.Password = login.Password
if err := utils.HashPassword(user, srv.Config.PasswordSalt); err != nil {

View File

@ -1 +1 @@
1.6.1
1.6.2