package client import ( "bytes" "context" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "time" "git.t-juice.club/torjus/gpaste/api" "git.t-juice.club/torjus/gpaste/files" "github.com/google/uuid" "github.com/kirsle/configdir" ) const defaultTimeout = 10 * time.Second type Client struct { BaseURL string `json:"baseUrl"` AuthToken string `json:"authToken"` httpClient http.Client } func (c *Client) WriteConfigToWriter(w io.Writer) error { encoder := json.NewEncoder(w) return encoder.Encode(c) } func (c *Client) WriteConfig() error { dir := configdir.LocalConfig("gpaste") // Ensure dir exists err := os.MkdirAll(dir, os.ModePerm) if err != nil { return err } path := filepath.Join(dir, "client.json") f, err := os.Create(path) if err != nil { return err } defer f.Close() return c.WriteConfigToWriter(f) } func (c *Client) LoadConfig() error { dir := configdir.LocalCache("gpaste") path := filepath.Join(dir, "client.json") f, err := os.Open(path) if err != nil { return err } defer f.Close() return c.LoadConfigFromReader(f) } func (c *Client) LoadConfigFromReader(r io.Reader) error { decoder := json.NewDecoder(r) return decoder.Decode(c) } func (c *Client) Login(ctx context.Context, username, password string) error { url := fmt.Sprintf("%s/api/login", c.BaseURL) // TODO: Change timeout ctx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() body := new(bytes.Buffer) requestData := api.RequestAPILogin{ Username: username, Password: password, } encoder := json.NewEncoder(body) if err := encoder.Encode(&requestData); err != nil { return fmt.Errorf("error encoding response: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) if err != nil { return fmt.Errorf("error creating request: %w", err) } resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("unable to perform request: %s", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("got non-ok response from server: %s", resp.Status) } var responseData api.ResponseAPILogin decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(&responseData); err != nil { return fmt.Errorf("unable to parse response: %s", err) } c.AuthToken = responseData.Token return nil } func (c *Client) UserCreate(ctx context.Context, username, password string) error { url := fmt.Sprintf("%s/api/user", c.BaseURL) body := new(bytes.Buffer) requestData := &api.RequestAPIUserCreate{ Username: username, Password: password, } encoder := json.NewEncoder(body) if err := encoder.Encode(requestData); err != nil { return fmt.Errorf("error encoding response: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) if err != nil { return fmt.Errorf("error creating request: %w", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("unable to perform request: %s", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusAccepted { return fmt.Errorf("got non-ok response from server: %s", resp.Status) } return nil } func (c *Client) Download(ctx context.Context, id string) (io.ReadCloser, error) { url := fmt.Sprintf("%s/api/file/%s", c.BaseURL, id) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("unable to perform request: %s", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("got non-ok response from server: %s", resp.Status) } return resp.Body, nil } func (c *Client) Upload(ctx context.Context, files ...*files.File) (*api.ResponseAPIFilePost, error) { url := fmt.Sprintf("%s/api/file", c.BaseURL) client := &http.Client{} // TODO: Change timeout // TODO: Improve buffering buf := &bytes.Buffer{} mw := multipart.NewWriter(buf) for _, file := range files { fw, err := mw.CreateFormFile(uuid.Must(uuid.NewRandom()).String(), file.OriginalFilename) if err != nil { return nil, err } if _, err := io.Copy(fw, file.Body); err != nil { return nil, err } file.Body.Close() } mw.Close() req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) if err != nil { return nil, err } req.Header.Add("Content-Type", mw.FormDataContentType()) resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var expectedResp *api.ResponseAPIFilePost decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(&expectedResp); err != nil { return nil, fmt.Errorf("error decoding response: %w", err) } return expectedResp, nil } func (c *Client) Delete(ctx context.Context, id string) error { url := fmt.Sprintf("%s/api/file/%s", c.BaseURL, id) req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil) if err != nil { return fmt.Errorf("error creating request: %w", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("unable to perform request: %s", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("got non-ok response from server: %s", resp.Status) } return nil }