diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml new file mode 100644 index 0000000..edc4fe1 --- /dev/null +++ b/.github/actions/setup-go/action.yml @@ -0,0 +1,23 @@ +name: Setup Go +description: Configure git auth and install Go + +inputs: + gh-token: + description: GitHub token for private module access + required: true + cache: + description: Whether to enable Go module caching + required: false + default: "true" + +runs: + using: composite + steps: + - name: Setup git + shell: bash + run: git config --global url."https://${{ inputs.gh-token }}@github.com/".insteadOf "https://github.com/" + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: ${{ inputs.cache }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..25270ad --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +env: + GOPRIVATE: github.com/teamwork/* + LANG: en_US.UTF-8 + GO_VERSION: "1.26" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-go + with: + gh-token: ${{ secrets.GH_TOKEN }} + cache: "false" + + - uses: golangci/golangci-lint-action@v9 + with: + version: latest + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-go + with: + gh-token: ${{ secrets.GH_TOKEN }} + + - name: Run tests + run: go test -race -count=1 ./... diff --git a/api/api.go b/api/api.go index 7c1b497..de8dfa0 100644 --- a/api/api.go +++ b/api/api.go @@ -32,7 +32,9 @@ func Call[T any, R any, L any](ctx context.Context, service Service[T, R, L], ac log.Fatal(err) } - enc.Encode(item) + if err := enc.Encode(item); err != nil { + log.Fatal(err) + } case "list": items, err := service.List(ctx, nil) @@ -40,7 +42,9 @@ func Call[T any, R any, L any](ctx context.Context, service Service[T, R, L], ac log.Fatal(err) } - enc.Encode(items) + if err := enc.Encode(items); err != nil { + log.Fatal(err) + } case "create": item := createItem() @@ -50,7 +54,9 @@ func Call[T any, R any, L any](ctx context.Context, service Service[T, R, L], ac return } - enc.Encode(created) + if err := enc.Encode(created); err != nil { + log.Fatal(err) + } case "update": if id == 0 { @@ -62,7 +68,9 @@ func Call[T any, R any, L any](ctx context.Context, service Service[T, R, L], ac log.Print(err) return } - enc.Encode(updated) + if err := enc.Encode(updated); err != nil { + log.Fatal(err) + } default: log.Fatalf("Unsupported action: %s", action) diff --git a/client/file.go b/client/file.go index ea1f61c..332642b 100644 --- a/client/file.go +++ b/client/file.go @@ -93,7 +93,9 @@ func (s *FileService) Upload(ctx context.Context, file *models.FileResponse, f [ return fmt.Errorf("copy file data: %w", err) } - writer.Close() + if err := writer.Close(); err != nil { + return fmt.Errorf("close multipart writer: %w", err) + } uploadURL := "" if file.URL != nil { @@ -110,7 +112,7 @@ func (s *FileService) Upload(ctx context.Context, file *models.FileResponse, f [ if err != nil { return err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) diff --git a/client/helpdocarticles.go b/client/helpdocarticles.go index 35a3ce0..953a20d 100644 --- a/client/helpdocarticles.go +++ b/client/helpdocarticles.go @@ -2,20 +2,26 @@ package client import ( "context" + "encoding/json" + "fmt" + "net/http" "net/url" + "github.com/sonh/qs" "github.com/teamwork/desksdkgo/models" ) // HelpDocArticleService handles help doc article-related operations type HelpDocArticleService struct { *Service[models.HelpDocArticleResponse, models.HelpDocArticlesResponse] + client *Client } // NewHelpDocArticleService creates a new help doc article service func NewHelpDocArticleService(client *Client) *HelpDocArticleService { return &HelpDocArticleService{ Service: NewService[models.HelpDocArticleResponse, models.HelpDocArticlesResponse](client, NewDefaultPathHandler("helpdocssites/helpdocarticles")), + client: client, } } @@ -38,3 +44,35 @@ func (s *HelpDocArticleService) Create(ctx context.Context, article *models.Help func (s *HelpDocArticleService) Update(ctx context.Context, id int, article *models.HelpDocArticleResponse) (*models.HelpDocArticleResponse, error) { return s.Service.Update(ctx, id, article) } + +// Search searches for help doc articles based on filter parameters +func (s *HelpDocArticleService) Search(ctx context.Context, filter *models.SearchHelpdocsFilter) (*models.HelpDocArticlesResponse, error) { + encoder := qs.NewEncoder() + values, err := encoder.Values(filter) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, + fmt.Sprintf("%s/search/helpdocs.json?%s", s.client.baseURL, values.Encode()), nil) + if err != nil { + return nil, err + } + + resp, err := s.client.doRequest(ctx, req) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var resources models.HelpDocArticlesResponse + if err := json.NewDecoder(resp.Body).Decode(&resources); err != nil { + return nil, err + } + + return &resources, nil +} diff --git a/client/messages.go b/client/messages.go index 74f80fb..7cb474e 100644 --- a/client/messages.go +++ b/client/messages.go @@ -74,7 +74,7 @@ func (s *MessageService) CreateForTicket(ctx context.Context, ticketID int, mess if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { b, err := io.ReadAll(resp.Body) diff --git a/client/resource.go b/client/resource.go index 044abdf..9aeb0db 100644 --- a/client/resource.go +++ b/client/resource.go @@ -60,7 +60,7 @@ func (s *Service[T, L]) Get(ctx context.Context, id int, params url.Values) (*T, s.logError("request failed", slog.Any("error", err), slog.String("method", http.MethodGet), slog.String("url", req.URL.String())) return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) @@ -100,7 +100,7 @@ func (s *Service[T, L]) List(ctx context.Context, params url.Values) (*L, error) s.logError("request failed", slog.Any("error", err), slog.String("method", http.MethodGet), slog.String("url", req.URL.String())) return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) @@ -146,7 +146,7 @@ func (s *Service[T, L]) Create(ctx context.Context, resource *T) (*T, error) { s.logError("request failed", slog.Any("error", err), slog.String("method", http.MethodPost), slog.String("url", req.URL.String())) return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { b, err := io.ReadAll(resp.Body) @@ -209,7 +209,7 @@ func (s *Service[T, L]) Update(ctx context.Context, id int, resource *T) (*T, er s.logError("request failed", slog.Any("error", err), slog.String("method", method), slog.String("url", req.URL.String())) return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) diff --git a/client/tickets.go b/client/tickets.go index 0f398d6..fcf3d61 100644 --- a/client/tickets.go +++ b/client/tickets.go @@ -55,7 +55,7 @@ func (s *TicketService) Search(ctx context.Context, filter *models.SearchTickets if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) diff --git a/go.mod b/go.mod index 2b08694..0c83631 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/teamwork/desksdkgo -go 1.24.2 +go 1.26 require ( github.com/brianvoe/gofakeit/v7 v7.2.1 diff --git a/main.go b/main.go index 9491dff..b21bf1b 100644 --- a/main.go +++ b/main.go @@ -122,7 +122,9 @@ func generateData( } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") - enc.Encode(resp) + if err := enc.Encode(resp); err != nil { + log.Fatalf("Failed to encode response: %v", err) + } return } api.Call(ctx, c.Tickets, action, id, func() *models.TicketResponse { @@ -328,7 +330,9 @@ func generateData( log.Fatalf("Failed to upload file: %v", err) } - enc.Encode(resp) + if err := enc.Encode(resp); err != nil { + log.Fatalf("Failed to encode response: %v", err) + } case "spamlists": api.Call(ctx, c.Spamlists, action, id, func() *models.SpamlistResponse { resp := &models.SpamlistResponse{Spamlist: models.Spamlist{ diff --git a/models/helpdocarticle.go b/models/helpdocarticle.go index 9f19d1d..5299b2f 100644 --- a/models/helpdocarticle.go +++ b/models/helpdocarticle.go @@ -1,5 +1,15 @@ package models +type SearchHelpdocsFilter struct { + Search string `qs:"search,omitempty"` + SiteID int64 `qs:"siteId,omitempty"` + Status string `qs:"status,omitempty"` + CategoryID int64 `qs:"categoryId,omitempty"` + Page int `qs:"page,omitempty"` + PageSize int `qs:"pageSize,omitempty"` + Includes []string `qs:"includes,omitempty"` +} + type HelpDocArticle struct { BaseEntity Helpdocsite EntityRef `json:"helpdocsite"`