More API refactoring and working on cleaning up
Signed-off-by: Kris Nóva <kris@nivenly.com>main
parent
e4323b6047
commit
3b41c9dd5f
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -16,69 +17,93 @@ const (
|
||||||
|
|
||||||
type V1Client struct {
|
type V1Client struct {
|
||||||
token string
|
token string
|
||||||
connectionString string
|
apihost *url.URL
|
||||||
client http.Client
|
client http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(connection, token string) *V1Client {
|
// New will only accept a url.URL so that we know
|
||||||
c := http.Client{}
|
// all errors have been handled up until this point
|
||||||
|
func New(connURL *url.URL, token string) *V1Client {
|
||||||
return &V1Client{
|
return &V1Client{
|
||||||
client: c,
|
client: http.Client{},
|
||||||
connectionString: connection,
|
apihost: connURL,
|
||||||
token: token,
|
token: token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v1 *V1Client) SetConnectionString(connection string) {
|
type V1Response struct {
|
||||||
v1.connectionString = connection
|
HTTPResponse *http.Response
|
||||||
|
StatusCode int
|
||||||
|
Error error
|
||||||
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v1 *V1Client) SetToken(token string) {
|
func (r *V1Response) JSON(i interface{}) error {
|
||||||
v1.token = token
|
if r.Error != nil {
|
||||||
|
// Handle errors from the HTTP request first
|
||||||
|
return fmt.Errorf("during HTTP request: %v", r.Error)
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(r.Body, &i)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("during JSON unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET is the V1 GET function. By design it will check globally for all non 200
|
// GET is the V1 GET function. By design it will check globally for all non 200
|
||||||
// responses and return an error if a non 200 is encountered.
|
// responses and return an error if a non 200 is encountered.
|
||||||
func (v1 *V1Client) GET(format string, a ...interface{}) (*http.Response, error) {
|
func (v1 *V1Client) GET(format string, a ...interface{}) *V1Response {
|
||||||
str := fmt.Sprintf(format, a...)
|
url := v1.Endpoint(fmt.Sprintf(format, a...))
|
||||||
//logger.Debug("GET [%s]", str)
|
//logger.Debug("GET [%s]", url)
|
||||||
url := v1.EndpointStr(str)
|
response := &V1Response{}
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
req, err := http.NewRequest("GET", url, buffer)
|
req, err := http.NewRequest("GET", url, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to generate new request: %v", err)
|
response.StatusCode = -1
|
||||||
|
response.Error = fmt.Errorf("unable to create new GET request: %v", err)
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", DefaultContentType)
|
req.Header.Set("Content-Type", DefaultContentType)
|
||||||
req.Header.Set("X-Session-Id", v1.token)
|
req.Header.Set("X-Session-Id", v1.token)
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := v1.client.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
response.Error = fmt.Errorf("error while executing GET request: %v", err)
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
response.StatusCode = resp.StatusCode
|
||||||
|
response.HTTPResponse = resp
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, fmt.Errorf("[%d]: unable to read body: %v", err)
|
response.Error = fmt.Errorf("unable to read body: %v", err)
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
return resp, fmt.Errorf("[%d]: %s", resp.StatusCode, body)
|
response.Body = body
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
response.Error = fmt.Errorf("[%d]: %s", resp.StatusCode, body)
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
return resp, nil
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v1 *V1Client) EndpointStr(str string) string {
|
// Endpoint supports "/api/v1" and "api/v1" like strings
|
||||||
if strings.HasPrefix("/", str) {
|
// to generate the string type of a given endpoint based on
|
||||||
str = fmt.Sprintf("%s%s", v1.connectionString, str)
|
// a client
|
||||||
|
//
|
||||||
|
// v1client := New("http://localhost:8080", "secret-token")
|
||||||
|
// v1client.EndpointStr("/api/v1/photos") http://localhost:8080/api/v1/photos/
|
||||||
|
// v1client.EndpointStr("api/v1/photos") http://localhost:8080/api/v1/photos/
|
||||||
|
func (v1 *V1Client) Endpoint(str string) string {
|
||||||
|
var joined string
|
||||||
|
if strings.HasPrefix(str, "/") {
|
||||||
|
joined = fmt.Sprintf("%s%s", v1.apihost.String(), str)
|
||||||
} else {
|
} else {
|
||||||
str = fmt.Sprintf("%s/%s", v1.connectionString, str)
|
joined = fmt.Sprintf("%s/%s", v1.apihost.String(), str)
|
||||||
}
|
}
|
||||||
return str
|
return joined
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v1 *V1Client) EndpointURL(str string) (*url.URL, error) {
|
// SetToken can be used to set an auth token to use as the X-Session-Id
|
||||||
if strings.HasPrefix("/", str) {
|
// for this client
|
||||||
str = fmt.Sprintf("%s%s", v1.connectionString, str)
|
func (v1 *V1Client) SetToken(token string) {
|
||||||
} else {
|
v1.token = token
|
||||||
str = fmt.Sprintf("%s/%s", v1.connectionString, str)
|
|
||||||
}
|
|
||||||
return url.Parse(str)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -74,26 +72,11 @@ type Photo struct {
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// uuid: string PhotoUID as returned by the API
|
// uuid: string PhotoUID as returned by the API
|
||||||
func (v1 *V1Client) GetPhoto(uuid string) (*Photo, error) {
|
func (v1 *V1Client) GetPhoto(uuid string) (*Photo, error) {
|
||||||
if uuid == "" {
|
object := Photo{
|
||||||
return nil, fmt.Errorf("missing uuid for GetPhoto [GET /api/v1/photos/:uuid]")
|
|
||||||
}
|
|
||||||
resp, err := v1.GET("api/v1/photos/%s", uuid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to get photo uuid=%s with error: %v", uuid, err)
|
|
||||||
}
|
|
||||||
photo := Photo{
|
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
}
|
}
|
||||||
bytes, err := ioutil.ReadAll(resp.Body)
|
err := v1.GET("/api/v1/photos/%s", uuid).JSON(&object)
|
||||||
if err != nil {
|
return &object, err
|
||||||
return nil, fmt.Errorf("unable to parse body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bytes, &photo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to JSON unmarshal response body: %v", err)
|
|
||||||
}
|
|
||||||
return &photo, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /api/v1/photos/:uid
|
// PUT /api/v1/photos/:uid
|
||||||
|
|
102
client.go
102
client.go
|
@ -6,72 +6,75 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kris-nova/client-go/api/v1"
|
v1 "github.com/kris-nova/client-go/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultContentType is the content type header the API expects
|
// DefaultContentType is the content type header the API expects
|
||||||
DefaultContentType string = "application/json; charset=utf-8"
|
APIContentType string = "application/json; charset=utf-8"
|
||||||
|
|
||||||
// Default Host Configuration
|
// Default Host Configuration
|
||||||
DefaultHost string = "localhost"
|
APIAuthHeaderKey string = "X-Session-Id"
|
||||||
DefaultLoopback string = "127.0.0.1"
|
|
||||||
DefaultPort string = "8080"
|
|
||||||
DefaultConnectionString string = "http://localhost:8080"
|
|
||||||
DefaultTokenKey string = "X-Session-Id"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// New is used to create a new Client to authenticate with
|
||||||
|
// Photoprism.
|
||||||
func New(connectionString string) *Client {
|
func New(connectionString string) *Client {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
contentType: DefaultContentType,
|
contentType: APIContentType,
|
||||||
connectionString: DefaultConnectionString,
|
connectionString: connectionString,
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client represents a client to a Photoprism application
|
// Client represents a client to a Photoprism application
|
||||||
type Client struct {
|
type Client struct {
|
||||||
v1client *api.V1Client
|
v1client *v1.V1Client
|
||||||
authenticator ClientAuthenticator
|
authenticator ClientAuthenticator
|
||||||
contentType string
|
contentType string
|
||||||
connectionString string
|
connectionString string
|
||||||
|
connectionURL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientAuthenticator is used to store the secret
|
// ClientAuthenticator is used to store the secret
|
||||||
// data for authenticating with the Photoprism API
|
// data for authenticating with the Photoprism API
|
||||||
//
|
|
||||||
// TODO @kris-nova obfuscate the data, and make immutable and unexported fields
|
|
||||||
type ClientAuthenticator interface {
|
type ClientAuthenticator interface {
|
||||||
getKey() string
|
getKey() string
|
||||||
getSecret() string
|
getSecret() string
|
||||||
JSON() ([]byte, error)
|
JSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- [ ClientAuthLogin ] --
|
// ClientAuthLogin holds secret login information
|
||||||
|
|
||||||
type ClientAuthLogin struct {
|
type ClientAuthLogin struct {
|
||||||
// Request
|
authPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
type authPayload struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|
||||||
// Response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientAuthLogin is used to build a new login struct
|
// NewClientAuthLogin is used to build a new login struct
|
||||||
func NewClientAuthLogin(user, pass string) ClientAuthenticator {
|
func NewClientAuthLogin(user, pass string) ClientAuthenticator {
|
||||||
return &ClientAuthLogin{
|
return &ClientAuthLogin{
|
||||||
|
authPayload: authPayload{
|
||||||
Username: user,
|
Username: user,
|
||||||
Password: pass,
|
Password: pass,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getKey is used internally to get the key with any modifiers
|
||||||
func (c *ClientAuthLogin) getKey() string {
|
func (c *ClientAuthLogin) getKey() string {
|
||||||
return c.Username
|
return c.authPayload.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getKey is used internally to get the secret with any modifiers
|
||||||
func (c *ClientAuthLogin) getSecret() string {
|
func (c *ClientAuthLogin) getSecret() string {
|
||||||
return c.Password
|
return c.authPayload.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON is used to marshal the fields to JSON
|
// JSON is used to marshal the fields to JSON
|
||||||
|
@ -80,13 +83,14 @@ func (c *ClientAuthLogin) JSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// V1 is used to access the V1 version of the Photoprism API
|
// V1 is used to access the V1 version of the Photoprism API
|
||||||
func (c *Client) V1() *api.V1Client {
|
func (c *Client) V1() *v1.V1Client {
|
||||||
return c.v1client
|
return c.v1client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login is used to attempt to authenticate with the Photoprism API
|
// Login is used to attempt to authenticate with the Photoprism API
|
||||||
func (c *Client) Auth(auth ClientAuthenticator) error {
|
func (c *Client) Auth(auth ClientAuthenticator) error {
|
||||||
c.authenticator = auth
|
c.authenticator = auth
|
||||||
|
|
||||||
// @kris-nova We are returning V1 by default
|
// @kris-nova We are returning V1 by default
|
||||||
return c.LoginV1()
|
return c.LoginV1()
|
||||||
}
|
}
|
||||||
|
@ -95,6 +99,15 @@ func (c *Client) Auth(auth ClientAuthenticator) error {
|
||||||
//
|
//
|
||||||
// Data: {username: "admin", password: "missy"}
|
// Data: {username: "admin", password: "missy"}
|
||||||
func (c *Client) LoginV1() error {
|
func (c *Client) LoginV1() error {
|
||||||
|
// Auth wil also validate the connection string
|
||||||
|
// We do this here so that New() will never return
|
||||||
|
// an error.
|
||||||
|
url, err := url.Parse(c.connectionString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse connection string url [%s]: %v", c.connectionString, err)
|
||||||
|
}
|
||||||
|
c.connectionURL = url
|
||||||
|
|
||||||
body, err := c.authenticator.JSON()
|
body, err := c.authenticator.JSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("JSON marshal error: %v", err)
|
return fmt.Errorf("JSON marshal error: %v", err)
|
||||||
|
@ -111,11 +124,11 @@ func (c *Client) LoginV1() error {
|
||||||
}
|
}
|
||||||
return fmt.Errorf("login error [%d] %s", resp.StatusCode, body)
|
return fmt.Errorf("login error [%d] %s", resp.StatusCode, body)
|
||||||
}
|
}
|
||||||
token := resp.Header.Get(DefaultTokenKey)
|
token := resp.Header.Get(APIAuthHeaderKey)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return fmt.Errorf("missing auth token from successful login")
|
return fmt.Errorf("missing auth token from successful login")
|
||||||
}
|
}
|
||||||
c.v1client = api.New(c.connectionString, token)
|
c.v1client = v1.New(c.connectionURL, token)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,45 +136,10 @@ func (c *Client) LoginV1() error {
|
||||||
// based on the API version and Host/Port
|
// based on the API version and Host/Port
|
||||||
func (c *Client) Endpoint(str string) string {
|
func (c *Client) Endpoint(str string) string {
|
||||||
if strings.HasPrefix("/", str) {
|
if strings.HasPrefix("/", str) {
|
||||||
return fmt.Sprintf("%s%s", c.connectionString, str)
|
str = fmt.Sprintf("%s%s", c.connectionString, str)
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprintf("%s/%s", c.connectionString, str)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s/%s", c.connectionString, str)
|
//logger.Debug(str)
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
// Dump from Chrome/Network
|
|
||||||
|
|
||||||
// [REQUEST]
|
|
||||||
//Request URL: http://localhost:8080/api/v1/session
|
|
||||||
//Request Method: POST
|
|
||||||
//Status Code: 200 OK
|
|
||||||
//Remote Address: 127.0.0.1:8080
|
|
||||||
//Referrer Policy: strict-origin-when-cross-origin
|
|
||||||
|
|
||||||
// [RESPONSE HEADERS]
|
|
||||||
//Content-Type: application/json; charset=utf-8
|
|
||||||
//Date: Thu, 04 Feb 2021 03:27:03 GMT
|
|
||||||
//Transfer-Encoding: chunked
|
|
||||||
//X-Session-Id: d92837cb1c41e37b9993d25e282efb3b337b6ae609a687d9
|
|
||||||
|
|
||||||
// [REQUEST HEADERS]
|
|
||||||
//Accept: application/json, text/plain, */*
|
|
||||||
//Accept-Encoding: gzip, deflate, br
|
|
||||||
//Accept-Language: en-US,en;q=0.9
|
|
||||||
//Connection: keep-alive
|
|
||||||
//Content-Length: 39
|
|
||||||
//Content-Type: application/json;charset=UTF-8
|
|
||||||
//Host: localhost:8080
|
|
||||||
//Origin: http://localhost:8080
|
|
||||||
//Referer: http://localhost:8080/login
|
|
||||||
//Sec-Fetch-Dest: empty
|
|
||||||
//Sec-Fetch-Mode: cors
|
|
||||||
//Sec-Fetch-Site: same-origin
|
|
||||||
//User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36
|
|
||||||
//X-Client-Hash: 2607a5a5
|
|
||||||
//X-Client-Version: 210121-07e559df-Linux-x86_64
|
|
||||||
|
|
||||||
// [POST DATA]
|
|
||||||
//{username: "admin", password: "missy"}
|
|
||||||
//password: "missy"
|
|
||||||
//username: "admin"
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
token="c558cccdd25917056e8b7b72a2a3e5f40215d707a6fac1aa"
|
|
||||||
server="localhost"
|
|
||||||
port="8080"
|
|
||||||
|
|
||||||
function photoget() {
|
|
||||||
url="http://${server}:${port}/${1}"
|
|
||||||
curl --header "X-Session-Id: ${token}" --header "Content-Type: application/json" ${url}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
token=""
|
|
||||||
server="localhost"
|
|
||||||
port="8080"
|
|
||||||
|
|
||||||
function photoget() {
|
|
||||||
url="http://${server}:${port}/${1}"
|
|
||||||
curl --header "X-Session-Id: ${token}" --header "Content-Type: application/json" ${url} | jq
|
|
||||||
}
|
|
|
@ -7,9 +7,8 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger.Level = 4
|
logger.Level = 4
|
||||||
creds := photoprism.NewClientAuthLogin("admin", "missy")
|
client := photoprism.New("http://localhost:8080")
|
||||||
client := photoprism.New("localhost:8080")
|
err := client.Auth(photoprism.NewClientAuthLogin("admin", "missy"))
|
||||||
err := client.Auth(creds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
halt(4, "Error logging into API: %v", err)
|
halt(4, "Error logging into API: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,8 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
logger.Level = 4
|
logger.Level = 4
|
||||||
uuid := "pqnzigq351j2fqgn" // This is a known ID
|
uuid := "pqnzigq351j2fqgn" // This is a known ID
|
||||||
creds := photoprism.NewClientAuthLogin("admin", "missy")
|
client := photoprism.New("http://localhost:8080")
|
||||||
client := photoprism.New("localhost:8080")
|
err := client.Auth(photoprism.NewClientAuthLogin("admin", "missy"))
|
||||||
err := client.Auth(creds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
halt(4, "Error logging into API: %v", err)
|
halt(4, "Error logging into API: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,29 @@
|
||||||
{
|
{
|
||||||
|
"029567a062c5b7e34cac36f0f7da66a7bfa8ee23fb0a9456": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613511488829185806
|
||||||
|
},
|
||||||
|
"032e5cd79a2c2e2622b27f167a98a9c92e44fc15c1aefb54": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613514526199122543
|
||||||
|
},
|
||||||
|
"0bf52bb31c11c5ca6c56646496b184eb39f33b004a46b203": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613511715216255731
|
||||||
|
},
|
||||||
"0da5bca9e57bbaa0d197ecac76a90a7b15e47a1ec93fec7c": {
|
"0da5bca9e57bbaa0d197ecac76a90a7b15e47a1ec93fec7c": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613501612939596202
|
"expiration": 1613501612939596202
|
||||||
},
|
},
|
||||||
|
"0f58fcf81fdef7038db70a777b7eef0eee21ec715fdd3eb2": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613511268639958073
|
||||||
|
},
|
||||||
"12b8b7ca5ebe52b071203520353652f21fda9f6e732966ba": {
|
"12b8b7ca5ebe52b071203520353652f21fda9f6e732966ba": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
|
@ -14,6 +34,21 @@
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502823940492608
|
"expiration": 1613502823940492608
|
||||||
},
|
},
|
||||||
|
"2521ed30985fb1991e3d1271cbf6e2b53840d614647361af": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513903738209742
|
||||||
|
},
|
||||||
|
"27ecafdd4819a88cc523aa95a0698d353ceb85ac6371cc4e": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512061416097907
|
||||||
|
},
|
||||||
|
"2c1925b037218048cae22344ef1073b4bfd4f47a8106ac87": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613515347303158151
|
||||||
|
},
|
||||||
"2cf0991e18a63f088b01b452ea985dbe91612fe0e26f6d84": {
|
"2cf0991e18a63f088b01b452ea985dbe91612fe0e26f6d84": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
|
@ -29,11 +64,36 @@
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613500755747387098
|
"expiration": 1613500755747387098
|
||||||
},
|
},
|
||||||
|
"3bd19dc60e5a515d2624f0d712db8471f433416935c2a045": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512395822771302
|
||||||
|
},
|
||||||
|
"41a99f15500d1eca9818bc3e0cae7cca319cd6eb2b38bb8d": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512433065437655
|
||||||
|
},
|
||||||
|
"4344f5002d9ed91b1f75c3d6c82055e9893270ab1c95dd01": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513662219179033
|
||||||
|
},
|
||||||
"43f0cfcca96a3671d42c3dad5a2feda055c03e95dc4b0ce3": {
|
"43f0cfcca96a3671d42c3dad5a2feda055c03e95dc4b0ce3": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502911243093934
|
"expiration": 1613502911243093934
|
||||||
},
|
},
|
||||||
|
"469a249abd7cf70eae9e39abd41c7a311b40d8c1e31f0199": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613509579377741749
|
||||||
|
},
|
||||||
|
"5225f5600acaefec701d3ab1f3cfe2cf4b10ad025f2bf58e": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512362197250367
|
||||||
|
},
|
||||||
"589279988dbd4ad774ef7a59392f6bc44f416ff8d34e653e": {
|
"589279988dbd4ad774ef7a59392f6bc44f416ff8d34e653e": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
|
@ -44,11 +104,31 @@
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502564441892628
|
"expiration": 1613502564441892628
|
||||||
},
|
},
|
||||||
|
"6196c51bd2db97cc8aeaaac53bef1c6400c50774fedc97b5": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512018478271866
|
||||||
|
},
|
||||||
"6fd28f3f6bff7f9542bd20cea0b358cc835cb655370aa961": {
|
"6fd28f3f6bff7f9542bd20cea0b358cc835cb655370aa961": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502943460686773
|
"expiration": 1613502943460686773
|
||||||
},
|
},
|
||||||
|
"7d381eaaea551483d8d50aa39007d7fc9fdddd3b6a06359d": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513949594487106
|
||||||
|
},
|
||||||
|
"86e47c8475bbab147dc714ca6ae10b2ea64471d20a74e248": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613510910693289281
|
||||||
|
},
|
||||||
|
"8dc3258f4ba5b40648c2917da1a711581f388d88fddfc46c": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513997238837821
|
||||||
|
},
|
||||||
"910c4f6a3943e402e338511b235f43f8728d6f7e18b2c1b3": {
|
"910c4f6a3943e402e338511b235f43f8728d6f7e18b2c1b3": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
|
@ -69,11 +149,21 @@
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502624809588007
|
"expiration": 1613502624809588007
|
||||||
},
|
},
|
||||||
|
"a03f2d8d33fbb447c1c0573735d93632dc2b81e44923f4c1": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513937752119685
|
||||||
|
},
|
||||||
"a55edeeb0ebaeba3f5b9bedf877dc02a71b79743337998ea": {
|
"a55edeeb0ebaeba3f5b9bedf877dc02a71b79743337998ea": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502517447074204
|
"expiration": 1613502517447074204
|
||||||
},
|
},
|
||||||
|
"aa9951b3b6533deadac0376a66a51e63ff6b9c306c82f4c8": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613511978587026969
|
||||||
|
},
|
||||||
"b12bcc8e03f51a00b17584596a213e41f3cddb5b4b14a29a": {
|
"b12bcc8e03f51a00b17584596a213e41f3cddb5b4b14a29a": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
|
@ -94,14 +184,44 @@
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502032233257787
|
"expiration": 1613502032233257787
|
||||||
},
|
},
|
||||||
|
"ca96e91eb4b29df4126290911525516ff521e1dae19b6544": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613513847101926534
|
||||||
|
},
|
||||||
"d86c17fdb63eb3ef0665cb91fe3b29e8d823dd04eed50bac": {
|
"d86c17fdb63eb3ef0665cb91fe3b29e8d823dd04eed50bac": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613502706269686566
|
"expiration": 1613502706269686566
|
||||||
},
|
},
|
||||||
|
"d8c6abdcb1675cedac502ffecd8b3a1d38d62aaf07d000cd": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613514267386702538
|
||||||
|
},
|
||||||
"d92837cb1c41e37b9993d25e282efb3b337b6ae609a687d9": {
|
"d92837cb1c41e37b9993d25e282efb3b337b6ae609a687d9": {
|
||||||
"user": "uqnzie01i1nypnt9",
|
"user": "uqnzie01i1nypnt9",
|
||||||
"tokens": null,
|
"tokens": null,
|
||||||
"expiration": 1613014023691734304
|
"expiration": 1613014023691734304
|
||||||
|
},
|
||||||
|
"dcd50767e8b3450a095c535963f521b3b52457d4bdcdb7c6": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613514516062495823
|
||||||
|
},
|
||||||
|
"e32e2c906864ae8cd98a474a918c9e1c9cbe64b1acf3e975": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613511459136720858
|
||||||
|
},
|
||||||
|
"ee605667573202ffa29dc053d78ace87675a52ef168d1c91": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613512474632135880
|
||||||
|
},
|
||||||
|
"f5dd3851137b73d1e039ffa521d0c02e60504aa1f972fe13": {
|
||||||
|
"user": "uqnzie01i1nypnt9",
|
||||||
|
"tokens": null,
|
||||||
|
"expiration": 1613515512912618531
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
Loading…
Reference in New Issue