SimpleCloudNotifier/scnserver/db/cursortoken/token.go

146 lines
2.8 KiB
Go

package cursortoken
import (
"encoding/base32"
"encoding/json"
"errors"
"strings"
"time"
)
type Mode string //@enum:type
const (
CTMStart = "START"
CTMNormal = "NORMAL"
CTMEnd = "END"
)
type CursorToken struct {
Mode Mode
Timestamp int64
Id string
Direction string
FilterHash string
}
type cursorTokenSerialize struct {
Timestamp *int64 `json:"ts,omitempty"`
Id *string `json:"id,omitempty"`
Direction *string `json:"dir,omitempty"`
FilterHash *string `json:"f,omitempty"`
}
func Start() CursorToken {
return CursorToken{
Mode: CTMStart,
Timestamp: 0,
Id: "",
Direction: "",
FilterHash: "",
}
}
func End() CursorToken {
return CursorToken{
Mode: CTMEnd,
Timestamp: 0,
Id: "",
Direction: "",
FilterHash: "",
}
}
func Normal(ts time.Time, id string, dir string, filter string) CursorToken {
return CursorToken{
Mode: CTMNormal,
Timestamp: ts.UnixMilli(),
Id: id,
Direction: dir,
FilterHash: filter,
}
}
func (c *CursorToken) Token() string {
if c.Mode == CTMStart {
return "@start"
}
if c.Mode == CTMEnd {
return "@end"
}
// We kinda manually implement omitempty for the CursorToken here
// because omitempty does not work for time.Time and otherwise we would always
// get weird time values when decoding a token that initially didn't have an Timestamp set
// For this usecase we treat Unix=0 as an empty timestamp
sertok := cursorTokenSerialize{}
if c.Id != "" {
sertok.Id = &c.Id
}
if c.Timestamp != 0 {
sertok.Timestamp = &c.Timestamp
}
if c.Direction != "" {
sertok.Direction = &c.Direction
}
if c.FilterHash != "" {
sertok.FilterHash = &c.FilterHash
}
body, err := json.Marshal(sertok)
if err != nil {
panic(err)
}
return "tok_" + base32.StdEncoding.EncodeToString(body)
}
func Decode(tok string) (CursorToken, error) {
if tok == "" {
return Start(), nil
}
if strings.ToLower(tok) == "@start" {
return Start(), nil
}
if strings.ToLower(tok) == "@end" {
return End(), nil
}
if !strings.HasPrefix(tok, "tok_") {
return CursorToken{}, errors.New("could not decode token, missing prefix")
}
body, err := base32.StdEncoding.DecodeString(tok[len("tok_"):])
if err != nil {
return CursorToken{}, err
}
var tokenDeserialize cursorTokenSerialize
err = json.Unmarshal(body, &tokenDeserialize)
if err != nil {
return CursorToken{}, err
}
token := CursorToken{Mode: CTMNormal}
if tokenDeserialize.Timestamp != nil {
token.Timestamp = *tokenDeserialize.Timestamp
}
if tokenDeserialize.Id != nil {
token.Id = *tokenDeserialize.Id
}
if tokenDeserialize.Direction != nil {
token.Direction = *tokenDeserialize.Direction
}
if tokenDeserialize.FilterHash != nil {
token.FilterHash = *tokenDeserialize.FilterHash
}
return token, nil
}