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 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ea29f19..c1e08c1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,7 +2,7 @@ name: checks on: push: - branches: [ master ] + branches: [ main ] pull_request: merge_group: workflow_dispatch: 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/README.md b/README.md index 8dee674..4b8456c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,48 @@ -# 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._ + +[//]: # (Footer) + +--- + +Read the documentation: [Getting Started](docs/00-gettingstarted.md) 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/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"` } diff --git a/docs/gettingstarted.md b/docs/00-gettingstarted.md similarity index 84% rename from docs/gettingstarted.md rename to docs/00-gettingstarted.md index d514690..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. @@ -15,14 +10,27 @@ 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 package main import ( - "testing" + "context" + "testing" "github.com/foomo/gocontentful/test" "github.com/foomo/gocontentful/test/testapi" @@ -31,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") } ``` @@ -55,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: @@ -82,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") @@ -104,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") ``` @@ -126,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 91% rename from docs/client/richtext.md rename to docs/02-client/04-richtext.md index 8c78842..1a5958f 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. @@ -28,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/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 96% rename from docs/api-reference.md rename to docs/04-api-reference.md index 93a336c..83f7883 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 @@ -87,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"._ @@ -373,7 +382,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. @@ -396,6 +405,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 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._ diff --git a/erm/generator.go b/erm/generator.go index 49f8718..b78033c 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 @@ -53,13 +54,14 @@ 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, } { 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 +72,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 f7124a9..93c890a 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" @@ -12,6 +12,7 @@ import ( "strings" "github.com/foomo/contentful" + "github.com/pkg/errors" ) // spaceConf is the space config object passed to the template @@ -26,15 +27,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 +48,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 +82,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 +107,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,16 +141,16 @@ 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) 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 9888c8e..5fea7ac 100644 --- a/erm/template.go +++ b/erm/template.go @@ -1,7 +1,11 @@ package erm import ( + "strings" "text/template" + + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func getFuncMap() template.FuncMap { @@ -27,6 +31,7 @@ func getFuncMap() template.FuncMap { "mapFieldType": mapFieldType, "mapFieldTypeLiteral": mapFieldTypeLiteral, "onlyLetters": onlyLetters, + "oneLine": oneLine, } } @@ -152,3 +157,11 @@ 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", " ") +} + +func firstCap(s string) string { + return strings.TrimRight(cases.Title(language.Und, cases.NoLower).String(s), "_") +} diff --git a/erm/templates/contentful_vo.gotmpl b/erm/templates/contentful_vo.gotmpl index e8d2a3b..46fa183 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 da61eb7..09f4ee8 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" @@ -159,4 +159,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 54ce960..78add7a 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 ( @@ -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" ) @@ -172,9 +173,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" + ErrorEntryIsNil = "entry is nil" + ErrorEntrySysIsNil = "entry.Sys is nil" ErrorEntryNotFound = "entry not found" - ErrorEntryIsNil = "entry is nil" - ErrorEntrySysIsNil = "entry.Sys is nil" ErrorEntrySysContentTypeIsNil = "entry.Sys.ContentType is nil" ErrorEntrySysContentTypeSysIsNil = "entry.Sys.ContentType.Sys is nil" ErrorEntryCachingFailed = "entry caching failed" @@ -185,7 +186,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) { @@ -264,18 +265,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 } @@ -294,11 +295,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") } @@ -315,7 +316,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 { @@ -342,7 +343,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") } @@ -361,7 +362,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 { @@ -455,7 +456,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") } @@ -498,7 +499,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 { @@ -540,7 +541,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) } @@ -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 { @@ -576,7 +600,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) } @@ -610,8 +634,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) { @@ -625,12 +651,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 } @@ -651,6 +677,70 @@ func (genericEntry *GenericEntry) InheritAsString(fieldName string, parentTypes 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 { @@ -681,12 +771,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 } @@ -700,7 +790,7 @@ func (genericEntry *GenericEntry) InheritAsFloat64(fieldName string, parentTypes if err != nil { return 0, err } else { - return parentVal, nil + return parentVal, nil } } } @@ -749,12 +839,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 } @@ -768,7 +858,250 @@ func (genericEntry *GenericEntry) InheritAsReference(fieldName string, parentTyp if err != nil { return nil, err } else { - return parentVal, nil + return parentVal, nil + } + } + } + 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 } } } @@ -800,7 +1133,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,16 +1151,20 @@ 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)) } return err - } + } return nil } +func (cc *ContentfulClient) ClientMode() ClientMode { + return cc.clientMode +} + func (cc *ContentfulClient) SetCacheUpdateTimeout(seconds int64) { cc.cacheUpdateTimeout = seconds } @@ -868,7 +1205,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 +1224,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 +1250,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,15 +1271,23 @@ 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 nil, nil, ctx.Err() + } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( + ctx, cc.SpaceID, cc.syncToken == "", cc.syncToken, ) cc.cacheMutex.sharedDataGcLock.RUnlock() - col.GetAll() + if _, err := col.GetAll(); err != nil { + return nil, nil, err + } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken cc.cacheInit = true @@ -951,11 +1296,14 @@ 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 nil, nil, ctx.Err() + } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) errEntry := json.NewDecoder(bytes.NewReader(byteArray)).Decode(entry) @@ -974,63 +1322,82 @@ 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 nil, nil, ctx.Err() + } switch entry.Sys.Type { case sysTypeEntry: if !stringSliceContains(spaceContentTypes, entry.Sys.ContentType.Sys.ID) { continue } - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "contentType": entry.Sys.ContentType.Sys.ID, "entryId": entry.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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]++ + 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() contentType := cc.Cache.idContentTypeMap[entry.Sys.ID] cc.cacheMutex.idContentTypeMapGcLock.RUnlock() - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "op":"delete", "entryId": entry.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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"]++ + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } + if _, ok := syncdEntries[contentType]; !ok { + syncdEntries[contentType] = []string{} + } + syncdEntries[contentType] = append(syncdEntries[contentType],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 nil, nil, ctx.Err() + } switch asset.Sys.Type { case sysTypeAsset: - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "assetId": asset.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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") + } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets,asset.Sys.ID) } case sysTypeDeletedAsset: - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "op":"delete", "assetId": asset.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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") + } } 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) { @@ -1053,7 +1420,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() @@ -1181,7 +1548,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 { @@ -1198,7 +1565,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 { @@ -1250,7 +1617,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") } @@ -1269,7 +1636,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() @@ -1311,8 +1678,8 @@ func getOfflineSpaceFromFile(file []byte) (*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 @@ -1331,7 +1698,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 @@ -1586,6 +1953,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: @@ -1769,7 +2141,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") } @@ -1823,7 +2195,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(cc *ContentfulClient, id string, contentTypes []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 @@ -1843,7 +2215,7 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p } 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 { @@ -1865,7 +2237,10 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p 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()) @@ -1992,4 +2367,4 @@ func (rawFields RawFields) GetChildIDs() (childIDs []string) { } } return childIDs -} \ 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 ce421eb..e562eee 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 ( @@ -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) } @@ -737,7 +737,6 @@ func (cc *ContentfulClient) cache{{ firstCap $contentType.Sys.ID }}ByID(ctx cont defer cc.cacheMutex.parentMapGcLock.Unlock() cc.cacheMutex.genericEntriesGcLock.Lock() defer cc.cacheMutex.genericEntriesGcLock.Unlock() - var col *contentful.Collection if entryPayload != nil { col = &contentful.Collection{ @@ -749,13 +748,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 { @@ -841,7 +840,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 { @@ -863,11 +862,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/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/erm/util.go b/erm/util.go index 38e7c73..c261c81 100644 --- a/erm/util.go +++ b/erm/util.go @@ -2,12 +2,10 @@ package erm import ( "regexp" - "strings" - - "golang.org/x/text/cases" - "golang.org/x/text/language" ) +var onlyLettersRegex = regexp.MustCompile("[^A-Za-z]") + func sliceIncludes(slice []string, key string) bool { for _, val := range slice { if val == key { @@ -17,13 +15,6 @@ func sliceIncludes(slice []string, key string) bool { return false } -func firstCap(inputString string) (outputString string) { - outputString = cases.Title(language.English, cases.Option(cases.NoLower)).String(inputString) - outputString = strings.ReplaceAll(outputString, "_", "") - 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 10e5cbd..94600cc 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,21 @@ module github.com/foomo/gocontentful go 1.21 require ( - github.com/foomo/contentful v0.3.6 - github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.7.0 - golang.org/x/sync v0.1.0 - golang.org/x/text v0.14.0 - golang.org/x/tools v0.6.0 - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + 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/net v0.26.0 + golang.org/x/sync v0.7.0 + golang.org/x/text v0.16.0 + golang.org/x/tools v0.22.0 + gopkg.in/yaml.v3 v3.0.1 ) 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 - github.com/smartystreets/goconvey v1.6.4 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.5.0 // indirect - moul.io/http2curl v1.0.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 394f2a8..e5c183d 100644 --- a/go.sum +++ b/go.sum @@ -1,43 +1,37 @@ +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.3.6 h1:yiwhWayrXCe0wpQGzhO32bl8AE/46T261bBqNdPODWk= -github.com/foomo/contentful v0.3.6/go.mod h1:6Pf8efSKeMbwKgVkjSFWeBsLYdUA0NTW7OUsMWzHXvY= -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.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/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= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -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.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +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.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.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index c7f8415..e453272 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "flag" "fmt" @@ -14,7 +15,7 @@ import ( "github.com/foomo/gocontentful/erm" ) -var VERSION = "v1.1.0" +var VERSION = "latest" type contentfulRc struct { ManagementToken string `json:"managementToken"` @@ -105,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") } @@ -136,7 +140,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 4f88015..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)) @@ -38,7 +39,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 +61,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 +69,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)) @@ -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,9 +101,9 @@ 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("JrePkDVYomE8AwcuCUyMi") + brand, err := contentfulClient.GetBrandByID(context.TODO(), "JrePkDVYomE8AwcuCUyMi") require.NoError(t, err) require.Equal(t, 2.0, brand.Sys.Version) } @@ -140,7 +141,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/concurrency_test.go b/test/concurrency_test.go index 23cdfc4..73844d0 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,37 @@ 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{ + 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("") + _ = product.SetQuantity(1) + _ = product.SetSeoText("") + _ = product.SetSizetypecolor("") + _ = product.SetSku("") + _ = product.SetSlug("") + _ = product.SetTags([]string{""}) + _ = product.SetWebsite("") return nil } @@ -94,7 +112,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) } @@ -105,7 +123,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 +134,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 +145,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 be76c56..3616c08 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()) } @@ -35,7 +36,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/gocontentfulvo.go b/test/testapi/gocontentfulvo.go index dc275c0..99cd1a9 100644 --- a/test/testapi/gocontentfulvo.go +++ b/test/testapi/gocontentfulvo.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - 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 6a4b708..30fd503 100644 --- a/test/testapi/gocontentfulvobase.go +++ b/test/testapi/gocontentfulvobase.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - 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 228e4c9..ca8065f 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( @@ -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" ) @@ -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" @@ -281,18 +281,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 } @@ -311,11 +311,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") } @@ -332,7 +332,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 { @@ -359,7 +359,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") } @@ -392,7 +392,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 { @@ -486,7 +486,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 { @@ -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) } @@ -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 { @@ -642,8 +665,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) { @@ -657,12 +682,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 } @@ -683,6 +708,70 @@ func (genericEntry *GenericEntry) InheritAsString(fieldName string, parentTypes 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 { @@ -713,12 +802,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 +870,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 } @@ -807,6 +896,249 @@ func (genericEntry *GenericEntry) InheritAsReference(fieldName string, parentTyp 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 { @@ -831,7 +1163,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 +1181,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)) @@ -859,6 +1191,10 @@ func (genericEntry *GenericEntry) Upsert() error { return nil } +func (cc *ContentfulClient) ClientMode() ClientMode { + return cc.clientMode +} + func (cc *ContentfulClient) SetCacheUpdateTimeout(seconds int64) { cc.cacheUpdateTimeout = seconds } @@ -899,7 +1235,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 +1254,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 +1285,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,15 +1301,23 @@ 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 nil, nil, ctx.Err() + } cc.cacheMutex.sharedDataGcLock.RLock() col := cc.Client.Entries.Sync( + ctx, cc.SpaceID, cc.syncToken == "", cc.syncToken, ) cc.cacheMutex.sharedDataGcLock.RUnlock() - col.GetAll() + if _, err := col.GetAll(); err != nil { + return nil, nil, err + } cc.cacheMutex.sharedDataGcLock.Lock() cc.syncToken = col.SyncToken cc.cacheInit = true @@ -982,11 +1326,14 @@ 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 nil, nil, ctx.Err() + } entry := &contentful.Entry{} byteArray, _ := json.Marshal(item) errEntry := json.NewDecoder(bytes.NewReader(byteArray)).Decode(entry) @@ -1005,63 +1352,82 @@ 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 nil, nil, ctx.Err() + } switch entry.Sys.Type { case sysTypeEntry: if !stringSliceContains(spaceContentTypes, entry.Sys.ContentType.Sys.ID) { continue } - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, entry.Sys.ContentType.Sys.ID, entry.Sys.ID, entry, false) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "contentType": entry.Sys.ContentType.Sys.ID, "entryId": entry.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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]++ + 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() contentType := cc.Cache.idContentTypeMap[entry.Sys.ID] cc.cacheMutex.idContentTypeMapGcLock.RUnlock() - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, contentType, entry.Sys.ID, entry, true) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "op": "delete", "entryId": entry.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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"]++ + if syncdEntries == nil { + syncdEntries = map[string][]string{} + } + if _, ok := syncdEntries[contentType]; !ok { + syncdEntries[contentType] = []string{} + } + syncdEntries[contentType] = append(syncdEntries[contentType], 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 nil, nil, ctx.Err() + } switch asset.Sys.Type { case sysTypeAsset: - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, asset, false) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "assetId": asset.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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") + } } else { - syncAssetCount++ + syncdAssets = append(syncdAssets, asset.Sys.ID) } case sysTypeDeletedAsset: - errUpdate := updateCacheForContentTypeAndEntity(ctx, cc, assetWorkerType, asset.Sys.ID, nil, true) - if errUpdate != nil && cc.logFn != nil && cc.logLevel <= LogInfo { - cc.logFn(map[string]interface{}{"task": "syncCache", "error": errUpdate, "op": "delete", "assetId": asset.Sys.ID}, - LogWarn, ErrorEntryCachingFailed) + 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") + } } 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) { @@ -1084,7 +1450,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() @@ -1216,7 +1582,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 { @@ -1295,7 +1661,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") } @@ -1314,7 +1680,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() @@ -1356,8 +1722,8 @@ func getOfflineSpaceFromFile(file []byte) (*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 @@ -1376,7 +1742,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 @@ -1631,6 +1997,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: @@ -1852,7 +2223,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") } @@ -1930,7 +2301,7 @@ func updateCacheForContentTypeAndEntity(ctx context.Context, cc *ContentfulClien return nil } -func commonGetParents(cc *ContentfulClient, id string, contentTypes []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 @@ -1950,7 +2321,7 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p } 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 { @@ -1972,7 +2343,10 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p 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()) @@ -1987,7 +2361,10 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p 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()) @@ -2002,7 +2379,10 @@ func commonGetParents(cc *ContentfulClient, id string, contentTypes []string) (p 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()) diff --git a/test/testapi/gocontentfulvolibbrand.go b/test/testapi/gocontentfulvolibbrand.go index b9e767e..601ca8c 100644 --- a/test/testapi/gocontentfulvolibbrand.go +++ b/test/testapi/gocontentfulvolibbrand.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( @@ -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) } @@ -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{ @@ -809,7 +808,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 2838cb4..a79214e 100644 --- a/test/testapi/gocontentfulvolibcategory.go +++ b/test/testapi/gocontentfulvolibcategory.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( @@ -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) } @@ -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{ @@ -573,7 +572,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 4cc9ff8..1f47d6d 100644 --- a/test/testapi/gocontentfulvolibproduct.go +++ b/test/testapi/gocontentfulvolibproduct.go @@ -1,4 +1,4 @@ -// Code generated by https://github.com/foomo/gocontentful v1.1.0 - DO NOT EDIT. +// Code generated by https://github.com/foomo/gocontentful - DO NOT EDIT. package testapi import ( @@ -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 { @@ -139,14 +139,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 { @@ -312,7 +312,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 } @@ -350,7 +350,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) @@ -417,7 +417,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 } @@ -453,7 +453,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) @@ -463,7 +463,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) @@ -473,7 +473,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) @@ -483,7 +483,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) @@ -534,7 +534,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 } @@ -569,7 +569,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) @@ -579,7 +579,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) @@ -589,7 +589,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) @@ -599,7 +599,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) @@ -612,7 +612,7 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference { 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) @@ -1179,7 +1179,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") } @@ -1199,13 +1199,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") } @@ -1224,13 +1224,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") } @@ -1249,13 +1249,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") } @@ -1275,7 +1275,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) } @@ -1287,13 +1287,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") } @@ -1313,12 +1313,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) } @@ -1351,7 +1351,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()) } @@ -1363,7 +1363,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) } @@ -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{ @@ -1446,7 +1445,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 { 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