internal/task/schedule_list.go
Rama main · réplica de lectura del repositorio original.
package task
import (
"encoding/base64"
"encoding/json"
"errors"
"sort"
"strconv"
"strings"
"time"
)
const (
scheduleListReadPageSize = 200
scheduleListDefaultLimit = 50
scheduleListMaximumLimit = 100
)
var ErrScheduleListCursor = errors.New("cursor is not one a previous schedule_list result gave")
type ScheduleListInput struct {
Status string `json:"status"`
Limit int `json:"limit"`
Cursor string `json:"cursor"`
}
type ScheduleListOutput struct {
Schedules []ScheduleListItem `json:"schedules"`
TotalCount int `json:"totalCount"`
NextCursor string `json:"nextCursor,omitempty"`
}
type ScheduleListItem struct {
ScheduleID string `json:"scheduleID"`
TaskInstruction string `json:"taskInstruction"`
Description string `json:"description,omitempty"`
Cadence string `json:"cadence"`
CronExpression string `json:"cronExpression,omitempty"`
RunAt *time.Time `json:"runAt,omitempty"`
Status string `json:"status"`
NextRunAt *time.Time `json:"nextRunAt,omitempty"`
LastRunAt *time.Time `json:"lastRunAt,omitempty"`
}
type scheduleListPosition struct {
CreatedAt time.Time `json:"createdAt"`
ScheduleID string `json:"scheduleID"`
}
func ScheduleListQuery(creatorPersonID string, referenceTime time.Time, page int) ScheduleListRequest {
return ScheduleListRequest{
CreatorPersonID: strings.TrimSpace(creatorPersonID),
IncludeExpired: true,
Page: page,
PageSize: scheduleListReadPageSize,
ReferenceTime: referenceTime,
}
}
func ProjectScheduleList(schedules []Schedule, input ScheduleListInput, referenceTime time.Time) (ScheduleListOutput, error) {
after, errorValue := decodedScheduleListPosition(input.Cursor)
if errorValue != nil {
return ScheduleListOutput{}, errorValue
}
matching := schedulesWithStatus(schedules, strings.TrimSpace(input.Status), referenceTime)
sortSchedulesNewestFirst(matching)
remaining := schedulesAfter(matching, after)
limit := normalizedScheduleListLimit(input.Limit)
page := remaining[:min(limit, len(remaining))]
output := ScheduleListOutput{Schedules: []ScheduleListItem{}, TotalCount: len(matching)}
for _, schedule := range page {
output.Schedules = append(output.Schedules, scheduleListItemFromSchedule(schedule, referenceTime))
}
if len(remaining) > len(page) {
output.NextCursor = encodedScheduleListPosition(page[len(page)-1])
}
return output, nil
}
func schedulesWithStatus(schedules []Schedule, status string, referenceTime time.Time) []Schedule {
matching := []Schedule{}
for _, schedule := range schedules {
if status != "" && scheduleStatus(schedule, referenceTime) != status {
continue
}
matching = append(matching, schedule)
}
return matching
}
func sortSchedulesNewestFirst(schedules []Schedule) {
sort.SliceStable(schedules, func(left, right int) bool {
return isScheduleListedBefore(schedules[left], scheduleListPosition{CreatedAt: schedules[right].CreatedAt, ScheduleID: schedules[right].ScheduleID})
})
}
func isScheduleListedBefore(schedule Schedule, position scheduleListPosition) bool {
if !schedule.CreatedAt.Equal(position.CreatedAt) {
return schedule.CreatedAt.After(position.CreatedAt)
}
return schedule.ScheduleID < position.ScheduleID
}
func schedulesAfter(schedules []Schedule, after *scheduleListPosition) []Schedule {
if after == nil {
return schedules
}
for index, schedule := range schedules {
if !isScheduleListedBefore(schedule, *after) && schedule.ScheduleID != after.ScheduleID {
return schedules[index:]
}
}
return []Schedule{}
}
func encodedScheduleListPosition(schedule Schedule) string {
encoded, _ := json.Marshal(scheduleListPosition{CreatedAt: schedule.CreatedAt.UTC(), ScheduleID: schedule.ScheduleID})
return base64.RawURLEncoding.EncodeToString(encoded)
}
func decodedScheduleListPosition(cursor string) (*scheduleListPosition, error) {
trimmedCursor := strings.TrimSpace(cursor)
if trimmedCursor == "" {
return nil, nil
}
decoded, errorValue := base64.RawURLEncoding.DecodeString(trimmedCursor)
if errorValue != nil {
return nil, ErrScheduleListCursor
}
var position scheduleListPosition
if errorValue := json.Unmarshal(decoded, &position); errorValue != nil || position.ScheduleID == "" || position.CreatedAt.IsZero() {
return nil, ErrScheduleListCursor
}
return &position, nil
}
func normalizedScheduleListLimit(limit int) int {
if limit <= 0 {
return scheduleListDefaultLimit
}
return min(limit, scheduleListMaximumLimit)
}
func scheduleListItemFromSchedule(schedule Schedule, referenceTime time.Time) ScheduleListItem {
return ScheduleListItem{
ScheduleID: schedule.ScheduleID,
TaskInstruction: schedule.Prompt,
Description: schedule.Name,
Cadence: scheduleCadence(schedule),
CronExpression: schedule.CronExpression,
RunAt: schedule.RunAt,
Status: scheduleStatus(schedule, referenceTime),
NextRunAt: schedule.NextRunAt,
LastRunAt: schedule.LastRunAt,
}
}
func scheduleCadence(schedule Schedule) string {
switch schedule.Kind {
case ScheduleKindInterval:
return "every " + strconv.Itoa(schedule.IntervalSecond) + " seconds"
case ScheduleKindCron:
return "cron"
default:
return "once"
}
}
func scheduleStatus(schedule Schedule, referenceTime time.Time) string {
if schedule.NextRunAt == nil || schedule.ExpiresAt != nil && !schedule.ExpiresAt.After(referenceTime) {
return "expired"
}
if strings.TrimSpace(schedule.LastError) != "" {
return "failed"
}
return "active"
}