diff --git a/api/schedule_history_last.go b/api/schedule_history_last.go
new file mode 100644
index 0000000..179479c
--- /dev/null
+++ b/api/schedule_history_last.go
@@ -0,0 +1,24 @@
+package api
+
+import (
+ "net/http"
+
+ "github.com/stephenafamo/bob/dialect/sqlite/sm"
+ "github.com/tigorlazuardi/redmage/models"
+ "github.com/tigorlazuardi/redmage/pkg/errs"
+ "golang.org/x/net/context"
+)
+
+func (api *API) ScheduleHistoryLatest(ctx context.Context) (result *models.ScheduleHistory, err error) {
+ ctx, span := tracer.Start(ctx, "*API.ScheduleHistoryLatest")
+ defer span.End()
+
+ result, err = models.ScheduleHistories.Query(ctx, api.db, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc()).One()
+ if err != nil {
+ if err.Error() == "sql: no rows in result set" {
+ return result, errs.Wrapw(err, "last schedule history not found").Code(http.StatusNotFound)
+ }
+ return result, errs.Wrapw(err, "failed to find last schedule history")
+ }
+ return result, nil
+}
diff --git a/api/schedule_history_list.go b/api/schedule_history_list.go
index 2f3fdf6..cf6f8b6 100644
--- a/api/schedule_history_list.go
+++ b/api/schedule_history_list.go
@@ -3,30 +3,28 @@ package api
import (
"context"
"strconv"
- "strings"
"time"
"github.com/stephenafamo/bob"
- "github.com/stephenafamo/bob/dialect/sqlite"
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
"github.com/stephenafamo/bob/dialect/sqlite/sm"
+ "github.com/tigorlazuardi/redmage/api/utils"
"github.com/tigorlazuardi/redmage/models"
"github.com/tigorlazuardi/redmage/pkg/errs"
)
type ScheduleHistoryListParams struct {
Subreddit string
- After time.Time
- Before time.Time
+ Time time.Time
+ Direction string
- Limit int64
- Offset int64
- OrderBy string
- Sort string
+ Limit int64
+ Offset int64
}
func (params *ScheduleHistoryListParams) FillFromQuery(query Queryable) {
params.Subreddit = query.Get("subreddit")
+ params.Direction = query.Get("direction")
params.Limit, _ = strconv.ParseInt(query.Get("limit"), 10, 64)
if params.Limit < 1 {
params.Limit = 100
@@ -42,33 +40,29 @@ func (params *ScheduleHistoryListParams) FillFromQuery(query Queryable) {
now := time.Now()
- afterInt, _ := strconv.ParseInt(query.Get("after"), 10, 64)
- if afterInt > 0 {
- params.After = time.Unix(afterInt, 0)
- } else if afterInt < 0 {
- params.After = now.Add(time.Duration(afterInt) * time.Second)
+ timeInt, _ := strconv.ParseInt(query.Get("time"), 10, 64)
+ if timeInt > 0 {
+ params.Time = time.Unix(timeInt, 0)
+ } else if timeInt < 0 {
+ params.Time = now.Add(time.Duration(timeInt) * time.Second)
}
-
- beforeInt, _ := strconv.ParseInt(query.Get("before"), 10, 64)
- if beforeInt > 0 {
- params.Before = time.Unix(beforeInt, 0)
- } else if beforeInt < 0 {
- params.Before = now.Add(time.Duration(beforeInt) * time.Second)
+ if params.Time.After(now) {
+ params.Time = time.Time{}
}
-
- params.OrderBy = query.Get("order_by")
- params.Sort = query.Get("sort")
}
func (params ScheduleHistoryListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
if params.Subreddit != "" {
expr = append(expr, models.SelectWhere.ScheduleHistories.Subreddit.EQ(params.Subreddit))
}
- if !params.After.IsZero() {
- expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.GTE(params.After.Unix()))
- }
- if !params.Before.IsZero() {
- expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.LTE(params.Before.Unix()))
+ if !params.Time.IsZero() {
+ if params.Direction == "before" {
+ expr = append(expr,
+ models.SelectWhere.ScheduleHistories.CreatedAt.GTE(params.Time.Unix()),
+ )
+ } else {
+ expr = append(expr, models.SelectWhere.ScheduleHistories.CreatedAt.LT(params.Time.Unix()))
+ }
}
return expr
@@ -82,15 +76,7 @@ func (params ScheduleHistoryListParams) Query() (expr []bob.Mod[*dialect.SelectQ
if params.Offset > 0 {
expr = append(expr, sm.Offset(params.Offset))
}
- if params.OrderBy != "" {
- if strings.ToLower(params.Sort) == "desc" {
- expr = append(expr, sm.OrderBy(sqlite.Quote(params.OrderBy)).Desc())
- } else {
- expr = append(expr, sm.OrderBy(sqlite.Quote(params.OrderBy)).Asc())
- }
- } else {
- expr = append(expr, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc(), sm.OrderBy(models.ScheduleHistoryColumns.Status).Desc())
- }
+ expr = append(expr, sm.OrderBy(models.ScheduleHistoryColumns.CreatedAt).Desc())
return expr
}
@@ -100,6 +86,71 @@ type ScheduleHistoryListResult struct {
Total int64 `json:"count"`
}
+func (result ScheduleHistoryListResult) GetLast() *models.ScheduleHistory {
+ if len(result.Schedules) > 0 {
+ return result.Schedules[len(result.Schedules)-1]
+ }
+ return nil
+}
+
+func (result ScheduleHistoryListResult) GetLastTime() time.Time {
+ if schedule := result.GetLast(); schedule != nil {
+ return time.Unix(schedule.CreatedAt, 0)
+ }
+ return time.Now()
+}
+
+func (result ScheduleHistoryListResult) GetFirstTime() time.Time {
+ if schedule := result.GetFirst(); schedule != nil {
+ return time.Unix(schedule.CreatedAt, 0)
+ }
+ return time.Now()
+}
+
+func (result ScheduleHistoryListResult) GetFirst() *models.ScheduleHistory {
+ if len(result.Schedules) > 0 {
+ return result.Schedules[0]
+ }
+ return nil
+}
+
+func (result ScheduleHistoryListResult) SplitByDay() (out []ScheduleHistoryListResultDay) {
+ out = make([]ScheduleHistoryListResultDay, 0, 4)
+
+ var lastDay time.Time
+ var lastIdx int
+ for _, schedule := range result.Schedules {
+ t := utils.StartOfDay(time.Unix(schedule.CreatedAt, 0).In(time.Local))
+ if !t.Equal(lastDay) {
+ out = append(out, ScheduleHistoryListResultDay{
+ Date: t,
+ })
+ lastDay = t
+ lastIdx = len(out) - 1
+
+ out[lastIdx].Schedules = append(out[lastIdx].Schedules, schedule)
+ out[lastIdx].Total += 1
+ } else {
+ out[lastIdx].Schedules = append(out[lastIdx].Schedules, schedule)
+ out[lastIdx].Total += 1
+ }
+ }
+
+ return
+}
+
+type ScheduleHistoryListResultDay struct {
+ Date time.Time `json:"date"`
+ ScheduleHistoryListResult
+}
+
+func (resultDay ScheduleHistoryListResultDay) GetLast() *models.ScheduleHistory {
+ if len(resultDay.Schedules) > 0 {
+ return resultDay.Schedules[len(resultDay.Schedules)-1]
+ }
+ return nil
+}
+
func (api *API) ScheduleHistoryList(ctx context.Context, params ScheduleHistoryListParams) (result ScheduleHistoryListResult, err error) {
ctx, span := tracer.Start(ctx, "*API.ScheduleHistoryList")
defer span.End()
diff --git a/api/utils/utils.go b/api/utils/utils.go
new file mode 100644
index 0000000..fae1f0b
--- /dev/null
+++ b/api/utils/utils.go
@@ -0,0 +1,7 @@
+package utils
+
+import "time"
+
+func StartOfDay(t time.Time) time.Time {
+ return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+}
diff --git a/pkg/pubsub/pubsub.go b/pkg/pubsub/pubsub.go
index 3267dfa..3e1ee24 100644
--- a/pkg/pubsub/pubsub.go
+++ b/pkg/pubsub/pubsub.go
@@ -1,11 +1,11 @@
package pubsub
import (
+ "github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill-bolt/pkg/bolt"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/pkg/errs"
- "github.com/tigorlazuardi/redmage/pkg/log"
"go.etcd.io/bbolt"
)
@@ -23,7 +23,7 @@ func NewPublisher(db *bbolt.DB) (message.Publisher, error) {
return bolt.NewPublisher(db, bolt.PublisherConfig{
Common: bolt.CommonConfig{
Bucket: []bolt.BucketName{bolt.BucketName("watermill")},
- Logger: &log.WatermillLogger{},
+ Logger: watermill.NopLogger{},
},
})
}
@@ -33,7 +33,7 @@ func NewSubscriber(db *bbolt.DB) (message.Subscriber, error) {
Common: bolt.CommonConfig{
Bucket: []bolt.BucketName{bolt.BucketName("watermill")},
Marshaler: nil,
- Logger: &log.WatermillLogger{},
+ Logger: watermill.NopLogger{},
},
})
}
diff --git a/server/routes/page_schedule_history.go b/server/routes/page_schedule_history.go
index 1480afa..8009800 100644
--- a/server/routes/page_schedule_history.go
+++ b/server/routes/page_schedule_history.go
@@ -18,7 +18,7 @@ func (routes *Routes) PageScheduleHistory(rw http.ResponseWriter, req *http.Requ
var data schedulehistories.Data
data.Params.FillFromQuery(req.URL.Query())
- result, err := routes.API.ScheduleHistoryListByDate(ctx, data.Params)
+ result, err := routes.API.ScheduleHistoryList(ctx, data.Params)
if err != nil {
log.New(ctx).Err(err).Error("Failed to list schedule histories")
code, message := errs.HTTPMessage(err)
@@ -30,7 +30,15 @@ func (routes *Routes) PageScheduleHistory(rw http.ResponseWriter, req *http.Requ
return
}
- data.ScheduleHistories = result.Schedules
+ data.ScheduleHistories = result
+
+ if latest, _ := routes.API.ScheduleHistoryLatest(ctx); latest != nil {
+ if first := data.ScheduleHistories.GetFirst(); first != nil {
+ if first.ID == latest.ID {
+ data.IsCurrent = true
+ }
+ }
+ }
if err := schedulehistories.View(c, data).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("Failed to render schedule histories view")
diff --git a/views/components/action_button.templ b/views/components/action_button.templ
index 3b4ade3..a50ee48 100644
--- a/views/components/action_button.templ
+++ b/views/components/action_button.templ
@@ -6,20 +6,20 @@ templ ActionButton(components ...templ.Component) {
@icons.Kebab("h-8 w-8")
for i, component := range components {
if i > 0 {
diff --git a/views/icons/chevrons_bold.templ b/views/icons/chevrons_bold.templ
index eb173ab..d2388dd 100644
--- a/views/icons/chevrons_bold.templ
+++ b/views/icons/chevrons_bold.templ
@@ -34,7 +34,6 @@ templ ChevronBoldLeft(class ...string) {
class={ strings.Join(class, " ") }
}
>
- chevron-left
Created with Sketch Beta.
diff --git a/views/schedulehistories/view.templ b/views/schedulehistories/view.templ
index 6584e30..8288974 100644
--- a/views/schedulehistories/view.templ
+++ b/views/schedulehistories/view.templ
@@ -2,24 +2,21 @@ package schedulehistories
import "github.com/tigorlazuardi/redmage/views"
import "github.com/tigorlazuardi/redmage/views/components"
-import "github.com/tigorlazuardi/redmage/models"
import "github.com/tigorlazuardi/redmage/api"
import "fmt"
import "time"
import "github.com/tigorlazuardi/redmage/views/icons"
+import "github.com/tigorlazuardi/redmage/models"
type Data struct {
- ScheduleHistories models.ScheduleHistorySlice
- Params api.ScheduleHistoryListByDateParams
+ ScheduleHistories api.ScheduleHistoryListResult
+ Params api.ScheduleHistoryListParams
+ FirstSchedule *models.ScheduleHistory
+ LastSchedule *models.ScheduleHistory
+ IsCurrent bool
Error string
}
-func (data Data) isCurrentDay() bool {
- now := time.Now()
-
- return now.Format(time.DateOnly) == data.Params.Date.Format(time.DateOnly)
-}
-
templ View(c *views.Context, data Data) {
@components.Doctype() {
@components.Head(c,
@@ -42,77 +39,81 @@ templ Content(c *views.Context, data Data) {
Schedule History ({ time.Local.String() })
- @dateBar(data, true)
- if len(data.ScheduleHistories) == 0 {
+ @dateBar(data)
+ if len(data.ScheduleHistories.Schedules) == 0 {
There are no history schedules found for current date.
}
- if len(data.ScheduleHistories) > 0 {
-
-
Time
-
Event
- for i, schedule := range data.ScheduleHistories {
- if i > 0 {
-
+ if len(data.ScheduleHistories.Schedules) > 0 {
+ for _, history := range data.ScheduleHistories.SplitByDay() {
+
{ history.Date.Format("Monday, 02 January 2006") }
+
+
+
Time
+
Event
+ for i, schedule := range history.Schedules {
+ if i > 0 {
+
+ }
+
+ if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusDisabled {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ scheduler has been set to { api.ScheduleStatusDisabled.String() } status.
+
+ } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusEnabled {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ { " " }
+ has been { api.ScheduleStatusEnabled.String() } { "for" } automatic scheduling.
+
+ } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusStandby {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ { " " }
+ has finished
+ { api.ScheduleStatusDownloading.String() }
+ and turned to { api.ScheduleStatusStandby.String() } status.
+
+ } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusEnqueued {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ { " " }
+ is { api.ScheduleStatusEnqueued.String() } { "for" } downloading.
+
+ } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusDownloading {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ { " " }
+ has started { api.ScheduleStatusDownloading.String() }.
+
+ } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusError {
+
+ Subreddit
+ @subredditLink(schedule.Subreddit)
+ { " " }
+ finishes { api.ScheduleStatusDownloading.String() }
+ with { api.ScheduleStatusError.String() } of "{ schedule.ErrorMessage }".
+
+ }
}
-
- if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusDisabled {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- scheduler has been set to { api.ScheduleStatusDisabled.String() } status.
-
- } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusEnabled {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- { " " }
- has been { api.ScheduleStatusEnabled.String() } { "for" } automatic scheduling.
-
- } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusStandby {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- { " " }
- has finished
- { api.ScheduleStatusDownloading.String() }
- and turned to { api.ScheduleStatusStandby.String() } status.
-
- } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusEnqueued {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- { " " }
- is { api.ScheduleStatusEnqueued.String() } { "for" } downloading.
-
- } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusDownloading {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- { " " }
- has started { api.ScheduleStatusDownloading.String() }.
-
- } else if api.ScheduleStatus(schedule.Status) == api.ScheduleStatusError {
-
- Subreddit
- @subredditLink(schedule.Subreddit)
- { " " }
- finishes { api.ScheduleStatusDownloading.String() }
- with { api.ScheduleStatusError.String() } of "{ schedule.ErrorMessage }".
-
- }
- }
-
+
+ }
}
- if len(data.ScheduleHistories) > 20 {
- @dateBar(data, false)
+ if len(data.ScheduleHistories.Schedules) > 20 {
+ @dateBar(data)
}
@actionButton(data)
@@ -120,14 +121,25 @@ templ Content(c *views.Context, data Data) {
templ actionButton(data Data) {
- @components.ActionButton(
- actionButtonNext(data),
- actionButtonPrev(data),
- )
+ @components.ActionButton(actionButtonItems(data)...)
}
-templ dateBar(data Data, showDate bool) {
+func actionButtonItems(data Data) []templ.Component {
+ out := make([]templ.Component, 0, 2)
+ if !data.IsCurrent {
+ out = append(out, actionButtonPrev(data))
+ }
+ if len(data.ScheduleHistories.Schedules) >= int(data.Params.Limit) {
+ out = append(out, actionButtonNext(data))
+ }
+ if data.IsCurrent {
+ out = append(out, actionButtonRefresh())
+ }
+ return out
+}
+
+templ dateBar(data Data) {
- if data.isCurrentDay() {
-
- @icons.Refresh("w-6 h-6")
-
+ if data.IsCurrent {
+
} else {
-
- @icons.ChevronBoldLeft("w-6 h-6")
-
+
}
- if showDate {
-
{ data.Params.Date.Format("Monday, 02 January 2006") }
-
{ data.Params.Date.Format("Mon, 02 Jan") }
+ if len(data.ScheduleHistories.Schedules) >= int(data.Params.Limit) {
+
}
-
}
templ actionButtonNext(data Data) {
Next
}
templ actionButtonPrev(data Data) {
Previous
}
+templ actionButtonRefresh() {
+ Refresh
+}
+
templ subredditLink(subreddit string) {
{ subreddit }
}