diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b45277b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +--- + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml new file mode 100644 index 0000000..04b52dc --- /dev/null +++ b/.github/linters/.golangci.yml @@ -0,0 +1,85 @@ +--- + +run: + concurrency: 4 + timeout: 240s + issues-exit-code: 2 + tests: false + go: '1.19' + +linters: + # start with everything + enable-all: true + + disable: + # deprecated + - golint + - interfacer + - maligned + - scopelint + - deadcode + - varcheck + - structcheck + + # too annoying + - cyclop + - exhaustive + - exhaustivestruct + - exhaustruct + - forbidigo + - funlen + - gochecknoglobals + - godot + - goerr113 + - gofumpt + - gomnd + - lll + - nakedret + - nestif + - nlreturn + - tagliatelle + - varnamelen + - wsl + - nonamedreturns + + # review later + - gofmt + - goimports + + # complexity linters + - gocognit + - gocyclo + - maintidx + + # i disagree with these + - wrapcheck + - nolintlint + - errorlint + - noctx + - gochecknoinits + + # should not block the build + - prealloc + - godox + - dupl + - goconst + - ifshort + - nilerr + - ireturn + - nosprintfhostport + + # buggy + - gci + + # this breaks with proto generation + - nosnakecase + + # disabled because of generics + - rowserrcheck + - sqlclosecheck + - structcheck + - wastedassign + +linters-settings: + wsl: + allow-cuddle-declarations: true \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..6667c4f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +--- + +name: CodeQL + +on: + push: + branches: main + pull_request: + branches: main + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['go'] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..a951fa8 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,27 @@ +--- + +name: Linters + +on: + push: + branches: main + pull_request: + branches: main + +jobs: + build: + name: Lint Code Base + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Lint Code Base + uses: github/super-linter/slim@v4 + env: + DEFAULT_BRANCH: main + VALIDATE_ALL_CODEBASE: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index 7805bde..fd4ff9f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ -# booklooker -Implementation of the Booklooker.de REST API v2.0 +# 📖 Booklooker + +[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![Linters](https://github.com/Plaenkler/Booklooker/actions/workflows/linters.yml/badge.svg)](https://github.com/Plaenkler/Booklooker/actions/workflows/linters.yml) +[![Support me](https://img.shields.io/badge/Support%20me%20%E2%98%95-orange.svg)](https://www.buymeacoffee.com/Plaenkler) + +Plaenkler/Booklooker enables HTTP requests to be made to the Booklooker API. The library abstracts the constructs and allows developers to interact with the Booklooker API through a convenient and intuitive interface. + + + + + + +
+ +## 🎯 Project goals + +- [x] Implementation of all models and handlers +- [x] Client with automatic token renewal +- [x] Implementation of one example for each handler + +## ✅ Examples + +A separate example has been implemented for each interface of the API. All examples use the client for authentication except the authentication example itself if there is a desire to implement a separate client. + +- [Immediate deletion of a single item](https://github.com/Plaenkler/Booklooker/tree/main/examples/article) +- [Download all active part numbers](https://github.com/Plaenkler/Booklooker/tree/main/examples/article_list) +- [Query the status of an item](https://github.com/Plaenkler/Booklooker/tree/main/examples/article_status) +- [Authentication via API Key](https://github.com/Plaenkler/Booklooker/tree/main/examples/authenticate) +- [Upload quote or image files](https://github.com/Plaenkler/Booklooker/tree/main/examples/file_import) +- [Query the status of an uploaded quote file](https://github.com/Plaenkler/Booklooker/tree/main/examples/file_status) +- [Download of all orders of a time range](https://github.com/Plaenkler/Booklooker/tree/main/examples/order) +- [Canceling a complete order](https://github.com/Plaenkler/Booklooker/tree/main/examples/order_cancel) +- [Canceling the order of a single item](https://github.com/Plaenkler/Booklooker/tree/main/examples/order_item_cancel) +- [Sending a message to the customer](https://github.com/Plaenkler/Booklooker/tree/main/examples/order_message) +- [Setting the status of an order](https://github.com/Plaenkler/Booklooker/tree/main/examples/order_status) diff --git a/api/handler/article.go b/api/handler/article.go deleted file mode 100644 index f154bfe..0000000 --- a/api/handler/article.go +++ /dev/null @@ -1,39 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func DeleteArticle(token string, req models.ArticleRequest) (*models.ArticleResponse, error) { - url := baseURL + models.ArticlePath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - httpReq, err := http.NewRequest("DELETE", url, bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - httpReq.Header.Set("Content-Type", "application/json") - client := &http.Client{} - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var articleResp models.ArticleResponse - err = json.Unmarshal(jsonResp, &articleResp) - if err != nil { - return nil, err - } - return &articleResp, nil -} diff --git a/api/handler/article_list.go b/api/handler/article_list.go deleted file mode 100644 index 2efe391..0000000 --- a/api/handler/article_list.go +++ /dev/null @@ -1,37 +0,0 @@ -package handler - -import ( - "encoding/json" - "io" - "net/http" - "net/url" - "strconv" - - "github.com/plaenkler/booklooker/api/models" -) - -func GetArticleList(token string, req models.ArticleListRequest) (*models.ArticleListResponse, error) { - values := url.Values{} - values.Set("field", req.Field) - values.Set("showPrice", strconv.FormatBool(req.ShowPrice)) - values.Set("showStock", strconv.FormatBool(req.ShowStock)) - values.Set("mediaType", string(req.MediaType)) - - url := baseURL + models.ArticleListPath + "?token=" + token + "&" + values.Encode() - - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var articleListResp models.ArticleListResponse - err = json.Unmarshal(jsonResp, &articleListResp) - if err != nil { - return nil, err - } - return &articleListResp, nil -} diff --git a/api/handler/article_status.go b/api/handler/article_status.go deleted file mode 100644 index 90c7210..0000000 --- a/api/handler/article_status.go +++ /dev/null @@ -1,28 +0,0 @@ -package handler - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func GetArticleStatus(token string, req models.ArticleStatusRequest) (*models.ArticleStatusResponse, error) { - url := baseURL + models.ArticleStatusPath + "?token=" + token + "&orderNo=" + req.OrderNo - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var articleStatusResp models.ArticleStatusResponse - err = json.Unmarshal(jsonResp, &articleStatusResp) - if err != nil { - return nil, err - } - return &articleStatusResp, nil -} diff --git a/api/handler/authenticate.go b/api/handler/authenticate.go deleted file mode 100644 index 3c5a7f3..0000000 --- a/api/handler/authenticate.go +++ /dev/null @@ -1,33 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func Authenticate(req models.AuthenticateRequest) (*models.AuthenticateResponse, error) { - url := baseURL + models.AuthenticatePath - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var authResp models.AuthenticateResponse - err = json.Unmarshal(jsonResp, &authResp) - if err != nil { - return nil, err - } - return &authResp, nil -} diff --git a/api/handler/constants.go b/api/handler/constants.go deleted file mode 100644 index 35b7551..0000000 --- a/api/handler/constants.go +++ /dev/null @@ -1,5 +0,0 @@ -package handler - -const ( - baseURL = "https://api.booklooker.de/2.0/" -) diff --git a/api/handler/file_import.go b/api/handler/file_import.go deleted file mode 100644 index bc21671..0000000 --- a/api/handler/file_import.go +++ /dev/null @@ -1,33 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func ImportFile(token string, req models.FileImportRequest) (*models.FileImportResponse, error) { - url := baseURL + models.FileImportPath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var fileImportResp models.FileImportResponse - err = json.Unmarshal(jsonResp, &fileImportResp) - if err != nil { - return nil, err - } - return &fileImportResp, nil -} diff --git a/api/handler/file_status.go b/api/handler/file_status.go deleted file mode 100644 index 4f37804..0000000 --- a/api/handler/file_status.go +++ /dev/null @@ -1,28 +0,0 @@ -package handler - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func GetFileStatus(token string, req models.FileStatusRequest) (*models.FileStatusResponse, error) { - url := baseURL + models.FileStatusPath + "?token=" + token + "&filename=" + req.Filename - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var fileStatusResp models.FileStatusResponse - err = json.Unmarshal(jsonResp, &fileStatusResp) - if err != nil { - return nil, err - } - return &fileStatusResp, nil -} diff --git a/api/handler/import_status.go b/api/handler/import_status.go deleted file mode 100644 index c82284d..0000000 --- a/api/handler/import_status.go +++ /dev/null @@ -1,28 +0,0 @@ -package handler - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func GetImportStatus(token string) (*models.ImportStatusResponse, error) { - url := baseURL + models.ImportStatusPath + "?token=" + token - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var importStatusResp models.ImportStatusResponse - err = json.Unmarshal(jsonResp, &importStatusResp) - if err != nil { - return nil, err - } - return &importStatusResp, nil -} diff --git a/api/handler/order.go b/api/handler/order.go deleted file mode 100644 index c8c0bb8..0000000 --- a/api/handler/order.go +++ /dev/null @@ -1,40 +0,0 @@ -package handler - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func GetOrder(token string, req models.OrderRequest) (*models.OrderResponse, error) { - url := baseURL + models.OrderPath + "?token=" + token - - query := url + "&orderId=" + req.OrderID - if req.Date != "" { - query = query + "&date=" + req.Date - } - if req.DateFrom != "" { - query = query + "&dateFrom=" + req.DateFrom - } - if req.DateTo != "" { - query = query + "&dateTo=" + req.DateTo - } - - resp, err := http.Get(query) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var orderResp models.OrderResponse - err = json.Unmarshal(jsonResp, &orderResp) - if err != nil { - return nil, err - } - return &orderResp, nil -} diff --git a/api/handler/order_cancel.go b/api/handler/order_cancel.go deleted file mode 100644 index fda8af9..0000000 --- a/api/handler/order_cancel.go +++ /dev/null @@ -1,39 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func CancelOrder(token string, req models.OrderCancelRequest) (*models.OrderCancelResponse, error) { - url := baseURL + models.OrderCancelPath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - httpReq, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - httpReq.Header.Set("Content-Type", "application/json") - client := &http.Client{} - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var orderCancelResp models.OrderCancelResponse - err = json.Unmarshal(jsonResp, &orderCancelResp) - if err != nil { - return nil, err - } - return &orderCancelResp, nil -} diff --git a/api/handler/order_item_cancel.go b/api/handler/order_item_cancel.go deleted file mode 100644 index 3bb5dc2..0000000 --- a/api/handler/order_item_cancel.go +++ /dev/null @@ -1,39 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func PutOrderItemCancel(token string, req *models.OrderItemCancelRequest) (*models.OrderItemCancelResponse, error) { - url := baseURL + models.OrderItemCancelPath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - client := &http.Client{} - httpReq, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - httpReq.Header.Set("Content-Type", "application/json") - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var orderItemCancelResp models.OrderItemCancelResponse - err = json.Unmarshal(jsonResp, &orderItemCancelResp) - if err != nil { - return nil, err - } - return &orderItemCancelResp, nil -} diff --git a/api/handler/order_message.go b/api/handler/order_message.go deleted file mode 100644 index e789fab..0000000 --- a/api/handler/order_message.go +++ /dev/null @@ -1,39 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func PutOrderMessage(token string, req models.OrderMessageRequest) (*models.OrderMessageResponse, error) { - url := baseURL + models.OrderMessagePath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - client := &http.Client{} - httpReq, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - httpReq.Header.Set("Content-Type", "application/json") - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var orderMessageResp models.OrderMessageResponse - err = json.Unmarshal(jsonResp, &orderMessageResp) - if err != nil { - return nil, err - } - return &orderMessageResp, nil -} diff --git a/api/handler/order_status.go b/api/handler/order_status.go deleted file mode 100644 index fa3171d..0000000 --- a/api/handler/order_status.go +++ /dev/null @@ -1,39 +0,0 @@ -package handler - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/plaenkler/booklooker/api/models" -) - -func SetOrderStatus(token string, req models.OrderStatusRequest) (*models.OrderStatusResponse, error) { - url := baseURL + models.OrderStatusPath + "?token=" + token - jsonReq, err := json.Marshal(req) - if err != nil { - return nil, err - } - httpReq, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonReq)) - if err != nil { - return nil, err - } - httpReq.Header.Set("Content-Type", "application/json") - client := &http.Client{} - resp, err := client.Do(httpReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - jsonResp, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - var orderStatusResp models.OrderStatusResponse - err = json.Unmarshal(jsonResp, &orderStatusResp) - if err != nil { - return nil, err - } - return &orderStatusResp, nil -} diff --git a/api/models/article.go b/api/models/article.go deleted file mode 100644 index ca7bb29..0000000 --- a/api/models/article.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -const ArticlePath = "article" - -type ArticleRequest struct { - OrderNo string `json:"orderNo"` -} - -type ArticleResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/article_list.go b/api/models/article_list.go deleted file mode 100644 index afa31ca..0000000 --- a/api/models/article_list.go +++ /dev/null @@ -1,16 +0,0 @@ -package models - -const ArticleListPath = "article_list" - -type ArticleListRequest struct { - Field string `json:"field"` - ShowPrice bool `json:"showPrice"` - ShowStock bool `json:"showStock"` - MediaType byte `json:"mediaType,omitempty"` -} - -type ArticleListResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` - ArticleList []string `json:"value,omitempty"` -} diff --git a/api/models/article_status.go b/api/models/article_status.go deleted file mode 100644 index f0e6d22..0000000 --- a/api/models/article_status.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -const ArticleStatusPath = "article_status" - -type ArticleStatusRequest struct { - OrderNo string `json:"orderNo"` -} - -type ArticleStatusResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/authenticate.go b/api/models/authenticate.go deleted file mode 100644 index 6c5d70b..0000000 --- a/api/models/authenticate.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -// Every other endpoint expects a token (?token=REST_API_TOKEN) obtained by authentication -const AuthenticatePath = "authenticate" - -type AuthenticateRequest struct { - APIKey string `json:"apiKey"` -} - -type AuthenticateResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` - Token string `json:"token,omitempty"` -} diff --git a/api/models/file_import.go b/api/models/file_import.go deleted file mode 100644 index cc80840..0000000 --- a/api/models/file_import.go +++ /dev/null @@ -1,17 +0,0 @@ -package models - -const FileImportPath = "file_import" - -type FileImportRequest struct { - File []byte `json:"file"` - FileType string `json:"fileType,omitempty"` - DataType byte `json:"dataType,omitempty"` - MediaType byte `json:"mediaType,omitempty"` - FormatID int `json:"formatId,omitempty"` - Encoding string `json:"encoding,omitempty"` -} - -type FileImportResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/file_status.go b/api/models/file_status.go deleted file mode 100644 index d75d39d..0000000 --- a/api/models/file_status.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -const FileStatusPath = "file_status" - -type FileStatusRequest struct { - Filename string `json:"filename"` -} - -type FileStatusResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/import_status.go b/api/models/import_status.go deleted file mode 100644 index f51fd88..0000000 --- a/api/models/import_status.go +++ /dev/null @@ -1,8 +0,0 @@ -package models - -const ImportStatusPath = "import_status" - -type ImportStatusResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/order.go b/api/models/order.go deleted file mode 100644 index 757fb36..0000000 --- a/api/models/order.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -const OrderPath = "order" - -type OrderRequest struct { - OrderID string `json:"orderId"` - Date string `json:"date"` - DateFrom string `json:"dateFrom"` - DateTo string `json:"dateTo"` -} - -type OrderResponse struct { - Status string `json:"status"` - // TODO: Add Order struct -} diff --git a/api/models/order_cancel.go b/api/models/order_cancel.go deleted file mode 100644 index adf1a74..0000000 --- a/api/models/order_cancel.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -const OrderCancelPath = "order_cancel" - -type OrderCancelRequest struct { - OrderID string `json:"orderId"` -} - -type OrderCancelResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/order_item_cancel.go b/api/models/order_item_cancel.go deleted file mode 100644 index e1fadd6..0000000 --- a/api/models/order_item_cancel.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -const OrderItemCancelPath = "order_item_cancel" - -type OrderItemCancelRequest struct { - OrderItemId string `json:"orderItemId"` - MediaType byte `json:"mediaType"` -} - -type OrderItemCancelResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/order_message.go b/api/models/order_message.go deleted file mode 100644 index 8ab5302..0000000 --- a/api/models/order_message.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -const OrderMessagePath = "order_message" - -type OrderMessageRequest struct { - OrderID string `json:"orderId"` - MessageType string `json:"messageType"` - AdditionalText string `json:"additionalText,omitempty"` -} - -type OrderMessageResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/api/models/order_status.go b/api/models/order_status.go deleted file mode 100644 index d0cbd98..0000000 --- a/api/models/order_status.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -const OrderStatusPath = "order_status" - -type OrderStatusRequest struct { - OrderID string `json:"orderId"` - Status string `json:"status"` -} - -type OrderStatusResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..854f29e --- /dev/null +++ b/client/client.go @@ -0,0 +1,47 @@ +package client + +import ( + "fmt" + "time" + + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" +) + +type Client struct { + APIKey string + Token model.Token + active bool +} + +func (c *Client) Start() error { + c.active = true + done := make(chan error) + go func() { + for c.active { + req := model.AuthenticateRequest{ + APIKey: c.APIKey, + } + resp, err := handler.Authenticate(req) + if err != nil { + done <- fmt.Errorf("client-error-1: %s", err) + return + } + if resp.Status != "OK" { + done <- fmt.Errorf("client-error-2: %s", resp.ReturnValue) + return + } + c.Token = model.Token{ + Value: resp.ReturnValue, + Expiry: time.Now().Add(10 * time.Minute), + } + done <- nil + time.Sleep(9 * time.Minute) + } + }() + return <-done +} + +func (c *Client) Stop() { + c.active = false +} diff --git a/examples/article/article.go b/examples/article/article.go index 44325ef..1df902b 100644 --- a/examples/article/article.go +++ b/examples/article/article.go @@ -1,37 +1,31 @@ package main import ( - "fmt" "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() - // - req := models.ArticleRequest{ - OrderNo: "123", + // Delete an article + req := model.ArticleRequest{ + OrderNo: "YOUR_ORDER_NUMBER", } - resp, err := handler.DeleteArticle(token, req) + articleResp, err := handler.DeleteArticle(c.Token, req) if err != nil { log.Fatalf("failed to delete article: %v", err) } - fmt.Println("Article Deletion Status:", resp.Status) + log.Println("Status:", articleResp.Status) + log.Println("Return:", articleResp.ReturnValue) } diff --git a/examples/article_list/article_list.go b/examples/article_list/article_list.go index 4d6ce76..493543f 100644 --- a/examples/article_list/article_list.go +++ b/examples/article_list/article_list.go @@ -1,39 +1,34 @@ package main import ( - "fmt" "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() - // - req := models.ArticleListRequest{ - Field: "title", - ShowPrice: true, - ShowStock: false, + // Download all active article numbers + req := model.ArticleListRequest{ + ReturnType: "isbn", // Possible values: orderNo, ISBN or EAN + ShowPrice: true, + ShowStock: true, + MediaType: model.Books, // Possible values: 0: Books, 1: Movies, 2: Music, 3: Audio books, 4: Games or n/a } - resp, err := handler.GetArticleList(token, req) + articleListResp, err := handler.GetArticleList(c.Token, req) if err != nil { log.Fatalf("failed to get article list: %v", err) } - fmt.Println("Article List:", resp.ArticleList) + log.Println("Status:", articleListResp.Status) + log.Println("Return:", articleListResp.ReturnValue) } diff --git a/examples/article_status/article_status.go b/examples/article_status/article_status.go index db89e09..966fded 100644 --- a/examples/article_status/article_status.go +++ b/examples/article_status/article_status.go @@ -1,35 +1,29 @@ package main import ( - "fmt" + "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return + log.Fatalf("failed to start client: %v", err) } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return - } - token := authResp.Token - - // - req := models.ArticleStatusRequest{OrderNo: "order_no_value"} + defer c.Stop() - resp, err := handler.GetArticleStatus(token, req) + // Query the status of an item + req := model.ArticleStatusRequest{OrderNo: "YOUR_ORDER_NUMBER"} + articleStatusResp, err := handler.GetArticleStatus(c.Token, req) if err != nil { - fmt.Println("error:", err) + log.Println("error:", err) return } - fmt.Println("response:", resp) + log.Println("Status:", articleStatusResp.Status) + log.Println("Return:", articleStatusResp.ReturnValue) } diff --git a/examples/authenticate/authenticate.go b/examples/authenticate/authenticate.go index 58eccc0..8ff6e4e 100644 --- a/examples/authenticate/authenticate.go +++ b/examples/authenticate/authenticate.go @@ -3,15 +3,17 @@ package main import ( "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - req := models.AuthenticateRequest{APIKey: "YOUR_API_KEY"} + // If you don't want to use the client, you can authenticate directly + req := model.AuthenticateRequest{APIKey: "YOUR_API_KEY"} authResp, err := handler.Authenticate(req) if err != nil { - log.Fatalf("Error authenticating: %v", err) + log.Fatalf("error authenticating: %v", err) } - log.Println("Authentication response:", authResp) + log.Println("Status:", authResp.Status) + log.Println("Return:", authResp.ReturnValue) } diff --git a/examples/file_import/export.txt b/examples/file_import/export.txt new file mode 100644 index 0000000..51d2097 --- /dev/null +++ b/examples/file_import/export.txt @@ -0,0 +1,2 @@ +Sparten-Nr. Autor Titel Verlag Auflage Jahr Ort Einband Zustand (1-4) Beschreibung Sprache ISBN Seiten Format Bestell-Nr Gewicht in g Ihr Preis in � unbenutzt unbenutzt Cover-URL Stichwort unbegrenzte St�ckzahl? Neuware? Erstausgabe? Signiert? +1348 Trilse-Finkelstein, Jochanan Gelebter Widerspruch. Heinrich Heine Biographie Aufbau-Verlag 1. Aufl. 1997 Berlin gebunden/original Schutzumschlag sehr gut-gut "minimaler Riss oben am Schutzumschlag, sonst sehr gut; vergriffen" deutsch 3351024614 420 S. 8� 1 537 12,00 � nein nein nein nein \ No newline at end of file diff --git a/examples/file_import/file_import.go b/examples/file_import/file_import.go index 63486a4..8af4d3f 100644 --- a/examples/file_import/file_import.go +++ b/examples/file_import/file_import.go @@ -1,44 +1,44 @@ package main import ( - "fmt" + "log" + "os" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() // Import a file - file := []byte("your file content") - fileImportReq := models.FileImportRequest{ - File: file, - FileType: "your file type", - DataType: 1, - FormatID: 123, - Encoding: "your encoding", + file, err := os.Open("export.txt") + if err != nil { + log.Println(err) + return + } + fileImportReq := model.FileImportRequest{ + File: file, // txt or zip file + FileType: "article", // or "pic" for picture(s) + DataType: 1, // 0: Add, change, delete, 1: Replace, 2: Delete + MediaType: model.Books, // Possible values: 0: Books, 1: Movies, 2: Music, 3: Audio books, 4: Games + // FormatID: "BOOKLOOKER_FORMAT_ID", // Possible values not documented (expect n/a) + // Encoding: "YOUR_FILE_ENCODING", // Default ISO8859-1 / Latin1 (n/a), IBMPC/CR (CP437), macintosh (Mac OS Roman), UTF-8 } - fileImportResp, err := handler.ImportFile(token, fileImportReq) + fileImportResp, err := handler.ImportFile(c.Token, fileImportReq) if err != nil { - fmt.Println(err) + log.Println(err) return } - if fileImportResp.Status != "success" { - fmt.Println(fileImportResp.Message) + if fileImportResp.Status != "OK" { + log.Println(fileImportResp.ReturnValue) return } } diff --git a/examples/file_status/file_status.go b/examples/file_status/file_status.go index 040f2db..e6ad249 100644 --- a/examples/file_status/file_status.go +++ b/examples/file_status/file_status.go @@ -1,33 +1,28 @@ package main import ( - "fmt" "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() // Get file status - fileStatusResp, err := handler.GetFileStatus(token, models.FileStatusRequest{Filename: "your_filename"}) + req := model.FileStatusRequest{Filename: "YOUR_FILE_NAME"} + fileStatusResp, err := handler.GetFileStatus(c.Token, req) if err != nil { log.Fatalf("error getting file status: %v", err) } - log.Println("file status:", fileStatusResp.Status) + log.Println("Status:", fileStatusResp.Status) + log.Println("Return:", fileStatusResp.ReturnValue) } diff --git a/examples/order/order.go b/examples/order/order.go index ff82c62..b21d5c9 100644 --- a/examples/order/order.go +++ b/examples/order/order.go @@ -1,41 +1,35 @@ package main import ( - "fmt" + "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return + log.Fatalf("failed to start client: %v", err) } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return - } - token := authResp.Token + defer c.Stop() // Get orders for a specific date or time range - req := models.OrderRequest{ - OrderID: "123", - Date: "2022-12-31", - DateFrom: "", - DateTo: "", + req := model.OrderRequest{ + Date: "2023-02-05", // Will override DateFrom and DateTo + // DateFrom: "2021-01-01", + // DateTo: "2021-01-31", + // OrderID: "123456789", } - orderResp, err := handler.GetOrder(token, req) + orderResp, err := handler.GetOrder(c.Token, req) if err != nil { - fmt.Println("Error getting order:", err) + log.Printf("error getting order: %v", err) return } - - fmt.Println("Order response:", orderResp) + log.Printf("Status: %v", orderResp.Status) + log.Printf("Return: %+v", orderResp.ReturnValue) } diff --git a/examples/order_cancel/order_cancel.go b/examples/order_cancel/order_cancel.go index 8860075..155be7d 100644 --- a/examples/order_cancel/order_cancel.go +++ b/examples/order_cancel/order_cancel.go @@ -1,38 +1,32 @@ package main import ( - "fmt" + "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return + log.Fatalf("failed to start client: %v", err) } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return - } - token := authResp.Token + defer c.Stop() // Cancel an order - req := models.OrderCancelRequest{ + req := model.OrderCancelRequest{ OrderID: "123", } - orderCancelResp, err := handler.CancelOrder(token, req) + orderCancelResp, err := handler.CancelOrder(c.Token, req) if err != nil { - fmt.Println("Error cancelling order:", err) + log.Println("Error cancelling order:", err) return } - - fmt.Println("Order cancel response:", orderCancelResp) + log.Println("Status:", orderCancelResp.Status) + log.Println("Status:", orderCancelResp.ReturnValue) } diff --git a/examples/order_item_cancel/order_item_cancel.go b/examples/order_item_cancel/order_item_cancel.go index 6e48249..60bc9cd 100644 --- a/examples/order_item_cancel/order_item_cancel.go +++ b/examples/order_item_cancel/order_item_cancel.go @@ -1,40 +1,36 @@ package main import ( - "fmt" + "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() // Cancel an order item - req := &models.OrderItemCancelRequest{ - OrderItemId: "order_item_id", - MediaType: 1, + req := model.OrderItemCancelRequest{ + OrderItemID: "123", // Can only contain numbers + MediaType: model.Books, // Possible values: 0: Books, 1: Movies, 2: Music, 3: Audio books, 4: Games } - orderItemCancelResp, err := handler.PutOrderItemCancel(token, req) + orderItemCancelResp, err := handler.PutOrderItemCancel(c.Token, req) if err != nil { - fmt.Println("Error:", err) + log.Println("Error:", err) return } - if orderItemCancelResp.Status != "success" { - fmt.Println(orderItemCancelResp.Message) + if orderItemCancelResp.Status != "OK" { + log.Println(orderItemCancelResp.ReturnValue) return } + log.Println("Status:", orderItemCancelResp.Status) + log.Println("Return:", orderItemCancelResp.ReturnValue) } diff --git a/examples/order_message/order_message.go b/examples/order_message/order_message.go index d1ec463..91e44a3 100644 --- a/examples/order_message/order_message.go +++ b/examples/order_message/order_message.go @@ -1,39 +1,32 @@ package main import ( - "fmt" "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() // Send a message to the customer - req := models.OrderMessageRequest{ - OrderID: "ORDER_ID", - MessageType: "MESSAGE_TYPE", - AdditionalText: "ADDITIONAL_TEXT", + req := model.OrderMessageRequest{ + OrderID: "123", // Can only contain numbers + MessageType: model.ShippingNotice, + AdditionalText: "YOUR_ADDITIONAL_TEXT", } - resp, err := handler.PutOrderMessage(token, req) + orderMessageResp, err := handler.PutOrderMessage(c.Token, req) if err != nil { log.Fatalln(err) } - fmt.Println("Status:", resp.Status) - fmt.Println("Message:", resp.Message) + log.Println("Status:", orderMessageResp.Status) + log.Println("Return:", orderMessageResp.ReturnValue) } diff --git a/examples/order_status/order_status.go b/examples/order_status/order_status.go index fcec09a..a6989b8 100644 --- a/examples/order_status/order_status.go +++ b/examples/order_status/order_status.go @@ -1,37 +1,32 @@ package main import ( - "fmt" + "log" - "github.com/plaenkler/booklooker/api/handler" - "github.com/plaenkler/booklooker/api/models" + "github.com/plaenkler/booklooker/client" + "github.com/plaenkler/booklooker/handler" + "github.com/plaenkler/booklooker/model" ) func main() { - // Authenticate to obtain a token - authReq := models.AuthenticateRequest{ - APIKey: "your_api_key", - } - authResp, err := handler.Authenticate(authReq) + // Create a new client + c := client.Client{APIKey: "YOUR_API_KEY"} + err := c.Start() if err != nil { - fmt.Println(err) - return - } - if authResp.Status != "success" { - fmt.Println(authResp.Message) - return + log.Fatalf("failed to start client: %v", err) } - token := authResp.Token + defer c.Stop() // Set the order status - req := models.OrderStatusRequest{ + req := model.OrderStatusRequest{ OrderID: "123", - Status: "complete", + Status: "CANCELED", // See possible return values in the documentation } - resp, err := handler.SetOrderStatus(token, req) + orderStatusResp, err := handler.SetOrderStatus(c.Token, req) if err != nil { - fmt.Println("Error:", err) + log.Println("Error:", err) return } - fmt.Println("Response:", resp) + log.Println("Status:", orderStatusResp.Status) + log.Println("Return:", orderStatusResp.ReturnValue) } diff --git a/go.mod b/go.mod index 6f7a197..86aae85 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/plaenkler/booklooker -go 1.19 +go 1.20 diff --git a/handler/article.go b/handler/article.go new file mode 100644 index 0000000..f4127d2 --- /dev/null +++ b/handler/article.go @@ -0,0 +1,44 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func DeleteArticle(token model.Token, req model.ArticleRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + if req.OrderNo == "" { + return nil, fmt.Errorf("orderNo is required") + } + url := model.BaseURL + model.ArticlePath + "?token=" + token.Value + "&orderNo=" + req.OrderNo + httpReq, err := http.NewRequest(http.MethodDelete, url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var articleResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &articleResp) + if err != nil { + return nil, err + } + return &articleResp, nil +} diff --git a/handler/article_list.go b/handler/article_list.go new file mode 100644 index 0000000..0aeaee4 --- /dev/null +++ b/handler/article_list.go @@ -0,0 +1,52 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func GetArticleList(token model.Token, req model.ArticleListRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.MediaType != "" { + params.Set("mediaType", string(req.MediaType)) + + // Can only be used if mediaType is present + if req.ReturnType != "" { + params.Set("field", req.ReturnType) + } + } + if req.ShowPrice { + params.Set("showPrice", "1") + } + if req.ShowStock { + params.Set("showStock", "1") + } + url := model.BaseURL + model.ArticleListPath + "?token=" + token.Value + "&" + params.Encode() + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var articleListResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &articleListResp) + if err != nil { + return nil, err + } + return &articleListResp, nil +} diff --git a/handler/article_status.go b/handler/article_status.go new file mode 100644 index 0000000..93cb53e --- /dev/null +++ b/handler/article_status.go @@ -0,0 +1,39 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func GetArticleStatus(token model.Token, req model.ArticleStatusRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + if req.OrderNo == "" { + return nil, fmt.Errorf("orderNo is required") + } + url := model.BaseURL + model.ArticleStatusPath + "?token=" + token.Value + "&orderNo=" + req.OrderNo + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var articleStatusResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &articleStatusResp) + if err != nil { + return nil, err + } + return &articleStatusResp, nil +} diff --git a/handler/authenticate.go b/handler/authenticate.go new file mode 100644 index 0000000..0111804 --- /dev/null +++ b/handler/authenticate.go @@ -0,0 +1,32 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/plaenkler/booklooker/model" +) + +func Authenticate(req model.AuthenticateRequest) (*model.GlobalResponse, error) { + if req.APIKey == "" { + return nil, fmt.Errorf("apiKey is required") + } + url := model.BaseURL + model.AuthenticatePath + "?apiKey=" + req.APIKey + resp, err := http.Post(url, "", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var authResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &authResp) + if err != nil { + return nil, err + } + return &authResp, nil +} diff --git a/handler/file_import.go b/handler/file_import.go new file mode 100644 index 0000000..234303d --- /dev/null +++ b/handler/file_import.go @@ -0,0 +1,74 @@ +package handler + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func ImportFile(token model.Token, req model.FileImportRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.File == nil { + return nil, fmt.Errorf("file is required") + } + if req.FileType != "" { + params.Set("fileType", string(req.FileType)) + } + if req.MediaType != "" { + params.Set("mediaType", string(req.MediaType)) + } + if req.DataType != 1 { + params.Set("dataType", fmt.Sprintf("%d", req.DataType)) + fmt.Println(params) + } + if req.FormatID != "" { + params.Set("formatId", req.FormatID) + } + if req.Encoding != "" { + params.Set("encoding", string(req.Encoding)) + } + url := model.BaseURL + model.FileImportPath + "?token=" + token.Value + "&" + params.Encode() + fmt.Println(url) + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", req.File.Name()) + if err != nil { + return nil, err + } + _, err = io.Copy(part, req.File) + if err != nil { + return nil, err + } + err = writer.Close() + if err != nil { + return nil, err + } + resp, err := http.Post(url, writer.FormDataContentType(), body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var fileImportResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &fileImportResp) + if err != nil { + return nil, err + } + return &fileImportResp, nil +} diff --git a/handler/file_status.go b/handler/file_status.go new file mode 100644 index 0000000..c59ccc3 --- /dev/null +++ b/handler/file_status.go @@ -0,0 +1,39 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func GetFileStatus(token model.Token, req model.FileStatusRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + if req.Filename == "" { + return nil, fmt.Errorf("filename is required") + } + url := model.BaseURL + model.FileStatusPath + "?token=" + token.Value + "&filename=" + req.Filename + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var fileStatusResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &fileStatusResp) + if err != nil { + return nil, err + } + return &fileStatusResp, nil +} diff --git a/handler/import_status.go b/handler/import_status.go new file mode 100644 index 0000000..f4118d4 --- /dev/null +++ b/handler/import_status.go @@ -0,0 +1,36 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func GetImportStatus(token model.Token) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + url := model.BaseURL + model.ImportStatusPath + "?token=" + token.Value + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var importStatusResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &importStatusResp) + if err != nil { + return nil, err + } + return &importStatusResp, nil +} diff --git a/handler/order.go b/handler/order.go new file mode 100644 index 0000000..d90d9b5 --- /dev/null +++ b/handler/order.go @@ -0,0 +1,59 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func GetOrder(token model.Token, req model.OrderRequest) (*model.OrderResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.OrderID != "" { + params.Set("orderID", req.OrderID) + } + if req.Date != "" { + params.Set("date", req.Date) + } + if !params.Has("date") { + if req.DateFrom == "" || req.DateTo == "" { + return nil, fmt.Errorf("time range not set") + } + params.Set("dateFrom", req.DateFrom) + params.Set("dateTo", req.DateTo) + } + url := model.BaseURL + model.OrderPath + "?token=" + token.Value + "&" + params.Encode() + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var statusResp model.ErrorResponse + err = json.Unmarshal(jsonResp, &statusResp) + if err != nil { + return nil, err + } + if statusResp.Status != "OK" { + return nil, fmt.Errorf("NOK %v", statusResp.ReturnValue) + } + var orderResp model.OrderResponse + err = json.Unmarshal(jsonResp, &orderResp) + if err != nil { + return nil, err + } + return &orderResp, nil +} diff --git a/handler/order_cancel.go b/handler/order_cancel.go new file mode 100644 index 0000000..4b76d39 --- /dev/null +++ b/handler/order_cancel.go @@ -0,0 +1,44 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func CancelOrder(token model.Token, req model.OrderCancelRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + if req.OrderID == "" { + return nil, fmt.Errorf("orderID is required") + } + url := model.BaseURL + model.OrderCancelPath + "?token=" + token.Value + "&orderId=" + req.OrderID + httpReq, err := http.NewRequest(http.MethodPut, url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var orderCancelResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &orderCancelResp) + if err != nil { + return nil, err + } + return &orderCancelResp, nil +} diff --git a/handler/order_item_cancel.go b/handler/order_item_cancel.go new file mode 100644 index 0000000..b64bdbf --- /dev/null +++ b/handler/order_item_cancel.go @@ -0,0 +1,51 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func PutOrderItemCancel(token model.Token, req model.OrderItemCancelRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.MediaType == "" { + return nil, fmt.Errorf("mediaType is required") + } + if req.OrderItemID == "" { + return nil, fmt.Errorf("orderItemId is required") + } + params.Set("mediaType", string(req.MediaType)) + params.Set("orderItemId", req.OrderItemID) + url := model.BaseURL + model.OrderItemCancelPath + "?token=" + token.Value + "&" + params.Encode() + httpReq, err := http.NewRequest(http.MethodPut, url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var orderItemCancelResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &orderItemCancelResp) + if err != nil { + return nil, err + } + return &orderItemCancelResp, nil +} diff --git a/handler/order_message.go b/handler/order_message.go new file mode 100644 index 0000000..2876208 --- /dev/null +++ b/handler/order_message.go @@ -0,0 +1,54 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func PutOrderMessage(token model.Token, req model.OrderMessageRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.OrderID == "" { + return nil, fmt.Errorf("orderId is not valid") + } + if req.MessageType == "" { + return nil, fmt.Errorf("messageType is not set") + } + if req.AdditionalText != "" { + params.Set("additionalText", req.AdditionalText) + } + params.Set("orderId", req.OrderID) + params.Set("messageType", string(req.MessageType)) + url := model.BaseURL + model.OrderMessagePath + "?token=" + token.Value + "&" + params.Encode() + httpReq, err := http.NewRequest(http.MethodPut, url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var orderMessageResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &orderMessageResp) + if err != nil { + return nil, err + } + return &orderMessageResp, nil +} diff --git a/handler/order_status.go b/handler/order_status.go new file mode 100644 index 0000000..563a63b --- /dev/null +++ b/handler/order_status.go @@ -0,0 +1,51 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/plaenkler/booklooker/model" +) + +func SetOrderStatus(token model.Token, req model.OrderStatusRequest) (*model.GlobalResponse, error) { + if token.Value == "" { + return nil, fmt.Errorf("token has no value") + } + if token.Expiry.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + params := url.Values{} + if req.OrderID == "" { + return nil, fmt.Errorf("orderId is not set") + } + if req.Status == "" { + return nil, fmt.Errorf("status is not set") + } + params.Set("orderId", req.OrderID) + params.Set("status", req.Status) + url := model.BaseURL + model.OrderStatusPath + "?token=" + token.Value + "&" + params.Encode() + httpReq, err := http.NewRequest(http.MethodPut, url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + resp, err := client.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + jsonResp, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var orderStatusResp model.GlobalResponse + err = json.Unmarshal(jsonResp, &orderStatusResp) + if err != nil { + return nil, err + } + return &orderStatusResp, nil +} diff --git a/model/article.go b/model/article.go new file mode 100644 index 0000000..7a1ffdb --- /dev/null +++ b/model/article.go @@ -0,0 +1,5 @@ +package model + +type ArticleRequest struct { + OrderNo string +} diff --git a/model/article_list.go b/model/article_list.go new file mode 100644 index 0000000..31b78bf --- /dev/null +++ b/model/article_list.go @@ -0,0 +1,8 @@ +package model + +type ArticleListRequest struct { + ReturnType string + ShowPrice bool + ShowStock bool + MediaType MediaType +} diff --git a/model/article_status.go b/model/article_status.go new file mode 100644 index 0000000..1ab2106 --- /dev/null +++ b/model/article_status.go @@ -0,0 +1,5 @@ +package model + +type ArticleStatusRequest struct { + OrderNo string +} diff --git a/model/authenticate.go b/model/authenticate.go new file mode 100644 index 0000000..f886851 --- /dev/null +++ b/model/authenticate.go @@ -0,0 +1,5 @@ +package model + +type AuthenticateRequest struct { + APIKey string +} diff --git a/model/file_import.go b/model/file_import.go new file mode 100644 index 0000000..3e92a21 --- /dev/null +++ b/model/file_import.go @@ -0,0 +1,12 @@ +package model + +import "os" + +type FileImportRequest struct { + File *os.File + FileType FileType + DataType DataType + MediaType MediaType + FormatID string + Encoding Encoding +} diff --git a/model/file_status.go b/model/file_status.go new file mode 100644 index 0000000..46d56f5 --- /dev/null +++ b/model/file_status.go @@ -0,0 +1,5 @@ +package model + +type FileStatusRequest struct { + Filename string +} diff --git a/model/global.go b/model/global.go new file mode 100644 index 0000000..e6b8314 --- /dev/null +++ b/model/global.go @@ -0,0 +1,79 @@ +package model + +import "time" + +const BaseURL = "https://api.booklooker.de/2.0/" + +// Endpoints +const ( + ArticleListPath = "article_list" + ArticleStatusPath = "article_status" + ArticlePath = "article" + AuthenticatePath = "authenticate" + FileImportPath = "file_import" + FileStatusPath = "file_status" + ImportStatusPath = "import_status" + OrderItemCancelPath = "order_item_cancel" + OrderCancelPath = "order_cancel" + OrderMessagePath = "order_message" + OrderStatusPath = "order_status" + OrderPath = "order" +) + +// Encodings +type Encoding string + +const ( + UTF8 Encoding = "UTF-8" + CP437 Encoding = "IBMPC/CR" + MacOS Encoding = "macintosh" +) + +// Data types +type DataType int + +const ( + AddChangeDelete DataType = 0 + Replace DataType = 1 + Delete DataType = 2 +) + +// File types +type FileType string + +const ( + Article FileType = "article" + Picture FileType = "pic" +) + +// Message types +type MessageType string + +const ( + PaymentInformation MessageType = "PAYMENT_INFORMATION" + PaymentReminder MessageType = "PAYMENT_REMINDER" + ShippingNotice MessageType = "SHIPPING_NOTICE" +) + +// Media types +type MediaType string + +const ( + Books MediaType = "0" + Movies MediaType = "1" + Music MediaType = "2" + AudioBooks MediaType = "3" + Games MediaType = "4" +) + +// Tokens have a lifetime of 10 minutes +type Token struct { + Value string `json:"token"` + Expiry time.Time +} + +// Implemented by most endpoints +type GlobalResponse struct { + Status string `json:"status"` + ReturnValue string `json:"returnValue,omitempty"` +} diff --git a/model/import_status.go b/model/import_status.go new file mode 100644 index 0000000..8c22989 --- /dev/null +++ b/model/import_status.go @@ -0,0 +1,3 @@ +package model + +// Does not need any parameters currently diff --git a/model/order.go b/model/order.go new file mode 100644 index 0000000..b0f7e78 --- /dev/null +++ b/model/order.go @@ -0,0 +1,59 @@ +package model + +type OrderRequest struct { + OrderID string + Date string + DateFrom string + DateTo string +} + +type Order struct { + OrderID int `json:"orderId"` + OrderDate string `json:"orderDate"` + OrderTime string `json:"orderTime"` + Status string `json:"status"` + Email string `json:"email"` + CalculatedShippingCost string `json:"calculatedShippingCost"` + PaymentID int `json:"paymentId"` + BuyerPositiveRatingPercentage string `json:"buyerPositiveRatingPercentage"` + BuyerTotalRatingNumber int `json:"buyerTotalRatingNumber"` + BuyerUsername string `json:"buyerUsername"` + OriginalProvisionNet string `json:"originalProvisionNet"` + CurrentProvisionNet string `json:"currentProvisionNet"` + InvoiceAddress OrderInvoiceAddress `json:"invoiceAddress"` + OrderItems []OrderItem `json:"orderItems"` + PaymentConfirmed string `json:"paymentConfirmed,omitempty"` + TransactionID string `json:"transactionId,omitempty"` +} + +type OrderInvoiceAddress struct { + Title string `json:"title"` + Name string `json:"name"` + FirstName string `json:"firstName"` + Street string `json:"street"` + Zip string `json:"zip"` + City string `json:"city"` + Country string `json:"country"` +} + +type OrderItem struct { + OrderItemID int `json:"orderItemId"` + Amount int `json:"amount"` + OrderNo string `json:"orderNo"` + OrderTitle string `json:"orderTitle"` + Author string `json:"author"` + SinglePrice string `json:"singlePrice"` + TotalPriceRebated string `json:"totalPriceRebated"` + MediaType int `json:"mediaType"` + Status string `json:"status"` +} + +type ErrorResponse struct { + Status string `json:"status"` + ReturnValue interface{} `json:"returnValue"` +} + +type OrderResponse struct { + Status string `json:"status"` + ReturnValue []Order `json:"returnValue"` +} diff --git a/model/order_cancel.go b/model/order_cancel.go new file mode 100644 index 0000000..1d9c082 --- /dev/null +++ b/model/order_cancel.go @@ -0,0 +1,5 @@ +package model + +type OrderCancelRequest struct { + OrderID string +} diff --git a/model/order_item_cancel.go b/model/order_item_cancel.go new file mode 100644 index 0000000..96dd4a2 --- /dev/null +++ b/model/order_item_cancel.go @@ -0,0 +1,6 @@ +package model + +type OrderItemCancelRequest struct { + OrderItemID string + MediaType MediaType +} diff --git a/model/order_message.go b/model/order_message.go new file mode 100644 index 0000000..8e0a387 --- /dev/null +++ b/model/order_message.go @@ -0,0 +1,7 @@ +package model + +type OrderMessageRequest struct { + OrderID string + MessageType MessageType + AdditionalText string +} diff --git a/model/order_status.go b/model/order_status.go new file mode 100644 index 0000000..d863f36 --- /dev/null +++ b/model/order_status.go @@ -0,0 +1,6 @@ +package model + +type OrderStatusRequest struct { + OrderID string + Status string +}