From 74a8835910095111edc55ff14597025565ced314 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:38:53 +0200 Subject: [PATCH 01/37] chore: add pr test workflow --- .github/workflows/pr.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..c1e08c1 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,37 @@ +name: checks + +on: + push: + branches: [ main ] + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - uses: gotesttools/gotestfmt-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: golangci/golangci-lint-action@v4 + with: + version: latest + + - name: Run tests + run: make test + + - uses: coverallsapp/github-action@v2 + with: + file: coverage.out From 424528213a625cba28545fc83c797fb7b2b36920 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:04 +0200 Subject: [PATCH 02/37] chore: add dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c9d1dc6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: '/' + schedule: + interval: weekly + - package-ecosystem: gomod + directory: '/' + schedule: + interval: weekly From 6b293a746d70908fbb795be72a6dd8d7ea61443c Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:22 +0200 Subject: [PATCH 03/37] chore: add editorconfig --- .editorconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2ed5dbb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +indent_size = 2 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[*.{yaml,yml,md,hcl}] +indent_style = space From 698bb8e1b9417e623c2659578e37ecfadb7ab7e5 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:36 +0200 Subject: [PATCH 04/37] chore: update workflows --- .github/workflows/release.yml | 37 ----------------------------------- .github/workflows/tag.yml | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tag.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 8ec9c6f..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: goreleaser - -on: - push: - tags: - - v*.*.* - -jobs: - goreleaser: - runs-on: ubuntu-latest - env: - GOFLAGS: -mod=readonly - GOPROXY: https://proxy.golang.org - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Unshallow - run: git fetch --prune --unshallow - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - # TODO enable - #- name: golangci-lint - # uses: golangci/golangci-lint-action@v2 - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }} diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..06276db --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,37 @@ +name: Release Tag + +on: + push: + tags: + - v*.*.* + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Unshallow + run: git fetch --prune --unshallow + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - id: app_token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.TOKEN_APP_ID }} + private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} + + - uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ steps.app_token.outputs.token }} From b02cb9b11cd98bb9ebfbbb1021237075719c64af Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:41 +0200 Subject: [PATCH 05/37] chore: update gitignore --- .gitignore | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 07ef42d..7bdcf68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,12 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE +.* +*.log *.out - -bin -.history* -*.idea -/.vscode/* -/untracked_*.go -cover.html -cover.out \ No newline at end of file +!.github/ +!.husky/ +!.editorconfig +!.gitignore +!.golangci.yml +!.goreleaser.yml +!.husky.yaml +/bin/ +/tmp/ From dee9178cc46245df235a4fde23053257e3325b82 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:47 +0200 Subject: [PATCH 06/37] chore: update goreleaser --- .goreleaser.yml | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index b3b3d5e..74de6ec 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,12 +1,13 @@ -# .goreleaser.yml -# Build customization +project_name: gocontentful + +release: + github: + owner: foomo + name: gocontentful + prerelease: auto + builds: - binary: gocontentful - main: ./main.go - env: - - CGO_ENABLED=0 - ldflags: - - -s -w -X main.VERSION={{.Version}} goos: - windows - darwin @@ -14,22 +15,34 @@ builds: goarch: - amd64 - arm64 - ignore: - - goos: windows - goarch: arm64 + goarm: + - '7' + env: + - CGO_ENABLED=0 + main: ./main.go + flags: + - -trimpath + ldflags: + - -s -w -X main.VERSION={{.Version}} -# .goreleaser.yml archives: - format: tar.gz format_overrides: - goos: windows format: zip + files: + - LICENSE + - README.md + +changelog: + use: github-native brews: - # Reporitory to push the tap to. - - tap: + - repository: owner: foomo - name: homebrew-gocontentful + name: homebrew-tap caveats: "gocontentful -h" homepage: "https://github.com/foomo/gocontentful" description: "An Contentful Entry-Reference Mapper for Go" + test: | + system "#{bin}/gocontentful --version" From 30d92d9fd1b8329b1c8622146df3ac54dd692c66 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:39:58 +0200 Subject: [PATCH 07/37] chore: rename license --- LICENSE.txt => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE.txt => LICENSE (100%) diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE From fffaf1cf718bdf9108592c35dcccdcd999fe9bbc Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 12:41:10 +0200 Subject: [PATCH 08/37] chore: update makefile --- Makefile | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 1d14f94..69676f2 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,59 @@ .DEFAULT_GOAL:=help +-include .makerc + +# --- Targets ----------------------------------------------------------------- + +# This allows us to accept extra arguments +%: + @: ## === Tasks === +.PHONY: doc +## Run tests +doc: + @open "http://localhost:6060/pkg/github.com/foomo/contentful/" + @godoc -http=localhost:6060 -play + ## Install binary install: - go build -o ${GOPATH}/bin/gocontenful main.go + @go build -o ${GOPATH}/bin/gocontenful main.go ## Build binary build: - mkdir -p bin - go build -o bin/gocontenful main.go + @mkdir -p bin + @go build -o bin/gocontenful main.go .PHONY: test ## Run tests test: - go run ./main.go -exportfile ./test/test-space-export.json ./test/testapi - go test -count=1 ./... - -race: - go run ./main.go -exportfile ./test/test-space-export.json ./test/testapi - go test -race -count=1 ./... + @go run ./main.go -exportfile ./test/test-space-export.json ./test/testapi + @go test -p 1 -coverprofile=coverage.out -race -json ./... | gotestfmt -cover: - rm cover.out cover.html - go run ./main.go -exportfile ./test/test-space-export.json ./test/testapi - go test -cover -coverprofile cover.out -coverpkg=./test/testapi ./... - go tool cover -html=cover.out -o cover.html; open cover.html +## Test & view coverage +cover: test + @go tool cover -html=coverage.out -o coverage.html; open coverage.html .PHONY: lint ## Run linter lint: - golangci-lint run + @golangci-lint run .PHONY: lint.fix ## Fix lint violations lint.fix: - golangci-lint run --fix + @golangci-lint run --fix + +.PHONY: tidy +## Run go mod tidy +tidy: + @go mod tidy +.PHONY: outdated +## Show outdated direct dependencies +outdated: + @go list -u -m -json all | go-mod-outdated -update -direct ## === Utils === From 8f8d9202c5c8f8823f0e9f6c24542f6429367c73 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:20:54 +0200 Subject: [PATCH 09/37] chore: update golangci lint settings --- .golangci.yml | 302 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 227 insertions(+), 75 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 84c2500..dcfca7c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,85 +1,237 @@ run: timeout: 5m +issues: + exclude-dirs: + - 'bin' + - 'tmp' + - 'vendor' + exclude-rules: + - path: _test\.go + linters: + - forcetypeassert + - gocheckcompilerdirectives + linters-settings: - gci: - local-prefixes: github.com/foomo/gocontentful - golint: - min-confidence: 0 - goimports: - local-prefixes: github.com/foomo/gocontentful + # https://golangci-lint.run/usage/linters/#misspell + misspell: + mode: restricted + # https://golangci-lint.run/usage/linters/#asasalint + asasalint: + ignore-test: true + # https://golangci-lint.run/usage/linters/#exhaustive + exhaustive: + default-signifies-exhaustive: true + # https://golangci-lint.run/usage/linters/#gocritic gocritic: - enabled-tags: - - diagnostic - - style - disabled-tags: - - performance - - experimental - - opinionated - lll: - line-length: 150 + disabled-checks: + - captLocal + - assignOp + - ifElseChain + - singleCaseSwitch + # https://golangci-lint.run/usage/linters/#testifylint + testifylint: + disable: + - float-compare + # https://golangci-lint.run/usage/linters/#gosec + gosec: + confidence: medium + excludes: + - G306 + # https://golangci-lint.run/usage/linters/#revive + revive: + enable-all-rules: true + ignore-generated-header: true + rules: + - name: line-length-limit + disabled: true + #- name: if-return + # disabled: true + - name: bare-return + disabled: true + - name: deep-exit + disabled: true + - name: cognitive-complexity + disabled: true + - name: unused-parameter + disabled: true + - name: add-constant + disabled: true + - name: indent-error-flow + disabled: true + - name: cyclomatic + disabled: true + - name: function-length + disabled: true + - name: early-return + disabled: true + #- name: nested-structs + # disabled: true + #- name: var-naming + # disabled: true + - name: use-any + disabled: true + - name: max-public-structs + disabled: true + #- name: function-result-limit + # disabled: true + - name: flag-parameter + disabled: true + - name: unused-receiver + disabled: true + - name: argument-limit + disabled: true + - name: empty-lines + disabled: true + - name: confusing-naming + disabled: true + #- name: import-alias-naming + # disabled: true + - name: empty-block + disabled: true + #- name: import-shadowing + # disabled: true + - name: unhandled-error + arguments: + - "fmt.Printf" + - "fmt.Println" + #- name: max-control-nesting + # disabled: true + - name: exported + disabled: true + - name: unchecked-type-assertion + disabled: true + - name: unnecessary-stmt + disabled: true + #- name: get-return + # disabled: true + #- name: context-keys-type + # disabled: true + #- name: comment-spacings + # disabled: true + #- name: struct-tag + # disabled: true + #- name: confusing-results + # disabled: true + - name: superfluous-else + disabled: true + - name: unexported-return + disabled: true + #- name: error-return + # disabled: true + #- name: redefines-builtin-id + # disabled: true + - name: unexported-naming + disabled: true linters: disable-all: true enable: - - bodyclose - - deadcode - - dogsled - - dupl - - exhaustive - - exportloopref - - gci - - goconst - - gofmt - - gofumpt - - goimports - - revive - - goprintffuncname - - govet - - ineffassign - - misspell - - nakedret - - noctx - - nolintlint - - prealloc - - rowserrcheck - - sqlclosecheck - - staticcheck - - structcheck - - stylecheck - - typecheck - - unconvert - - unparam - - unused - - varcheck - - whitespace - - errcheck - - gocritic - - gosimple - - - gocyclo - - gosec - - lll - - exportloopref - - # unused - # - godot - # - gocognit - # - nlreturn - # - gochecknoglobals - # - gochecknoinits - # - depguard - # - goheader - # - gomodguard + # Enabled by default linters: + - errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] + - gosimple # (megacheck) Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false] + - govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false] + - ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] + - staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false] + - unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] - # don't enable: - # - asciicheck - # - funlen - # - godox - # - goerr113 - # - gomnd - # - interfacer - # - maligned - # - nestif - # - testpackage - # - wsl + # Disabled by your configuration linters: + - asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false] + - asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false] + - bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] + - bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] + #- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false] + - contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false] + #- copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false] + #- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false] + - decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false] + #- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] + #- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] + #- dupl # Tool for code clone detection [fast: true, auto-fix: false] + #- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false] + - durationcheck # check for two durations multiplied together [fast: false, auto-fix: false] + - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false] + #- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] + #- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] + - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds [fast: false, auto-fix: false] + #- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] + #- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false] + - exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false] + #- forbidigo # Forbids identifiers [fast: false, auto-fix: false] + #- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false] + #- funlen # Tool for detection of long functions [fast: true, auto-fix: false] + #- gci # Gci controls Go package import order and makes it always deterministic. [fast: true, auto-fix: true] + #- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false] + - gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false] + #- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false] + #- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] + - gochecksumtype # Run exhaustiveness checks on Go "sum types" [fast: false, auto-fix: false] + #- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] + #- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] + - gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: true] + #- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] + #- godot # Check if comments end in a period [fast: true, auto-fix: true] + #- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] + #- goerr113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false] + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] + #- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] + - goheader # Checks is file header matches to pattern [fast: true, auto-fix: true] + - goimports # Check import statements are formatted according to the 'goimport' command. Reformat imports in autofix mode. [fast: true, auto-fix: true] + #- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] + - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false] + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false] + - goprintffuncname # Checks that printf-like functions are named with `f` at the end. [fast: true, auto-fix: false] + - gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false] + #- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false] + - grouper # Analyze expression groups. [fast: true, auto-fix: false] + - importas # Enforces consistent import aliases [fast: false, auto-fix: false] + #- inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false] + #- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false] + #- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false] + #- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false] + #- lll # Reports long lines [fast: true, auto-fix: false] + #- loggercheck # (logrlint) Checks key value pairs for common logger libraries (kitlog,klog,logr,zap). [fast: false, auto-fix: false] + #- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false] + - makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false] + - misspell # Finds commonly misspelled English words [fast: true, auto-fix: true] + - mirror # reports wrong mirror patterns of bytes/strings usage [fast: false, auto-fix: true] + - musttag # enforce field tags in (un)marshaled structs [fast: false, auto-fix: false] + #- nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). [fast: true, auto-fix: false] + #- nestif # Reports deeply nested if statements [fast: true, auto-fix: false] + - nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false] + - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] + #- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false] + - noctx # Finds sending http request without context.Context [fast: false, auto-fix: false] + - nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: true] + #- nonamedreturns # Reports all named returns [fast: false, auto-fix: false] + - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false] + #- paralleltest # Detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false] + #- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false] + #- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false] + - predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false] + - promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false] + #- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true] + - reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false] + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] + #- rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false] + #- sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false] + #- spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false] + #- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. [fast: false, auto-fix: false] + - stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false] + #- tagalign # check that struct tags are well aligned [fast: true, auto-fix: true] + #- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false] + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false] + #- testableexamples # linter checks if examples are testable (have an expected output) [fast: true, auto-fix: false] + #- testifylint # Checks usage of github.com/stretchr/testify. [fast: false, auto-fix: false] + #- testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false] + - thelper # thelper detects tests helpers which is not start with t.Helper() method. [fast: false, auto-fix: false] + #- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false] + - unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false] + #- unparam # Reports unused function parameters [fast: false, auto-fix: false] + - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false] + #- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false] + - wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false] + #- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true] + #- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false] + #- wsl # add or remove empty lines [fast: true, auto-fix: false] + #- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false] From 81670afe027ea100676c651a713425f749a5e7fc Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:21:16 +0200 Subject: [PATCH 10/37] feat: bump foomo/contentful-v0.5.0 --- Makefile | 11 ++- config/config_test.go | 2 +- erm/space.go | 33 ++++---- erm/template.go | 6 +- erm/templates/contentful_vo_lib.gotmpl | 49 ++++++------ .../contentful_vo_lib_contenttype.gotmpl | 78 +++++++++---------- erm/util.go | 11 +-- erm/vo.go | 4 +- go.mod | 28 ++++--- go.sum | 66 ++++++---------- main.go | 3 +- test/cache_test.go | 10 +-- test/concurrency_test.go | 54 ++++++------- test/other_test.go | 9 ++- test/richtext_test.go | 3 +- test/testapi/gocontentfulvolib.go | 43 +++++----- test/testapi/gocontentfulvolibbrand.go | 50 ++++++------ test/testapi/gocontentfulvolibcategory.go | 50 ++++++------ test/testapi/gocontentfulvolibproduct.go | 70 ++++++++--------- 19 files changed, 290 insertions(+), 290 deletions(-) diff --git a/Makefile b/Makefile index 69676f2..32c31d5 100644 --- a/Makefile +++ b/Makefile @@ -15,21 +15,26 @@ doc: @open "http://localhost:6060/pkg/github.com/foomo/contentful/" @godoc -http=localhost:6060 -play +.PHONY: install ## Install binary install: @go build -o ${GOPATH}/bin/gocontenful main.go +.PHONY: build ## Build binary build: @mkdir -p bin @go build -o bin/gocontenful main.go .PHONY: test +## Run tests +test: testapi + @go test -p 1 -coverprofile=coverage.out -race -json ./... | gotestfmt +.PHONY: test ## Run tests -test: +testapi: @go run ./main.go -exportfile ./test/test-space-export.json ./test/testapi - @go test -p 1 -coverprofile=coverage.out -race -json ./... | gotestfmt ## Test & view coverage cover: test @@ -37,7 +42,7 @@ cover: test .PHONY: lint ## Run linter -lint: +lint: testapi @golangci-lint run .PHONY: lint.fix diff --git a/config/config_test.go b/config/config_test.go index 6603340..cd4c636 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -12,5 +12,5 @@ func TestConfigFromYAML(t *testing.T) { require.Equal(t, "abc123", config.SpaceID) require.Equal(t, "dev", config.Environment) require.Equal(t, "v1.0.19", config.RequireVersion) - require.Equal(t, 2, len(config.ContentTypes)) + require.Len(t, config.ContentTypes, 2) } diff --git a/erm/space.go b/erm/space.go index f7124a9..face218 100644 --- a/erm/space.go +++ b/erm/space.go @@ -2,9 +2,9 @@ package erm import ( "bytes" + "context" "encoding/json" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -26,15 +26,17 @@ type spaceConf struct { } // GetLocales retrieves locale definition from Contentful -func getLocales(CMA *contentful.Contentful, spaceID string) (locales []Locale, err error) { - - col, err := CMA.Locales.List(spaceID).GetAll() +func getLocales(ctx context.Context, CMA *contentful.Contentful, spaceID string) (locales []Locale, err error) { + col, err := CMA.Locales.List(ctx, spaceID).GetAll() if err != nil { log.Fatal("Couldn't get locales") } for _, item := range col.Items { var locale Locale - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + break + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&locale) if err != nil { break @@ -45,16 +47,19 @@ func getLocales(CMA *contentful.Contentful, spaceID string) (locales []Locale, e } // GetContentTypes retrieves content type definition from Contentful -func getContentTypes(CMA *contentful.Contentful, spaceID string) (contentTypes []ContentType, err error) { +func getContentTypes(ctx context.Context, CMA *contentful.Contentful, spaceID string) (contentTypes []ContentType, err error) { - col := CMA.ContentTypes.List(spaceID) + col := CMA.ContentTypes.List(ctx, spaceID) _, err = col.GetAll() if err != nil { log.Fatal("Couldn't get content types") } for _, item := range col.Items { var contentType ContentType - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + break + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&contentType) if err != nil { break @@ -76,12 +81,12 @@ func getContentTypes(CMA *contentful.Contentful, spaceID string) (contentTypes [ return } -func getData(spaceID, cmaKey, environment, exportFile string, flagContentTypes []string) ( +func getData(ctx context.Context, spaceID, cmaKey, environment, exportFile string, flagContentTypes []string) ( finalContentTypes []ContentType, locales []Locale, err error, ) { var contentTypes []ContentType if exportFile != "" { - fileBytes, err := ioutil.ReadFile(exportFile) + fileBytes, err := os.ReadFile(exportFile) if err != nil { return nil, nil, fmt.Errorf("error reading export file: %v", err) } @@ -101,14 +106,14 @@ func getData(spaceID, cmaKey, environment, exportFile string, flagContentTypes [ } // Get space locales - locales, err = getLocales(CMA, spaceID) + locales, err = getLocales(ctx, CMA, spaceID) if err != nil { return nil, nil, fmt.Errorf("could not get locales: %v", err) } fmt.Println("Locales found:", locales) // Get content types - contentTypes, err = getContentTypes(CMA, spaceID) + contentTypes, err = getContentTypes(ctx, CMA, spaceID) if err != nil { return nil, nil, fmt.Errorf("could not get content types: %v", err) } @@ -135,8 +140,8 @@ func getData(spaceID, cmaKey, environment, exportFile string, flagContentTypes [ } // GenerateAPI calls the generators -func GenerateAPI(dir, packageName, spaceID, cmaKey, environment, exportFile string, flagContentTypes []string, version string) (err error) { - contentTypes, locales, errGetData := getData(spaceID, cmaKey, environment, exportFile, flagContentTypes) +func GenerateAPI(ctx context.Context, dir, packageName, spaceID, cmaKey, environment, exportFile string, flagContentTypes []string, version string) (err error) { + contentTypes, locales, errGetData := getData(ctx, spaceID, cmaKey, environment, exportFile, flagContentTypes) if errGetData != nil { return errGetData } diff --git a/erm/template.go b/erm/template.go index aa16525..f5ec2fb 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,13 +1,15 @@ package erm import ( - "strings" "text/template" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func getFuncMap() template.FuncMap { return template.FuncMap{ - "firstCap": strings.Title, + "firstCap": cases.Title(language.Und, cases.NoLower).String, "fieldIsBasic": fieldIsBasic, "fieldIsComplex": fieldIsComplex, "fieldIsAsset": fieldIsAsset, diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index f2aaa4e..f7d66fe 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -246,7 +246,7 @@ func (cc *ContentfulClient) ClientStats() { for _, parents := range cc.Cache.parentMap { referenceCount += len(parents) } - fieldsMap["cache parentMap parents"] = referenceCount + fieldsMap["cache parentMap parents"] = referenceCount } cc.logFn(fieldsMap, LogInfo, "Contentful ClientStats") } @@ -256,18 +256,18 @@ func (ref ContentfulReferencedEntry) ContentType() (contentType string) { return ref.Entry.Sys.ContentType.Sys.ID } -func (cc *ContentfulClient) DeleteAsset(asset *contentful.Asset) error { +func (cc *ContentfulClient) DeleteAsset(ctx context.Context, asset *contentful.Asset) error { if cc == nil || cc.Client == nil { return errors.New("DeleteAsset: No client available") } if cc.clientMode != ClientModeCMA { return errors.New("DeleteAsset: Only available in ClientModeCMA") } - errUnpublish := cc.Client.Assets.Unpublish(cc.SpaceID, asset) + errUnpublish := cc.Client.Assets.Unpublish(ctx, cc.SpaceID, asset) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return errUnpublish } - errDelete := cc.Client.Assets.Delete(cc.SpaceID, asset) + errDelete := cc.Client.Assets.Delete(ctx, cc.SpaceID, asset) if errDelete != nil { return errDelete } @@ -286,11 +286,11 @@ func (cc *ContentfulClient) EnableTextJanitor() { cc.textJanitor = true } -func (cc *ContentfulClient) GetAllAssets() (map[string]*contentful.Asset, error) { - return cc.getAllAssets(true) +func (cc *ContentfulClient) GetAllAssets(ctx context.Context) (map[string]*contentful.Asset, error) { + return cc.getAllAssets(ctx, true) } -func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*contentful.Asset, error) { +func (cc *ContentfulClient) GetAssetByID(ctx context.Context, id string, forceNoCache ...bool) (*contentful.Asset, error) { if cc == nil || cc.Client == nil { return nil, errors.New("GetAssetByID: No client available") } @@ -307,7 +307,7 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont return nil, errors.New("GetAssetByID: not found") } } - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Equal("sys.id",id) _, err := col.Next() if err != nil { @@ -334,7 +334,7 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont return &asset, nil } -func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) { +func (cc *ContentfulClient) GetContentTypeOfID(ctx context.Context, id string) (string, error) { if cc == nil || cc.Client == nil { return "", errors.New("GetContentTypeOfID: No client available") } @@ -353,7 +353,7 @@ func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) { {{ end }} return "", fmt.Errorf("GetContentTypeOfID: %s Not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.Include(0).Equal("sys.id",id) _, err := col.GetAll() if err != nil { @@ -447,7 +447,7 @@ func NewAssetFromURL(id string, uploadUrl string, imageFileType string, title st return asset } -func NewContentfulClient(spaceID string, clientMode ClientMode, clientKey string, optimisticPageSize uint16, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, debug bool) (*ContentfulClient, error) { +func NewContentfulClient(ctx context.Context, spaceID string, clientMode ClientMode, clientKey string, optimisticPageSize uint16, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, debug bool) (*ContentfulClient, error) { if spaceID == "" { return nil, errors.New("NewContentfulClient: SpaceID cannot be empty") } @@ -497,7 +497,7 @@ func NewContentfulClient(spaceID string, clientMode ClientMode, clientKey string SpaceID: spaceID, sync: clientMode == ClientModeCDA, } - _, err = cc.Client.Spaces.Get(spaceID) + _, err = cc.Client.Spaces.Get(ctx, spaceID) if err != nil { _, ok := err.(contentful.NotFoundError) if ok { @@ -684,6 +684,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string for { cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( + ctx, cc.SpaceID, cc.syncToken == "", cc.syncToken, @@ -767,7 +768,7 @@ func (cc *ContentfulClient) cacheSpace(ctx context.Context, contentTypes []strin if cacheAssets { contentTypes = append([]string{assetWorkerType}, contentTypes...) } - _, errCanWeEvenConnect := cc.Client.Spaces.Get(cc.SpaceID) + _, errCanWeEvenConnect := cc.Client.Spaces.Get(ctx, cc.SpaceID) cc.cacheMutex.sharedDataGcLock.RLock() offlinePreviousState := cc.offline cc.cacheMutex.sharedDataGcLock.RUnlock() @@ -893,7 +894,7 @@ func (cc *ContentfulClient) cacheGcAssetByID(ctx context.Context, id string, ass if cc.Client == nil { return errors.New("cacheGcAssetByID: No client available") } - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Equal("sys.id", id) _, err := col.Next() if err != nil { @@ -910,7 +911,7 @@ func (cc *ContentfulClient) cacheGcAssetByID(ctx context.Context, id string, ass err = json.Unmarshal(byt, &asset) if err != nil { return err - } + } } for _, loc := range []Locale{ {{ range $index , $locale := $locales }}SpaceLocale{{ onlyLetters $locale.Name }},{{end}} } { if _, ok := asset.Fields.File[string(loc)]; ok { @@ -962,7 +963,7 @@ func getContentfulAPIClient(clientMode ClientMode, clientKey string) (*contentfu } } -func (cc *ContentfulClient) getAllAssets(tryCacheFirst bool) (map[string]*contentful.Asset, error) { +func (cc *ContentfulClient) getAllAssets(ctx context.Context, tryCacheFirst bool) (map[string]*contentful.Asset, error) { if cc == nil || cc.Client == nil { return nil, errors.New("getAllAssets: No client available") } @@ -981,7 +982,7 @@ func (cc *ContentfulClient) getAllAssets(tryCacheFirst bool) (map[string]*conten allItems = append(allItems,asset) } } else { - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Limit(assetPageSize) for { _, err := col.Next() @@ -1027,8 +1028,8 @@ func getOfflineSpaceFromFile(filename string) (*offlineTemp, error) { return offlineTemp, nil } -func (cc *ContentfulClient) optimisticPageSizeGetAll(contentType string, limit uint16) (*contentful.Collection, error) { - col := cc.Client.Entries.List(cc.SpaceID) +func (cc *ContentfulClient) optimisticPageSizeGetAll(ctx context.Context, contentType string, limit uint16) (*contentful.Collection, error) { + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType(contentType).Locale("*").Include(0).Limit(limit) allItems := []interface{}{} var err error @@ -1047,7 +1048,7 @@ func (cc *ContentfulClient) optimisticPageSizeGetAll(contentType string, limit u case contentful.ErrorResponse: msg := errTyped.Message if strings.Contains(msg, "Response size too big") && limit >= 20 { - smallerPageCol, err := cc.optimisticPageSizeGetAll(contentType, limit/2) + smallerPageCol, err := cc.optimisticPageSizeGetAll(ctx, contentType, limit/2) return smallerPageCol, err } return nil, err @@ -1476,7 +1477,7 @@ func updateCacheForContentType(ctx context.Context, results chan ContentTypeResu } {{ end }} case assetWorkerType: - allAssets, err := cc.getAllAssets(false) + allAssets, err := cc.getAllAssets(ctx, false) if err != nil { return errors.New("updateCacheForContentType failed for assets") } @@ -1530,7 +1531,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { +func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { parents = []EntryReference{} cc.cacheMutex.sharedDataGcLock.RLock() cacheInit := cc.cacheInit @@ -1548,7 +1549,7 @@ func commonGetParents(cc *ContentfulClient, id string, contentType []string) (pa } return cc.Cache.parentMap[id], nil } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.Equal("links_to_entry", id).Locale("*") _, err = col.GetAll() if err != nil { @@ -1672,4 +1673,4 @@ func stripInvisibleUnicodeChars(dirty string) string { return -1 }, dirty) return clean -} \ No newline at end of file +} diff --git a/erm/templates/contentful_vo_lib_contenttype.gotmpl b/erm/templates/contentful_vo_lib_contenttype.gotmpl index 0af1d3d..586ad12 100644 --- a/erm/templates/contentful_vo_lib_contenttype.gotmpl +++ b/erm/templates/contentful_vo_lib_contenttype.gotmpl @@ -16,7 +16,7 @@ const ContentType{{ firstCap $contentType.Sys.ID }} = "{{ $contentType.Sys.ID }} // ---{{ firstCap $contentType.Sys.ID }} public methods--- -func (cc *ContentfulClient) GetAll{{ firstCap $contentType.Sys.ID }}() (voMap map[string]*Cf{{ firstCap $contentType.Sys.ID }}, err error) { +func (cc *ContentfulClient) GetAll{{ firstCap $contentType.Sys.ID }}(ctx context.Context) (voMap map[string]*Cf{{ firstCap $contentType.Sys.ID }}, err error) { if cc == nil { return nil, errors.New("GetAll{{ firstCap $contentType.Sys.ID }}: No client available") } @@ -27,7 +27,7 @@ func (cc *ContentfulClient) GetAll{{ firstCap $contentType.Sys.ID }}() (voMap ma if cacheInit { return cc.Cache.entryMaps.{{ $contentType.Sys.ID }}, nil } - col, err := cc.optimisticPageSizeGetAll("{{ $contentType.Sys.ID }}", optimisticPageSize) + col, err := cc.optimisticPageSizeGetAll(ctx, "{{ $contentType.Sys.ID }}", optimisticPageSize) if err != nil { return nil, err } @@ -42,11 +42,11 @@ func (cc *ContentfulClient) GetAll{{ firstCap $contentType.Sys.ID }}() (voMap ma return {{ $contentType.Sys.ID }}Map, nil } -func (cc *ContentfulClient) GetFiltered{{ firstCap $contentType.Sys.ID }}(query *contentful.Query) (voMap map[string]*Cf{{ firstCap $contentType.Sys.ID }}, err error) { +func (cc *ContentfulClient) GetFiltered{{ firstCap $contentType.Sys.ID }}(ctx context.Context, query *contentful.Query) (voMap map[string]*Cf{{ firstCap $contentType.Sys.ID }}, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("getFiltered{{ firstCap $contentType.Sys.ID }}: No client available") } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) if query != nil { col.Query = *query } @@ -66,7 +66,7 @@ func (cc *ContentfulClient) GetFiltered{{ firstCap $contentType.Sys.ID }}(query return {{ $contentType.Sys.ID }}Map, nil } -func (cc *ContentfulClient) Get{{ firstCap $contentType.Sys.ID }}ByID(id string, forceNoCache ...bool) (vo *Cf{{ firstCap $contentType.Sys.ID }}, err error) { +func (cc *ContentfulClient) Get{{ firstCap $contentType.Sys.ID }}ByID(ctx context.Context, id string, forceNoCache ...bool) (vo *Cf{{ firstCap $contentType.Sys.ID }}, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("Get{{ firstCap $contentType.Sys.ID }}ByID: No client available") } @@ -79,7 +79,7 @@ func (cc *ContentfulClient) Get{{ firstCap $contentType.Sys.ID }}ByID(id string, } return nil, fmt.Errorf("Get{{ firstCap $contentType.Sys.ID }}ByID: entry '%s' not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("{{ $contentType.Sys.ID }}").Locale("*").Include(0).Equal("sys.id",id) _, err = col.GetAll() if err != nil { @@ -109,14 +109,14 @@ func NewCf{{ firstCap $contentType.Sys.ID }}(contentfulClient ...*ContentfulClie cf{{ firstCap $contentType.Sys.ID }}.Sys.ContentType.Sys.LinkType = "ContentType" return } -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) GetParents(contentType ...string) (parents []EntryReference, err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) GetParents(ctx context.Context, contentType ...string) (parents []EntryReference, err error) { if vo == nil { return nil, errors.New("GetParents: Value Object is nil") } if vo.CC == nil { return nil, errors.New("GetParents: Value Object has no Contentful Client set") } - return commonGetParents(vo.CC, vo.Sys.ID, contentType) + return commonGetParents(ctx, vo.CC, vo.Sys.ID, contentType) } func (vo *Cf{{ firstCap $contentType.Sys.ID }}) GetPublishingStatus() string { @@ -212,7 +212,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } {{ end }} {{ if fieldIsMultipleReference $field }} -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale ...Locale) []*EntryReference { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(ctx context.Context, locale ...Locale) []*EntryReference { if vo == nil { return nil } @@ -248,7 +248,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } } for _, eachLocalized{{ firstCap $field.ID }} := range vo.Fields.{{ firstCap $field.ID }}[string(loc)] { - contentType, err := vo.CC.GetContentTypeOfID(eachLocalized{{ firstCap $field.ID }}.Sys.ID) + contentType, err := vo.CC.GetContentTypeOfID(ctx, eachLocalized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, ErrNoTypeOfRefEntry) @@ -258,7 +258,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale switch contentType { {{ range $index , $contentType := $contentTypes }} case ContentType{{ firstCap $contentType.Sys.ID }}: - referencedVO, err := vo.CC.Get{{ firstCap $contentType.Sys.ID }}ByID(eachLocalized{{ firstCap $field.ID }}.Sys.ID) + referencedVO, err := vo.CC.Get{{ firstCap $contentType.Sys.ID }}ByID(ctx, eachLocalized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, err) @@ -273,7 +273,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } {{ end }} {{ if fieldIsReference $field }} -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale ...Locale) *EntryReference { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(ctx context.Context, locale ...Locale) *EntryReference { if vo == nil { return nil } @@ -308,7 +308,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } } localized{{ firstCap $field.ID }} := vo.Fields.{{ firstCap $field.ID }}[string(loc)] - contentType, err := vo.CC.GetContentTypeOfID(localized{{ firstCap $field.ID }}.Sys.ID) + contentType, err := vo.CC.GetContentTypeOfID(ctx, localized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, ErrNoTypeOfRefEntry) @@ -318,7 +318,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale switch contentType { {{ range $index , $contentType := $contentTypes }} case ContentType{{ firstCap $contentType.Sys.ID }}: - referencedVO, err := vo.CC.Get{{ firstCap $contentType.Sys.ID }}ByID(localized{{ firstCap $field.ID }}.Sys.ID) + referencedVO, err := vo.CC.Get{{ firstCap $contentType.Sys.ID }}ByID(ctx, localized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, err) @@ -332,7 +332,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } {{ end }} {{ if fieldIsMultipleAsset $field }} -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale ...Locale) []*contentful.AssetNoLocale { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(ctx context.Context, locale ...Locale) []*contentful.AssetNoLocale { if vo == nil { return nil } @@ -370,7 +370,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } } for _, eachLocalized{{ firstCap $field.ID }} := range vo.Fields.{{ firstCap $field.ID }}[string(loc)] { - asset, err := vo.CC.GetAssetByID(eachLocalized{{ firstCap $field.ID }}.Sys.ID) + asset, err := vo.CC.GetAssetByID(ctx, eachLocalized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel == LogDebug { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, ErrNoTypeOfRefAsset) @@ -401,7 +401,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } {{ end }} {{ if fieldIsAsset $field }} -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale ...Locale) *contentful.AssetNoLocale { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(ctx context.Context, locale ...Locale) *contentful.AssetNoLocale { if vo == nil { return nil } @@ -438,7 +438,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale } } localized{{ firstCap $field.ID }} := vo.Fields.{{ firstCap $field.ID }}[string(loc)] - asset, err := vo.CC.GetAssetByID(localized{{ firstCap $field.ID }}.Sys.ID) + asset, err := vo.CC.GetAssetByID(ctx, localized{{ firstCap $field.ID }}.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel == LogDebug { vo.CC.logFn(map[string]interface{}{"content type":vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method":"{{ firstCap $field.ID }}()"}, LogError, ErrNoTypeOfRefAsset) @@ -490,7 +490,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) Set{{ firstCap $field.ID }}({{ $ return } {{ end }} -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpsertEntry() (err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpsertEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpsertEntry: Value Object is nil") } @@ -510,13 +510,13 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpsertEntry() (err error) { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} UpsertEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} UpsertEntry: Operation failed: %w", err) } return } -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) PublishEntry() (err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) PublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("PublishEntry: Value Object is nil") } @@ -535,13 +535,13 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) PublishEntry() (err error) { if errUnmarshal != nil { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} PublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} PublishEntry: publish operation failed: %w", err) } return } -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UnpublishEntry() (err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UnpublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UnpublishEntry: Value Object is nil") } @@ -560,13 +560,13 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UnpublishEntry() (err error) { if errUnmarshal != nil { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} UnpublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} UnpublishEntry: unpublish operation failed: %w", err) } return } -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpdateEntry() (err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpdateEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpdateEntry: Value Object is nil") } @@ -586,7 +586,7 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpdateEntry() (err error) { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} UpdateEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} UpdateEntry: upsert operation failed: %w", err) } @@ -598,13 +598,13 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) UpdateEntry() (err error) { if errUnmarshal != nil { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} UpdateEntry: Can't unmarshal JSON back into VO") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} UpdateEntry: publish operation failed: %w", err) } return } -func (vo *Cf{{ firstCap $contentType.Sys.ID }}) DeleteEntry() (err error) { +func (vo *Cf{{ firstCap $contentType.Sys.ID }}) DeleteEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("DeleteEntry: Value Object is nil") } @@ -624,12 +624,12 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) DeleteEntry() (err error) { return errors.New("Cf{{ firstCap $contentType.Sys.ID }} DeleteEntry: Can't unmarshal JSON into CF entry") } if cfEntry.Sys.PublishedCounter > 0 { - errUnpublish := vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + errUnpublish := vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} DeleteEntry: Unpublish entry failed: %w", errUnpublish) } } - errDelete := vo.CC.Client.Entries.Delete(vo.CC.SpaceID, cfEntry.Sys.ID) + errDelete := vo.CC.Client.Entries.Delete(ctx, vo.CC.SpaceID, cfEntry.Sys.ID) if errDelete != nil { return fmt.Errorf("Cf{{ firstCap $contentType.Sys.ID }} DeleteEntry: Delete entry failed: %w", errDelete) } @@ -662,7 +662,7 @@ func (cc *ContentfulClient) cacheAll{{ firstCap $contentType.Sys.ID }}(ctx conte } } } else { - col, err = cc.optimisticPageSizeGetAll("{{ $contentType.Sys.ID }}", cc.optimisticPageSize) + col, err = cc.optimisticPageSizeGetAll(ctx, "{{ $contentType.Sys.ID }}", cc.optimisticPageSize) if err != nil { return nil, errors.New("optimisticPageSizeGetAll for {{ firstCap $contentType.Sys.ID }} failed: "+err.Error()) } @@ -674,7 +674,7 @@ func (cc *ContentfulClient) cacheAll{{ firstCap $contentType.Sys.ID }}(ctx conte {{ $contentType.Sys.ID }}Map := map[string]*Cf{{ firstCap $contentType.Sys.ID }}{} for _, {{ $contentType.Sys.ID }} := range all{{ firstCap $contentType.Sys.ID }} { if cc.cacheInit { - existing{{ firstCap $contentType.Sys.ID }}, err := cc.Get{{ firstCap $contentType.Sys.ID }}ByID({{ $contentType.Sys.ID }}.Sys.ID) + existing{{ firstCap $contentType.Sys.ID }}, err := cc.Get{{ firstCap $contentType.Sys.ID }}ByID(ctx, {{ $contentType.Sys.ID }}.Sys.ID) if err == nil && existing{{ firstCap $contentType.Sys.ID }} != nil && existing{{ firstCap $contentType.Sys.ID }}.Sys.Version > {{ $contentType.Sys.ID }}.Sys.Version { return nil, fmt.Errorf("cache update canceled because {{ firstCap $contentType.Sys.ID }} entry %s is newer in cache", {{ $contentType.Sys.ID }}.Sys.ID) } @@ -735,7 +735,7 @@ func (cc *ContentfulClient) cache{{ firstCap $contentType.Sys.ID }}ByID(ctx cont defer cc.cacheMutex.idContentTypeMapGcLock.Unlock() cc.cacheMutex.parentMapGcLock.Lock() defer cc.cacheMutex.parentMapGcLock.Unlock() - + var col *contentful.Collection if entryPayload != nil { col = &contentful.Collection{ @@ -747,13 +747,13 @@ func (cc *ContentfulClient) cache{{ firstCap $contentType.Sys.ID }}ByID(ctx cont return errors.New("cache{{ firstCap $contentType.Sys.ID }}ByID: No client available") } if !entryDelete { - col = cc.Client.Entries.List(cc.SpaceID) + col = cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("{{ $contentType.Sys.ID }}").Locale("*").Include(0).Equal("sys.id", id) _, err := col.GetAll() if err != nil { return err } - } + } } // It was deleted if col != nil && len(col.Items) == 0 || entryDelete { @@ -833,7 +833,7 @@ func (cc *ContentfulClient) cache{{ firstCap $contentType.Sys.ID }}ByID(ctx cont for childID, parents := range cc.Cache.parentMap { if _, isCollectedChildID := allChildrensIds[childID]; isCollectedChildID { continue - } + } newParents := []EntryReference{} for _, parent := range parents { if parent.ID != id { @@ -855,11 +855,11 @@ func colToCf{{ firstCap $contentType.Sys.ID }}(col *contentful.Collection, cc *C } if cc.textJanitor { {{ range $fieldIndex, $field := $contentType.Fields }} - {{ if or (fieldIsSymbol $field) (fieldIsText $field) }} + {{ if or (fieldIsSymbol $field) (fieldIsText $field) }} vo.Fields.{{ firstCap $field.ID }} = cleanUpStringField(vo.Fields.{{ firstCap $field.ID }}){{ end }} - {{ if fieldIsSymbolList $field }} + {{ if fieldIsSymbolList $field }} vo.Fields.{{ firstCap $field.ID }} = cleanUpStringSliceField(vo.Fields.{{ firstCap $field.ID }}){{ end }} - {{ if fieldIsRichText $field }} + {{ if fieldIsRichText $field }} vo.Fields.{{ firstCap $field.ID }} = cleanUpRichTextField(vo.Fields.{{ firstCap $field.ID }}){{ end }} {{ end }} } diff --git a/erm/util.go b/erm/util.go index 8da3669..c261c81 100644 --- a/erm/util.go +++ b/erm/util.go @@ -2,9 +2,10 @@ package erm import ( "regexp" - "strings" ) +var onlyLettersRegex = regexp.MustCompile("[^A-Za-z]") + func sliceIncludes(slice []string, key string) bool { for _, val := range slice { if val == key { @@ -14,12 +15,6 @@ func sliceIncludes(slice []string, key string) bool { return false } -func firstCap(inputString string) (outputString string) { - outputString = strings.Title(inputString) - return -} - func onlyLetters(inputString string) (outputString string) { - re := regexp.MustCompile("[^A-Za-z]") - return re.ReplaceAllString(inputString, "") + return onlyLettersRegex.ReplaceAllString(inputString, "") } diff --git a/erm/vo.go b/erm/vo.go index 20bcef2..12d060f 100644 --- a/erm/vo.go +++ b/erm/vo.go @@ -51,8 +51,8 @@ type ContentTypeField struct { Type string `json:"type,omitempty"` Items *ContentTypeFieldItems `json:"items,omitempty"` LinkType string `json:"linkType,omitempty"` - Omitted bool `json:"omitted,omitempty"` - ReferencedTypes []string + Omitted bool `json:"omitted,omitempty"` + ReferencedTypes []string `json:"referencedTypes,omitempty"` } // ContentType VO diff --git a/go.mod b/go.mod index 1ebd165..c69b9e9 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,24 @@ module github.com/foomo/gocontentful -go 1.16 +go 1.21 require ( - github.com/foomo/contentful v0.3.6 - github.com/sirupsen/logrus v1.8.1 - github.com/smartystreets/goconvey v1.6.4 // indirect - github.com/stretchr/testify v1.7.0 - golang.org/x/mod v0.4.2 // indirect - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect - golang.org/x/tools v0.1.0 - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.7.0 + golang.org/x/text v0.14.0 + golang.org/x/tools v0.20.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/smartystreets/assertions v1.2.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sys v0.19.0 // indirect moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 1ca8467..b2038fc 100644 --- a/go.sum +++ b/go.sum @@ -1,59 +1,39 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/foomo/contentful v0.3.6 h1:yiwhWayrXCe0wpQGzhO32bl8AE/46T261bBqNdPODWk= -github.com/foomo/contentful v0.3.6/go.mod h1:6Pf8efSKeMbwKgVkjSFWeBsLYdUA0NTW7OUsMWzHXvY= +github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0 h1:RrL0mO0RhA7e01Uogjz2AWEletuNUFQymyn8PJZEnoQ= +github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0/go.mod h1:x9QtObXj7qKw4Rpxg6kI6bi6RGXzD7o/l+XPj1cr2Og= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/main.go b/main.go index 8b14d80..b86372e 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -134,7 +135,7 @@ func main() { } } - err = erm.GenerateAPI(filepath.Dir(path), packageName, conf.SpaceID, cmaKey, conf.Environment, conf.ExportFile, cleanContentTypes, VERSION) + err = erm.GenerateAPI(context.Background(), filepath.Dir(path), packageName, conf.SpaceID, cmaKey, conf.Environment, conf.ExportFile, cleanContentTypes, VERSION) if err != nil { fatal("Something went horribly wrong...", err) } diff --git a/test/cache_test.go b/test/cache_test.go index fc3fb77..6c976fc 100644 --- a/test/cache_test.go +++ b/test/cache_test.go @@ -38,7 +38,7 @@ func TestCacheHasContentType(t *testing.T) { func TestGetAsset(t *testing.T) { contentfulClient, err := getTestClient() require.NoError(t, err) - _, err = contentfulClient.GetAssetByID("Xc0ny7GWsMEMCeASWO2um") + _, err = contentfulClient.GetAssetByID(context.TODO(), "Xc0ny7GWsMEMCeASWO2um") require.NoError(t, err) newAsset := testapi.NewAssetFromURL("12345", "https://example.com", "PNG", "New Asset") require.NotNil(t, newAsset) @@ -60,7 +60,7 @@ func TestDeleteAssetFromCache(t *testing.T) { func TestGetContentTypeOfID(t *testing.T) { contentfulClient, err := getTestClient() require.NoError(t, err) - contentType, err := contentfulClient.GetContentTypeOfID("651CQ8rLoIYCeY6G0QG22q") + contentType, err := contentfulClient.GetContentTypeOfID(context.TODO(), "651CQ8rLoIYCeY6G0QG22q") require.NoError(t, err) require.Equal(t, "brand", contentType) } @@ -68,9 +68,9 @@ func TestGetContentTypeOfID(t *testing.T) { func TestGetParents(t *testing.T) { contentfulClient, err := getTestClient() require.NoError(t, err) - product, err := contentfulClient.GetProductByID("6dbjWqNd9SqccegcqYq224") + product, err := contentfulClient.GetProductByID(context.TODO(), "6dbjWqNd9SqccegcqYq224") require.NoError(t, err) - brandRef := product.Brand() + brandRef := product.Brand(context.TODO()) brandParents, err := brandRef.GetParents(contentfulClient) require.NoError(t, err) require.Equal(t, 2, len(brandParents)) @@ -98,7 +98,7 @@ func TestPreserveCacheIfNewer(t *testing.T) { require.NoError(t, err) err = contentfulClient.UpdateCache(context.TODO(), nil, false) require.NoError(t, err) - brand, err := contentfulClient.GetBrandByID("JrePkDVYomE8AwcuCUyMi") + brand, err := contentfulClient.GetBrandByID(context.TODO(), "JrePkDVYomE8AwcuCUyMi") require.NoError(t, err) require.Equal(t, 2.0, brand.Sys.Version) } diff --git a/test/concurrency_test.go b/test/concurrency_test.go index 23cdfc4..2907dcf 100644 --- a/test/concurrency_test.go +++ b/test/concurrency_test.go @@ -14,20 +14,20 @@ var ( concurrency = 10000 ) -func readWorker(contentfulClient *testapi.ContentfulClient, i int) error { - product, err := contentfulClient.GetProductByID(testProductID) +func readWorker(ctx context.Context, contentfulClient *testapi.ContentfulClient, i int) error { + product, err := contentfulClient.GetProductByID(ctx, testProductID) if err != nil { return err } - _, err = contentfulClient.GetAllProduct() + _, err = contentfulClient.GetAllProduct(ctx) if err != nil { return err } price := product.Price() testLogger.Infof("Read worker %d read price: %f", i, price) - _ = product.Brand() - _ = product.Categories() - _ = product.Image() + _ = product.Brand(ctx) + _ = product.Categories(ctx) + _ = product.Image(ctx) _ = product.Nodes() _ = product.ProductDescription() _ = product.ProductName() @@ -42,12 +42,12 @@ func readWorker(contentfulClient *testapi.ContentfulClient, i int) error { return nil } -func parentWorker(contentfulClient *testapi.ContentfulClient, i int) error { - brand, err := contentfulClient.GetBrandByID(testBrandID) +func parentWorker(ctx context.Context, contentfulClient *testapi.ContentfulClient, i int) error { + brand, err := contentfulClient.GetBrandByID(ctx, testBrandID) if err != nil { return err } - parents, err := brand.GetParents() + parents, err := brand.GetParents(ctx) if err != nil { return err } @@ -55,8 +55,8 @@ func parentWorker(contentfulClient *testapi.ContentfulClient, i int) error { return nil } -func writeWorker(contentfulClient *testapi.ContentfulClient, i int) error { - product, err := contentfulClient.GetProductByID(testProductID) +func writeWorker(ctx context.Context, contentfulClient *testapi.ContentfulClient, i int) error { + product, err := contentfulClient.GetProductByID(ctx, testProductID) if err != nil { return err } @@ -66,19 +66,19 @@ func writeWorker(contentfulClient *testapi.ContentfulClient, i int) error { } contentfulClient.SetProductInCache(product) testLogger.Infof("Write worker %d set price: %d", i, i) - product.SetBrand(testapi.ContentTypeSys{}) - product.SetCategories([]testapi.ContentTypeSys{}) - product.SetImage([]testapi.ContentTypeSys{}) - product.SetNodes(nil) - product.SetProductDescription("") - product.SetProductName("") - product.SetQuantity(1) - product.SetSeoText("") - product.SetSizetypecolor("") - product.SetSku("") - product.SetSlug("") - product.SetTags([]string{""}) - product.SetWebsite("") + _ = product.SetBrand(testapi.ContentTypeSys{}) + _ = product.SetCategories([]testapi.ContentTypeSys{}) + _ = product.SetImage([]testapi.ContentTypeSys{}) + _ = product.SetNodes(nil) + _ = product.SetProductDescription("") + _ = product.SetProductName("") + _ = product.SetQuantity(1) + _ = product.SetSeoText("") + _ = product.SetSizetypecolor("") + _ = product.SetSku("") + _ = product.SetSlug("") + _ = product.SetTags([]string{""}) + _ = product.SetWebsite("") return nil } @@ -105,7 +105,7 @@ func TestConcurrentReadWrites(t *testing.T) { i := i go func() { defer wg.Done() - err := writeWorker(contentfulClient, i) + err := writeWorker(context.TODO(), contentfulClient, i) if err != nil { testLogger.Errorf("testConcurrentReadWrites: %v", err) } @@ -116,7 +116,7 @@ func TestConcurrentReadWrites(t *testing.T) { i := i go func() { defer wg.Done() - err := readWorker(contentfulClient, i) + err := readWorker(context.TODO(), contentfulClient, i) if err != nil { testLogger.Errorf("testConcurrentReadWrites: %v", err) } @@ -127,7 +127,7 @@ func TestConcurrentReadWrites(t *testing.T) { i := i go func() { defer wg.Done() - err := parentWorker(contentfulClient, i) + err := parentWorker(context.TODO(), contentfulClient, i) if err != nil { testLogger.Errorf("testConcurrentReadWrites: %v", err) } diff --git a/test/other_test.go b/test/other_test.go index 1f4a807..4d6ac7b 100644 --- a/test/other_test.go +++ b/test/other_test.go @@ -1,6 +1,7 @@ package test import ( + "context" "testing" "time" @@ -14,13 +15,13 @@ func TestPublishingStatus(t *testing.T) { contentfulClient, err := getTestClient() require.NoError(t, err) time.Sleep(time.Second) - draft, err := contentfulClient.GetProductByID("6dbjWqNd9SqccegcqYq224") + draft, err := contentfulClient.GetProductByID(context.TODO(), "6dbjWqNd9SqccegcqYq224") require.NoError(t, err) require.Equal(t, testapi.StatusDraft, draft.GetPublishingStatus()) - published, err := contentfulClient.GetCategoryByID("7LAnCobuuWYSqks6wAwY2a") + published, err := contentfulClient.GetCategoryByID(context.TODO(), "7LAnCobuuWYSqks6wAwY2a") require.NoError(t, err) require.Equal(t, testapi.StatusPublished, published.GetPublishingStatus()) - changed, err := contentfulClient.GetProductByID("3DVqIYj4dOwwcKu6sgqOgg") + changed, err := contentfulClient.GetProductByID(context.TODO(), "3DVqIYj4dOwwcKu6sgqOgg") require.NoError(t, err) require.Equal(t, testapi.StatusChanged, changed.GetPublishingStatus()) } @@ -33,7 +34,7 @@ func TestCleanUpUnicode(t *testing.T) { true, true) require.NoError(t, errClient) - testCleanUpUnicode, err := cc.GetProductByID("6dbjWqNd9SqccegcqYq224") + testCleanUpUnicode, err := cc.GetProductByID(context.TODO(), "6dbjWqNd9SqccegcqYq224") require.NoError(t, err) html, err := testapi.RichTextToHtml(testCleanUpUnicode.SeoText(testapi.SpaceLocaleGerman), nil, nil, nil, nil, testapi.SpaceLocaleGerman) require.NoError(t, err) diff --git a/test/richtext_test.go b/test/richtext_test.go index 474c2ba..590080e 100644 --- a/test/richtext_test.go +++ b/test/richtext_test.go @@ -1,9 +1,10 @@ package test import ( + "testing" + "github.com/foomo/gocontentful/test/testapi" "github.com/stretchr/testify/require" - "testing" ) var targetRichText = &testapi.RichTextNode{ diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 9934150..f24d879 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -273,18 +273,18 @@ func (ref ContentfulReferencedEntry) ContentType() (contentType string) { return ref.Entry.Sys.ContentType.Sys.ID } -func (cc *ContentfulClient) DeleteAsset(asset *contentful.Asset) error { +func (cc *ContentfulClient) DeleteAsset(ctx context.Context, asset *contentful.Asset) error { if cc == nil || cc.Client == nil { return errors.New("DeleteAsset: No client available") } if cc.clientMode != ClientModeCMA { return errors.New("DeleteAsset: Only available in ClientModeCMA") } - errUnpublish := cc.Client.Assets.Unpublish(cc.SpaceID, asset) + errUnpublish := cc.Client.Assets.Unpublish(ctx, cc.SpaceID, asset) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return errUnpublish } - errDelete := cc.Client.Assets.Delete(cc.SpaceID, asset) + errDelete := cc.Client.Assets.Delete(ctx, cc.SpaceID, asset) if errDelete != nil { return errDelete } @@ -303,11 +303,11 @@ func (cc *ContentfulClient) EnableTextJanitor() { cc.textJanitor = true } -func (cc *ContentfulClient) GetAllAssets() (map[string]*contentful.Asset, error) { - return cc.getAllAssets(true) +func (cc *ContentfulClient) GetAllAssets(ctx context.Context) (map[string]*contentful.Asset, error) { + return cc.getAllAssets(ctx, true) } -func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*contentful.Asset, error) { +func (cc *ContentfulClient) GetAssetByID(ctx context.Context, id string, forceNoCache ...bool) (*contentful.Asset, error) { if cc == nil || cc.Client == nil { return nil, errors.New("GetAssetByID: No client available") } @@ -324,7 +324,7 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont return nil, errors.New("GetAssetByID: not found") } } - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Equal("sys.id", id) _, err := col.Next() if err != nil { @@ -351,7 +351,7 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont return &asset, nil } -func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) { +func (cc *ContentfulClient) GetContentTypeOfID(ctx context.Context, id string) (string, error) { if cc == nil || cc.Client == nil { return "", errors.New("GetContentTypeOfID: No client available") } @@ -384,7 +384,7 @@ func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) { return "", fmt.Errorf("GetContentTypeOfID: %s Not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.Include(0).Equal("sys.id", id) _, err := col.GetAll() if err != nil { @@ -478,7 +478,7 @@ func NewAssetFromURL(id string, uploadUrl string, imageFileType string, title st return asset } -func NewContentfulClient(spaceID string, clientMode ClientMode, clientKey string, optimisticPageSize uint16, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, debug bool) (*ContentfulClient, error) { +func NewContentfulClient(ctx context.Context, spaceID string, clientMode ClientMode, clientKey string, optimisticPageSize uint16, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, debug bool) (*ContentfulClient, error) { if spaceID == "" { return nil, errors.New("NewContentfulClient: SpaceID cannot be empty") } @@ -529,7 +529,7 @@ func NewContentfulClient(spaceID string, clientMode ClientMode, clientKey string SpaceID: spaceID, sync: clientMode == ClientModeCDA, } - _, err = cc.Client.Spaces.Get(spaceID) + _, err = cc.Client.Spaces.Get(ctx, spaceID) if err != nil { _, ok := err.(contentful.NotFoundError) if ok { @@ -718,6 +718,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string for { cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( + ctx, cc.SpaceID, cc.syncToken == "", cc.syncToken, @@ -801,7 +802,7 @@ func (cc *ContentfulClient) cacheSpace(ctx context.Context, contentTypes []strin if cacheAssets { contentTypes = append([]string{assetWorkerType}, contentTypes...) } - _, errCanWeEvenConnect := cc.Client.Spaces.Get(cc.SpaceID) + _, errCanWeEvenConnect := cc.Client.Spaces.Get(ctx, cc.SpaceID) cc.cacheMutex.sharedDataGcLock.RLock() offlinePreviousState := cc.offline cc.cacheMutex.sharedDataGcLock.RUnlock() @@ -931,7 +932,7 @@ func (cc *ContentfulClient) cacheGcAssetByID(ctx context.Context, id string, ass if cc.Client == nil { return errors.New("cacheGcAssetByID: No client available") } - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Equal("sys.id", id) _, err := col.Next() if err != nil { @@ -1010,7 +1011,7 @@ func getContentfulAPIClient(clientMode ClientMode, clientKey string) (*contentfu } } -func (cc *ContentfulClient) getAllAssets(tryCacheFirst bool) (map[string]*contentful.Asset, error) { +func (cc *ContentfulClient) getAllAssets(ctx context.Context, tryCacheFirst bool) (map[string]*contentful.Asset, error) { if cc == nil || cc.Client == nil { return nil, errors.New("getAllAssets: No client available") } @@ -1029,7 +1030,7 @@ func (cc *ContentfulClient) getAllAssets(tryCacheFirst bool) (map[string]*conten allItems = append(allItems, asset) } } else { - col := cc.Client.Assets.List(cc.SpaceID) + col := cc.Client.Assets.List(ctx, cc.SpaceID) col.Query.Locale("*").Limit(assetPageSize) for { _, err := col.Next() @@ -1075,8 +1076,8 @@ func getOfflineSpaceFromFile(filename string) (*offlineTemp, error) { return offlineTemp, nil } -func (cc *ContentfulClient) optimisticPageSizeGetAll(contentType string, limit uint16) (*contentful.Collection, error) { - col := cc.Client.Entries.List(cc.SpaceID) +func (cc *ContentfulClient) optimisticPageSizeGetAll(ctx context.Context, contentType string, limit uint16) (*contentful.Collection, error) { + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType(contentType).Locale("*").Include(0).Limit(limit) allItems := []interface{}{} var err error @@ -1095,7 +1096,7 @@ func (cc *ContentfulClient) optimisticPageSizeGetAll(contentType string, limit u case contentful.ErrorResponse: msg := errTyped.Message if strings.Contains(msg, "Response size too big") && limit >= 20 { - smallerPageCol, err := cc.optimisticPageSizeGetAll(contentType, limit/2) + smallerPageCol, err := cc.optimisticPageSizeGetAll(ctx, contentType, limit/2) return smallerPageCol, err } return nil, err @@ -1544,7 +1545,7 @@ func updateCacheForContentType(ctx context.Context, results chan ContentTypeResu } case assetWorkerType: - allAssets, err := cc.getAllAssets(false) + allAssets, err := cc.getAllAssets(ctx, false) if err != nil { return errors.New("updateCacheForContentType failed for assets") } @@ -1622,7 +1623,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { +func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { parents = []EntryReference{} cc.cacheMutex.sharedDataGcLock.RLock() cacheInit := cc.cacheInit @@ -1640,7 +1641,7 @@ func commonGetParents(cc *ContentfulClient, id string, contentType []string) (pa } return cc.Cache.parentMap[id], nil } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.Equal("links_to_entry", id).Locale("*") _, err = col.GetAll() if err != nil { diff --git a/test/testapi/gocontentfulvolibbrand.go b/test/testapi/gocontentfulvolibbrand.go index d5127f9..141f846 100644 --- a/test/testapi/gocontentfulvolibbrand.go +++ b/test/testapi/gocontentfulvolibbrand.go @@ -18,7 +18,7 @@ const ContentTypeBrand = "brand" // ---Brand public methods--- -func (cc *ContentfulClient) GetAllBrand() (voMap map[string]*CfBrand, err error) { +func (cc *ContentfulClient) GetAllBrand(ctx context.Context) (voMap map[string]*CfBrand, err error) { if cc == nil { return nil, errors.New("GetAllBrand: No client available") } @@ -29,7 +29,7 @@ func (cc *ContentfulClient) GetAllBrand() (voMap map[string]*CfBrand, err error) if cacheInit { return cc.Cache.entryMaps.brand, nil } - col, err := cc.optimisticPageSizeGetAll("brand", optimisticPageSize) + col, err := cc.optimisticPageSizeGetAll(ctx, "brand", optimisticPageSize) if err != nil { return nil, err } @@ -44,11 +44,11 @@ func (cc *ContentfulClient) GetAllBrand() (voMap map[string]*CfBrand, err error) return brandMap, nil } -func (cc *ContentfulClient) GetFilteredBrand(query *contentful.Query) (voMap map[string]*CfBrand, err error) { +func (cc *ContentfulClient) GetFilteredBrand(ctx context.Context, query *contentful.Query) (voMap map[string]*CfBrand, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("getFilteredBrand: No client available") } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) if query != nil { col.Query = *query } @@ -68,7 +68,7 @@ func (cc *ContentfulClient) GetFilteredBrand(query *contentful.Query) (voMap map return brandMap, nil } -func (cc *ContentfulClient) GetBrandByID(id string, forceNoCache ...bool) (vo *CfBrand, err error) { +func (cc *ContentfulClient) GetBrandByID(ctx context.Context, id string, forceNoCache ...bool) (vo *CfBrand, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("GetBrandByID: No client available") } @@ -81,7 +81,7 @@ func (cc *ContentfulClient) GetBrandByID(id string, forceNoCache ...bool) (vo *C } return nil, fmt.Errorf("GetBrandByID: entry '%s' not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("brand").Locale("*").Include(0).Equal("sys.id", id) _, err = col.GetAll() if err != nil { @@ -123,14 +123,14 @@ func NewCfBrand(contentfulClient ...*ContentfulClient) (cfBrand *CfBrand) { cfBrand.Sys.ContentType.Sys.LinkType = "ContentType" return } -func (vo *CfBrand) GetParents(contentType ...string) (parents []EntryReference, err error) { +func (vo *CfBrand) GetParents(ctx context.Context, contentType ...string) (parents []EntryReference, err error) { if vo == nil { return nil, errors.New("GetParents: Value Object is nil") } if vo.CC == nil { return nil, errors.New("GetParents: Value Object has no Contentful Client set") } - return commonGetParents(vo.CC, vo.Sys.ID, contentType) + return commonGetParents(ctx, vo.CC, vo.Sys.ID, contentType) } func (vo *CfBrand) GetPublishingStatus() string { @@ -185,7 +185,7 @@ func (vo *CfBrand) CompanyName(locale ...Locale) string { return vo.Fields.CompanyName[string(loc)] } -func (vo *CfBrand) Logo(locale ...Locale) *contentful.AssetNoLocale { +func (vo *CfBrand) Logo(ctx context.Context, locale ...Locale) *contentful.AssetNoLocale { if vo == nil { return nil } @@ -222,7 +222,7 @@ func (vo *CfBrand) Logo(locale ...Locale) *contentful.AssetNoLocale { } } localizedLogo := vo.Fields.Logo[string(loc)] - asset, err := vo.CC.GetAssetByID(localizedLogo.Sys.ID) + asset, err := vo.CC.GetAssetByID(ctx, localizedLogo.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel == LogDebug { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Logo()"}, LogError, ErrNoTypeOfRefAsset) @@ -577,7 +577,7 @@ func (vo *CfBrand) SetPhone(phone []string, locale ...Locale) (err error) { return } -func (vo *CfBrand) UpsertEntry() (err error) { +func (vo *CfBrand) UpsertEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpsertEntry: Value Object is nil") } @@ -597,13 +597,13 @@ func (vo *CfBrand) UpsertEntry() (err error) { return errors.New("CfBrand UpsertEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfBrand UpsertEntry: Operation failed: %w", err) } return } -func (vo *CfBrand) PublishEntry() (err error) { +func (vo *CfBrand) PublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("PublishEntry: Value Object is nil") } @@ -622,13 +622,13 @@ func (vo *CfBrand) PublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfBrand PublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfBrand PublishEntry: publish operation failed: %w", err) } return } -func (vo *CfBrand) UnpublishEntry() (err error) { +func (vo *CfBrand) UnpublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UnpublishEntry: Value Object is nil") } @@ -647,13 +647,13 @@ func (vo *CfBrand) UnpublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfBrand UnpublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfBrand UnpublishEntry: unpublish operation failed: %w", err) } return } -func (vo *CfBrand) UpdateEntry() (err error) { +func (vo *CfBrand) UpdateEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpdateEntry: Value Object is nil") } @@ -673,7 +673,7 @@ func (vo *CfBrand) UpdateEntry() (err error) { return errors.New("CfBrand UpdateEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfBrand UpdateEntry: upsert operation failed: %w", err) } @@ -685,13 +685,13 @@ func (vo *CfBrand) UpdateEntry() (err error) { if errUnmarshal != nil { return errors.New("CfBrand UpdateEntry: Can't unmarshal JSON back into VO") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfBrand UpdateEntry: publish operation failed: %w", err) } return } -func (vo *CfBrand) DeleteEntry() (err error) { +func (vo *CfBrand) DeleteEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("DeleteEntry: Value Object is nil") } @@ -711,12 +711,12 @@ func (vo *CfBrand) DeleteEntry() (err error) { return errors.New("CfBrand DeleteEntry: Can't unmarshal JSON into CF entry") } if cfEntry.Sys.PublishedCounter > 0 { - errUnpublish := vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + errUnpublish := vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return fmt.Errorf("CfBrand DeleteEntry: Unpublish entry failed: %w", errUnpublish) } } - errDelete := vo.CC.Client.Entries.Delete(vo.CC.SpaceID, cfEntry.Sys.ID) + errDelete := vo.CC.Client.Entries.Delete(ctx, vo.CC.SpaceID, cfEntry.Sys.ID) if errDelete != nil { return fmt.Errorf("CfBrand DeleteEntry: Delete entry failed: %w", errDelete) } @@ -749,7 +749,7 @@ func (cc *ContentfulClient) cacheAllBrand(ctx context.Context, resultChan chan<- } } } else { - col, err = cc.optimisticPageSizeGetAll("brand", cc.optimisticPageSize) + col, err = cc.optimisticPageSizeGetAll(ctx, "brand", cc.optimisticPageSize) if err != nil { return nil, errors.New("optimisticPageSizeGetAll for Brand failed: " + err.Error()) } @@ -761,7 +761,7 @@ func (cc *ContentfulClient) cacheAllBrand(ctx context.Context, resultChan chan<- brandMap := map[string]*CfBrand{} for _, brand := range allBrand { if cc.cacheInit { - existingBrand, err := cc.GetBrandByID(brand.Sys.ID) + existingBrand, err := cc.GetBrandByID(ctx, brand.Sys.ID) if err == nil && existingBrand != nil && existingBrand.Sys.Version > brand.Sys.Version { return nil, fmt.Errorf("cache update canceled because Brand entry %s is newer in cache", brand.Sys.ID) } @@ -807,7 +807,7 @@ func (cc *ContentfulClient) cacheBrandByID(ctx context.Context, id string, entry return errors.New("cacheBrandByID: No client available") } if !entryDelete { - col = cc.Client.Entries.List(cc.SpaceID) + col = cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("brand").Locale("*").Include(0).Equal("sys.id", id) _, err := col.GetAll() if err != nil { diff --git a/test/testapi/gocontentfulvolibcategory.go b/test/testapi/gocontentfulvolibcategory.go index f9d3770..980997a 100644 --- a/test/testapi/gocontentfulvolibcategory.go +++ b/test/testapi/gocontentfulvolibcategory.go @@ -18,7 +18,7 @@ const ContentTypeCategory = "category" // ---Category public methods--- -func (cc *ContentfulClient) GetAllCategory() (voMap map[string]*CfCategory, err error) { +func (cc *ContentfulClient) GetAllCategory(ctx context.Context) (voMap map[string]*CfCategory, err error) { if cc == nil { return nil, errors.New("GetAllCategory: No client available") } @@ -29,7 +29,7 @@ func (cc *ContentfulClient) GetAllCategory() (voMap map[string]*CfCategory, err if cacheInit { return cc.Cache.entryMaps.category, nil } - col, err := cc.optimisticPageSizeGetAll("category", optimisticPageSize) + col, err := cc.optimisticPageSizeGetAll(ctx, "category", optimisticPageSize) if err != nil { return nil, err } @@ -44,11 +44,11 @@ func (cc *ContentfulClient) GetAllCategory() (voMap map[string]*CfCategory, err return categoryMap, nil } -func (cc *ContentfulClient) GetFilteredCategory(query *contentful.Query) (voMap map[string]*CfCategory, err error) { +func (cc *ContentfulClient) GetFilteredCategory(ctx context.Context, query *contentful.Query) (voMap map[string]*CfCategory, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("getFilteredCategory: No client available") } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) if query != nil { col.Query = *query } @@ -68,7 +68,7 @@ func (cc *ContentfulClient) GetFilteredCategory(query *contentful.Query) (voMap return categoryMap, nil } -func (cc *ContentfulClient) GetCategoryByID(id string, forceNoCache ...bool) (vo *CfCategory, err error) { +func (cc *ContentfulClient) GetCategoryByID(ctx context.Context, id string, forceNoCache ...bool) (vo *CfCategory, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("GetCategoryByID: No client available") } @@ -81,7 +81,7 @@ func (cc *ContentfulClient) GetCategoryByID(id string, forceNoCache ...bool) (vo } return nil, fmt.Errorf("GetCategoryByID: entry '%s' not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("category").Locale("*").Include(0).Equal("sys.id", id) _, err = col.GetAll() if err != nil { @@ -115,14 +115,14 @@ func NewCfCategory(contentfulClient ...*ContentfulClient) (cfCategory *CfCategor cfCategory.Sys.ContentType.Sys.LinkType = "ContentType" return } -func (vo *CfCategory) GetParents(contentType ...string) (parents []EntryReference, err error) { +func (vo *CfCategory) GetParents(ctx context.Context, contentType ...string) (parents []EntryReference, err error) { if vo == nil { return nil, errors.New("GetParents: Value Object is nil") } if vo.CC == nil { return nil, errors.New("GetParents: Value Object has no Contentful Client set") } - return commonGetParents(vo.CC, vo.Sys.ID, contentType) + return commonGetParents(ctx, vo.CC, vo.Sys.ID, contentType) } func (vo *CfCategory) GetPublishingStatus() string { @@ -177,7 +177,7 @@ func (vo *CfCategory) Title(locale ...Locale) string { return vo.Fields.Title[string(loc)] } -func (vo *CfCategory) Icon(locale ...Locale) *contentful.AssetNoLocale { +func (vo *CfCategory) Icon(ctx context.Context, locale ...Locale) *contentful.AssetNoLocale { if vo == nil { return nil } @@ -214,7 +214,7 @@ func (vo *CfCategory) Icon(locale ...Locale) *contentful.AssetNoLocale { } } localizedIcon := vo.Fields.Icon[string(loc)] - asset, err := vo.CC.GetAssetByID(localizedIcon.Sys.ID) + asset, err := vo.CC.GetAssetByID(ctx, localizedIcon.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel == LogDebug { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Icon()"}, LogError, ErrNoTypeOfRefAsset) @@ -341,7 +341,7 @@ func (vo *CfCategory) SetCategoryDescription(categoryDescription string, locale return } -func (vo *CfCategory) UpsertEntry() (err error) { +func (vo *CfCategory) UpsertEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpsertEntry: Value Object is nil") } @@ -361,13 +361,13 @@ func (vo *CfCategory) UpsertEntry() (err error) { return errors.New("CfCategory UpsertEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfCategory UpsertEntry: Operation failed: %w", err) } return } -func (vo *CfCategory) PublishEntry() (err error) { +func (vo *CfCategory) PublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("PublishEntry: Value Object is nil") } @@ -386,13 +386,13 @@ func (vo *CfCategory) PublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfCategory PublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfCategory PublishEntry: publish operation failed: %w", err) } return } -func (vo *CfCategory) UnpublishEntry() (err error) { +func (vo *CfCategory) UnpublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UnpublishEntry: Value Object is nil") } @@ -411,13 +411,13 @@ func (vo *CfCategory) UnpublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfCategory UnpublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfCategory UnpublishEntry: unpublish operation failed: %w", err) } return } -func (vo *CfCategory) UpdateEntry() (err error) { +func (vo *CfCategory) UpdateEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpdateEntry: Value Object is nil") } @@ -437,7 +437,7 @@ func (vo *CfCategory) UpdateEntry() (err error) { return errors.New("CfCategory UpdateEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfCategory UpdateEntry: upsert operation failed: %w", err) } @@ -449,13 +449,13 @@ func (vo *CfCategory) UpdateEntry() (err error) { if errUnmarshal != nil { return errors.New("CfCategory UpdateEntry: Can't unmarshal JSON back into VO") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfCategory UpdateEntry: publish operation failed: %w", err) } return } -func (vo *CfCategory) DeleteEntry() (err error) { +func (vo *CfCategory) DeleteEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("DeleteEntry: Value Object is nil") } @@ -475,12 +475,12 @@ func (vo *CfCategory) DeleteEntry() (err error) { return errors.New("CfCategory DeleteEntry: Can't unmarshal JSON into CF entry") } if cfEntry.Sys.PublishedCounter > 0 { - errUnpublish := vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + errUnpublish := vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return fmt.Errorf("CfCategory DeleteEntry: Unpublish entry failed: %w", errUnpublish) } } - errDelete := vo.CC.Client.Entries.Delete(vo.CC.SpaceID, cfEntry.Sys.ID) + errDelete := vo.CC.Client.Entries.Delete(ctx, vo.CC.SpaceID, cfEntry.Sys.ID) if errDelete != nil { return fmt.Errorf("CfCategory DeleteEntry: Delete entry failed: %w", errDelete) } @@ -513,7 +513,7 @@ func (cc *ContentfulClient) cacheAllCategory(ctx context.Context, resultChan cha } } } else { - col, err = cc.optimisticPageSizeGetAll("category", cc.optimisticPageSize) + col, err = cc.optimisticPageSizeGetAll(ctx, "category", cc.optimisticPageSize) if err != nil { return nil, errors.New("optimisticPageSizeGetAll for Category failed: " + err.Error()) } @@ -525,7 +525,7 @@ func (cc *ContentfulClient) cacheAllCategory(ctx context.Context, resultChan cha categoryMap := map[string]*CfCategory{} for _, category := range allCategory { if cc.cacheInit { - existingCategory, err := cc.GetCategoryByID(category.Sys.ID) + existingCategory, err := cc.GetCategoryByID(ctx, category.Sys.ID) if err == nil && existingCategory != nil && existingCategory.Sys.Version > category.Sys.Version { return nil, fmt.Errorf("cache update canceled because Category entry %s is newer in cache", category.Sys.ID) } @@ -571,7 +571,7 @@ func (cc *ContentfulClient) cacheCategoryByID(ctx context.Context, id string, en return errors.New("cacheCategoryByID: No client available") } if !entryDelete { - col = cc.Client.Entries.List(cc.SpaceID) + col = cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("category").Locale("*").Include(0).Equal("sys.id", id) _, err := col.GetAll() if err != nil { diff --git a/test/testapi/gocontentfulvolibproduct.go b/test/testapi/gocontentfulvolibproduct.go index d767562..83df2ed 100644 --- a/test/testapi/gocontentfulvolibproduct.go +++ b/test/testapi/gocontentfulvolibproduct.go @@ -18,7 +18,7 @@ const ContentTypeProduct = "product" // ---Product public methods--- -func (cc *ContentfulClient) GetAllProduct() (voMap map[string]*CfProduct, err error) { +func (cc *ContentfulClient) GetAllProduct(ctx context.Context) (voMap map[string]*CfProduct, err error) { if cc == nil { return nil, errors.New("GetAllProduct: No client available") } @@ -29,7 +29,7 @@ func (cc *ContentfulClient) GetAllProduct() (voMap map[string]*CfProduct, err er if cacheInit { return cc.Cache.entryMaps.product, nil } - col, err := cc.optimisticPageSizeGetAll("product", optimisticPageSize) + col, err := cc.optimisticPageSizeGetAll(ctx, "product", optimisticPageSize) if err != nil { return nil, err } @@ -44,11 +44,11 @@ func (cc *ContentfulClient) GetAllProduct() (voMap map[string]*CfProduct, err er return productMap, nil } -func (cc *ContentfulClient) GetFilteredProduct(query *contentful.Query) (voMap map[string]*CfProduct, err error) { +func (cc *ContentfulClient) GetFilteredProduct(ctx context.Context, query *contentful.Query) (voMap map[string]*CfProduct, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("getFilteredProduct: No client available") } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) if query != nil { col.Query = *query } @@ -68,7 +68,7 @@ func (cc *ContentfulClient) GetFilteredProduct(query *contentful.Query) (voMap m return productMap, nil } -func (cc *ContentfulClient) GetProductByID(id string, forceNoCache ...bool) (vo *CfProduct, err error) { +func (cc *ContentfulClient) GetProductByID(ctx context.Context, id string, forceNoCache ...bool) (vo *CfProduct, err error) { if cc == nil || cc.Client == nil { return nil, errors.New("GetProductByID: No client available") } @@ -81,7 +81,7 @@ func (cc *ContentfulClient) GetProductByID(id string, forceNoCache ...bool) (vo } return nil, fmt.Errorf("GetProductByID: entry '%s' not found in cache", id) } - col := cc.Client.Entries.List(cc.SpaceID) + col := cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("product").Locale("*").Include(0).Equal("sys.id", id) _, err = col.GetAll() if err != nil { @@ -137,14 +137,14 @@ func NewCfProduct(contentfulClient ...*ContentfulClient) (cfProduct *CfProduct) cfProduct.Sys.ContentType.Sys.LinkType = "ContentType" return } -func (vo *CfProduct) GetParents(contentType ...string) (parents []EntryReference, err error) { +func (vo *CfProduct) GetParents(ctx context.Context, contentType ...string) (parents []EntryReference, err error) { if vo == nil { return nil, errors.New("GetParents: Value Object is nil") } if vo.CC == nil { return nil, errors.New("GetParents: Value Object has no Contentful Client set") } - return commonGetParents(vo.CC, vo.Sys.ID, contentType) + return commonGetParents(ctx, vo.CC, vo.Sys.ID, contentType) } func (vo *CfProduct) GetPublishingStatus() string { @@ -310,7 +310,7 @@ func (vo *CfProduct) Sizetypecolor(locale ...Locale) string { return vo.Fields.Sizetypecolor[string(loc)] } -func (vo *CfProduct) Image(locale ...Locale) []*contentful.AssetNoLocale { +func (vo *CfProduct) Image(ctx context.Context, locale ...Locale) []*contentful.AssetNoLocale { if vo == nil { return nil } @@ -348,7 +348,7 @@ func (vo *CfProduct) Image(locale ...Locale) []*contentful.AssetNoLocale { } } for _, eachLocalizedImage := range vo.Fields.Image[string(loc)] { - asset, err := vo.CC.GetAssetByID(eachLocalizedImage.Sys.ID) + asset, err := vo.CC.GetAssetByID(ctx, eachLocalizedImage.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel == LogDebug { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Image()"}, LogError, ErrNoTypeOfRefAsset) @@ -415,7 +415,7 @@ func (vo *CfProduct) Tags(locale ...Locale) []string { return vo.Fields.Tags[string(loc)] } -func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference { +func (vo *CfProduct) Categories(ctx context.Context, locale ...Locale) []*EntryReference { if vo == nil { return nil } @@ -451,7 +451,7 @@ func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference { } } for _, eachLocalizedCategories := range vo.Fields.Categories[string(loc)] { - contentType, err := vo.CC.GetContentTypeOfID(eachLocalizedCategories.Sys.ID) + contentType, err := vo.CC.GetContentTypeOfID(ctx, eachLocalizedCategories.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Categories()"}, LogError, ErrNoTypeOfRefEntry) @@ -461,7 +461,7 @@ func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference { switch contentType { case ContentTypeBrand: - referencedVO, err := vo.CC.GetBrandByID(eachLocalizedCategories.Sys.ID) + referencedVO, err := vo.CC.GetBrandByID(ctx, eachLocalizedCategories.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Categories()"}, LogError, err) @@ -471,7 +471,7 @@ func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference { categories = append(categories, &EntryReference{ContentType: contentType, ID: eachLocalizedCategories.Sys.ID, VO: referencedVO}) case ContentTypeCategory: - referencedVO, err := vo.CC.GetCategoryByID(eachLocalizedCategories.Sys.ID) + referencedVO, err := vo.CC.GetCategoryByID(ctx, eachLocalizedCategories.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Categories()"}, LogError, err) @@ -481,7 +481,7 @@ func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference { categories = append(categories, &EntryReference{ContentType: contentType, ID: eachLocalizedCategories.Sys.ID, VO: referencedVO}) case ContentTypeProduct: - referencedVO, err := vo.CC.GetProductByID(eachLocalizedCategories.Sys.ID) + referencedVO, err := vo.CC.GetProductByID(ctx, eachLocalizedCategories.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Categories()"}, LogError, err) @@ -532,7 +532,7 @@ func (vo *CfProduct) Price(locale ...Locale) float64 { return vo.Fields.Price[string(loc)] } -func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { +func (vo *CfProduct) Brand(ctx context.Context, locale ...Locale) *EntryReference { if vo == nil { return nil } @@ -567,7 +567,7 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { } } localizedBrand := vo.Fields.Brand[string(loc)] - contentType, err := vo.CC.GetContentTypeOfID(localizedBrand.Sys.ID) + contentType, err := vo.CC.GetContentTypeOfID(ctx, localizedBrand.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Brand()"}, LogError, ErrNoTypeOfRefEntry) @@ -577,7 +577,7 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { switch contentType { case ContentTypeBrand: - referencedVO, err := vo.CC.GetBrandByID(localizedBrand.Sys.ID) + referencedVO, err := vo.CC.GetBrandByID(ctx, localizedBrand.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Brand()"}, LogError, err) @@ -587,7 +587,7 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { return &EntryReference{ContentType: contentType, ID: localizedBrand.Sys.ID, VO: referencedVO} case ContentTypeCategory: - referencedVO, err := vo.CC.GetCategoryByID(localizedBrand.Sys.ID) + referencedVO, err := vo.CC.GetCategoryByID(ctx, localizedBrand.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Brand()"}, LogError, err) @@ -597,7 +597,7 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { return &EntryReference{ContentType: contentType, ID: localizedBrand.Sys.ID, VO: referencedVO} case ContentTypeProduct: - referencedVO, err := vo.CC.GetProductByID(localizedBrand.Sys.ID) + referencedVO, err := vo.CC.GetProductByID(ctx, localizedBrand.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "Brand()"}, LogError, err) @@ -1079,7 +1079,7 @@ func (vo *CfProduct) SetNodes(nodes interface{}, locale ...Locale) (err error) { return } -func (vo *CfProduct) UpsertEntry() (err error) { +func (vo *CfProduct) UpsertEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpsertEntry: Value Object is nil") } @@ -1099,13 +1099,13 @@ func (vo *CfProduct) UpsertEntry() (err error) { return errors.New("CfProduct UpsertEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfProduct UpsertEntry: Operation failed: %w", err) } return } -func (vo *CfProduct) PublishEntry() (err error) { +func (vo *CfProduct) PublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("PublishEntry: Value Object is nil") } @@ -1124,13 +1124,13 @@ func (vo *CfProduct) PublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfProduct PublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfProduct PublishEntry: publish operation failed: %w", err) } return } -func (vo *CfProduct) UnpublishEntry() (err error) { +func (vo *CfProduct) UnpublishEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UnpublishEntry: Value Object is nil") } @@ -1149,13 +1149,13 @@ func (vo *CfProduct) UnpublishEntry() (err error) { if errUnmarshal != nil { return errors.New("CfProduct UnpublishEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfProduct UnpublishEntry: unpublish operation failed: %w", err) } return } -func (vo *CfProduct) UpdateEntry() (err error) { +func (vo *CfProduct) UpdateEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("UpdateEntry: Value Object is nil") } @@ -1175,7 +1175,7 @@ func (vo *CfProduct) UpdateEntry() (err error) { return errors.New("CfProduct UpdateEntry: Can't unmarshal JSON into CF entry") } - err = vo.CC.Client.Entries.Upsert(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Upsert(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfProduct UpdateEntry: upsert operation failed: %w", err) } @@ -1187,13 +1187,13 @@ func (vo *CfProduct) UpdateEntry() (err error) { if errUnmarshal != nil { return errors.New("CfProduct UpdateEntry: Can't unmarshal JSON back into VO") } - err = vo.CC.Client.Entries.Publish(vo.CC.SpaceID, cfEntry) + err = vo.CC.Client.Entries.Publish(ctx, vo.CC.SpaceID, cfEntry) if err != nil { return fmt.Errorf("CfProduct UpdateEntry: publish operation failed: %w", err) } return } -func (vo *CfProduct) DeleteEntry() (err error) { +func (vo *CfProduct) DeleteEntry(ctx context.Context) (err error) { if vo == nil { return errors.New("DeleteEntry: Value Object is nil") } @@ -1213,12 +1213,12 @@ func (vo *CfProduct) DeleteEntry() (err error) { return errors.New("CfProduct DeleteEntry: Can't unmarshal JSON into CF entry") } if cfEntry.Sys.PublishedCounter > 0 { - errUnpublish := vo.CC.Client.Entries.Unpublish(vo.CC.SpaceID, cfEntry) + errUnpublish := vo.CC.Client.Entries.Unpublish(ctx, vo.CC.SpaceID, cfEntry) if errUnpublish != nil && !strings.Contains(errUnpublish.Error(), "Not published") { return fmt.Errorf("CfProduct DeleteEntry: Unpublish entry failed: %w", errUnpublish) } } - errDelete := vo.CC.Client.Entries.Delete(vo.CC.SpaceID, cfEntry.Sys.ID) + errDelete := vo.CC.Client.Entries.Delete(ctx, vo.CC.SpaceID, cfEntry.Sys.ID) if errDelete != nil { return fmt.Errorf("CfProduct DeleteEntry: Delete entry failed: %w", errDelete) } @@ -1251,7 +1251,7 @@ func (cc *ContentfulClient) cacheAllProduct(ctx context.Context, resultChan chan } } } else { - col, err = cc.optimisticPageSizeGetAll("product", cc.optimisticPageSize) + col, err = cc.optimisticPageSizeGetAll(ctx, "product", cc.optimisticPageSize) if err != nil { return nil, errors.New("optimisticPageSizeGetAll for Product failed: " + err.Error()) } @@ -1263,7 +1263,7 @@ func (cc *ContentfulClient) cacheAllProduct(ctx context.Context, resultChan chan productMap := map[string]*CfProduct{} for _, product := range allProduct { if cc.cacheInit { - existingProduct, err := cc.GetProductByID(product.Sys.ID) + existingProduct, err := cc.GetProductByID(ctx, product.Sys.ID) if err == nil && existingProduct != nil && existingProduct.Sys.Version > product.Sys.Version { return nil, fmt.Errorf("cache update canceled because Product entry %s is newer in cache", product.Sys.ID) } @@ -1333,7 +1333,7 @@ func (cc *ContentfulClient) cacheProductByID(ctx context.Context, id string, ent return errors.New("cacheProductByID: No client available") } if !entryDelete { - col = cc.Client.Entries.List(cc.SpaceID) + col = cc.Client.Entries.List(ctx, cc.SpaceID) col.Query.ContentType("product").Locale("*").Include(0).Equal("sys.id", id) _, err := col.GetAll() if err != nil { From 763644f496ef291bb02921595b21c746c8f996b6 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:38:26 +0200 Subject: [PATCH 11/37] feat: bump foomo/contentful-v0.5.0 --- go.mod | 3 ++- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c69b9e9..f7680ad 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/foomo/gocontentful go 1.21 require ( - github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0 + github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.7.0 @@ -13,6 +13,7 @@ require ( ) require ( + github.com/aoliveti/curling v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect diff --git a/go.sum b/go.sum index b2038fc..a84bbf7 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,12 @@ +github.com/aoliveti/curling v1.1.0 h1:/1k05HmPUEGYXNHo3aX5BWRJWvWQbiU0A7n9ugqhdLY= +github.com/aoliveti/curling v1.1.0/go.mod h1:xoDmoUg9vX3pMTltyG/rp9tFtIlweL2QeCJVnvyAvzw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0 h1:RrL0mO0RhA7e01Uogjz2AWEletuNUFQymyn8PJZEnoQ= github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0/go.mod h1:x9QtObXj7qKw4Rpxg6kI6bi6RGXzD7o/l+XPj1cr2Og= +github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f h1:NbSeTpZVyIFa2tGHugJ+znP6StgwLnu/NUmuCV8LTjU= +github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f/go.mod h1:vdEkAQ25w3O4RjpdnIj2PDyTrErf7Snhc73HIXO5+cc= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= From 473a3e7be6c325ee4f00b1d6fdbf3eaa455a4cdb Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:49:31 +0200 Subject: [PATCH 12/37] feat: remove version from generated code --- erm/templates/contentful_vo.gotmpl | 2 +- erm/templates/contentful_vo_base.gotmpl | 4 ++-- erm/templates/contentful_vo_lib.gotmpl | 2 +- erm/templates/contentful_vo_lib_contenttype.gotmpl | 2 +- test/testapi/gocontentfulvo.go | 2 +- test/testapi/gocontentfulvobase.go | 2 +- test/testapi/gocontentfulvolib.go | 2 +- test/testapi/gocontentfulvolibbrand.go | 2 +- test/testapi/gocontentfulvolibcategory.go | 2 +- test/testapi/gocontentfulvolibproduct.go | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/erm/templates/contentful_vo.gotmpl b/erm/templates/contentful_vo.gotmpl index f1a00ac..d853962 100644 --- a/erm/templates/contentful_vo.gotmpl +++ b/erm/templates/contentful_vo.gotmpl @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful {{ .Version }} - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. {{ $cfg := . }}{{ $contentTypes := .ContentTypes }}package {{ .PackageName }} import "github.com/foomo/contentful" diff --git a/erm/templates/contentful_vo_base.gotmpl b/erm/templates/contentful_vo_base.gotmpl index b8f5e80..d547c0b 100644 --- a/erm/templates/contentful_vo_base.gotmpl +++ b/erm/templates/contentful_vo_base.gotmpl @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful {{ .Version }} - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. {{ $cfg := . }}package {{ .PackageName }} import "github.com/foomo/contentful" @@ -148,4 +148,4 @@ const ( StatusDraft = "draft" StatusChanged = "changed" StatusPublished = "published" -) \ No newline at end of file +) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index f7d66fe..eaceed0 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful {{ .Version }} - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. {{ $cfg := . }}{{ $contentTypes := .ContentTypes }}{{ $locales := .Locales }}package {{ .PackageName }} import ( diff --git a/erm/templates/contentful_vo_lib_contenttype.gotmpl b/erm/templates/contentful_vo_lib_contenttype.gotmpl index 586ad12..fe147f2 100644 --- a/erm/templates/contentful_vo_lib_contenttype.gotmpl +++ b/erm/templates/contentful_vo_lib_contenttype.gotmpl @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful {{ .Version }} - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. {{ $cfg := . }}{{ $contentTypes := .ContentTypes }}{{ $contentType := .ContentType }}package {{ .PackageName }} import ( diff --git a/test/testapi/gocontentfulvo.go b/test/testapi/gocontentfulvo.go index 89ba373..0a7cd59 100644 --- a/test/testapi/gocontentfulvo.go +++ b/test/testapi/gocontentfulvo.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( diff --git a/test/testapi/gocontentfulvobase.go b/test/testapi/gocontentfulvobase.go index 3cac386..cd0c480 100644 --- a/test/testapi/gocontentfulvobase.go +++ b/test/testapi/gocontentfulvobase.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import "github.com/foomo/contentful" diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index f24d879..13f53e7 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( diff --git a/test/testapi/gocontentfulvolibbrand.go b/test/testapi/gocontentfulvolibbrand.go index 141f846..377fbfd 100644 --- a/test/testapi/gocontentfulvolibbrand.go +++ b/test/testapi/gocontentfulvolibbrand.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( diff --git a/test/testapi/gocontentfulvolibcategory.go b/test/testapi/gocontentfulvolibcategory.go index 980997a..478a5f5 100644 --- a/test/testapi/gocontentfulvolibcategory.go +++ b/test/testapi/gocontentfulvolibcategory.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( diff --git a/test/testapi/gocontentfulvolibproduct.go b/test/testapi/gocontentfulvolibproduct.go index 83df2ed..e8112ec 100644 --- a/test/testapi/gocontentfulvolibproduct.go +++ b/test/testapi/gocontentfulvolibproduct.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.0.26 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( From 6b3a933fcb832d6cd1e667eb2d10d8a1c7afd180 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:49:48 +0200 Subject: [PATCH 13/37] feat: use latest for development --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index b86372e..80857e2 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "github.com/foomo/gocontentful/erm" ) -var VERSION = "v1.0.26" +var VERSION = "latest" type contentfulRc struct { ManagementToken string `json:"managementToken"` From d02418aaa299f891a1ec0af70ea553e56c14ec99 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 13:49:56 +0200 Subject: [PATCH 14/37] chore: fix name --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 32c31d5..e0c0be4 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ doc: .PHONY: install ## Install binary install: - @go build -o ${GOPATH}/bin/gocontenful main.go + go build -o ${GOPATH}/bin/gocontentful main.go .PHONY: build ## Build binary From fdf6770494e0e8c378fe65a4376dfd40717fefa5 Mon Sep 17 00:00:00 2001 From: franklin Date: Fri, 26 Apr 2024 14:03:26 +0200 Subject: [PATCH 15/37] fix: handle context deadline --- erm/templates/contentful_vo_lib.gotmpl | 45 +++++++++++++++++++---- test/testapi/gocontentfulvolib.go | 51 ++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index eaceed0..12238e5 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -169,8 +169,8 @@ var ( InfoPreservingExistingCache = "could not connect for cache update, preserving the existing cache" InfoUpdateCacheTime = "space caching done, time recorded" ErrorEnvironmentSetToMaster = "environment was empty string, set to master" - ErrorEntryIsNil = "entry is nil" - ErrorEntrySysIsNil = "entry.Sys is nil" + ErrorEntryIsNil = "entry is nil" + ErrorEntrySysIsNil = "entry.Sys is nil" ErrorEntrySysContentTypeIsNil = "entry.Sys.ContentType is nil" ErrorEntrySysContentTypeSysIsNil = "entry.Sys.ContentType.Sys is nil" ) @@ -682,6 +682,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"task": "syncCache"}, LogInfo, InfoCacheUpdateQueued) } for { + if ctx.Err() != nil { + return ctx.Err() + } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( ctx, @@ -704,6 +707,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string var entries []*contentful.Entry var assets []*contentful.Asset for _, item := range col.Items { + if ctx.Err() != nil { + return ctx.Err() + } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) errEntry := json.NewDecoder(bytes.NewReader(byteArray)).Decode(entry) @@ -723,26 +729,48 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string } } for _, entry := range entries { + if ctx.Err() != nil { + return ctx.Err() + } switch entry.Sys.Type { case sysTypeEntry: if !stringSliceContains(spaceContentTypes, entry.Sys.ContentType.Sys.ID) { continue } - updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false) + if err := updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") + } + } case sysTypeDeletedEntry: cc.cacheMutex.idContentTypeMapGcLock.RLock() contentType := cc.Cache.idContentTypeMap[entry.Sys.ID] cc.cacheMutex.idContentTypeMapGcLock.RUnlock() - updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true) + if err := updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") + } + } default: } } for _, asset := range assets { + if ctx.Err() != nil { + return ctx.Err() + } switch asset.Sys.Type { case sysTypeAsset: - updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false) + if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") + } + } case sysTypeDeletedAsset: - updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true) + if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") + } + } default: } } @@ -1571,7 +1599,10 @@ func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, cont switch entry.Sys.ContentType.Sys.ID { {{ range $index , $contentType := $contentTypes }} case ContentType{{ firstCap $contentType.Sys.ID }}: var parentVO Cf{{ firstCap $contentType.Sys.ID }} - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + return nil, errors.New("GetParents: " + err.Error()) + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&parentVO) if err != nil { return nil, errors.New("GetParents: " + err.Error()) diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 13f53e7..73e7256 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -716,6 +716,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"task": "syncCache"}, LogInfo, InfoCacheUpdateQueued) } for { + if ctx.Err() != nil { + return ctx.Err() + } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( ctx, @@ -738,6 +741,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string var entries []*contentful.Entry var assets []*contentful.Asset for _, item := range col.Items { + if ctx.Err() != nil { + return ctx.Err() + } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) errEntry := json.NewDecoder(bytes.NewReader(byteArray)).Decode(entry) @@ -757,26 +763,48 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string } } for _, entry := range entries { + if ctx.Err() != nil { + return ctx.Err() + } switch entry.Sys.Type { case sysTypeEntry: if !stringSliceContains(spaceContentTypes, entry.Sys.ContentType.Sys.ID) { continue } - updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false) + if err := updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") + } + } case sysTypeDeletedEntry: cc.cacheMutex.idContentTypeMapGcLock.RLock() contentType := cc.Cache.idContentTypeMap[entry.Sys.ID] cc.cacheMutex.idContentTypeMapGcLock.RUnlock() - updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true) + if err := updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") + } + } default: } } for _, asset := range assets { + if ctx.Err() != nil { + return ctx.Err() + } switch asset.Sys.Type { case sysTypeAsset: - updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false) + if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") + } + } case sysTypeDeletedAsset: - updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true) + if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true); err != nil { + if cc.logFn != nil && cc.logLevel <= LogWarn { + cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") + } + } default: } } @@ -1663,7 +1691,10 @@ func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, cont switch entry.Sys.ContentType.Sys.ID { case ContentTypeBrand: var parentVO CfBrand - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + return nil, errors.New("GetParents: " + err.Error()) + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&parentVO) if err != nil { return nil, errors.New("GetParents: " + err.Error()) @@ -1678,7 +1709,10 @@ func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, cont case ContentTypeCategory: var parentVO CfCategory - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + return nil, errors.New("GetParents: " + err.Error()) + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&parentVO) if err != nil { return nil, errors.New("GetParents: " + err.Error()) @@ -1693,7 +1727,10 @@ func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, cont case ContentTypeProduct: var parentVO CfProduct - byteArray, _ := json.Marshal(item) + byteArray, err := json.Marshal(item) + if err != nil { + return nil, errors.New("GetParents: " + err.Error()) + } err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&parentVO) if err != nil { return nil, errors.New("GetParents: " + err.Error()) From 7fc32425da015823041a0e9393f52504a7716495 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Sat, 27 Apr 2024 19:49:35 +0200 Subject: [PATCH 16/37] feat: bump contentful-v0.5.1 --- go.mod | 7 ++----- go.sum | 20 ++++++-------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index f7680ad..55f005d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/foomo/gocontentful go 1.21 require ( - github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f + github.com/foomo/contentful v0.5.1 + github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.7.0 @@ -15,11 +16,7 @@ require ( require ( github.com/aoliveti/curling v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect - github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/smartystreets/assertions v1.2.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sys v0.19.0 // indirect - moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index a84bbf7..7faa63f 100644 --- a/go.sum +++ b/go.sum @@ -3,22 +3,16 @@ github.com/aoliveti/curling v1.1.0/go.mod h1:xoDmoUg9vX3pMTltyG/rp9tFtIlweL2QeCJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0 h1:RrL0mO0RhA7e01Uogjz2AWEletuNUFQymyn8PJZEnoQ= -github.com/foomo/contentful v0.4.6-0.20240426090016-36498310ced0/go.mod h1:x9QtObXj7qKw4Rpxg6kI6bi6RGXzD7o/l+XPj1cr2Og= -github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f h1:NbSeTpZVyIFa2tGHugJ+znP6StgwLnu/NUmuCV8LTjU= -github.com/foomo/contentful v0.4.6-0.20240426113637-1a2238ef6a0f/go.mod h1:vdEkAQ25w3O4RjpdnIj2PDyTrErf7Snhc73HIXO5+cc= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/foomo/contentful v0.5.1 h1:qQTEUAtlO5MffaXMgl5p01zIgqyoWZZH8U9YnWFO0uw= +github.com/foomo/contentful v0.5.1/go.mod h1:vdEkAQ25w3O4RjpdnIj2PDyTrErf7Snhc73HIXO5+cc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -39,5 +33,3 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= -moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= From 6585f176defdf8bcf87ebb89f4b3e2d031699b5a Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Sat, 27 Apr 2024 19:49:46 +0200 Subject: [PATCH 17/37] feat: wrap errors --- erm/generator.go | 13 +++++++------ erm/space.go | 7 ++++--- erm/template.go | 6 ++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/erm/generator.go b/erm/generator.go index 49f8718..417bc44 100644 --- a/erm/generator.go +++ b/erm/generator.go @@ -9,6 +9,7 @@ import ( "text/template" "github.com/foomo/gocontentful/erm/templates" + "github.com/pkg/errors" "golang.org/x/tools/imports" ) @@ -32,19 +33,19 @@ func generate(filename string, tpl []byte, conf spaceConf) error { fmt.Println("Processing", filename) tmpl, err := template.New("generate-" + filename).Funcs(conf.FuncMap).Parse(string(tpl)) if err != nil { - return err + return errors.Wrap(err, "failed to parse template") } f, err := os.Create(filename) if err != nil { - return err + return errors.Wrap(err, "failed to create file") } err = tmpl.Execute(f, conf) if err != nil { - return err + return errors.Wrap(err, "failed to execute template") } errFormatAndFix := formatAndFixImports(filename) if errFormatAndFix != nil { - return errFormatAndFix + return errors.Wrap(errFormatAndFix, "failed to format and fix imports") } return nil @@ -59,7 +60,7 @@ func generateCode(conf spaceConf) (err error) { } { errGenerate := generate(file, tpl, conf) if errGenerate != nil { - return errGenerate + return errors.Wrapf(errGenerate, "failed to generate code (%s)", file) } } for _, contentType := range conf.ContentTypes { @@ -70,7 +71,7 @@ func generateCode(conf spaceConf) (err error) { conf, ) if errGenerate != nil { - return errGenerate + return errors.Wrapf(errGenerate, "failed to generate code for content type (%s)", contentType.Name) } } return diff --git a/erm/space.go b/erm/space.go index face218..93c890a 100644 --- a/erm/space.go +++ b/erm/space.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/foomo/contentful" + "github.com/pkg/errors" ) // spaceConf is the space config object passed to the template @@ -140,16 +141,16 @@ func getData(ctx context.Context, spaceID, cmaKey, environment, exportFile strin } // GenerateAPI calls the generators -func GenerateAPI(ctx context.Context, dir, packageName, spaceID, cmaKey, environment, exportFile string, flagContentTypes []string, version string) (err error) { +func GenerateAPI(ctx context.Context, dir, packageName, spaceID, cmaKey, environment, exportFile string, flagContentTypes []string, version string) error { contentTypes, locales, errGetData := getData(ctx, spaceID, cmaKey, environment, exportFile, flagContentTypes) if errGetData != nil { - return errGetData + return errors.Wrap(errGetData, "could not get data") } packageDir := filepath.Join(dir, packageName) errMkdir := os.MkdirAll(packageDir, 0766) if errMkdir != nil { - return errMkdir + return errors.Wrap(errMkdir, "could not create target folder") } funcMap := getFuncMap() conf := spaceConf{ diff --git a/erm/template.go b/erm/template.go index f5ec2fb..2012e72 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,6 +1,7 @@ package erm import ( + "strings" "text/template" "golang.org/x/text/cases" @@ -30,6 +31,7 @@ func getFuncMap() template.FuncMap { "mapFieldType": mapFieldType, "mapFieldTypeLiteral": mapFieldTypeLiteral, "onlyLetters": onlyLetters, + "oneLine": oneLine, } } @@ -153,3 +155,7 @@ func fieldIsBasic(field ContentTypeField) bool { func fieldIsComplex(field ContentTypeField) bool { return field.Type == fieldTypeJSON || field.Type == fieldTypeLocation || field.Type == fieldTypeRichText } + +func oneLine(v string) string { + return strings.ReplaceAll(v, "\n", " ") +} From 15c05d4f62dc0ee0afb248633984b5a582a38573 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Sat, 27 Apr 2024 19:50:24 +0200 Subject: [PATCH 18/37] feat: ensure one line description --- erm/templates/contentful_vo_lib.gotmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 12238e5..668e1d7 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -180,7 +180,7 @@ var SpaceContentTypeInfoMap = ContentTypeInfoMap{ {{ range $index , $contentType "{{ $contentType.Sys.ID }}": ContentTypeInfo{ ContentType: "{{ $contentType.Sys.ID }}", Title: "{{ $contentType.Name }}", - Description: "{{ $contentType.Description }}", + Description: "{{ oneLine $contentType.Description }}", },{{ end }} } func (cc *ContentfulClient) BrokenReferences() (brokenReferences []BrokenReference) { From ff7e08255301326bc8aaf9877c428f9a76d4d03c Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Sat, 27 Apr 2024 19:50:32 +0200 Subject: [PATCH 19/37] feat: handle error --- erm/templates/contentful_vo_lib.gotmpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 668e1d7..26e411f 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -693,7 +693,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.syncToken, ) cc.cacheMutex.sharedDataGcLock.RUnlock() - col.GetAll() + if _, err := col.GetAll(); err != nil { + return err + } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken cc.cacheInit = true From 3ce91a9560e2116f1a5ff6463040efc20f1ad4e8 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Sat, 27 Apr 2024 19:52:21 +0200 Subject: [PATCH 20/37] feat: handle error --- test/testapi/gocontentfulvolib.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 73e7256..f136b1d 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -727,7 +727,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.syncToken, ) cc.cacheMutex.sharedDataGcLock.RUnlock() - col.GetAll() + if _, err := col.GetAll(); err != nil { + return err + } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken cc.cacheInit = true From 22c33006881133e9aa5bcd1bd760810f18c1d0d6 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 29 Apr 2024 13:39:09 +0200 Subject: [PATCH 21/37] missing import --- erm/template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/erm/template.go b/erm/template.go index 03d461a..116ea05 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,6 +1,7 @@ package erm import ( + "strings" "text/template" "golang.org/x/text/cases" From 441c2f91362f1be1d2f752cab072cc861a8a178d Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 29 Apr 2024 14:53:50 +0200 Subject: [PATCH 22/37] chore: integrate and fix more changes from master --- README.md | 47 +++++++++++++++++++---- docs/gettingstarted.md | 14 ++++++- erm/templates/contentful_vo_lib.gotmpl | 20 +++++----- test/cache_test.go | 3 +- test/testapi/gocontentfulvolib.go | 22 ++++++----- test/testapi/gocontentfulvolibbrand.go | 1 - test/testapi/gocontentfulvolibcategory.go | 1 - test/testapi/gocontentfulvolibproduct.go | 11 +++--- 8 files changed, 82 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 8dee674..4cba726 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,42 @@ -# gocontentful +# Gocontentful -A Contentful API code generator for Go. Features: +Gocontentful is a command line tool that generates a set of APIs for the [Go Language](https://go.dev) to interact with a [Contentful](https://www.contentful.com) CMS space. -- Creates and updates a full set of Value Objects from Contentful content model -- Supports CDA, CPA and CMA operations through a simplified, idiomatic Go API based on the model -- Caches entire spaces and handles updates automatically -- Simplifies management/resolution of references -- Adds several utility functions for RichText from/to HTML conversion, assets handling and more +Unlike the plain Contentful API for Go, the Gocontentful API is idiomatic. Go types are provided with names that mirror the content types of the Contentful space, and get/set methods are named after each field. -Full documentation available at [foomo.org](https://www.foomo.org/docs/projects/cms/gocontentful/introduction) +In addition, Gocontentful supports in-memory caching and updates of spaces. This way, the space is always accessible through fast Go function calls, even offline. + +## What is Contentful + +[Contentful](https://www.contentful.com/) is a content platform (often referred to as headless CMS) for [micro-content](https://www.contentful.com/r/knowledgebase/content-as-a-microservice/). + +Unlike traditional CMSes, there's no pages or content trees in Contentful. The data model is built from scratch for the purpose of the consuming application, is completely flexible and can be created and hot-changed through the same Web UI that the content editors use. The model dictates which content types can reference others and the final structure is a graph. + +## How applications interact with Contentful + +Contentful hosts several APIs that remote applications use to create, retrieve, update and delete content. Content is any of the following: + +- **Entries**, each with a content type name and a list of data fields as defined by the developer in the content model editor at Contentful +- **Assets** (images, videos, other binary files) + +The Contentful APIs exist as either REST or GraphQL endpoints. Gocontentful only supports the REST APIs. + +The REST APIs used to manage and retrieve content use standard HTTP verbs (GET, POST, PUT and DELETE) and a JSON payload for both the request (where needed) and the response. + +## What is gocontentful + +A golang API code generator that simplifies interacting with a Contentful space. The generated API: + +- Supports most of the Contentful APIs to perform all read/write operation on entries and assets +- Hides the complexity of the Contentful REST/JSON APIs behind an idiomatic set of golang functions and methods +- Allows for in-memory caching of an entire Contentful space + +## Why we need a Go API generator + +While it's perfectly fine to call a REST service and receive data in JSON format, in Go that is not very practical. For each content type, the developer needs to maintan type definitions by hand and decode the JSON coming from the Contentful server into the value object. + +In addition, calling a remote API across the Internet each time a piece of content is needed, even multiple times for a single page rendering, can have significant impact on performance. + +Gocontentful generates a Go API that handles both issues above and can be regenerated every time the content model changes. The developer never needs to update the types by hand, or deal with the complexity of caching content locally. It all happens auytomatically in the generated client. + +> **NOTE** - _How much code does Gocontentful generate? In a real-world production scenario where Gocontentful is in use as of 2024, a space content model with 43 content types of various field counts generates around 65,000 lines of Go code._ diff --git a/docs/gettingstarted.md b/docs/gettingstarted.md index d514690..22a8265 100644 --- a/docs/gettingstarted.md +++ b/docs/gettingstarted.md @@ -15,7 +15,19 @@ in your IDE. The repository includes an offline representation of a Contentful space that can is used for testing gocontentful without depending on an online connection and an existing Contentful space. -Create a file in the repository home directory and name it `untracked_test.go`. This ensures it's not tracked by git. +First, open a terminal and install + +```bash +go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest +``` + +Then `cd` to the repository folder and make sure tests run fine on your machine + +```bash +make test +``` + +Create a test file in the repository home directory (`api_test.go` might be a good choice). Paste the following into the file: ```go diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 55863f4..a8d1b77 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -625,12 +625,12 @@ func (genericEntry *GenericEntry) FieldAsString(fieldName string, locale ...Loca } } -func (genericEntry *GenericEntry) InheritAsString(fieldName string, parentTypes []string, locale ...Locale) (string, error) { +func (genericEntry *GenericEntry) InheritAsString(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (string, error) { val, err := genericEntry.FieldAsString(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return "", err } @@ -681,12 +681,12 @@ func (genericEntry *GenericEntry) FieldAsFloat64(fieldName string, locale ...Loc } } -func (genericEntry *GenericEntry) InheritAsFloat64(fieldName string, parentTypes []string, locale ...Locale) (float64, error) { +func (genericEntry *GenericEntry) InheritAsFloat64(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (float64, error) { val, err := genericEntry.FieldAsFloat64(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return 0, err } @@ -749,12 +749,12 @@ func (genericEntry *GenericEntry) FieldAsReference(fieldName string, locale ...L return nil, ErrNotSet } -func (genericEntry *GenericEntry) InheritAsReference(fieldName string, parentTypes []string, locale ...Locale) (*EntryReference, error) { +func (genericEntry *GenericEntry) InheritAsReference(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (*EntryReference, error) { val, err := genericEntry.FieldAsReference(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return nil, err } @@ -800,7 +800,7 @@ func (genericEntry *GenericEntry) SetField(fieldName string, fieldValue interfac } -func (genericEntry *GenericEntry) Upsert() error { +func (genericEntry *GenericEntry) Upsert(ctx context.Context) error { cfEntry := &contentful.Entry{ Fields: map[string]interface{}{}, } @@ -818,7 +818,7 @@ func (genericEntry *GenericEntry) Upsert() error { cfEntry.Fields[key] = fieldValue } // upsert the entry - err := genericEntry.CC.Client.Entries.Upsert(genericEntry.CC.SpaceID, cfEntry) + err := genericEntry.CC.Client.Entries.Upsert(ctx, genericEntry.CC.SpaceID, cfEntry) if err != nil { if genericEntry.CC.logFn != nil && genericEntry.CC.logLevel <= LogWarn { genericEntry.CC.logFn(map[string]interface{}{"task": "UpdateCache"}, LogWarn, fmt.Errorf("CfAkeneoSettings UpsertEntry: Operation failed: %w", err)) @@ -996,6 +996,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if err := updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false); err != nil { if cc.logFn != nil && cc.logLevel <= LogWarn { cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") + } } else { syncEntryCount[entry.Sys.ContentType.Sys.ID]++ } @@ -1006,6 +1007,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if err := updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true); err != nil { if cc.logFn != nil && cc.logLevel <= LogWarn { cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") + } } else { syncEntryCount["deletedEntry"]++ } @@ -1836,7 +1838,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { +func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentTypes []string) (parents []EntryReference, err error) { parents = []EntryReference{} cc.cacheMutex.sharedDataGcLock.RLock() cacheInit := cc.cacheInit diff --git a/test/cache_test.go b/test/cache_test.go index 30fc4df..16418b9 100644 --- a/test/cache_test.go +++ b/test/cache_test.go @@ -140,7 +140,8 @@ func TestGenericEntries(t *testing.T) { sku, err := genericProduct.FieldAsString("sku") require.Error(t, err) require.Equal(t, "", sku) - inheritedSKU, err := genericProduct.InheritAsString("sku", nil) + ctx := context.Background() + inheritedSKU, err := genericProduct.InheritAsString(ctx, "sku", nil) require.NoError(t, err) require.Equal(t, "B00MG4ULK2", inheritedSKU) } diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 7bdf475..a0a4480 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -178,9 +178,9 @@ var ( InfoPreservingExistingCache = "could not connect for cache update, preserving the existing cache" InfoUpdateCacheTime = "space caching done, time recorded" ErrorEnvironmentSetToMaster = "environment was empty string, set to master" - ErrorEntryNotFound = "entry not found" ErrorEntryIsNil = "entry is nil" ErrorEntrySysIsNil = "entry.Sys is nil" + ErrorEntryNotFound = "entry not found" ErrorEntrySysContentTypeIsNil = "entry.Sys.ContentType is nil" ErrorEntrySysContentTypeSysIsNil = "entry.Sys.ContentType.Sys is nil" ErrorEntryCachingFailed = "entry caching failed" @@ -657,12 +657,12 @@ func (genericEntry *GenericEntry) FieldAsString(fieldName string, locale ...Loca } } -func (genericEntry *GenericEntry) InheritAsString(fieldName string, parentTypes []string, locale ...Locale) (string, error) { +func (genericEntry *GenericEntry) InheritAsString(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (string, error) { val, err := genericEntry.FieldAsString(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return "", err } @@ -713,12 +713,12 @@ func (genericEntry *GenericEntry) FieldAsFloat64(fieldName string, locale ...Loc } } -func (genericEntry *GenericEntry) InheritAsFloat64(fieldName string, parentTypes []string, locale ...Locale) (float64, error) { +func (genericEntry *GenericEntry) InheritAsFloat64(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (float64, error) { val, err := genericEntry.FieldAsFloat64(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return 0, err } @@ -781,12 +781,12 @@ func (genericEntry *GenericEntry) FieldAsReference(fieldName string, locale ...L return nil, ErrNotSet } -func (genericEntry *GenericEntry) InheritAsReference(fieldName string, parentTypes []string, locale ...Locale) (*EntryReference, error) { +func (genericEntry *GenericEntry) InheritAsReference(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (*EntryReference, error) { val, err := genericEntry.FieldAsReference(fieldName, locale...) if err == nil { return val, nil } - parentRefs, err := commonGetParents(genericEntry.CC, genericEntry.Sys.ID, parentTypes) + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) if err != nil { return nil, err } @@ -831,7 +831,7 @@ func (genericEntry *GenericEntry) SetField(fieldName string, fieldValue interfac return ErrNotSet } -func (genericEntry *GenericEntry) Upsert() error { +func (genericEntry *GenericEntry) Upsert(ctx context.Context) error { cfEntry := &contentful.Entry{ Fields: map[string]interface{}{}, } @@ -849,7 +849,7 @@ func (genericEntry *GenericEntry) Upsert() error { cfEntry.Fields[key] = fieldValue } // upsert the entry - err := genericEntry.CC.Client.Entries.Upsert(genericEntry.CC.SpaceID, cfEntry) + err := genericEntry.CC.Client.Entries.Upsert(ctx, genericEntry.CC.SpaceID, cfEntry) if err != nil { if genericEntry.CC.logFn != nil && genericEntry.CC.logLevel <= LogWarn { genericEntry.CC.logFn(map[string]interface{}{"task": "UpdateCache"}, LogWarn, fmt.Errorf("CfAkeneoSettings UpsertEntry: Operation failed: %w", err)) @@ -1059,6 +1059,8 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogWarn { cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } + } else { + syncAssetCount++ } case sysTypeDeletedAsset: if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true); err != nil { @@ -1943,7 +1945,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentType []string) (parents []EntryReference, err error) { +func commonGetParents(ctx context.Context, cc *ContentfulClient, id string, contentTypes []string) (parents []EntryReference, err error) { parents = []EntryReference{} cc.cacheMutex.sharedDataGcLock.RLock() cacheInit := cc.cacheInit diff --git a/test/testapi/gocontentfulvolibbrand.go b/test/testapi/gocontentfulvolibbrand.go index f1b44d6..601ca8c 100644 --- a/test/testapi/gocontentfulvolibbrand.go +++ b/test/testapi/gocontentfulvolibbrand.go @@ -797,7 +797,6 @@ func (cc *ContentfulClient) cacheBrandByID(ctx context.Context, id string, entry defer cc.cacheMutex.parentMapGcLock.Unlock() cc.cacheMutex.genericEntriesGcLock.Lock() defer cc.cacheMutex.genericEntriesGcLock.Unlock() - var col *contentful.Collection if entryPayload != nil { col = &contentful.Collection{ diff --git a/test/testapi/gocontentfulvolibcategory.go b/test/testapi/gocontentfulvolibcategory.go index d7c0946..a79214e 100644 --- a/test/testapi/gocontentfulvolibcategory.go +++ b/test/testapi/gocontentfulvolibcategory.go @@ -561,7 +561,6 @@ func (cc *ContentfulClient) cacheCategoryByID(ctx context.Context, id string, en defer cc.cacheMutex.parentMapGcLock.Unlock() cc.cacheMutex.genericEntriesGcLock.Lock() defer cc.cacheMutex.genericEntriesGcLock.Unlock() - var col *contentful.Collection if entryPayload != nil { col = &contentful.Collection{ diff --git a/test/testapi/gocontentfulvolibproduct.go b/test/testapi/gocontentfulvolibproduct.go index e9fc0f8..1f47d6d 100644 --- a/test/testapi/gocontentfulvolibproduct.go +++ b/test/testapi/gocontentfulvolibproduct.go @@ -612,7 +612,7 @@ func (vo *CfProduct) Brand(ctx context.Context, locale ...Locale) *EntryReferenc return nil } -func (vo *CfProduct) SubProduct(locale ...Locale) *EntryReference { +func (vo *CfProduct) SubProduct(ctx context.Context, locale ...Locale) *EntryReference { if vo == nil { return nil } @@ -647,7 +647,7 @@ func (vo *CfProduct) SubProduct(locale ...Locale) *EntryReference { } } localizedSubProduct := vo.Fields.SubProduct[string(loc)] - contentType, err := vo.CC.GetContentTypeOfID(localizedSubProduct.Sys.ID) + contentType, err := vo.CC.GetContentTypeOfID(ctx, localizedSubProduct.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "SubProduct()"}, LogError, ErrNoTypeOfRefEntry) @@ -657,7 +657,7 @@ func (vo *CfProduct) SubProduct(locale ...Locale) *EntryReference { switch contentType { case ContentTypeBrand: - referencedVO, err := vo.CC.GetBrandByID(localizedSubProduct.Sys.ID) + referencedVO, err := vo.CC.GetBrandByID(ctx, localizedSubProduct.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "SubProduct()"}, LogError, err) @@ -667,7 +667,7 @@ func (vo *CfProduct) SubProduct(locale ...Locale) *EntryReference { return &EntryReference{ContentType: contentType, ID: localizedSubProduct.Sys.ID, VO: referencedVO} case ContentTypeCategory: - referencedVO, err := vo.CC.GetCategoryByID(localizedSubProduct.Sys.ID) + referencedVO, err := vo.CC.GetCategoryByID(ctx, localizedSubProduct.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "SubProduct()"}, LogError, err) @@ -677,7 +677,7 @@ func (vo *CfProduct) SubProduct(locale ...Locale) *EntryReference { return &EntryReference{ContentType: contentType, ID: localizedSubProduct.Sys.ID, VO: referencedVO} case ContentTypeProduct: - referencedVO, err := vo.CC.GetProductByID(localizedSubProduct.Sys.ID) + referencedVO, err := vo.CC.GetProductByID(ctx, localizedSubProduct.Sys.ID) if err != nil { if vo.CC.logFn != nil && vo.CC.logLevel <= LogError { vo.CC.logFn(map[string]interface{}{"content type": vo.Sys.ContentType.Sys.ID, "entry ID": vo.Sys.ID, "method": "SubProduct()"}, LogError, err) @@ -1434,7 +1434,6 @@ func (cc *ContentfulClient) cacheProductByID(ctx context.Context, id string, ent defer cc.cacheMutex.parentMapGcLock.Unlock() cc.cacheMutex.genericEntriesGcLock.Lock() defer cc.cacheMutex.genericEntriesGcLock.Unlock() - var col *contentful.Collection if entryPayload != nil { col = &contentful.Collection{ From 54c71081a905717f87aa2638e2ca2356c67fec59 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 29 Apr 2024 15:01:28 +0200 Subject: [PATCH 23/37] fix: lint err check --- main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index a5ccb5f..e453272 100644 --- a/main.go +++ b/main.go @@ -106,7 +106,10 @@ func main() { } if conf.ExportFile == "" && conf.SpaceID == "" || conf.ExportFile != "" && conf.SpaceID != "" { - byt, _ := json.MarshalIndent(conf, "", " ") + byt, errMarshal := json.MarshalIndent(conf, "", " ") + if errMarshal != nil { + fatal(errMarshal) + } fmt.Println(string(byt)) usageError("Please provide either a Contentful Space ID and CMA access token or an export file name") } From 5738aee97c7b47b6496c18f1310cb78b83c94d51 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 29 Apr 2024 15:41:52 +0200 Subject: [PATCH 24/37] chore: linting --- config/vo.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/vo.go b/config/vo.go index 9036cfc..7a37ca2 100644 --- a/config/vo.go +++ b/config/vo.go @@ -1,10 +1,10 @@ package config type Config struct { - SpaceID string `yaml:"spaceId,omitempty"` - Environment string `yaml:"environment,omitempty"` - ExportFile string `yaml:"exportFile,omitempty"` - ContentTypes []string `yaml:"contentTypes,omitempty"` - PathTargetPackage string `yaml:"pathTargetPackage,omitempty"` - RequireVersion string `yaml:"requireVersion,omitempty"` + SpaceID string `json:"spaceId,omitempty" yaml:"spaceId,omitempty"` + Environment string `json:"environment,omitempty" yaml:"environment,omitempty"` + ExportFile string `json:"exportFile,omitempty" yaml:"exportFile,omitempty"` + ContentTypes []string `json:"contentTypes,omitempty" yaml:"contentTypes,omitempty"` + PathTargetPackage string `json:"pathTargetPackage,omitempty" yaml:"pathTargetPackage,omitempty"` + RequireVersion string `json:"requireVersion,omitempty" yaml:"requireVersion,omitempty"` } From 31334152530ce6f186e4113b1bbf5d1fd8cac7d5 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Wed, 8 May 2024 11:25:41 +0200 Subject: [PATCH 25/37] feat: cache sync counts --- erm/templates/contentful_vo_lib.gotmpl | 77 +++++++++++++++----------- test/cache_test.go | 7 ++- test/concurrency_test.go | 2 +- test/testapi/gocontentfulvolib.go | 49 +++++++++------- 4 files changed, 80 insertions(+), 55 deletions(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index a8d1b77..3407a92 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -540,7 +540,7 @@ func NewOfflineContentfulClient(file []byte, logFn func(fields map[string]interf if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"entries": len(offlineTemp.Entries),"assets": len(offlineTemp.Assets)}, LogInfo, InfoLoadingFromFile) } - err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) + _, _, err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) if err != nil { return nil, fmt.Errorf("NewOfflineContentfulClient could not cache offline space: %v", err) } @@ -576,7 +576,7 @@ func (cc *ContentfulClient) GetGenericEntry(entryID string) (*GenericEntry, erro } return nil, errors.New(InfoCacheIsNil) } - if _, ok := cc.Cache.genericEntries[entryID]; !ok { + if _, ok := cc.Cache.genericEntries[entryID]; !ok { if cc.logFn != nil && cc.logLevel <= LogWarn { cc.logFn(map[string]interface{}{"task": "GetGenericEntry", "entryId":entryID}, LogWarn, ErrorEntryNotFound) } @@ -700,7 +700,7 @@ func (genericEntry *GenericEntry) InheritAsFloat64(ctx context.Context, fieldNam if err != nil { return 0, err } else { - return parentVal, nil + return parentVal, nil } } } @@ -768,7 +768,7 @@ func (genericEntry *GenericEntry) InheritAsReference(ctx context.Context, fieldN if err != nil { return nil, err } else { - return parentVal, nil + return parentVal, nil } } } @@ -824,7 +824,7 @@ func (genericEntry *GenericEntry) Upsert(ctx context.Context) error { genericEntry.CC.logFn(map[string]interface{}{"task": "UpdateCache"}, LogWarn, fmt.Errorf("CfAkeneoSettings UpsertEntry: Operation failed: %w", err)) } return err - } + } return nil } @@ -868,7 +868,7 @@ func (cc *ContentfulClient) ResetSync() { cc.syncToken = "" } -func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []string, cacheAssets bool) error { +func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []string, cacheAssets bool) (map[string][]string, []string, error) { cc.cacheMutex.sharedDataGcLock.RLock() ctxAtWork, cancel := context.WithTimeout(ctx, time.Second*time.Duration(cc.cacheUpdateTimeout)) defer cancel() @@ -887,7 +887,7 @@ func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []stri } else { for _, contentType := range contentTypes { if !stringSliceContains(spaceContentTypes, contentType) { - return fmt.Errorf("UpdateCache: Content Type %q not available in this space", contentType) + return nil, nil, fmt.Errorf("UpdateCache: Content Type %q not available in this space", contentType) } } } @@ -913,20 +913,20 @@ func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []stri } } cc.cacheMutex.sharedDataGcLock.Lock() - cc.cacheInit = true + cc.cacheInit = true cc.cacheMutex.sharedDataGcLock.Unlock() if cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task":"UpdateCache"}, LogInfo, InfoCacheUpdateDone) - } - return nil + cc.logFn(map[string]interface{}{"task":"UpdateCache"}, LogInfo, InfoCacheUpdateDone) + } + return nil, nil, nil } - if cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task":"UpdateCache"}, LogInfo, InfoCacheUpdateSkipped) - } - return nil + if cc.logFn != nil && cc.logLevel <= LogInfo { + cc.logFn(map[string]interface{}{"task":"UpdateCache"}, LogInfo, InfoCacheUpdateSkipped) + } + return nil, nil, nil } -func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string) error { +func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string) (map[string][]string, []string, error) { start := time.Now() cc.cacheMutex.sharedDataGcLock.Lock() cc.Cache.contentTypes = contentTypes @@ -934,9 +934,11 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"task": "syncCache"}, LogInfo, InfoCacheUpdateQueued) } + var syncdEntries map[string][]string + var syncdAssets []string for { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( @@ -947,7 +949,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string ) cc.cacheMutex.sharedDataGcLock.RUnlock() if _, err := col.GetAll(); err != nil { - return err + return nil, nil, err } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken @@ -957,13 +959,13 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"time elapsed": fmt.Sprint(time.Since(start)), "task": "syncCache"}, LogInfo, InfoUpdateCacheTime) } - return nil + return syncdEntries, syncdAssets, nil } var entries []*contentful.Entry var assets []*contentful.Asset for _, item := range col.Items { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) @@ -983,10 +985,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string entries = append(entries, entry) } } - syncEntryCount := map[string]int{} for _, entry := range entries { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } switch entry.Sys.Type { case sysTypeEntry: @@ -998,7 +999,13 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } } else { - syncEntryCount[entry.Sys.ContentType.Sys.ID]++ + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } + if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { + syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + } + syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID],entry.Sys.ID) } case sysTypeDeletedEntry: cc.cacheMutex.idContentTypeMapGcLock.RLock() @@ -1009,18 +1016,25 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") } } else { - syncEntryCount["deletedEntry"]++ + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } + if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { + syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + } + syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID],entry.Sys.ID) } default: } } if cc.logFn != nil && len(entries) > 0 && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "syncEntryCount": syncEntryCount}, LogInfo, InfoCacheSyncOp) + for contentType, ids := range syncdEntries { + cc.logFn(map[string]interface{}{"task": "syncCache", "contentType":contentType, "syncEntryCount": len(ids)}, LogInfo, InfoCacheSyncOp) + } } - var syncAssetCount int for _, asset := range assets { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } switch asset.Sys.Type { case sysTypeAsset: @@ -1029,7 +1043,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets,asset.Sys.ID) } case sysTypeDeletedAsset: if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true); err != nil { @@ -1037,15 +1051,16 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets,asset.Sys.ID) } default: } } if cc.logFn != nil && len(assets) > 0 && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "syncAssetCount": syncAssetCount}, LogInfo, InfoCacheSyncOp) + cc.logFn(map[string]interface{}{"task": "syncCache", "syncAssetCount": len(syncdAssets)}, LogInfo, InfoCacheSyncOp) } } + return syncdEntries, syncdAssets, nil } func (cc *ContentfulClient) cacheWorker(ctx context.Context, contentTypes []string, cacheAssets bool) { @@ -2010,4 +2025,4 @@ func (rawFields RawFields) GetChildIDs() (childIDs []string) { } } return childIDs -} \ No newline at end of file +} diff --git a/test/cache_test.go b/test/cache_test.go index 16418b9..f1f7b11 100644 --- a/test/cache_test.go +++ b/test/cache_test.go @@ -10,8 +10,9 @@ import ( func TestCache(t *testing.T) { contentfulClient, err := getTestClient() - contentfulClient.ClientStats() + require.NotNil(t, contentfulClient) require.NoError(t, err) + contentfulClient.ClientStats() stats, err := contentfulClient.GetCacheStats() require.NoError(t, err) require.Equal(t, 3, len(stats.ContentTypes)) @@ -86,7 +87,7 @@ func TestCacheIfNewEntry(t *testing.T) { require.NoError(t, err) err = contentfulClient.SetOfflineFallback(testFile) require.NoError(t, err) - err = contentfulClient.UpdateCache(context.Background(), nil, false) + _, _, err = contentfulClient.UpdateCache(context.Background(), nil, false) require.NoError(t, err) stats, err = contentfulClient.GetCacheStats() require.NoError(t, err) @@ -100,7 +101,7 @@ func TestPreserveCacheIfNewer(t *testing.T) { require.NoError(t, err) err = contentfulClient.SetOfflineFallback(testFile) require.NoError(t, err) - err = contentfulClient.UpdateCache(context.TODO(), nil, false) + _, _, err = contentfulClient.UpdateCache(context.TODO(), nil, false) require.NoError(t, err) brand, err := contentfulClient.GetBrandByID(context.TODO(), "JrePkDVYomE8AwcuCUyMi") require.NoError(t, err) diff --git a/test/concurrency_test.go b/test/concurrency_test.go index 2907dcf..2c8171b 100644 --- a/test/concurrency_test.go +++ b/test/concurrency_test.go @@ -94,7 +94,7 @@ func TestConcurrentReadWrites(t *testing.T) { go func() { defer wg.Done() testLogger.Infof("testConcurrentReadWrites: caching run %d", i) - err := contentfulClient.UpdateCache(context.TODO(), nil, false) + _, _, err := contentfulClient.UpdateCache(context.TODO(), nil, false) if err != nil { testLogger.Errorf("testConcurrentReadWrites: %v", err) } diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index a0a4480..b72a6ff 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -572,7 +572,7 @@ func NewOfflineContentfulClient(file []byte, logFn func(fields map[string]interf if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"entries": len(offlineTemp.Entries), "assets": len(offlineTemp.Assets)}, LogInfo, InfoLoadingFromFile) } - err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) + _, _, err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) if err != nil { return nil, fmt.Errorf("NewOfflineContentfulClient could not cache offline space: %v", err) } @@ -899,7 +899,7 @@ func (cc *ContentfulClient) ResetSync() { cc.syncToken = "" } -func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []string, cacheAssets bool) error { +func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []string, cacheAssets bool) (map[string][]string, []string, error) { cc.cacheMutex.sharedDataGcLock.RLock() ctxAtWork, cancel := context.WithTimeout(ctx, time.Second*time.Duration(cc.cacheUpdateTimeout)) defer cancel() @@ -918,7 +918,7 @@ func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []stri } else { for _, contentType := range contentTypes { if !stringSliceContains(spaceContentTypes, contentType) { - return fmt.Errorf("UpdateCache: Content Type %q not available in this space", contentType) + return nil, nil, fmt.Errorf("UpdateCache: Content Type %q not available in this space", contentType) } } } @@ -949,15 +949,15 @@ func (cc *ContentfulClient) UpdateCache(ctx context.Context, contentTypes []stri if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"task": "UpdateCache"}, LogInfo, InfoCacheUpdateDone) } - return nil + return nil, nil, nil } if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"task": "UpdateCache"}, LogInfo, InfoCacheUpdateSkipped) } - return nil + return nil, nil, nil } -func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string) error { +func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string) (map[string][]string, []string, error) { start := time.Now() cc.cacheMutex.sharedDataGcLock.Lock() cc.Cache.contentTypes = contentTypes @@ -965,9 +965,11 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"task": "syncCache"}, LogInfo, InfoCacheUpdateQueued) } + syncdEntries := map[string][]string{} + syncdAssets := []string{} for { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( @@ -978,7 +980,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string ) cc.cacheMutex.sharedDataGcLock.RUnlock() if _, err := col.GetAll(); err != nil { - return err + return nil, nil, err } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken @@ -988,13 +990,13 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"time elapsed": fmt.Sprint(time.Since(start)), "task": "syncCache"}, LogInfo, InfoUpdateCacheTime) } - return nil + return syncdEntries, syncdAssets, nil } var entries []*contentful.Entry var assets []*contentful.Asset for _, item := range col.Items { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) @@ -1014,10 +1016,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string entries = append(entries, entry) } } - syncEntryCount := map[string]int{} for _, entry := range entries { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } switch entry.Sys.Type { case sysTypeEntry: @@ -1029,7 +1030,10 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } } else { - syncEntryCount[entry.Sys.ContentType.Sys.ID]++ + if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { + syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + } + syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID], entry.Sys.ID) } case sysTypeDeletedEntry: cc.cacheMutex.idContentTypeMapGcLock.RLock() @@ -1040,18 +1044,22 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") } } else { - syncEntryCount["deletedEntry"]++ + if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { + syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + } + syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID], entry.Sys.ID) } default: } } if cc.logFn != nil && len(entries) > 0 && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "syncEntryCount": syncEntryCount}, LogInfo, InfoCacheSyncOp) + for contentType, ids := range syncdEntries { + cc.logFn(map[string]interface{}{"task": "syncCache", "contentType": contentType, "syncEntryCount": len(ids)}, LogInfo, InfoCacheSyncOp) + } } - var syncAssetCount int for _, asset := range assets { if ctx.Err() != nil { - return ctx.Err() + return nil, nil, ctx.Err() } switch asset.Sys.Type { case sysTypeAsset: @@ -1060,7 +1068,7 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets, asset.Sys.ID) } case sysTypeDeletedAsset: if err := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true); err != nil { @@ -1068,15 +1076,16 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": asset.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets, asset.Sys.ID) } default: } } if cc.logFn != nil && len(assets) > 0 && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "syncAssetCount": syncAssetCount}, LogInfo, InfoCacheSyncOp) + cc.logFn(map[string]interface{}{"task": "syncCache", "syncAssetCount": len(syncdAssets)}, LogInfo, InfoCacheSyncOp) } } + return syncdEntries, syncdAssets, nil } func (cc *ContentfulClient) cacheWorker(ctx context.Context, contentTypes []string, cacheAssets bool) { From b3410cb437bc6ffe787823825b3613e47c731893 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Wed, 8 May 2024 16:58:11 +0200 Subject: [PATCH 26/37] chore: docs update --- README.md | 6 +++ ...gettingstarted.md => 00-gettingstarted.md} | 35 +++++++------- docs/{setup.md => 01-setup.md} | 5 -- .../index.md => 02-client/00-index.md} | 5 -- .../01-basicclientoperations.md} | 12 ++--- .../entries.md => 02-client/02-entries.md} | 26 +++++----- .../assets.md => 02-client/03-assets.md} | 7 +-- .../richtext.md => 02-client/04-richtext.md} | 7 +-- .../05-references.md} | 7 +-- .../06-otherfunctions.md} | 7 +-- docs/{caching.md => 03-caching.md} | 7 +-- .../{api-reference.md => 04-api-reference.md} | 7 +-- docs/_category_.json | 3 -- docs/client/_category_.json | 4 -- docs/index.md | 47 ------------------- 15 files changed, 45 insertions(+), 140 deletions(-) rename docs/{gettingstarted.md => 00-gettingstarted.md} (89%) rename docs/{setup.md => 01-setup.md} (98%) rename docs/{client/index.md => 02-client/00-index.md} (85%) rename docs/{client/basicclientoperations.md => 02-client/01-basicclientoperations.md} (95%) rename docs/{client/entries.md => 02-client/02-entries.md} (84%) rename docs/{client/assets.md => 02-client/03-assets.md} (90%) rename docs/{client/richtext.md => 02-client/04-richtext.md} (95%) rename docs/{client/references.md => 02-client/05-references.md} (90%) rename docs/{client/otherfunctions.md => 02-client/06-otherfunctions.md} (70%) rename docs/{caching.md => 03-caching.md} (98%) rename docs/{api-reference.md => 04-api-reference.md} (98%) delete mode 100644 docs/_category_.json delete mode 100644 docs/client/_category_.json delete mode 100644 docs/index.md diff --git a/README.md b/README.md index 4cba726..a08d475 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,9 @@ In addition, calling a remote API across the Internet each time a piece of conte Gocontentful generates a Go API that handles both issues above and can be regenerated every time the content model changes. The developer never needs to update the types by hand, or deal with the complexity of caching content locally. It all happens auytomatically in the generated client. > **NOTE** - _How much code does Gocontentful generate? In a real-world production scenario where Gocontentful is in use as of 2024, a space content model with 43 content types of various field counts generates around 65,000 lines of Go code._ + +[//]: # (Footer) + +--- + +Read the documentation: [Getting Started](docs/00-gettingstarted) diff --git a/docs/gettingstarted.md b/docs/00-gettingstarted.md similarity index 89% rename from docs/gettingstarted.md rename to docs/00-gettingstarted.md index 22a8265..e73df54 100644 --- a/docs/gettingstarted.md +++ b/docs/00-gettingstarted.md @@ -1,8 +1,3 @@ ---- -sidebar_label: Getting started -sidebar_position: 0 ---- - # Getting Started Before you install Gocontentful as a command-line tool to use it in your projects, we suggest you get a taste of how it works by playing with the test API from the Gocontentful repository. This doesn't yet require you to have access to Contentful. @@ -34,7 +29,8 @@ Paste the following into the file: package main import ( - "testing" + "context" + "testing" "github.com/foomo/gocontentful/test" "github.com/foomo/gocontentful/test/testapi" @@ -43,15 +39,17 @@ import ( ) func TestTheAPI(t *testing.T) { - testLogger := logrus.StandardLogger() - cc, errClient := testapi.NewOfflineContentfulClient("./test/test-space-export.json", + testLogger := logrus.StandardLogger() + testFile, err := test.GetTestFile("./test/test-space-export.json") + require.NoError(t, err) + cc, errClient := testapi.NewOfflineContentfulClient(testFile, test.GetContenfulLogger(testLogger), test.LogDebug, - true) - require.NoError(t, errClient) - prods, errProds := cc.GetAllProduct() - require.NoError(t, errProds) - testLogger.WithField("prods", len(prods)).Info("Loaded products") + true, false) + require.NoError(t, errClient) + prods, errProds := cc.GetAllProduct(context.TODO()) + require.NoError(t, errProds) + testLogger.WithField("prods", len(prods)).Info("Loaded products") } ``` @@ -67,7 +65,10 @@ value object defined for all content types and functions to convert from/to thos by gocontentful all you need to do to load all the products is one single line: ```go - prods, errProds := cc.GetAllProduct() +// First get a context, this is needed for all operations +// that potentially require a network connection to Contentful +ctx := context.TODO() +prods, errProds := cc.GetAllProduct(ctx) ``` Open a terminal and from the repository home directory run the test. Your output should looks similar to this: @@ -94,7 +95,7 @@ The last line shows that we loaded 4 products. Let's go ahead and play with the We'll load a specific product and log its name. Add this at the end of the unit test: ```go -prod, errProd := cc.GetProductByID("6dbjWqNd9SqccegcqYq224") +prod, errProd := cc.GetProductByID(ctx, "6dbjWqNd9SqccegcqYq224") require.NoError(t, errProd) prodName := prod.ProductName("de") testLogger.WithField("name", prodName).Info("Product loaded") @@ -116,7 +117,7 @@ Let's load the product's brand: ```go // Get the brand -brandReference := prod.Brand() +brandReference := prod.Brand(ctx) brand := brandReference.VO.(*testapi.CfBrand) testLogger.WithField("name", brand.CompanyName()).Info("Brand") ``` @@ -138,7 +139,7 @@ INFO[0000] Brand name="Normann Copenhage What if we want to follow the reference the other way around and find out which entries link to this brand? ```go -parentRefs, errParents := brand.GetParents() +parentRefs, errParents := brand.GetParents(ctx) require.NoError(t, errParents) testLogger.WithField("parent count", len(parentRefs)).Info("Parents") for _, parentRef := range parentRefs { diff --git a/docs/setup.md b/docs/01-setup.md similarity index 98% rename from docs/setup.md rename to docs/01-setup.md index b6e0163..66745db 100644 --- a/docs/setup.md +++ b/docs/01-setup.md @@ -1,8 +1,3 @@ ---- -sidebar_label: Setup -sidebar_position: 1 ---- - # Gocontentful Setup ## Installation diff --git a/docs/client/index.md b/docs/02-client/00-index.md similarity index 85% rename from docs/client/index.md rename to docs/02-client/00-index.md index b75cab1..3d37d34 100644 --- a/docs/client/index.md +++ b/docs/02-client/00-index.md @@ -1,8 +1,3 @@ ---- -sidebar_label: The Client At Work -sidebar_position: 0 ---- - # The Client At Work This section explains how to work with the Gocontentful client to create, retrieve, update and delete entities, such as entries and assets. It also walks you through working with references to handle a graph-like structure in a Contentful space. Finally, it dives into some more specific functionalities, like converting to/from RichText and HTML. diff --git a/docs/client/basicclientoperations.md b/docs/02-client/01-basicclientoperations.md similarity index 95% rename from docs/client/basicclientoperations.md rename to docs/02-client/01-basicclientoperations.md index d98449c..73354c9 100644 --- a/docs/client/basicclientoperations.md +++ b/docs/02-client/01-basicclientoperations.md @@ -1,8 +1,3 @@ ---- -sidebar_label: Basic client operations -sidebar_position: 1 ---- - # Basic client operations Let's consider a very simple use case. You have a Contentful space where you store information @@ -39,11 +34,12 @@ The generated files will be in the "people" subdirectory of your project. Your g client from them: ```go -cc, err := people.NewContentfulClient(YOUR_SPACE_ID, people.ClientModeCDA, YOUR_API_KEY, 1000, contentfulLogger, people.LogDebug,false) +cc, err := people.NewContentfulClient(ctx context.Context, YOUR_SPACE_ID, people.ClientModeCDA, YOUR_API_KEY, 1000, contentfulLogger, people.LogDebug,false) ``` The parameters to pass to NewContentfulClient are: +- _ctx_ (context.Context) - _spaceID_ (string) - _clientMode_ (string) supports the constants ClientModeCDA, ClientModeCPA and ClientModeCMA. If you need to operate on multiple APIs (e.g. one for reading and CMA for writing) you need to get two clients @@ -154,7 +150,7 @@ Finally, you can get the parents (AKA referring) entries of either an entry or an EntryReference with the _GetParents()_ method. This returns a slice of `[]EntryReference`: ```go -(vo *CfPerson) GetParents() (parents []EntryReference, err error) +(vo *CfPerson) GetParents(ctx context.Context) (parents []EntryReference, err error) (ref *EntryReference) GetParents(cc *ContentfulClient) (parents []EntryReference, err error) ``` @@ -163,7 +159,7 @@ an EntryReference with the _GetParents()_ method. This returns a slice of `[]Ent Another thing you might want to know is the content type of an entry with a given ID: ```go -(cc *ContentfulClient) GetContentTypeOfID(ID string) (contentType string) +(cc *ContentfulClient) GetContentTypeOfID(ctx context.Context, ID string) (contentType string) ``` ### Caveats and limitations diff --git a/docs/client/entries.md b/docs/02-client/02-entries.md similarity index 84% rename from docs/client/entries.md rename to docs/02-client/02-entries.md index 8cb07af..19f0229 100644 --- a/docs/client/entries.md +++ b/docs/02-client/02-entries.md @@ -1,20 +1,16 @@ ---- -sidebar_label: Entries -sidebar_position: 2 ---- - # Working with entries -Refer to the [Getting started section](../gettingstarted) for an introduction on entry operations. +Refer to the [Getting started section](../00-gettingstarted) for an introduction on entry operations. With your newly created client you can do things like: ```go +ctx := context.Background() // Load all persons -persons, err := cc.GetAllPerson() +persons, err := cc.GetAllPerson(ctx) // Load a specific person -person, err := cc.GetPersonByID(THE_PERSON_ID) +person, err := cc.GetPersonByID(ctx, THE_PERSON_ID) // or pass a query -person, err := GetFilteredPerson(&contentful.Query{ +person, err := GetFilteredPerson(ctx, &contentful.Query{ "contentType":"person", "exists": []string{"fields.resume"} }) @@ -25,7 +21,7 @@ name := person.Name() // the getter functions will return that if the value is not set for locale passed to the function. name := person.Title(people.SpaceLocaleItalian) // Get references to the person's pets -petRefs := person.Pets() +petRefs := person.Pets(ctx) // Deal with pets for _, pet := range petRefs { switch pet.ContentType { @@ -48,13 +44,13 @@ To save the entry to Contentful you need to explicitly call one of these methods ```go // Upsert (save) an entry -err := dog.UpsertEntry() +err := dog.UpsertEntry(ctx) // Publish it (after it's been upserted) -err := dog.PublishEntry() // change your mind with err := dog.UnpublishEntry() +err := dog.PublishEntry(ctx) // change your mind with err := dog.UnpublishEntry() // Or do it in one step -err := dog.UpdateEntry() // upserts and publishes +err := dog.UpdateEntry(ctx) // upserts and publishes // And delete it -err := dog.DeleteEntry() +err := dog.DeleteEntry(ctx) ``` If you want to know the publication status of an entry as represented in Contentful's UI you @@ -92,4 +88,4 @@ type GenericEntry struct { While these seem to defeat the purpose of the idiomatic API, they are useful in cases where you need to pass-through entries from Contentful to any recipient without type switching. Each generic entry carries a reference to the Gocontentful client it was used to retrieve it, so that other operations can benefit from it. For example, get the corresponding idiomatic entry only when needed for processing. -Gocontentful supports retrieving either all generic entries in the cache or single generic entries by ID. It also provides methods to get a localized field's value as a string or a float64, set a field's value and upsert the generic entry. Take a look at [the API reference](../api-reference.md) for the method signatures. +Gocontentful supports retrieving either all generic entries in the cache or single generic entries by ID. It also provides methods to get a localized field's value as a string or a float64, set a field's value and upsert the generic entry. Take a look at [the API reference](../04-api-reference) for the method signatures. diff --git a/docs/client/assets.md b/docs/02-client/03-assets.md similarity index 90% rename from docs/client/assets.md rename to docs/02-client/03-assets.md index 8d51ecc..25e7693 100644 --- a/docs/client/assets.md +++ b/docs/02-client/03-assets.md @@ -1,15 +1,10 @@ ---- -sidebar_label: Assets -sidebar_position: 3 ---- - # Assets Contentful allows upload and reference of binary assets and gocontentful fully supports them. Assuming the dog entry references a picture in a field you can get it with: ```go -picture := dog.Picture() // you can pass a locale to this function as usual +picture := dog.Picture(ctx) // you can pass a locale to this function as usual ``` This returns a \*contenful.AssetNoLocale object handling localization for you in two ways. diff --git a/docs/client/richtext.md b/docs/02-client/04-richtext.md similarity index 95% rename from docs/client/richtext.md rename to docs/02-client/04-richtext.md index 8c78842..7163865 100644 --- a/docs/client/richtext.md +++ b/docs/02-client/04-richtext.md @@ -1,9 +1,4 @@ ---- -sidebar_label: Richtext -sidebar_position: 4 ---- - -# RichText support +# RichText Contentful supports Rich Text fields. Behind the scenes, these are JSON objects that represent the content through a Contentful-specific data model. Sooner or later you might want to convert such values to and from HTML. diff --git a/docs/client/references.md b/docs/02-client/05-references.md similarity index 90% rename from docs/client/references.md rename to docs/02-client/05-references.md index ca947b1..5425aed 100644 --- a/docs/client/references.md +++ b/docs/02-client/05-references.md @@ -1,8 +1,3 @@ ---- -sidebar_label: References -sidebar_position: 5 ---- - # More on references When working with references it's often useful to know if there are any broken ones in the space. @@ -28,6 +23,6 @@ Finally, you can get the parents (AKA referring) entries of either an entry or an EntryReference with the _GetParents()_ method. This returns a slice of `[]EntryReference`: ```go -(vo *CfPerson) GetParents() (parents []EntryReference, err error) +(vo *CfPerson) GetParents(ctx) (parents []EntryReference, err error) (ref *EntryReference) GetParents(cc *ContentfulClient) (parents []EntryReference, err error) ``` diff --git a/docs/client/otherfunctions.md b/docs/02-client/06-otherfunctions.md similarity index 70% rename from docs/client/otherfunctions.md rename to docs/02-client/06-otherfunctions.md index 7e935d8..580d313 100644 --- a/docs/client/otherfunctions.md +++ b/docs/02-client/06-otherfunctions.md @@ -1,14 +1,9 @@ ---- -sidebar_label: Other functions -sidebar_position: 6 ---- - # Other useful functions Another thing you might want to know is the content type of an entry with a given ID: ```go -(cc *ContentfulClient) GetContentTypeOfID(ID string) (contentType string) +(cc *ContentfulClient) GetContentTypeOfID(ctx, ID string) (contentType string) ``` ## Caveats and limitations diff --git a/docs/caching.md b/docs/03-caching.md similarity index 98% rename from docs/caching.md rename to docs/03-caching.md index 4293722..cd3ddc4 100644 --- a/docs/caching.md +++ b/docs/03-caching.md @@ -1,8 +1,3 @@ ---- -sidebar_label: Caching -sidebar_position: 3 ---- - # Caching Caching is a fundamental part of working with remote data across the Internet, @@ -86,7 +81,7 @@ Note that the Sync API is not officially supported by Contentful on the Preview Cache update operations time out by default after 120 seconds. This makes sure that no routine is left hanging, blocking subsequent updates in case the main application or service recovers from a panic. If you need to increase this limit because you have a huge space with -a lot of entries you can use the _SetCacheUpdateTimeout_ method. See the [API Reference](./api-reference) for details. +a lot of entries you can use the _SetCacheUpdateTimeout_ method. See the [API Reference](./04-api-reference) for details. ## Asset caching diff --git a/docs/api-reference.md b/docs/04-api-reference.md similarity index 98% rename from docs/api-reference.md rename to docs/04-api-reference.md index 93a336c..2412af4 100644 --- a/docs/api-reference.md +++ b/docs/04-api-reference.md @@ -1,8 +1,3 @@ ---- -sidebar_label: API Reference -sidebar_position: 4 ---- - # API Reference ## Client and cache @@ -19,7 +14,7 @@ NewContentfulClient( ) (*ContentfulClient, error) ``` -Creates a Contentful client, [read this](client/basicclientoperations) for an explanation of all parameters. +Creates a Contentful client, [read this](02-client/01-basicclientoperations) for an explanation of all parameters. ```go SetOfflineFallback(filename string) error diff --git a/docs/_category_.json b/docs/_category_.json deleted file mode 100644 index 8bd1cc9..0000000 --- a/docs/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Gocontentful" -} diff --git a/docs/client/_category_.json b/docs/client/_category_.json deleted file mode 100644 index ace54ed..0000000 --- a/docs/client/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "The client at work", - "position": 2 -} \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 41af1dd..0000000 --- a/docs/index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_label: Gocontentful -sidebar_position: 1 ---- - -# Gocontentful documentation - -Gocontentful is a command line tool that generates a set of APIs for the [Go Language](https://go.dev) to interact with a [Contentful](https://www.contentful.com) CMS space. - -Unlike the plain Contentful API for Go, the Gocontentful API is idiomatic. Go types are provided with names that mirror the content types of the Contentful space, and get/set methods are named after each field. - -In addition, Gocontentful supports in-memory caching and updates of spaces. This way, the space is always accessible through fast Go function calls, even offline. - -## What is Contentful - -[Contentful](https://www.contentful.com/) is a content platform (often referred to as headless CMS) for [micro-content](https://www.contentful.com/r/knowledgebase/content-as-a-microservice/). - -Unlike traditional CMSes, there's no pages or content trees in Contentful. The data model is built from scratch for the purpose of the consuming application, is completely flexible and can be created and hot-changed through the same Web UI that the content editors use. The model dictates which content types can reference others and the final structure is a graph. - -## How applications interact with Contentful - -Contentful hosts several APIs that remote applications use to create, retrieve, update and delete content. Content is any of the following: - -- **Entries**, each with a content type name and a list of data fields as defined by the developer in the content model editor at Contentful -- **Assets** (images, videos, other binary files) - -The Contentful APIs exist as either REST or GraphQL endpoints. Gocontentful only supports the REST APIs. - -The REST APIs used to manage and retrieve content use standard HTTP verbs (GET, POST, PUT and DELETE) and a JSON payload for both the request (where needed) and the response. - -## What is gocontentful - -A golang API code generator that simplifies interacting with a Contentful space. The generated API: - -- Supports most of the Contentful APIs to perform all read/write operation on entries and assets -- Hides the complexity of the Contentful REST/JSON APIs behind an idiomatic set of golang functions and methods -- Allows for in-memory caching of an entire Contentful space - -## Why we need a Go API generator - -While it's perfectly fine to call a REST service and receive data in JSON format, in Go that is not very practical. For each content type, the developer needs to maintan type definitions by hand and decode the JSON coming from the Contentful server into the value object. - -In addition, calling a remote API across the Internet each time a piece of content is needed, even multiple times for a single page rendering, can have significant impact on performance. - -Gocontentful generates a Go API that handles both issues above and can be regenerated every time the content model changes. The developer never needs to update the types by hand, or deal with the complexity of caching content locally. It all happens auytomatically in the generated client. - -> **NOTE** - _How much code does Gocontentful generate? In a real-world production scenario where Gocontentful is in use as of 2024, a space content model with 43 content types of various field counts generates around 65,000 lines of Go code._ From caeb4c9de1621ab45d78b12884393197fdf9bcdf Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Thu, 9 May 2024 10:37:56 +0200 Subject: [PATCH 27/37] feat: meta file for generator version --- erm/generator.go | 1 + erm/templates/meta.gotmpl | 2 ++ erm/templates/templates.go | 3 +++ test/testapi/meta.go | 2 ++ 4 files changed, 8 insertions(+) create mode 100644 erm/templates/meta.gotmpl create mode 100644 test/testapi/meta.go diff --git a/erm/generator.go b/erm/generator.go index 417bc44..b78033c 100644 --- a/erm/generator.go +++ b/erm/generator.go @@ -54,6 +54,7 @@ func generate(filename string, tpl []byte, conf spaceConf) error { // generateCode generates API to and value objects for the space func generateCode(conf spaceConf) (err error) { for file, tpl := range map[string][]byte{ + filepath.Join(conf.PackageDir, "meta"+goExt): templates.TemplateMeta, filepath.Join(conf.PackageDir, "gocontentfulvobase"+goExt): templates.TemplateVoBase, filepath.Join(conf.PackageDir, "gocontentfulvo"+goExt): templates.TemplateVo, filepath.Join(conf.PackageDir, "gocontentfulvolib"+goExt): templates.TemplateVoLib, diff --git a/erm/templates/meta.gotmpl b/erm/templates/meta.gotmpl new file mode 100644 index 0000000..ffe71d9 --- /dev/null +++ b/erm/templates/meta.gotmpl @@ -0,0 +1,2 @@ +// gocontentful version: {{ .Version }} +{{ $cfg := . }}package {{ .PackageName }} diff --git a/erm/templates/templates.go b/erm/templates/templates.go index 5748195..56d2f2e 100644 --- a/erm/templates/templates.go +++ b/erm/templates/templates.go @@ -2,6 +2,9 @@ package templates import _ "embed" +//go:embed "meta.gotmpl" +var TemplateMeta []byte + //go:embed "contentful_vo.gotmpl" var TemplateVo []byte diff --git a/test/testapi/meta.go b/test/testapi/meta.go new file mode 100644 index 0000000..7252f20 --- /dev/null +++ b/test/testapi/meta.go @@ -0,0 +1,2 @@ +// gocontentful version: latest +package testapi From 0fbf407ec2de556ebd03b0f84831f222e6ce98cd Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Thu, 9 May 2024 10:39:10 +0200 Subject: [PATCH 28/37] chore: missing testapi file --- test/testapi/gocontentfulvolib.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index b72a6ff..284c631 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -965,8 +965,8 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"task": "syncCache"}, LogInfo, InfoCacheUpdateQueued) } - syncdEntries := map[string][]string{} - syncdAssets := []string{} + var syncdEntries map[string][]string + var syncdAssets []string for { if ctx.Err() != nil { return nil, nil, ctx.Err() @@ -1030,6 +1030,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to update cache for entry") } } else { + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} } @@ -1044,6 +1047,9 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string cc.logFn(map[string]interface{}{"id": entry.Sys.ID, "task": "syncCache", "error": err.Error()}, LogWarn, "failed to delete cache for entry") } } else { + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} } From b8b21cdf6711b14c8ac33e8bf34b769194c1e63b Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Thu, 9 May 2024 10:39:24 +0200 Subject: [PATCH 29/37] fix: concurrency test --- test/concurrency_test.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/concurrency_test.go b/test/concurrency_test.go index 2c8171b..73844d0 100644 --- a/test/concurrency_test.go +++ b/test/concurrency_test.go @@ -66,9 +66,27 @@ func writeWorker(ctx context.Context, contentfulClient *testapi.ContentfulClient } contentfulClient.SetProductInCache(product) testLogger.Infof("Write worker %d set price: %d", i, i) - _ = product.SetBrand(testapi.ContentTypeSys{}) - _ = product.SetCategories([]testapi.ContentTypeSys{}) - _ = product.SetImage([]testapi.ContentTypeSys{}) + _ = product.SetBrand(testapi.ContentTypeSys{ + Sys: testapi.ContentTypeSysAttributes{ + ID: "651CQ8rLoIYCeY6G0QG22q", + Type: "Link", + LinkType: "Entry", + }, + }) + _ = product.SetCategories([]testapi.ContentTypeSys{ + {Sys: testapi.ContentTypeSysAttributes{ + ID: "7LAnCobuuWYSqks6wAwY2a", + Type: "Link", + LinkType: "Entry", + }}, + }) + _ = product.SetImage([]testapi.ContentTypeSys{ + {Sys: testapi.ContentTypeSysAttributes{ + ID: "10TkaLheGeQG6qQGqWYqUI", + Type: "Link", + LinkType: "Asset", + }}, + }) _ = product.SetNodes(nil) _ = product.SetProductDescription("") _ = product.SetProductName("") From 046cefd60019cb0c839ef9280ecdc2810651828b Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Thu, 9 May 2024 10:40:20 +0200 Subject: [PATCH 30/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a08d475..4b8456c 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,4 @@ Gocontentful generates a Go API that handles both issues above and can be regene --- -Read the documentation: [Getting Started](docs/00-gettingstarted) +Read the documentation: [Getting Started](docs/00-gettingstarted.md) From b684c32e2f4907884f9ac77e8439ca71c95f68ef Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Fri, 17 May 2024 09:34:22 +0200 Subject: [PATCH 31/37] feat: more generic entry field getters --- erm/templates/contentful_vo_lib.gotmpl | 315 ++++++++++++++++++++++++- test/testapi/gocontentfulvolib.go | 315 ++++++++++++++++++++++++- 2 files changed, 624 insertions(+), 6 deletions(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 3407a92..0d68a84 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -540,7 +540,7 @@ func NewOfflineContentfulClient(file []byte, logFn func(fields map[string]interf if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"entries": len(offlineTemp.Entries),"assets": len(offlineTemp.Assets)}, LogInfo, InfoLoadingFromFile) } - _, _, err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) + _, _, err = cc.UpdateCache(context.Background(), spaceContentTypes, cacheAssets) if err != nil { return nil, fmt.Errorf("NewOfflineContentfulClient could not cache offline space: %v", err) } @@ -610,8 +610,10 @@ func (genericEntry *GenericEntry) FieldAsString(fieldName string, locale ...Loca } switch field := genericEntry.RawFields[fieldName].(type) { case map[string]interface{}: - fieldLoc := field[string(loc)] - if fieldLoc == "" && field[string(DefaultLocale)] != "" { + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { fieldLoc = field[string(DefaultLocale)] } switch fieldLocStr := fieldLoc.(type) { @@ -651,6 +653,70 @@ func (genericEntry *GenericEntry) InheritAsString(ctx context.Context, fieldName return "", ErrNotSet } +func (genericEntry *GenericEntry) FieldAsStringSlice(fieldName string, locale ...Locale) ([]string, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]any: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + if fieldLoc != nil { + switch fieldLoc.(type) { + case []any: + var out []string + for _, v := range fieldLoc.([]any) { + switch v.(type) { + case string: + out = append(out, v.(string)) + } + } + return out, nil + } + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsStringSlice(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) ([]string, error) { + val, err := genericEntry.FieldAsStringSlice(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsStringSlice(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + func (genericEntry *GenericEntry) FieldAsFloat64(fieldName string, locale ...Locale) (float64, error) { var loc Locale if len(locale) != 0 { @@ -775,6 +841,249 @@ func (genericEntry *GenericEntry) InheritAsReference(ctx context.Context, fieldN return nil, ErrNotSet } +func (genericEntry *GenericEntry) FieldAsAsset(ctx context.Context, fieldName string, locale ...Locale) (*contentful.AssetNoLocale, error) { + var loc Locale + reqLoc := DefaultLocale + if len(locale) != 0 { + loc = locale[0] + reqLoc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + var cts ContentTypeSys + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + fieldVal := field[string(loc)] + if fieldVal == nil { + fieldVal = field[string(DefaultLocale)] + } + byt, err := json.Marshal(fieldVal) + if err != nil { + return nil, err + } + err = json.Unmarshal(byt, &cts) + if err != nil { + return nil, err + } + asset, err := genericEntry.CC.GetAssetByID(ctx, cts.Sys.ID) + if err != nil { + if genericEntry.CC.logFn != nil && genericEntry.CC.logLevel == LogDebug { + genericEntry.CC.logFn(map[string]interface{}{"content type": genericEntry.Sys.ContentType.Sys.ID, "entry ID": genericEntry.Sys.ID, "method": "HeaderImage()"}, LogError, ErrNoTypeOfRefAsset) + } + return nil, ErrNotSet + } + tempAsset := &contentful.AssetNoLocale{} + tempAsset.Sys = asset.Sys + tempAsset.Fields = &contentful.FileFieldsNoLocale{} + if _, ok := asset.Fields.Title[string(reqLoc)]; ok { + tempAsset.Fields.Title = asset.Fields.Title[string(reqLoc)] + } else { + tempAsset.Fields.Title = asset.Fields.Title[string(loc)] + } + if _, ok := asset.Fields.Description[string(reqLoc)]; ok { + tempAsset.Fields.Description = asset.Fields.Description[string(reqLoc)] + } else { + tempAsset.Fields.Description = asset.Fields.Description[string(loc)] + } + if _, ok := asset.Fields.File[string(reqLoc)]; ok { + tempAsset.Fields.File = asset.Fields.File[string(reqLoc)] + } else { + tempAsset.Fields.File = asset.Fields.File[string(loc)] + } + return tempAsset, nil + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsMultipleReference(fieldName string, locale ...Locale) ([]*EntryReference, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + var ctss []ContentTypeSys + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + fieldVal := field[string(loc)] + if fieldVal == nil { + fieldVal = field[string(DefaultLocale)] + } + byt, err := json.Marshal(fieldVal) + if err != nil { + return nil, err + } + err = json.Unmarshal(byt, &ctss) + if err != nil { + return nil, err + } + var refs []*EntryReference + for _, cts := range ctss { + referencedEntry, err := genericEntry.CC.GetGenericEntry(cts.Sys.ID) + if err != nil || referencedEntry == nil { + return nil, err + } + refs = append(refs, &EntryReference{ + ID: cts.Sys.ID, + ContentType: referencedEntry.Sys.ContentType.Sys.ID, + FromField: fieldName, + }) + } + return refs, nil + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsMultipleReference(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) ([]*EntryReference, error) { + val, err := genericEntry.FieldAsMultipleReference(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsMultipleReference(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsBool(fieldName string, locale ...Locale) (bool, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return false, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return false, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + switch fieldLocStr := fieldLoc.(type) { + case bool: + return fieldLocStr, nil + default: + return false, ErrNotSet + } + default: + return false, ErrNotSet + } +} + +func (genericEntry *GenericEntry) InheritAsBool(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (bool, error) { + val, err := genericEntry.FieldAsBool(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return false, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return false, err + } + parentVal, err := genericParent.FieldAsBool(fieldName, locale...) + if err != nil { + return false, err + } else { + return parentVal, nil + } + } + } + return false, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsAny(fieldName string, locale ...Locale) (any, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + if fieldLoc != nil { + return fieldLoc, nil + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsAny(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (any, error) { + val, err := genericEntry.FieldAsAny(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsAny(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + func (genericEntry *GenericEntry) SetField(fieldName string, fieldValue interface{}, locale ...Locale) error { var loc Locale if len(locale) != 0 { diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 284c631..0150c1d 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -572,7 +572,7 @@ func NewOfflineContentfulClient(file []byte, logFn func(fields map[string]interf if cc.logFn != nil && cc.logLevel <= LogInfo { cc.logFn(map[string]interface{}{"entries": len(offlineTemp.Entries), "assets": len(offlineTemp.Assets)}, LogInfo, InfoLoadingFromFile) } - _, _, err = cc.UpdateCache(context.TODO(), spaceContentTypes, cacheAssets) + _, _, err = cc.UpdateCache(context.Background(), spaceContentTypes, cacheAssets) if err != nil { return nil, fmt.Errorf("NewOfflineContentfulClient could not cache offline space: %v", err) } @@ -642,8 +642,10 @@ func (genericEntry *GenericEntry) FieldAsString(fieldName string, locale ...Loca } switch field := genericEntry.RawFields[fieldName].(type) { case map[string]interface{}: - fieldLoc := field[string(loc)] - if fieldLoc == "" && field[string(DefaultLocale)] != "" { + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { fieldLoc = field[string(DefaultLocale)] } switch fieldLocStr := fieldLoc.(type) { @@ -683,6 +685,70 @@ func (genericEntry *GenericEntry) InheritAsString(ctx context.Context, fieldName return "", ErrNotSet } +func (genericEntry *GenericEntry) FieldAsStringSlice(fieldName string, locale ...Locale) ([]string, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]any: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + if fieldLoc != nil { + switch fieldLoc.(type) { + case []any: + var out []string + for _, v := range fieldLoc.([]any) { + switch v.(type) { + case string: + out = append(out, v.(string)) + } + } + return out, nil + } + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsStringSlice(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) ([]string, error) { + val, err := genericEntry.FieldAsStringSlice(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsStringSlice(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + func (genericEntry *GenericEntry) FieldAsFloat64(fieldName string, locale ...Locale) (float64, error) { var loc Locale if len(locale) != 0 { @@ -807,6 +873,249 @@ func (genericEntry *GenericEntry) InheritAsReference(ctx context.Context, fieldN return nil, ErrNotSet } +func (genericEntry *GenericEntry) FieldAsAsset(ctx context.Context, fieldName string, locale ...Locale) (*contentful.AssetNoLocale, error) { + var loc Locale + reqLoc := DefaultLocale + if len(locale) != 0 { + loc = locale[0] + reqLoc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + var cts ContentTypeSys + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + fieldVal := field[string(loc)] + if fieldVal == nil { + fieldVal = field[string(DefaultLocale)] + } + byt, err := json.Marshal(fieldVal) + if err != nil { + return nil, err + } + err = json.Unmarshal(byt, &cts) + if err != nil { + return nil, err + } + asset, err := genericEntry.CC.GetAssetByID(ctx, cts.Sys.ID) + if err != nil { + if genericEntry.CC.logFn != nil && genericEntry.CC.logLevel == LogDebug { + genericEntry.CC.logFn(map[string]interface{}{"content type": genericEntry.Sys.ContentType.Sys.ID, "entry ID": genericEntry.Sys.ID, "method": "HeaderImage()"}, LogError, ErrNoTypeOfRefAsset) + } + return nil, ErrNotSet + } + tempAsset := &contentful.AssetNoLocale{} + tempAsset.Sys = asset.Sys + tempAsset.Fields = &contentful.FileFieldsNoLocale{} + if _, ok := asset.Fields.Title[string(reqLoc)]; ok { + tempAsset.Fields.Title = asset.Fields.Title[string(reqLoc)] + } else { + tempAsset.Fields.Title = asset.Fields.Title[string(loc)] + } + if _, ok := asset.Fields.Description[string(reqLoc)]; ok { + tempAsset.Fields.Description = asset.Fields.Description[string(reqLoc)] + } else { + tempAsset.Fields.Description = asset.Fields.Description[string(loc)] + } + if _, ok := asset.Fields.File[string(reqLoc)]; ok { + tempAsset.Fields.File = asset.Fields.File[string(reqLoc)] + } else { + tempAsset.Fields.File = asset.Fields.File[string(loc)] + } + return tempAsset, nil + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsMultipleReference(fieldName string, locale ...Locale) ([]*EntryReference, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + var ctss []ContentTypeSys + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + fieldVal := field[string(loc)] + if fieldVal == nil { + fieldVal = field[string(DefaultLocale)] + } + byt, err := json.Marshal(fieldVal) + if err != nil { + return nil, err + } + err = json.Unmarshal(byt, &ctss) + if err != nil { + return nil, err + } + var refs []*EntryReference + for _, cts := range ctss { + referencedEntry, err := genericEntry.CC.GetGenericEntry(cts.Sys.ID) + if err != nil || referencedEntry == nil { + return nil, err + } + refs = append(refs, &EntryReference{ + ID: cts.Sys.ID, + ContentType: referencedEntry.Sys.ContentType.Sys.ID, + FromField: fieldName, + }) + } + return refs, nil + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsMultipleReference(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) ([]*EntryReference, error) { + val, err := genericEntry.FieldAsMultipleReference(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsMultipleReference(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsBool(fieldName string, locale ...Locale) (bool, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return false, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return false, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + switch fieldLocStr := fieldLoc.(type) { + case bool: + return fieldLocStr, nil + default: + return false, ErrNotSet + } + default: + return false, ErrNotSet + } +} + +func (genericEntry *GenericEntry) InheritAsBool(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (bool, error) { + val, err := genericEntry.FieldAsBool(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return false, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return false, err + } + parentVal, err := genericParent.FieldAsBool(fieldName, locale...) + if err != nil { + return false, err + } else { + return parentVal, nil + } + } + } + return false, ErrNotSet +} + +func (genericEntry *GenericEntry) FieldAsAny(fieldName string, locale ...Locale) (any, error) { + var loc Locale + if len(locale) != 0 { + loc = locale[0] + if _, ok := localeFallback[loc]; !ok { + return nil, ErrLocaleUnsupported + } + } else { + loc = DefaultLocale + } + if _, ok := genericEntry.RawFields[fieldName]; !ok { + return nil, ErrNotSet + } + switch field := genericEntry.RawFields[fieldName].(type) { + case map[string]interface{}: + var fieldLoc any + if _, ok := field[string(loc)]; ok { + fieldLoc = field[string(loc)] + } else { + fieldLoc = field[string(DefaultLocale)] + } + if fieldLoc != nil { + return fieldLoc, nil + } + } + return nil, ErrNotSet +} + +func (genericEntry *GenericEntry) InheritAsAny(ctx context.Context, fieldName string, parentTypes []string, locale ...Locale) (any, error) { + val, err := genericEntry.FieldAsAny(fieldName, locale...) + if err == nil { + return val, nil + } + parentRefs, err := commonGetParents(ctx, genericEntry.CC, genericEntry.Sys.ID, parentTypes) + if err != nil { + return nil, err + } + if len(parentRefs) > 0 { + for _, parentRef := range parentRefs { + genericParent, err := genericEntry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, err + } + parentVal, err := genericParent.FieldAsAny(fieldName, locale...) + if err != nil { + return nil, err + } else { + return parentVal, nil + } + } + } + return nil, ErrNotSet +} + func (genericEntry *GenericEntry) SetField(fieldName string, fieldValue interface{}, locale ...Locale) error { var loc Locale if len(locale) != 0 { From a9261602af565b8539570dad789ff7e7bd6fee36 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 17 Jun 2024 10:44:13 +0200 Subject: [PATCH 32/37] fix: panic with deleted entries sync --- erm/templates/contentful_vo_lib.gotmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 0d68a84..b2e6c1e 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -1328,10 +1328,10 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if syncdEntries == nil { syncdEntries = map[string][]string{} } - if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { - syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + if _, ok := syncdEntries[contentType]; !ok { + syncdEntries[contentType] = []string{} } - syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID],entry.Sys.ID) + syncdEntries[contentType] = append(syncdEntries[contentType],entry.Sys.ID) } default: } From d27118442f0ad27f82a581f33a6d764f3fd597af Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 1 Jul 2024 10:40:04 +0200 Subject: [PATCH 33/37] fix: RichTextToPlainText --- erm/templates/contentful_vo_lib.gotmpl | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index b2e6c1e..8c63174 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -7,14 +7,15 @@ import ( "encoding/json" "errors" "fmt" - "html" "io" "regexp" "strings" "sync" "time" "unicode" + "github.com/foomo/contentful" + "golang.org/x/net/html" "golang.org/x/sync/errgroup" ) @@ -569,6 +570,29 @@ func RichTextToHtml(rt interface{}, linkResolver LinkResolverFunc, entryLinkReso return out, nil } +func RichTextToPlainText(rt interface{}, locale Locale) (string, error) { + htmlStr, err := RichTextToHtml(rt, nil, nil, nil, nil, locale) + if err != nil { + return "", err + } + doc, err := html.Parse(strings.NewReader(htmlStr)) + if err != nil { + return "", err + } + var f func(*html.Node) + text := "" + f = func(n *html.Node) { + if n.Type == html.TextNode { + text += n.Data + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + return strings.TrimSpace(text), nil +} + func (cc *ContentfulClient) GetGenericEntry(entryID string) (*GenericEntry, error) { if cc.Cache == nil { if cc.logFn != nil && cc.logLevel <= LogWarn { @@ -1925,6 +1949,11 @@ func (n *RichTextGenericNode) richTextRenderHTML(w io.Writer, linkResolver LinkR return "", nil } } + if imageResolver == nil { + imageResolver = func(assetID string, locale Locale) (attrs map[string]string, customHTML string, resolveError error) { + return map[string]string{},"",nil + } + } tags := richTextHtmlTags{} switch n.NodeType { case RichTextNodeParagraph: From 30c98babfffdfa8bb96db0dd352ee4e1e1975547 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 1 Jul 2024 10:56:36 +0200 Subject: [PATCH 34/37] fix: run make test --- go.mod | 9 ++++---- go.sum | 18 +++++++++------- test/testapi/gocontentfulvolib.go | 36 +++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 55f005d..94600cc 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,10 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 + golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 - golang.org/x/text v0.14.0 - golang.org/x/tools v0.20.0 + golang.org/x/text v0.16.0 + golang.org/x/tools v0.22.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -17,6 +18,6 @@ require ( github.com/aoliveti/curling v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 7faa63f..e5c183d 100644 --- a/go.sum +++ b/go.sum @@ -17,17 +17,19 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index 0150c1d..b62a558 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "html" "io" "regexp" "strings" @@ -16,6 +15,7 @@ import ( "unicode" "github.com/foomo/contentful" + "golang.org/x/net/html" "golang.org/x/sync/errgroup" ) @@ -601,6 +601,29 @@ func RichTextToHtml(rt interface{}, linkResolver LinkResolverFunc, entryLinkReso return out, nil } +func RichTextToPlainText(rt interface{}, locale Locale) (string, error) { + htmlStr, err := RichTextToHtml(rt, nil, nil, nil, nil, locale) + if err != nil { + return "", err + } + doc, err := html.Parse(strings.NewReader(htmlStr)) + if err != nil { + return "", err + } + var f func(*html.Node) + text := "" + f = func(n *html.Node) { + if n.Type == html.TextNode { + text += n.Data + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(doc) + return strings.TrimSpace(text), nil +} + func (cc *ContentfulClient) GetGenericEntry(entryID string) (*GenericEntry, error) { if cc.Cache == nil { if cc.logFn != nil && cc.logLevel <= LogWarn { @@ -1359,10 +1382,10 @@ func (cc *ContentfulClient) syncCache(ctx context.Context, contentTypes []string if syncdEntries == nil { syncdEntries = map[string][]string{} } - if _, ok := syncdEntries[entry.Sys.ContentType.Sys.ID]; !ok { - syncdEntries[entry.Sys.ContentType.Sys.ID] = []string{} + if _, ok := syncdEntries[contentType]; !ok { + syncdEntries[contentType] = []string{} } - syncdEntries[entry.Sys.ContentType.Sys.ID] = append(syncdEntries[entry.Sys.ContentType.Sys.ID], entry.Sys.ID) + syncdEntries[contentType] = append(syncdEntries[contentType], entry.Sys.ID) } default: } @@ -1970,6 +1993,11 @@ func (n *RichTextGenericNode) richTextRenderHTML(w io.Writer, linkResolver LinkR return "", nil } } + if imageResolver == nil { + imageResolver = func(assetID string, locale Locale) (attrs map[string]string, customHTML string, resolveError error) { + return map[string]string{}, "", nil + } + } tags := richTextHtmlTags{} switch n.NodeType { case RichTextNodeParagraph: From ffa71716ce5ecb6943e224fe592aff94ebb05834 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 1 Jul 2024 12:00:31 +0200 Subject: [PATCH 35/37] chore: docs update --- docs/02-client/04-richtext.md | 6 ++++++ docs/04-api-reference.md | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/02-client/04-richtext.md b/docs/02-client/04-richtext.md index 7163865..1a5958f 100644 --- a/docs/02-client/04-richtext.md +++ b/docs/02-client/04-richtext.md @@ -23,4 +23,10 @@ The conversion works the other way around too, when you need to source data from myRichText := HtmlToRichText(htmlSrc) ``` +You can also convert RichText to plain text with + +```go +txt, err := RichTextToPlainText(rt interface{}, locale Locale) +``` + See the [API Reference](./api-reference) for more details about these functions. diff --git a/docs/04-api-reference.md b/docs/04-api-reference.md index 2412af4..825939d 100644 --- a/docs/04-api-reference.md +++ b/docs/04-api-reference.md @@ -368,7 +368,7 @@ bold tags, horizontal rules, blockquote, ordered and unordered lists, code. Unkn doesn't return any error as it converts the input text into something as good as possible, without validation. ```go -RichTextToHtml(rt interface{}, linkResolver LinkResolverFunc, entryLinkResolver EntryLinkResolverFunc, imageResolver ImageResolverFunc, locale Locale) (string, error) { +RichTextToHtml(rt interface{}, linkResolver LinkResolverFunc, entryLinkResolver EntryLinkResolverFunc, imageResolver ImageResolverFunc, locale Locale) (string, error) ``` Converts an interface representing a Contentful RichText value (usually from a field getter) into HTML. @@ -391,6 +391,12 @@ type EmbeddedEntryResolverFunc func(entryID string, locale Locale) (html string, All the three functions above can be passed as nil with different levels of graceful degrading. +```go +RichTextToPlainText(rt interface{}, locale Locale) (string, error) + +Converts the RichText to plain text. +``` + ### Constants and global variables Each generated content type library file exports a constant with the Contentful ID of the content type itself, for From 9dc2d50b34f6e71ae20b31affeb20349db0ae925 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Mon, 12 Aug 2024 10:43:24 +0200 Subject: [PATCH 36/37] feat: ClientMode() and docs update --- docs/04-api-reference.md | 14 ++++++++++++++ erm/template.go | 11 +++++++---- erm/templates/contentful_vo_lib.gotmpl | 4 ++++ test/testapi/gocontentfulvolib.go | 4 ++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/04-api-reference.md b/docs/04-api-reference.md index 825939d..83f7883 100644 --- a/docs/04-api-reference.md +++ b/docs/04-api-reference.md @@ -82,6 +82,20 @@ Builds or re-builds the entire client cache. Updates a single entry or asset (the sysType can take const sysTypeEntry or sysTypeAsset values) in the cache. +```go +(cc *ContentfulClient) ClientMode() ClientMode +``` + +Returns the internal client mode. There are three constants defined in the generated API: + +```go +const ( + ClientModeCDA ClientMode = "CDA" + ClientModeCPA ClientMode = "CPA" + ClientModeCMA ClientMode = "CMA" +) +``` + ## Content functions and methods _For these we're assuming a content type named "Person"._ diff --git a/erm/template.go b/erm/template.go index 116ea05..12610ac 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,16 +1,15 @@ package erm import ( - "strings" - "text/template" - "golang.org/x/text/cases" "golang.org/x/text/language" + "strings" + "text/template" ) func getFuncMap() template.FuncMap { return template.FuncMap{ - "firstCap": cases.Title(language.Und, cases.NoLower).String, + "firstCap": firstCap, "fieldIsBasic": fieldIsBasic, "fieldIsComplex": fieldIsComplex, "fieldIsAsset": fieldIsAsset, @@ -161,3 +160,7 @@ func fieldIsComplex(field ContentTypeField) bool { func oneLine(v string) string { return strings.ReplaceAll(v, "\n", " ") } + +func firstCap(s string) string { + return strings.TrimRight(cases.Title(language.Und, cases.NoLower).String(s), "_") +} diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index 8c63174..78add7a 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -1161,6 +1161,10 @@ func (genericEntry *GenericEntry) Upsert(ctx context.Context) error { return nil } +func (cc *ContentfulClient) ClientMode() ClientMode { + return cc.clientMode +} + func (cc *ContentfulClient) SetCacheUpdateTimeout(seconds int64) { cc.cacheUpdateTimeout = seconds } diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index b62a558..ca8065f 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -1191,6 +1191,10 @@ func (genericEntry *GenericEntry) Upsert(ctx context.Context) error { return nil } +func (cc *ContentfulClient) ClientMode() ClientMode { + return cc.clientMode +} + func (cc *ContentfulClient) SetCacheUpdateTimeout(seconds int64) { cc.cacheUpdateTimeout = seconds } From d5873e4018b84a3b1860a60c4cd74799fbe979b9 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Mon, 12 Aug 2024 17:57:35 +0200 Subject: [PATCH 37/37] fix: lint errors --- .golangci.yml | 1 - erm/template.go | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index dcfca7c..3be41d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -153,7 +153,6 @@ linters: - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false] #- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] #- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] - - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds [fast: false, auto-fix: false] #- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] #- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false] - exportloopref # checks for pointers to enclosing loop variables [fast: false, auto-fix: false] diff --git a/erm/template.go b/erm/template.go index 12610ac..5fea7ac 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,10 +1,11 @@ package erm import ( - "golang.org/x/text/cases" - "golang.org/x/text/language" "strings" "text/template" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func getFuncMap() template.FuncMap {