Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contentful v0.5.1 #7

Merged
merged 39 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
74a8835
chore: add pr test workflow
franklinkim Apr 26, 2024
4245282
chore: add dependabot
franklinkim Apr 26, 2024
6b293a7
chore: add editorconfig
franklinkim Apr 26, 2024
698bb8e
chore: update workflows
franklinkim Apr 26, 2024
b02cb9b
chore: update gitignore
franklinkim Apr 26, 2024
dee9178
chore: update goreleaser
franklinkim Apr 26, 2024
30d92d9
chore: rename license
franklinkim Apr 26, 2024
fffaf1c
chore: update makefile
franklinkim Apr 26, 2024
8f8d920
chore: update golangci lint settings
franklinkim Apr 26, 2024
81670af
feat: bump foomo/contentful-v0.5.0
franklinkim Apr 26, 2024
763644f
feat: bump foomo/contentful-v0.5.0
franklinkim Apr 26, 2024
473a3e7
feat: remove version from generated code
franklinkim Apr 26, 2024
6b3a933
feat: use latest for development
franklinkim Apr 26, 2024
d02418a
chore: fix name
franklinkim Apr 26, 2024
fdf6770
fix: handle context deadline
franklinkim Apr 26, 2024
7fc3242
feat: bump contentful-v0.5.1
franklinkim Apr 27, 2024
6585f17
feat: wrap errors
franklinkim Apr 27, 2024
15c05d4
feat: ensure one line description
franklinkim Apr 27, 2024
ff7e082
feat: handle error
franklinkim Apr 27, 2024
3ce91a9
feat: handle error
franklinkim Apr 27, 2024
f0b9b35
Merge branch 'master' into contentful-v0.5.0
cvidmar Apr 29, 2024
22c3300
missing import
cvidmar Apr 29, 2024
441c2f9
chore: integrate and fix more changes from master
Apr 29, 2024
54c7108
fix: lint err check
Apr 29, 2024
5738aee
chore: linting
Apr 29, 2024
3133415
feat: cache sync counts
May 8, 2024
b3410cb
chore: docs update
May 8, 2024
caeb4c9
feat: meta file for generator version
May 9, 2024
0fbf407
chore: missing testapi file
May 9, 2024
b8b21cd
fix: concurrency test
May 9, 2024
046cefd
Update README.md
cvidmar May 9, 2024
b684c32
feat: more generic entry field getters
May 17, 2024
3029154
Merge remote-tracking branch 'origin/contentful-v0.5.0' into contentf…
May 17, 2024
a926160
fix: panic with deleted entries sync
Jun 17, 2024
d271184
fix: RichTextToPlainText
Jul 1, 2024
30c98ba
fix: run make test
franklinkim Jul 1, 2024
ffa7171
chore: docs update
Jul 1, 2024
9dc2d50
feat: ClientMode() and docs update
Aug 12, 2024
d5873e4
fix: lint errors
franklinkim Aug 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: weekly
- package-ecosystem: gomod
directory: '/'
schedule:
interval: weekly
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: checks

on:
push:
branches: [ master ]
branches: [ main ]
pull_request:
merge_group:
workflow_dispatch:
Expand Down
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
53 changes: 45 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
12 changes: 6 additions & 6 deletions config/vo.go
Original file line number Diff line number Diff line change
@@ -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"`
}
49 changes: 31 additions & 18 deletions docs/gettingstarted.md → docs/00-gettingstarted.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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"
Expand All @@ -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")
}
```

Expand All @@ -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:
Expand All @@ -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")
Expand All @@ -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")
```
Expand All @@ -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 {
Expand Down
5 changes: 0 additions & 5 deletions docs/setup.md → docs/01-setup.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
---
sidebar_label: Setup
sidebar_position: 1
---

# Gocontentful Setup

## Installation
Expand Down
5 changes: 0 additions & 5 deletions docs/client/index.md → docs/02-client/00-index.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
```

Expand All @@ -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
Expand Down
26 changes: 11 additions & 15 deletions docs/client/entries.md → docs/02-client/02-entries.md
Original file line number Diff line number Diff line change
@@ -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"}
})
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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.
7 changes: 1 addition & 6 deletions docs/client/assets.md → docs/02-client/03-assets.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
13 changes: 7 additions & 6 deletions docs/client/richtext.md → docs/02-client/04-richtext.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
```
Loading