From e4a2fbd51a342c677cd91d47e24a2442d4a1079f Mon Sep 17 00:00:00 2001 From: NickAc <32451103+NickAcPT@users.noreply.github.com> Date: Sat, 30 May 2020 21:01:54 +0100 Subject: [PATCH 1/4] Automatically build the project for Windows users This change makes it simpler for Windows users to use the project by automatically building the project with GitHub Actions on every release. This allows for an easier way to use the project by automatically adding a zip file with the built executables to new releases. --- .github/workflows/win-build-on-release.yml | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/win-build-on-release.yml diff --git a/.github/workflows/win-build-on-release.yml b/.github/workflows/win-build-on-release.yml new file mode 100644 index 0000000..85c4d4d --- /dev/null +++ b/.github/workflows/win-build-on-release.yml @@ -0,0 +1,51 @@ +name: Build WakAPI on Windows + +on: + release: + types: + - created + +jobs: + build-and-release: + name: Build and add to release + runs-on: windows-latest + steps: + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Enable Go 1.11 modules + run: cmd /c "set GO111MODULE=on" + + - name: Build + run: go build -v . + + - name: Upload Built Executable to Github Actions Artifacts + uses: actions/upload-artifact@v2 + with: + name: release.zip + # A file, directory or wildcard pattern that describes what to upload + path: ./ + + - name: Compress working folder + run: Compress-Archive -Path .\* -DestinationPath release.zip + + - name: Upload built executable to Release + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: release.zip + asset_name: wakapi_win_amd64.zip + asset_content_type: application/gzip From c68ee0a81e68b85ebf887ad9253187cf54a3be0e Mon Sep 17 00:00:00 2001 From: NickAc <32451103+NickAcPT@users.noreply.github.com> Date: Sat, 30 May 2020 21:04:22 +0100 Subject: [PATCH 2/4] Remove upload of artifact to Actions' artifacts There is no need to upload the artifact to the Action itself since it will be uploaded to the release. --- .github/workflows/win-build-on-release.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/win-build-on-release.yml b/.github/workflows/win-build-on-release.yml index 85c4d4d..f74aaf0 100644 --- a/.github/workflows/win-build-on-release.yml +++ b/.github/workflows/win-build-on-release.yml @@ -29,13 +29,6 @@ jobs: - name: Build run: go build -v . - - - name: Upload Built Executable to Github Actions Artifacts - uses: actions/upload-artifact@v2 - with: - name: release.zip - # A file, directory or wildcard pattern that describes what to upload - path: ./ - name: Compress working folder run: Compress-Archive -Path .\* -DestinationPath release.zip From 0a513e959be6760bd42c30643c77d81dd4e4a557 Mon Sep 17 00:00:00 2001 From: NickAc <32451103+NickAcPT@users.noreply.github.com> Date: Sat, 30 May 2020 21:50:16 +0100 Subject: [PATCH 3/4] Automatically build the project for Linux users Add a GitHub Action to build on Linux when a release is created --- .github/workflows/linux-build-on-release.yml | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/linux-build-on-release.yml diff --git a/.github/workflows/linux-build-on-release.yml b/.github/workflows/linux-build-on-release.yml new file mode 100644 index 0000000..f762644 --- /dev/null +++ b/.github/workflows/linux-build-on-release.yml @@ -0,0 +1,43 @@ +name: Build WakAPI on Linux + +on: + release: + types: + - created + +jobs: + build-and-release: + name: Build and add to Release + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Build + run: GO111MODULE=on go build -v . + + - name: Zip Release + uses: TheDoctor0/zip-release@v0.3.0 + with: + filename: release.zip + + - name: Upload built executable to Release + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: release.zip + asset_name: wakapi_linux.zip + asset_content_type: application/gzip From 629a3212c7d4171da8d88095ccd5627680db572d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferdinand=20M=C3=BCtsch?= Date: Sat, 30 May 2020 22:19:05 +0200 Subject: [PATCH 4/4] feat: persist user creation date (resolve #31) --- migrations/sqlite3/3_user_creation_date.sql | 20 ++++++ models/heartbeat.go | 79 ++++----------------- models/shared.go | 54 +++++++++++++- models/user.go | 8 ++- routes/public.go | 4 ++ services/user.go | 13 ++++ version.txt | 2 +- 7 files changed, 111 insertions(+), 69 deletions(-) create mode 100644 migrations/sqlite3/3_user_creation_date.sql diff --git a/migrations/sqlite3/3_user_creation_date.sql b/migrations/sqlite3/3_user_creation_date.sql new file mode 100644 index 0000000..8450d8e --- /dev/null +++ b/migrations/sqlite3/3_user_creation_date.sql @@ -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`; \ No newline at end of file diff --git a/models/heartbeat.go b/models/heartbeat.go index 61fad35..62893a6 100644 --- a/models/heartbeat.go +++ b/models/heartbeat.go @@ -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) -} diff --git a/models/shared.go b/models/shared.go index bc03026..01d820f 100644 --- a/models/shared.go +++ b/models/shared.go @@ -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) +} diff --git a/models/user.go b/models/user.go index 208d5e6..ca4a09e 100644 --- a/models/user.go +++ b/models/user.go @@ -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 { diff --git a/routes/public.go b/routes/public.go index 44ed869..4b2cf47 100644 --- a/routes/public.go +++ b/routes/public.go @@ -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, diff --git a/services/user.go b/services/user.go index 81768a8..19802c8 100644 --- a/services/user.go +++ b/services/user.go @@ -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 { diff --git a/version.txt b/version.txt index 2eda823..308b6fa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.6.1 \ No newline at end of file +1.6.2 \ No newline at end of file