From f545aeb3bd3a4196212fc994c966f1bc93917811 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Wed, 24 May 2023 21:57:00 +0300 Subject: [PATCH 1/9] Add Unix Timestamp support for last_read_at in notifications API --- routers/api/v1/notify/user.go | 21 +++++++++++++++++---- tests/integration/api_notification_test.go | 8 ++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 2261610c09238..1cda9274d7e5e 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -5,6 +5,7 @@ package notify import ( "net/http" + "strconv" "time" activities_model "code.gitea.io/gitea/models/activities" @@ -130,10 +131,22 @@ func ReadNotifications(ctx *context.APIContext) { lastRead := int64(0) qLastRead := ctx.FormTrim("last_read_at") if len(qLastRead) > 0 { - tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) - if err != nil { - ctx.InternalServerError(err) - return + var tmpLastRead time.Time + + // If qLastRead consists solely digits then parse it as a timestamp + if _, err := strconv.Atoi(qLastRead); err == nil { + timestamp, err := strconv.ParseInt(qLastRead, 10, 64) + if err != nil { + ctx.InternalServerError(err) + return + } + tmpLastRead = time.Unix(timestamp, 0) + } else { + tmpLastRead, err = time.Parse(time.RFC3339, qLastRead) + if err != nil { + ctx.InternalServerError(err) + return + } } if !tmpLastRead.IsZero() { lastRead = tmpLastRead.Unix() diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index 0ff13704cf69d..79bbc042ffb69 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -115,8 +115,12 @@ func TestAPINotification(t *testing.T) { DecodeJSON(t, resp, &apiNL) assert.Len(t, apiNL, 2) - lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token)) + lastReadAt1 := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 <- only Notification 4 is in this filter ... + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt1, token)) + MakeRequest(t, req, http.StatusResetContent) + + lastReadAt2 := "946687801" + req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt2, token)) MakeRequest(t, req, http.StatusResetContent) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token)) From 550ac86c0b7a7e52a6f302ebcfb90bdee3944f70 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 17:33:34 +0300 Subject: [PATCH 2/9] Implement timeutil.ParseDateTimeGraceful() --- modules/timeutil/datetime.go | 27 ++++++++++++ modules/timeutil/datetime_test.go | 72 +++++++++++++++++++++++++++++++ routers/api/v1/notify/user.go | 24 +++-------- 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 83170b374b54c..1b86e94e3e162 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -7,6 +7,7 @@ import ( "fmt" "html" "html/template" + "strconv" "time" ) @@ -58,3 +59,29 @@ func DateTime(format string, datetime any) template.HTML { } panic(fmt.Sprintf("Unsupported format %s", format)) } + +func ParseDateTimeGraceful(datetime any) (time.Time, error) { + switch val := datetime.(type) { + case string: + if timestamp, err := strconv.ParseInt(val, 10, 64); err == nil { + return ParseDateTimeGraceful(timestamp) + } else { + t, err := time.Parse(time.RFC3339, val) + if err != nil { + return time.Time{}, err + } + return t, nil + } + case int64: + switch { + case val > 946684800000000: // 2999-12-31 00:00:00 in milliseconds + return time.UnixMicro(val), nil + case val > 946645200000: // 2000-01-01 00:00:00 in milliseconds + return time.UnixMilli(val), nil + default: + return time.Unix(val, 0), nil + } + default: + return time.Time{}, fmt.Errorf("unsupported data type: %T", datetime) + } +} diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index f44b7aaae3c1b..cdfc12d5c5d27 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -43,3 +43,75 @@ func TestDateTime(t *testing.T) { actual = DateTime("full", refTimeStamp) assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) } + +func TestParseDateTimeGraceful(t *testing.T) { + testData := []struct { + value any + expected any + isFail bool + }{ + { + value: "2006-01-02T15:04:05Z", + expected: 1136214245, + }, + { + value: "2006-01-02T15:04:05.999Z", + expected: 1136214245, + }, + { + value: "2006-01-02T15:04:05-07:00", + expected: 1136239445, + }, + { + value: "2006-01-02T15:04:05.999-07:00", + expected: 1136239445, + }, + { + value: 1136214245, + expected: 1136214245, + }, + { + value: "1136214245", + expected: 1136214245, + }, + { + value: 1622040867000, + expected: 1622040867, + }, + { + value: "1622040867000", + expected: 1622040867, + }, + { + value: "1622040867000", + expected: 1622040867, + }, + { + value: 0, + expected: -62135596800, + isFail: true, + }, + { + value: nil, + expected: -62135596800, + isFail: true, + }, + { + value: "", + expected: -62135596800, + isFail: true, + }, + } + + for _, testCase := range testData { + actual, err := ParseDateTimeGraceful(testCase.value) + + if testCase.isFail == true { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + + assert.EqualValues(t, testCase.expected, actual.Unix()) + } +} diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 1cda9274d7e5e..02c1de512c037 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -5,12 +5,11 @@ package notify import ( "net/http" - "strconv" - "time" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/services/convert" ) @@ -131,23 +130,12 @@ func ReadNotifications(ctx *context.APIContext) { lastRead := int64(0) qLastRead := ctx.FormTrim("last_read_at") if len(qLastRead) > 0 { - var tmpLastRead time.Time - - // If qLastRead consists solely digits then parse it as a timestamp - if _, err := strconv.Atoi(qLastRead); err == nil { - timestamp, err := strconv.ParseInt(qLastRead, 10, 64) - if err != nil { - ctx.InternalServerError(err) - return - } - tmpLastRead = time.Unix(timestamp, 0) - } else { - tmpLastRead, err = time.Parse(time.RFC3339, qLastRead) - if err != nil { - ctx.InternalServerError(err) - return - } + tmpLastRead, err := timeutil.ParseDateTimeGraceful(qLastRead) + if err != nil { + ctx.InternalServerError(err) + return } + if !tmpLastRead.IsZero() { lastRead = tmpLastRead.Unix() } From 45d625b85247fc922059d1d246aa1ce0327ef397 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 18:37:41 +0300 Subject: [PATCH 3/9] fix test --- routers/api/v1/notify/repo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index bd3b86a6f1525..744fe9bc70274 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -6,12 +6,12 @@ package notify import ( "net/http" "strings" - "time" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/services/convert" ) @@ -181,7 +181,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { lastRead := int64(0) qLastRead := ctx.FormTrim("last_read_at") if len(qLastRead) > 0 { - tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) + tmpLastRead, err := timeutil.ParseDateTimeGraceful(qLastRead) if err != nil { ctx.InternalServerError(err) return From ee3479ec96e6bdfaf1d3525864f58554a0591009 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 19:12:19 +0300 Subject: [PATCH 4/9] fix lint --- modules/timeutil/datetime.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 1b86e94e3e162..c5a93b4f86b61 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -65,13 +65,13 @@ func ParseDateTimeGraceful(datetime any) (time.Time, error) { case string: if timestamp, err := strconv.ParseInt(val, 10, 64); err == nil { return ParseDateTimeGraceful(timestamp) - } else { - t, err := time.Parse(time.RFC3339, val) - if err != nil { - return time.Time{}, err - } - return t, nil } + + t, err := time.Parse(time.RFC3339, val) + if err != nil { + return time.Time{}, err + } + return t, nil case int64: switch { case val > 946684800000000: // 2999-12-31 00:00:00 in milliseconds From 3095ee17203c6191cd831667e9d547cb6351a50e Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 19:52:29 +0300 Subject: [PATCH 5/9] fix tests --- modules/timeutil/datetime_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index cdfc12d5c5d27..ae5733a5359ce 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -52,53 +52,53 @@ func TestParseDateTimeGraceful(t *testing.T) { }{ { value: "2006-01-02T15:04:05Z", - expected: 1136214245, + expected: int64(1136214245), }, { value: "2006-01-02T15:04:05.999Z", - expected: 1136214245, + expected: int64(1136214245), }, { value: "2006-01-02T15:04:05-07:00", - expected: 1136239445, + expected: int64(1136239445), }, { value: "2006-01-02T15:04:05.999-07:00", - expected: 1136239445, + expected: int64(1136239445), }, { - value: 1136214245, - expected: 1136214245, + value: int64(1136214245), + expected: int64(1136214245), }, { value: "1136214245", - expected: 1136214245, + expected: int64(1136214245), }, { - value: 1622040867000, - expected: 1622040867, + value: int64(1622040867000), + expected: int64(1622040867), }, { value: "1622040867000", - expected: 1622040867, + expected: int64(1622040867), }, { value: "1622040867000", - expected: 1622040867, + expected: int64(1622040867), }, { value: 0, - expected: -62135596800, + expected: int64(-62135596800), isFail: true, }, { value: nil, - expected: -62135596800, + expected: int64(-62135596800), isFail: true, }, { value: "", - expected: -62135596800, + expected: int64(-62135596800), isFail: true, }, } From db46c7b031168afd14faa40fad91d6bd936ce3da Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 20:11:57 +0300 Subject: [PATCH 6/9] remove UnixMicro support --- modules/timeutil/datetime.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index c5a93b4f86b61..92d2d45f766b0 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -74,8 +74,6 @@ func ParseDateTimeGraceful(datetime any) (time.Time, error) { return t, nil case int64: switch { - case val > 946684800000000: // 2999-12-31 00:00:00 in milliseconds - return time.UnixMicro(val), nil case val > 946645200000: // 2000-01-01 00:00:00 in milliseconds return time.UnixMilli(val), nil default: From cefbf57738014da7f9dae91a65f7efc0f8b1789c Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sat, 27 May 2023 23:10:50 +0300 Subject: [PATCH 7/9] remove UnixMilli support --- modules/timeutil/datetime.go | 7 +------ modules/timeutil/datetime_test.go | 10 ++++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 92d2d45f766b0..02b192ee232de 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -73,12 +73,7 @@ func ParseDateTimeGraceful(datetime any) (time.Time, error) { } return t, nil case int64: - switch { - case val > 946645200000: // 2000-01-01 00:00:00 in milliseconds - return time.UnixMilli(val), nil - default: - return time.Unix(val, 0), nil - } + return time.Unix(val, 0), nil default: return time.Time{}, fmt.Errorf("unsupported data type: %T", datetime) } diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index ae5733a5359ce..ea66b58fd0211 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -76,15 +76,13 @@ func TestParseDateTimeGraceful(t *testing.T) { }, { value: int64(1622040867000), - expected: int64(1622040867), - }, - { - value: "1622040867000", - expected: int64(1622040867), + expected: int64(-62135596800), + isFail: true, }, { value: "1622040867000", - expected: int64(1622040867), + expected: int64(-62135596800), + isFail: true, }, { value: 0, From af4e5ac8a1226a2f6c3e820f6d0eedb9dd98de05 Mon Sep 17 00:00:00 2001 From: Yevhen Pavlov Date: Sun, 28 May 2023 00:11:33 +0300 Subject: [PATCH 8/9] fix tests --- modules/timeutil/datetime_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index ea66b58fd0211..020de05534842 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -76,13 +76,11 @@ func TestParseDateTimeGraceful(t *testing.T) { }, { value: int64(1622040867000), - expected: int64(-62135596800), - isFail: true, + expected: int64(1622040867000), }, { value: "1622040867000", - expected: int64(-62135596800), - isFail: true, + expected: int64(1622040867000), }, { value: 0, From 047b6dd3a57873c2389ed4a9b064625529bb6711 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Jul 2023 14:05:59 +0800 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: wxiaoguang --- modules/timeutil/datetime.go | 2 +- modules/timeutil/datetime_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 02b192ee232de..6757c11cb6c56 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -64,7 +64,7 @@ func ParseDateTimeGraceful(datetime any) (time.Time, error) { switch val := datetime.(type) { case string: if timestamp, err := strconv.ParseInt(val, 10, 64); err == nil { - return ParseDateTimeGraceful(timestamp) + return time.Unix(val, 0), nil } t, err := time.Parse(time.RFC3339, val) diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index 020de05534842..898391eb5691e 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -104,10 +104,10 @@ func TestParseDateTimeGraceful(t *testing.T) { if testCase.isFail == true { assert.NotNil(t, err) + assert.True(t, actual.IsZero()) } else { assert.Nil(t, err) + assert.EqualValues(t, testCase.expected, actual.Unix()) } - - assert.EqualValues(t, testCase.expected, actual.Unix()) } }