Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
contentful-management
Advanced tools
Ruby client for the Contentful Content Management API.
Contentful provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster.
Add this line to your application's Gemfile:
gem 'contentful-management'
Some examples can be found in the examples/
directory or you take a look at this extended example script.
At the beginning the API client instance should be created for each thread that is going to be used in your application:
require 'contentful/management'
client = Contentful::Management::Client.new('access_token')
The access token can easily be created through the management api documentation.
Retrieving all spaces:
spaces = client.spaces.all
Retrieving one space by ID:
blog_space = client.spaces.find('blog_space_id')
Destroying a space:
blog_space.destroy
Creating a space:
blog_space = client.spaces.new
blog_space.name = 'Blog Space'
blog_space.save
or
blog_space = client.spaces.create(name: 'Blog Space')
or in the context of the organization (if you have multiple organizations within your account):
blog_space = client.spaces.create(name: 'Blog Space', organization_id: 'organization_id')
If you want to create a default locale different from en-US
:
blog_space = client.spaces.create(name: 'Blog Space', default_locale: 'de-DE')
Updating a space:
blog_space.update(name: 'New Blog Space')
or
blog_space.name = 'New Blog Space'
blog_space.save
Retrieving all environments:
environments = client.environments('space_id').all
Or if you already have a fetched space:
environments = space.environments.all
Retrieving one environment by ID:
environment = client.environments('space_id').find('master')
Destroying a environment:
environment.destroy
Creating an environment:
environment = client.environments('space_id').new
environment.id = 'dev'
environment.name = 'Development'
environment.save
or
environment = client.environments(space_id).create(id: 'dev', name: 'Development')
Creating an evironment with a different source:
environment = client.environments(space_id).create(id: 'dev', name: 'Development', source_environment_id: 'other_environment')
Updating a environment:
environment.update(name: 'New Blog environment')
or
environment.name = 'Dev'
environment.save
Retrieving all assets from the environment:
blog_post_assets = environment.assets.all
Retrieving an asset by id:
blog_post_asset = environment.assets.find('asset_id')
Creating a file:
image_file = Contentful::Management::File.new
image_file.properties[:contentType] = 'image/jpeg'
image_file.properties[:fileName] = 'example.jpg'
image_file.properties[:upload] = 'http://www.example.com/example.jpg'
Creating an asset:
my_image_asset = environment.assets.create(title: 'My Image', description: 'My Image Description', file: image_file)
or an asset with multiple locales
my_image_localized_asset = environment.assets.new
my_image_localized_asset.title_with_locales= {'en-US' => 'title', 'pl' => 'pl title'}
my_image_localized_asset.description_with_locales= {'en-US' => 'description', 'pl' => 'pl description'}
en_file = Contentful::Management::File.new
en_file.properties[:contentType] = 'image/jpeg'
en_file.properties[:fileName] = 'pic1.jpg'
en_file.properties[:upload] = 'http://www.example.com/pic1.jpg'
pl_file = Contentful::Management::File.new
pl_file.properties[:contentType] = 'image/jpeg'
pl_file.properties[:fileName] = 'pic2.jpg'
pl_file.properties[:upload] = 'http://www.example.com/pic2.jpg'
asset.file_with_locales= {'en-US' => en_file, 'pl' => pl_file}
asset.save
Process an asset file after create:
asset.process_file
Updating an asset:
my_image_asset.update(title: 'My Image', description: 'My Image Description', file: image_file)
my_image_asset.locale = 'nl'
my_image_asset.update(title: 'NL Title', description: 'NL Description', file: nl_image)
my_image_asset.title_with_locales = {'en-US' => 'US Title', 'nl' => 'NL Title'}
my_image_asset.save
Destroying an asset:
my_image_asset.destroy
Archiving or unarchiving an asset:
my_image_asset.archive
my_image_asset.unarchive
Checking if an asset is archived:
my_image_asset.archived?
Publishing or unpublishing an asset:
my_image_asset.publish
my_image_asset.unpublish
Checking if an asset is published:
my_image_asset.published?
Checking if has been updated from last published version:
my_image_asset.updated?
Creating an upload from a file path:
upload = client.uploads('space_id').create('/path/to/file.md')
Alternatively, create it from an ::IO
object:
File.open('/path/to/file.md', 'rb') do |file|
upload = client.uploads('space_id').create(file)
end
Finding an upload:
upload = client.uploads('space_id').find('upload_id')
Deleting an upload:
upload.destroy
Associating an upload with an asset:
# We find or create an upload:
upload = client.uploads('space_id').find('upload_id')
# We create a File object with the associated upload:
file = Contentful::Management::File.new
file.properties[:contentType] = 'text/plain'
file.properties[:fileName] = 'file.md'
file.properties[:uploadFrom] = upload.to_link_json # We create the Link from the upload.
# We create an asset with the associated file:
asset = client.assets('space_id', 'environment_id').create(title: 'My Upload', file: file)
asset.process_file # We process the file, to generate an URL for our upload.
Retrieving all entries from the environment:
entries = environment.entries.all
Retrieving all entries from the environment with given content type:
entries = environment.entries.all(content_type: content_type.id)
or
entries = content_type.entries.all
Retrieving an entry by ID:
entry = environment.entries.find('entry_id')
Retrieving entries by any other field value:
entries = environment.entries.all(content_type: content_type.id, 'fields.fieldName' => 'value')
Note: all search parameters are supported.
Creating a location:
location = Location.new
location.lat = 22.44
location.lon = 33.33
Creating an entry:
my_entry = blog_post_content_type.entries.create(post_title: 'Title', assets_array_field: [image_asset_1, ...], entries_array_field: [entry_1, ...], location_field: location)
my_entry = blog_post_content_type.entries.new
my_entry.post_title_with_locales = {'en-US' => 'EN Title', 'pl' => 'PL Title'}
my_entry.save
Updating an entry:
my_entry.update(params)
entry.locale = 'nl'
entry.update(params)
my_entry.post_title_with_locales = {'en-US' => 'EN Title', 'pl' => 'PL Title'}
my_entry.save
Destroying an entry:
my_entry.destroy
Archiving or unarchiving the entry:
my_entry.archive
my_entry.unarchive
Checking if the entry is archived:
my_entry.archived?
Publishing or unpublishing the entry:
my_entry.publish
my_entry.unpublish
Checking if the entry is published:
my_entry.published?
Checking if the entry has been updated from last publish:
my_entry.updated?
Entries created with empty fields, will not return those fields in the response. Therefore, entries that don't have cache enabled, will need to make an extra request to fetch the content type and fill the missing fields. To allow for content type caching:
- Enable content type cache at client instantiation time
- Query entries through
environment.entries.find
instead ofEntry.find(environment_id, entry_id)
Retrieving all content types from a environment:
blog_post_content_types = environment.content_types.all
Retrieving all published content types from a environment:
blog_post_content_types = environment.content_types.all_published
Retrieving one content type by ID from a environment:
blog_post_content_type = environment.content_types.find(id)
Creating a field for a content type:
title_field = Contentful::Management::Field.new
title_field.id = 'blog_post_title'
title_field.name = 'Post Title'
title_field.type = 'Text'
blog_post_content_type.fields.add(field)
or
blog_post_content_type.fields.create(id: 'title_field_id', name: 'Post Title', type: 'Text')
or the field of link type:
blog_post_content_type.fields.create(id: 'my_entry_link_field', name: 'My Entry Link Field', type: 'Link', link_type: 'Entry')
or the field of ResourceLink type:
blog_post_content_type.fields.create(
id: 'my_resource_link_id',
name: 'My Resource Link',
type: 'ResourceLink',
localized: true,
disabled: false,
omitted: false,
allowed_resources: [
{
type: 'Contentful:Entry',
source: 'crn:contentful:::content:spaces/space_id',
contentTypes: ["foo", "bar"]
}
]
)
or the field of an array type:
items = Contentful::Management::Field.new
items.type = 'Link'
items.link_type = 'Entry'
blog_post_content_type.fields.create(id: 'my_array_field', name: 'My Array Field', type: 'Array', items: items)
Deleting a field from the content type:
blog_post_content_type.fields.destroy(title_field_id)
Creating a content type:
environment.content_types.create(name: 'Post', fields: [title_field, body_field])
or
blog_post_content_type = environment.content_types.new
blog_post_content_type.name = 'Post'
blog_post_content_type.fields = [title_field, body_field]
blog_post_content_type.save
Destroying a content type:
blog_post_content_type.destroy
Activating or deactivating a content type:
blog_post_content_type.activate
blog_post_content_type.deactivate
Checking if a content type is active:
blog_post_content_type.active?
Updating a content type:
blog_post_content_type.update(name: 'Post', description: 'Post Description', fields: [title_field])
Takes an array of values and validates that the field value is in this array.
validation_in = Contentful::Management::Validation.new
validation_in.in = ['foo', 'bar', 'baz']
blog_post_content_type.fields.create(id: 'valid', name: 'Testing IN', type: 'Text', validations: [validation_in])
Takes optional min and max parameters and validates the size of the array (number of objects in it).
validation_size = Contentful::Management::Validation.new
validation_size.size = { min: 10, max: 15 }
blog_post_content_type.fields.create(id: 'valid', name: 'Test SIZE', type: 'Text', validations: [validation_size])
Takes optional min and max parameters and validates the range of a value.
validation_range = Contentful::Management::Validation.new
validation_range.range = { min: 100, max: 150 }
blog_post_content_type.fields.create(id: 'valid', name: 'Range', type: 'Text', validations: [validation_range])
Takes a string that reflects a JS regex and flags, validates against a string. See JS Reference for the parameters.
validation_regexp = Contentful::Management::Validation.new
validation_regexp.regexp = {pattern: '^such', flags: 'im'}
blog_post_content_type.fields.create(id: 'valid', name: 'Regex', type: 'Text', validations: [validation_regexp])
Takes an array of content type ids and validates that the link points to an entry of that content type.
validation_link_content_type = Contentful::Management::Validation.new
validation_link_content_type.link_content_type = ['post_content_type_id']
blog_post_content_type.fields.create(id: 'entry', name: 'Test linkContentType', type: 'Entry', validations: [validation_link_content_type])
Takes a MimeType group name and validates that the link points to an asset of this group.
validation_link_mimetype_group = Contentful::Management::Validation.new
validation_link_mimetype_group.link_mimetype_group = 'image'
content_type.fields.create(id: 'asset', validations: [validation_link_mimetype_group])
Validates that a value is present.
validation_present = Contentful::Management::Validation.new
validation_present.present = true
content_type.fields.create(id: 'number', validations: [validation_present])
Validates that the property is a link (must not be a valid link, just that it looks like one).
validation_link_field = Contentful::Management::Validation.new
validation_link_field.link_field = true
content_type.fields.create(id: 'entry', validations: [validation_link_field])
Retrieving all locales from the environment:
blog_post_locales = environment.locales.all
Retrieving one locale by ID from the environment:
blog_post_locale = environment.locales.find(locale_id)
Creating a locale:
environment.locales.create(name: 'German', code: 'de-DE')
Creating a locale with fallback:
environment.locales.create(name: 'German', code: 'de-DE', fallback_code: 'en-US')
Updating a locale:
blog_post_locale.update(name: 'German', code: 'de-DE')
Updating a locale with fallback:
blog_post_locale.update(name: 'German', code: 'de-DE', fallback_code: 'en-US')
Destroying a locale:
blog_post_locale.destroy
Retrieving all tags from the environment:
tags = environment.tags.all
Retrieving one tag by ID from the environment:
tag = environment.tags.find(tag_id)
Creating a tag:
environment.tags.create(name: 'tag name', id: 'tagID')
Updating a tag:
tag.update(name: 'new name')
Destroying a tag:
tag.destroy
Tagging an entry:
entry.update(_metadata: {"tags": [{ "sys": { "type": "Link", "linkType": "Tag", "id": "fooTag" } }]})
Tagging an asset:
asset.update(_metadata: {"tags": [{ "sys": { "type": "Link", "linkType": "Tag", "id": "fooTag" } }]})
Retrieving all roles from the space:
blog_post_roles = blog_space.roles.all
Retrieving one role by ID from the space:
blog_post_role = blog_space.role.find(role_id)
Creating a role:
role_attributes = {
name: 'My Role',
description: 'foobar role',
permissions: {
'ContentDelivery': 'all',
'ContentModel': ['read'],
'Settings': []
},
policies: [
{
effect: 'allow',
actions: 'all',
constraint: {
and: [
{
equals: [
{ doc: 'sys.type' },
'Entry'
]
},
{
equals: [
{ doc: 'sys.type' },
'Asset'
]
}
]
}
}
]
}
blog_space.roles.create(role_attributes)
Updating a role:
blog_post_role.update(name: 'Some Other Role') # Can change any attribute here
Destroying a role:
blog_post_role.destroy
Retrieving all webhooks from the space:
webhooks = blog_space.webhooks.all
Retrieving one webhook by ID from the space:
blog_post_webhook = blog_space.webhooks.find(webhook_id)
Creating a webhook:
blog_space.webhooks.create(
name: 'My Webhook',
url: 'https://www.example.com',
httpBasicUsername: 'username',
httpBasicPassword: 'password'
)
Updating a webhook:
blog_post_webhook.update(url: 'https://www.newlink.com')
Destroying a webhook:
blog_post_webhook.destroy
Creating a webhook with custom headers and custom topics:
blog_space.webhooks.create(
name: 'Entry Save Only',
url: 'https://www.example.com',
topics: [ 'Entry.save' ],
headers: [
{
key: 'X-My-Custom-Header',
value: 'Some Value'
}
]
)
Retrieving all webhook call details from a webhook:
all_call_details = my_webhook.webhook_calls.all
Retrieving one webhook call detail by ID from a webhook:
call_details = my_webhook.webhook_calls.find(call_id)
Retrieving webhook health details from a webhook:
health_details = my_webhook.webhook_health.find
Retrieving all space memberships from the space:
memberships = blog_space.space_memberships.all
Retrieving one space membership by ID from the space:
blog_post_membership = blog_space.space_memberships.find(membership_id)
Creating a space membership:
blog_space.space_memberships.create(
admin: false,
roles: [
{
'sys' => {
'type' => 'Link',
'linkType' => 'Role',
'id' => 'my_role_id'
}
}
],
email: 'foobar@example.com'
)
Updating a space membership:
blog_post_membership.update(admin: true)
Destroying a space membership:
blog_post_membership.destroy
Retrieving all organization details:
organizations = client.organizations.all
Note: This feature is available only to Commited v2 customers.
Retrieving all API Usage statistics for an Organization during a given usage period, broken down by organization for all APIs:
# Optionally, you can pass the metric, start and end date filters
usage = client.organization_periodic_usages('organization_id').all
# For example only CDA and CMA metrics from yesterday onwards
usage = client.organization_periodic_usages('organization_id').all('metric[in]': ['cda', 'cma'], startDate: (Date.today - 1).iso8601)
Alternatively, if you have an already fetched organization:
# Breaking down CMA usage by space, for a given period.
usage = organization.periodic_usages.all
Retrieving all API Usage statistics for an Organization during a given usage period, broken down by space for all APIs:
# Optionally, you can pass the metric, start and end date filters
usage = client.space_periodic_usages('organization_id').all
# For example only CDA and CMA metrics from yesterday onwards
usage = client.space_periodic_usages('organization_id').all('metric[in]': ['cda', 'cma'], startDate: (Date.today - 1).iso8601)
Alternatively, if you have an already fetched organization:
# Breaking down CMA usage by space, for a given period.
usage = organization.space_periodic_usages.all
Retrieving current user details:
user = client.users.me
Retrieving all users in organization:
user = organization.users.all
Retrieving one user by ID from an organization:
user = organization.users.find('user_id')
Retrieving all users in a space:
user = blog_space.users.all
Retrieving one user by ID from the space:
user = blog_space.users.find('user_id')
Retrieving all UI extensions from the environment:
extensions = environment.ui_extensions.all
Retrieving one UI extension by ID from the environment:
blog_post_extension = environment.ui_extensions.find(extension_id)
Creating a UI extension:
environment.ui_extensions.create(
extension: {
'name' => 'My extension',
'src' => 'https://www.example.com',
'fieldTypes' => [{"type": "Symbol"}],
'sidebar' => false
}
)
Destroying a UI extension:
blog_post_extension.destroy
Retrieving all API keys from the space:
blog_post_api_keys = blog_space.api_keys.all
Retrieving one API key by ID from the space:
blog_post_api_key = blog_space.api_keys.find(api_key_id)
Creating an API key:
blog_space.api_keys.create(name: 'foobar key', description: 'key for foobar mobile app')
Creating an API key with multiple environments:
blog_space.api_keys.create(
name: 'foobar key - multiple environments',
description: 'key for foobar app',
environments: [
{
sys: {
type: 'Link',
linkType: 'Environment',
id: 'master'
}
},
{
sys: {
type: 'Link',
linkType: 'Environment',
id: 'staging'
}
}
]
)
Retrieving all Preview API keys from the space:
blog_post_preview_api_keys = blog_space.preview_api_keys.all
Retrieving one Preview API key by ID from the space:
blog_post_preview_api_key = blog_space.preview_api_keys.find(api_key_id)
If you already have an API key fetched, you can retrieve the Preview API key from it:
blog_post_preview_api_key = blog_post_api_key.preview_api_key
Retrieving all personal access tokens:
tokens = client.personal_access_tokens.all
Retrieving one personal access token by ID:
token = client.personal_access_tokens.find(token_id)
Creating a personal access token:
client.personal_access_tokens.create(name: 'foobar key', scopes: ['content_management_manage'])
Revoking a personal access token:
token.revoke
Retrieving editor interface for a content type:
blog_post_editor_interface = blog_post_content_type.editor_interface.default
You can call the EditorInterface API from any level within the content model hierarchy, take into account that you'll need to pass the IDs of the levels below it.
Hierarchy is as follows:
No Object -> Environment -> ContentType -> EditorInterface
Retrieving all snapshots for a given entry:
snapshots = entry.snapshots.all
Retrieving a snapshot for a given entry:
snapshot = entry.snapshots.find('some_snapshot_id')
Retrieving entry references:
references = entry.references(include: 1)
Retrieving all snapshots for a given content type:
snapshots = content_type.snapshots.all
Retrieving a snapshot for a given content type:
snapshot = content_type.snapshots.find('some_snapshot_id')
environment.entries.all(limit: 5).next_page
environment.assets.all(limit: 5).next_page
environment.entries.all(limit: 5).next_page
Logging is disabled by default, it can be enabled by setting a logger instance and a logging severity.
client = Contentful::Management::Client.new('access_token', logger: logger_instance, log_level: Logger::DEBUG)
Example loggers:
Rails.logger
Logger.new('logfile.log')
The default severity is set to INFO and logs only the request attributes (headers, parameters and url). Setting it to DEBUG will also log the raw JSON response.
If :raise_errors
is set to true, an Exception will be raised in case of an error. The default is false, in this case a Contentful::Management::Error
object will be returned.
client = Contentful::Management::Client.new('access_token', raise_errors: true)
This allows for fetching content types for your environment at client instantiation time, which prevents extra requests per entry. To enable this, in your client instantiation do:
client = Contentful::Management::Client.new(token, dynamic_entries: {'my_space_id' => 'my_environment_id'})
You can enable the cache for as many environments as you want. If no environment is added, content types will be fetched upon environment find.
To completely disable this feature, upon client instantiation do:
client = Contentful::Management::Client.new(token, disable_content_type_caching: true)
This allows for using the CMA SDK through a proxy, for this, your proxy must support HTTPS and your server must have a valid signed certificate.
To enable this, in your client instantiation do:
PROXY_HOST = 'localhost'
PROXY_PORT = 8888
# Just host/port
client = Contributing::Management::Client.new(
token,
proxy_host: PROXY_HOST,
proxy_port: PROXY_PORT
)
# With username/password
client = Contributing::Management::Client.new(
token,
proxy_host: PROXY_HOST,
proxy_port: PROXY_PORT,
proxy_username: 'YOUR_USERNAME',
proxy_password: 'YOUR_PASSWORD'
)
With the following configuration options you can handle how rate limits are handled within your applications.
To increase or decrease the retry attempts after a 429 Rate Limit error. Default value is 1. Using 0 will disable retry behaviour.
Each retry will be attempted after the value (in seconds) of the X-Contentful-RateLimit-Reset
header, which contains the amount of seconds until the next
non rate limited request is available, has passed. This is blocking per execution thread.
Maximum time to wait for next available request (in seconds). Default value is 60 seconds. Keep in mind that if you hit the houly rate limit maximum, you can have up to 60 minutes of blocked requests. It is set to a default of 60 seconds in order to avoid blocking processes for too long, as rate limit retry behaviour is blocking per execution thread.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)FAQs
Unknown package
We found that contentful-management demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.