|  |  | @@ -0,0 +1,205 @@ | 
    
    |  |  | package pocketbase | 
    
    |  |  | 
 | 
    
    |  |  | import ( | 
    
    |  |  | "errors" | 
    
    |  |  | "fmt" | 
    
    |  |  | "time" | 
    
    |  |  | 
 | 
    
    |  |  | "github.com/duke-git/lancet/v2/convertor" | 
    
    |  |  | "github.com/go-resty/resty/v2" | 
    
    |  |  | "golang.org/x/sync/singleflight" | 
    
    |  |  | ) | 
    
    |  |  | 
 | 
    
    |  |  | var ErrInvalidResponse = errors.New("invalid response") | 
    
    |  |  | 
 | 
    
    |  |  | type Client struct { | 
    
    |  |  | client      *resty.Client | 
    
    |  |  | email       string | 
    
    |  |  | password    string | 
    
    |  |  | url         string | 
    
    |  |  | tokenValid  time.Time | 
    
    |  |  | tokenSingle singleflight.Group | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | type ( | 
    
    |  |  | authResponse struct { | 
    
    |  |  | Token string `json:"token"` | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | Params struct { | 
    
    |  |  | Page    int | 
    
    |  |  | Size    int | 
    
    |  |  | Filters string | 
    
    |  |  | Sort    string | 
    
    |  |  | } | 
    
    |  |  | ) | 
    
    |  |  | 
 | 
    
    |  |  | func NewClient(url, email, password string) *Client { | 
    
    |  |  | client := resty.New() | 
    
    |  |  | client. | 
    
    |  |  | // SetDebug(true). | 
    
    |  |  | SetRetryCount(3). | 
    
    |  |  | SetRetryWaitTime(3 * time.Second). | 
    
    |  |  | SetRetryMaxWaitTime(10 * time.Second) | 
    
    |  |  | 
 | 
    
    |  |  | return &Client{ | 
    
    |  |  | client:      client, | 
    
    |  |  | url:         url, | 
    
    |  |  | email:       email, | 
    
    |  |  | password:    password, | 
    
    |  |  | tokenSingle: singleflight.Group{}, | 
    
    |  |  | } | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | func (c *Client) Update(collection string, id string, body any) error { | 
    
    |  |  | if err := c.auth(); err != nil { | 
    
    |  |  | return err | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | request := c.client.R(). | 
    
    |  |  | SetHeader("Content-Type", "application/json"). | 
    
    |  |  | SetPathParam("collection", collection). | 
    
    |  |  | SetBody(body) | 
    
    |  |  | 
 | 
    
    |  |  | resp, err := request.Patch(c.url + "/api/collections/{collection}/records/" + id) | 
    
    |  |  | if err != nil { | 
    
    |  |  | return fmt.Errorf("[update] can't send update request to pocketbase, err %w", err) | 
    
    |  |  | } | 
    
    |  |  | if resp.IsError() { | 
    
    |  |  | return fmt.Errorf("[update] pocketbase returned status: %d, msg: %s, err %w", | 
    
    |  |  | resp.StatusCode(), | 
    
    |  |  | resp.String(), | 
    
    |  |  | ErrInvalidResponse, | 
    
    |  |  | ) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | return nil | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | func (c *Client) Create(collection string, body any) error { | 
    
    |  |  | if err := c.auth(); err != nil { | 
    
    |  |  | return err | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | request := c.client.R(). | 
    
    |  |  | SetHeader("Content-Type", "application/json"). | 
    
    |  |  | SetPathParam("collection", collection). | 
    
    |  |  | SetBody(body) | 
    
    |  |  | 
 | 
    
    |  |  | resp, err := request.Post(c.url + "/api/collections/{collection}/records") | 
    
    |  |  | if err != nil { | 
    
    |  |  | return fmt.Errorf("[create] can't send update request to pocketbase, err %w", err) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | if resp.IsError() { | 
    
    |  |  | return fmt.Errorf("[create] pocketbase returned status: %d, msg: %s, body: %s, err %w", | 
    
    |  |  | resp.StatusCode(), | 
    
    |  |  | resp.String(), | 
    
    |  |  | fmt.Sprintf("%+v", body), // TODO remove that after debugging | 
    
    |  |  | ErrInvalidResponse, | 
    
    |  |  | ) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | return nil | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | func (c *Client) Delete(collection string, id string) error { | 
    
    |  |  | if err := c.auth(); err != nil { | 
    
    |  |  | return err | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | request := c.client.R(). | 
    
    |  |  | SetHeader("Content-Type", "application/json"). | 
    
    |  |  | SetPathParam("collection", collection). | 
    
    |  |  | SetPathParam("id", id) | 
    
    |  |  | 
 | 
    
    |  |  | resp, err := request.Delete(c.url + "/api/collections/{collection}/records/{id}") | 
    
    |  |  | if err != nil { | 
    
    |  |  | return fmt.Errorf("[delete] can't send update request to pocketbase, err %w", err) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | if resp.IsError() { | 
    
    |  |  | return fmt.Errorf("[delete] pocketbase returned status: %d, msg: %s, err %w", | 
    
    |  |  | resp.StatusCode(), | 
    
    |  |  | resp.String(), | 
    
    |  |  | ErrInvalidResponse, | 
    
    |  |  | ) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | return nil | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | func (c *Client) List(collection string, params Params) ([]byte, error) { | 
    
    |  |  | if err := c.auth(); err != nil { | 
    
    |  |  | return []byte{}, err | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | request := c.client.R(). | 
    
    |  |  | SetHeader("Content-Type", "application/json"). | 
    
    |  |  | SetPathParam("collection", collection) | 
    
    |  |  | 
 | 
    
    |  |  | if params.Page > 0 { | 
    
    |  |  | request.SetQueryParam("page", convertor.ToString(params.Page)) | 
    
    |  |  | } | 
    
    |  |  | if params.Size > 0 { | 
    
    |  |  | request.SetQueryParam("perPage", convertor.ToString(params.Size)) | 
    
    |  |  | } | 
    
    |  |  | if params.Filters != "" { | 
    
    |  |  | request.SetQueryParam("filter", params.Filters) | 
    
    |  |  | } | 
    
    |  |  | if params.Sort != "" { | 
    
    |  |  | request.SetQueryParam("sort", params.Sort) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | resp, err := request.Get(c.url + "/api/collections/{collection}/records") | 
    
    |  |  | if err != nil { | 
    
    |  |  | return []byte{}, fmt.Errorf("[list] can't send update request to pocketbase, err %w", err) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | if resp.IsError() { | 
    
    |  |  | return []byte{}, fmt.Errorf("[list] pocketbase returned status: %d, msg: %s, err %w", | 
    
    |  |  | resp.StatusCode(), | 
    
    |  |  | resp.String(), | 
    
    |  |  | ErrInvalidResponse, | 
    
    |  |  | ) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | return resp.Body(), nil | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | func (c *Client) auth() error { | 
    
    |  |  | _, err, _ := c.tokenSingle.Do("auth", func() (interface{}, error) { | 
    
    |  |  | if time.Now().Before(c.tokenValid) { | 
    
    |  |  | return nil, nil | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | resp, err := c.client.R(). | 
    
    |  |  | SetHeader("Content-Type", "application/json"). | 
    
    |  |  | SetBody(map[string]interface{}{ | 
    
    |  |  | "email":    c.email, | 
    
    |  |  | "password": c.password, | 
    
    |  |  | }). | 
    
    |  |  | SetResult(&authResponse{}). | 
    
    |  |  | SetHeader("Authorization", ""). | 
    
    |  |  | Post(c.url + "/api/admins/auth-via-email") | 
    
    |  |  | 
 | 
    
    |  |  | if err != nil { | 
    
    |  |  | return nil, fmt.Errorf("[auth] can't send request to pocketbase %w", err) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | if resp.IsError() { | 
    
    |  |  | return nil, fmt.Errorf("[auth] pocketbase returned status: %d, msg: %s, err %w", | 
    
    |  |  | resp.StatusCode(), | 
    
    |  |  | resp.String(), | 
    
    |  |  | ErrInvalidResponse, | 
    
    |  |  | ) | 
    
    |  |  | } | 
    
    |  |  | 
 | 
    
    |  |  | auth := *resp.Result().(*authResponse) | 
    
    |  |  | c.client.SetHeader("Authorization", "Admin "+auth.Token) | 
    
    |  |  | c.tokenValid = time.Now().Add(60 * time.Minute) | 
    
    |  |  | 
 | 
    
    |  |  | return nil, nil | 
    
    |  |  | }) | 
    
    |  |  | return err | 
    
    |  |  | } |