SlideShare a Scribd company logo
NICK
A NEARLY HEADLESS CMS
Plone Conference 2022, Namur
-
Rob Gietema @robgietema
ABOUT ME
What is Nick?
Why?
Architecture
Getting started
Documentation
Database
Contenttypes
Security
Catalog & Search
Vocabularies
Security
Utility calls
i18n
Logging
Database schema
Roadmap
WHAT WILL WE COVER?
WHAT IS NICK?
Headless CMS
Build with Node.js
RESTfull API compatible with plone.restapi (Volto)
WHY?
Fun to build!
Plone has a great architecture, great way to learn the
internals
Plone has a great Rest API
Started as a proof of concept on Ploneconf 2018 in
Tokyo
Frontend and backend using the same language
ISSUES WITH PLONE
Disclaimer: my opinion
Lots of legacy code
Lot of code to maintain ourself
Deployment
COMPLEX STACK
Python
Zope
Generic Setup (xml)
ZCML
Page templates
REST
Yaml
JSON
cfg
ini
Markdown
Javascript
Webpack
CSS / LESS / SASS
XSLT
Buildout
KSS
Portal Skins
Restricted Python
DTML
ARCHITECTURE
LANGUAGES
Javascript
JSON
Markdown
POSTGRES
Transactional
JSON integration
(text) indexing
I18N
gettext
GETTING STARTED
https://nickcms.org
GETTING STARTED
CREATE DATABASE nick;
CREATE USER nick WITH ENCRYPTED PASSWORD 'nick';
GRANT ALL PRIVILEGES ON DATABASE nick TO nick;
$ yarn bootstrap
$ yarn start
YEOMAN GENERATOR
$ npm install -g yo
$ npm install -g @robgietema/generator-nick
$ yo @robgietema/nick my-project
CREATE DATABASE my-project;
CREATE USER nick WITH ENCRYPTED PASSWORD 'my-project';
GRANT ALL PRIVILEGES ON DATABASE my-project TO my-project;
$ cd my-project
$ yarn bootstrap
$ yarn start
DEMO
http://localhost:3000
ONLINE DEMO
https://demo.nickcms.org
CONTRIBUTE
https://github.com/robgietema/nick
DOCUMENTATION
https://docs.nickcms.org
TESTS (GET.REQ)
GET /events/event-1/@breadcrumbs HTTP/1.1
Accept: application/json
TESTS (GET.RES)
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/events/event-1/@breadcrumbs",
"items": [
{
"@id": "http://localhost:8000/events",
"title": "Events"
},
{
"@id": "http://localhost:8000/events/event-1",
"title": "Event 1"
}
]
TESTS (BREADCRUMBS.MD)
---
nav_order: 14
permalink: /breadcrumbs
---
# Breadcrumbs
Getting the breadcrumbs for the current page:
```
{% include_relative examples/breadcrumbs/get.req %}
```
Example response:
RUNNING TESTS
$ yarn test
RUNNING TESTS
DATABASE
Postgres
Knex.js ( )
Objection.js (
https://knexjs.org
https://vincit.github.io/objection.js/
KNEX.JS
knex.schema.createTable('group', (table) => {
table.string('id').primary();
table.string('title');
table.string('description');
table.string('email').unique();
}
MIGRATIONS
nick
└─ src
└─ migration
└─ 202203200901_permission.js
└─ 202203200902_role.js
└─ 202203200903_group.js
└─ 202203200904_user.js
└─ 202203200905_workflow.js
└─ 202203200906_type.js
└─ 202203200907_document.js
└─ ...
$ yarn migrate
202203200901_PERMISSION.JS
export const up = async (knex) => {
await knex.schema.createTable('permission', (table) => {
table.string('id').primary();
table.string('title');
});
};
export const down = async (knex) => {
await knex.schema.dropTable('permission');
};
OBJECTION.JS (MODELS)
/**
* Permission Model.
* @module models/permission/permission
*/
import { Model } from '../../models';
/**
* A model for Permission.
* @class Permission
* @extends Model
*/
export class Permission extends Model {
// Set relation mappings
static get relationMappings() {
OBJECTION.JS (BASE MODEL)
/**
* Objection Model.
* @module helpers/base-model/base-model
*/
import { mixin, Model as ObjectionModel } from 'objection';
import TableName from 'objection-table-name';
import _, {
difference,
isArray,
isEmpty,
isObject,
isString,
keys,
map,
OBJECTION.JS (COLLECTIONS)
/**
* ActionCollection.
* @module collection/action/action
*/
import { Collection } from '../../collections';
import _, { includes, map, omit } from 'lodash';
/**
* Action Collection
* @class ActionCollection
* @extends Collection
*/
export class ActionCollection extends Collection {
/**
OBJECTION.JS (BASE
COLLECTION)
/**
* Collection.
* @module collection/_collection/_collection
*/
import { map, omitBy } from 'lodash';
/**
* Base collection used to extend collections from.
* @class Collection
*/
export class Collection {
/**
* Construct a Collection.
* @constructs Collection
SEEDS / PROFILES
nick
└─ src
└─ profiles
└─ core
└─ types
└─ file.js
└─ image.js
└─ ...
└─ groups.js
└─ permissions.js
└─ default
└─ users.js
└─ ...
$ yarn seed
TRANSACTIONS
const trx = await Model.startTransaction();
...
// Get user
req.user = await User.fetchById(
getUserId(req),
{
related: '[_roles, _groups._roles]',
},
trx,
);
...
BLOBS
nick
└─ var
└─ blobstorage
└─ 1d2362de-8090-472b-a06a-0e4d23705f3c
└─ 2bd8d8f2-6d01-4f39-a799-a521acd17dbf
└─ 5e178390-2cf6-498b-9bb3-424c1aa4dea3
└─ ...
HELPERS
$ yarn bootstrap
$ yarn reset
CONFIG
nick/src/config.js
export const config = {
connection: {
port: 5432,
host: 'localhost',
database: 'nick',
user: 'nick',
password: 'nick',
},
blobsDir: `${__dirname}/var/blobstorage`,
port: 8000,
secret: 'secret',
clientMaxSize: '64mb',
systemUsers: ['admin', 'anonymous'],
$ yarn create-config
CONTENTTYPES
CONTENTTYPES (SCHEMA
BASED)
nick
└─ src
└─ profiles
└─ core
└─ types
└─ file.json
└─ folder.json
└─ image.json
└─ page.json
└─ site.json
IMAGE.JSON
{
"id": "Image",
"title:i18n": "Image",
"description:i18n": "Images can be referenced in pages or di
"global_allow": true,
"filter_content_types": true,
"allowed_content_types": [],
"schema": {
"fieldsets": [
{
"fields": ["image"],
"id": "default",
"title:i18n": "Default"
}
],
BEHAVIORS (SCHEMA BASED)
nick
└─ src
└─ profiles
└─ core
└─ behaviors
└─ basic.json
└─ blocks.json
└─ categorization.json
└─ dates.json
└─ dublin_core.json
└─ exclude_from_nav.json
└─ ownership.json
└─ short_name.json
└─ versions.json
BEHAVIORS (BASIC.JSON)
{
"id": "basic",
"title:i18n": "Basic metadata",
"description:i18n": "Adds title and description fields.",
"schema": {
"fieldsets": [
{
"fields": ["title", "description"],
"id": "default",
"title:i18n": "Default"
}
],
"properties": {
"description": {
"description:i18n": "A description of this item.",
NESTED BEHAVIORS
{
"id": "dublin_core",
"title:i18n": "Dublin Core metadata",
"description:i18n": "Adds standard metadatafields",
"schema": {
"behaviors": ["basic", "categorization", "ownership"]
}
}
BEHAVIORS (CLASS BASED)
nick
└─ src
└─ behaviors
└─ id_from_title
└─ id_from_title.js
ID_FROM_TITLE.JS
/**
* Id from title behavior.
* @module behaviors/id_from_title/id_from_title
*/
import slugify from 'slugify';
import { uniqueId } from '../../helpers';
/**
* Id from title behavior.
* @constant id_from_title
*/
export const id_from_title = {
/**
INITIAL CONTENT
nick/src/profiles/default/documents/events.event-1.json
{
"uuid": "405ca717-0c68-43a0-88ac-629a82658675",
"type": "Page",
"title": "Event 1",
"owner": "admin",
"workflow_state": "published",
"created": "2022-04-02T20:10:00.000Z",
"modified": "2022-04-02T20:10:00.000Z",
"effective": "2022-04-02T20:10:00.000Z",
"subjects": ["event"],
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
SHARING
{
"uuid": "80994493-74ca-4b94-9a7c-145a33a6dd80",
"type": "Folder",
"title": "Users",
"owner": "admin",
"workflow_state": "published",
"created": "2022-04-02T20:24:00.000Z",
"modified": "2022-04-02T20:24:00.000Z",
"effective": "2022-04-02T20:24:00.000Z",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
"blocks layout": {
HISTORY
{
"uuid": "32215c67-86de-462a-8cc0-eabcd2b39c26",
"type": "Folder",
"title": "News",
"description": "News Items",
"owner": "admin",
"workflow_state": "published",
"created": "2022-04-02T20:22:00.000Z",
"modified": "2022-04-02T20:22:00.000Z",
"effective": "2022-04-02T20:22:00.000Z",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
REDIRECTS
{
"purge": true,
"redirects": [{
"path": "/events/event-2",
"document": "79ba8858-1dd3-4719-b731-5951e32fbf79"
}]
}
REST API CALLS
GET
GET /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"title": "News",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
"description": "News Items",
"blocks_layout": {
"items": [
"79ba8858-1dd3-4719-b731-5951e32fbf79"
]
POST
POST /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"@type": "Page",
"title": "My News Item",
"description": "News Description"
}
HTTP/1.1 201 Created
Content-Type: application/json
{
"title": "My News Item",
"description": "News Description",
"@id": "http://localhost:8000/news/my-news-item",
"@type": "Page",
"id": "my-news-item",
"created": "2022-04-08T16:00:00.000Z",
"modified": "2022-04-08T16:00:00.000Z",
"UID": "a95388f2-e4b3-4292-98aa-62656cbd5b9c",
"is_folderish": true,
"review state": "private",
PATCH
PATCH /news/my-news-item HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"title": "My New News Item"
}
HTTP/1.1 204 No Content
DELETE
DELETE /news/my-news-item HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 204 No Content
ORDERING
PATCH /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"ordering": {"obj_id": "my-news-item", "delta": "top"}
}
HTTP/1.1 204 No Content
HISTORY
GET /news/@history HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/news/@history/1",
"action": "Edited",
"actor": {
"@id": "http://localhost:8000/@users/admin",
"fullname": "Admin",
"id": "admin",
"username": "admin"
},
"comments": "Changed title",
GET VERSION
GET /news/@history/0 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"title": "Old News",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
"description": "News Items",
"blocks_layout": {
"items": [
"79ba8858-1dd3-4719-b731-5951e32fbf79"
]
REVERT TO VERSION
PATCH /news/my-news-item/@history HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"version": 0
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"message": "My News Item has been reverted to revision 0."
}
COPY
POST /news/@copy HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"source": "/events/event-1"
}
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"source": "http://localhost:8000/events/event-1",
"target": "http://localhost:8000/news/event-1"
}
]
MOVE
POST /news/@move HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"source": "/events/event-1"
}
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"source": "http://localhost:8000/events/event-1",
"target": "http://localhost:8000/news/event-1"
}
]
COPY MULTIPLE
POST /news/@copy HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"source": [
"/events",
"/users"
]
}
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"source": "http://localhost:8000/events",
"target": "http://localhost:8000/news/events"
},
{
"source": "http://localhost:8000/users",
"target": "http://localhost:8000/news/users"
}
]
ACTIONS
GET /@actions HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"object": [
{
"id": "view",
"title": "View"
},
{
"id": "edit",
"title": "Edit"
},
{
"id": "folderContents",
CREATE LOCK
POST /news/my-news-item/@lock HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"created": "2022-04-08T16:00:00.000Z",
"creator": "admin",
"creator_name": "Admin",
"creator_url": "http://localhost:8000/@users/admin",
"locked": true,
"stealable": true,
"time": "2022-04-08T16:00:00.000Z",
"timeout": 600,
"token": "a95388f2-e4b3-4292-98aa-62656cbd5b9c"
}
GET LOCK
GET /news/my-news-item/@lock HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"created": "2022-04-08T16:00:00.000Z",
"creator": "admin",
"creator_name": "Admin",
"creator_url": "http://localhost:8000/@users/admin",
"locked": true,
"stealable": true,
"time": "2022-04-08T16:00:00.000Z",
"timeout": 600,
"token": "a95388f2-e4b3-4292-98aa-62656cbd5b9c"
}
UPDATE WITH LOCK
PATCH /news/my-news-item HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Lock-Token: a95388f2-e4b3-4292-98aa-62656cbd5b9c
Content-Type: application/json
{
"title": "My New News Item"
}
HTTP/1.1 204 No Content
DELETE LOCK
DELETE /news/my-news-item/@lock HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"locked": false,
"stealable": true
}
SECURITY
AUTHENTICATION
POST /@login HTTP/1.1
Accept: application/json
{
"login": "admin",
"password": "admin"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ
}
RENEW
POST /@login-renew HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ
}
LOGOUT
POST /@logout HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 204 No Content
PERMISSION SYSTEM
Permissions
Roles (have permissions)
Groups (have roles)
Users (have roles, groups)
Local roles (user/group has a role on an object)
Local role permissions are inherited from the parent
Local role inheritence can be disabled per object
Workflows (have states and transitions)
States (have permissions per role)
Transitions (have permissions)
PERMISSIONS.JSON
{
"purge": true,
"permissions": [
{
"id": "View",
"title:i18n": "View"
},
{
"id": "Add",
"title:i18n": "Add"
},
{
"id": "Login",
"title:i18n": "Login"
},
ROLES.JSON
{
"purge": true,
"roles": [
{
"id": "Anonymous",
"title:i18n": "Anonymous",
"permissions": ["Login", "Register"]
},
{
"id": "Authenticated",
"title:i18n": "Authenticated",
"permissions": ["Logout", "Manage Preferences"]
},
{
"id": "Owner",
USERS.JSON
{
"purge": true,
"users": [
{
"id": "admin",
"password": "admin",
"fullname": "Admin",
"email": "admin@example.com",
"roles": ["Administrator"]
},
{
"id": "anonymous",
"password": "anonymous",
"fullname": "Anonymous",
"email": "anonymous@example.com",
GROUPS.JSON
{
"purge": true,
"groups": [
{
"id": "Administrators",
"title:i18n": "Administrators",
"description:i18n": "",
"email": "",
"roles": ["Administrator"]
}
]
}
WORKFLOWS.JSON
{
"purge": true,
"workflows": [
{
"id": "simple_publication_workflow",
"title:i18n": "Simple Publication Workflow",
"description:i18n": "Simple workflow that is useful for
"json": {
"initial_state": "private",
"states": {
"private": {
"title:i18n": "Private",
"description:i18n": "Can only be seen and edited b
"transitions": ["publish", "submit"],
"permissions": {
REST API CALLS
GET ROLES
GET /@roles HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@roles/Anonymous",
"@type": "role",
"id": "Anonymous",
"title": "Anonymous"
},
{
"@id": "http://localhost:8000/@roles/Authenticated",
"@type": "role",
"id": "Authenticated",
"title": "Authenticated"
LIST USERS
GET /@users HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@users/admin",
"id": "admin",
"fullname": "Admin",
"email": "admin@example.com",
"roles": [
"Administrator"
],
"groups": []
},
{
LIST WITH A QUERY
GET /@users?query=admin HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@users/admin",
"id": "admin",
"fullname": "Admin",
"email": "admin@example.com",
"roles": [
"Administrator"
],
"groups": []
}
]
GET USER
GET /@users/admin HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@users/admin",
"id": "admin",
"fullname": "Admin",
"email": "admin@example.com",
"roles": [
"Administrator"
],
"groups": []
}
UPDATE USER
PATCH /@users/headlessnick HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"fullname": "Headless Nick"
}
HTTP/1.1 204 No Content
DELETE USER
DELETE /@users/headlessnick HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 204 No Content
REGISTRATION
POST /@users HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"email": "nearly.headless.nick@example.com",
"fullname": "Nearly Headless Nick",
"username": "headlessnick",
"sendPasswordReset": true
}
HTTP/1.1 201 Created
Content-Type: application/json
{
"@id": "http://localhost:8000/@users/headlessnick",
"id": "headlessnick",
"fullname": "Nearly Headless Nick",
"email": "nearly.headless.nick@example.com",
"roles": [],
"groups": []
}
RESET PASSWORD
POST /@users/headlessnick/reset-password HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
SET RESET PASSWORD
POST /@users/headlessnick/reset-password HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"reset_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI
"new_password": "headless"
}
HTTP/1.1 200 OK
LIST GROUPS
GET /@groups HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@groups/Administrators",
"description": "",
"email": "",
"groupname": "Administrators",
"id": "Administrators",
"roles": [
"Administrator"
],
"title": "Administrators"
}
GET GROUP
GET /@groups/Administrators HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@groups/Administrators",
"id": "Administrators",
"title": "Administrators",
"description": "",
"groupname": "Administrators",
"email": "",
"roles": [
"Administrator"
]
}
ADD GROUP
POST /@groups HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"groupname": "nicks",
"title": "Nicks",
"description": "Nearly Headless Nicks",
"email": "nearly.headless.nicks@example.com",
"roles": [
"Contributor"
]
}
HTTP/1.1 201 Created
Content-Type: application/json
{
"@id": "http://localhost:8000/@groups/nicks",
"id": "nicks",
"groupname": "nicks",
"description": "Nearly Headless Nicks",
"email": "nearly.headless.nicks@example.com",
"roles": [],
"title": "Nicks"
}
UPDATE GROUP
PATCH /@groups/nicks HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"title": "Headless Nicks"
}
HTTP/1.1 204 No Content
DELETE GROUP
DELETE /@groups/nicks HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 204 No Content
GET WORKFLOW
GET /news/@workflow HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/news/@workflow",
"history": [],
"state": {
"id": "published",
"title": "Published"
},
"transitions": [
{
"@id": "http://localhost:8000/news/@workflow/reject",
"title": "Send back"
},
WORKFLOW TRANSITION
POST /news/my-news-item/@workflow/publish HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"action": "publish",
"actor": "admin",
"comments": "",
"review_state": "published",
"time": "2022-04-08T16:00:00.000Z",
"title": "Published"
}
SHARING
GET /@sharing HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"available_roles": [
{
"id": "Anonymous",
"title": "Anonymous"
},
{
"id": "Authenticated",
"title": "Authenticated"
},
{
"id": "Owner",
ADD SHARING
POST /@sharing HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"entries": [
{
"id": "Administrators",
"roles": {
"Contributor": true,
"Reader": true
},
"type": "user"
}
HTTP/1.1 204 No Content
CATALOG & SEARCH
CATALOG
Indexes
type (path, uuid, integer, date, text, string,
boolean, string[])
operators
Metadata
name
type
attribute
CATALOG.JSON
{
"indexes": [
{
"name": "path",
"type": "path",
"attr": "path",
"title:i18n": "Location",
"description:i18n": "The location of an item",
"group": "Metadata",
"enabled": true,
"sortable": false,
"operators": {
"string.absolutePath": {
"title:i18n": "Absolute path",
"description:i18n": "Location in the site structure"
QUERYSTRING
GET /@querystring HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@querystring",
"indexes": {
"created": {
"title": "Creation date",
"description": "The date an item was created",
"group": "Dates",
"enabled": true,
"sortable": true,
"values": {},
"vocabulary": null,
"operations": [
SEARCH
GET /@search?SearchableText=news* HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@search",
"items": [
{
"@id": "http://localhost:8000/news",
"@type": "Folder",
"title": "News",
"UID": "32215c67-86de-462a-8cc0-eabcd2b39c26",
"path": "/news",
"Description": "News Items",
"Title": "News",
"Subject": null,
BATCHING
GET /@search?b_size=2 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@search",
"items": [
{
"@id": "http://localhost:8000/events",
"@type": "Folder",
"title": "Events",
"UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41",
"path": "/events",
"Description": null,
"Title": "Events",
"Subject": null,
DEPTH
GET /@search?path.depth=1 HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@search",
"items": [
{
"@id": "http://localhost:8000/events",
"@type": "Folder",
"title": "Events",
"UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41",
"path": "/events",
"Description": null,
"Title": "Events",
"Subject": null,
SORT
GET /@search?sort_on=effective HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@search",
"items": [
{
"@id": "http://localhost:8000/",
"@type": "Site",
"title": "Welcome to Nick!",
"UID": "92a80817-f5b7-400d-8f58-b08126f0f09b",
"path": "/",
"Description": "Congratulations! You have successfully i
"Title": "Welcome to Nick!",
"Subject": null,
VOCABULARIES
VOCABULARIES
nick
└─ src
└─ vocabularies
└─ actions
└─ actions.js
└─ behaviors
└─ behaviors.js
└─ groups
└─ groups.js
└─ image-scales
└─ image-scales.js
...
└─ index.js
CREATE VOCABULARY
/**
* Actions vocabulary.
* @module vocabularies/actions/actions
*/
import { Action } from '../../models';
/**
* Returns the acions vocabulary.
* @method actions
* @returns {Array} Array of terms.
*/
export async function actions(req, trx) {
const actions = await Action.fetchAll(
{},
PROFILE VOCABULARIES
nick
└─ src
└─ profiles
└─ core
└─ vocabularies
└─ boolean.json
PROFILE VOCABULARY
{
"id": "boolean",
"title:i18n": "boolean",
"items": [
{ "title:i18n": "Yes", "token": "Yes" },
{ "title:i18n": "No", "token": "No" }
]
}
LIST VOCABULARIES
GET /@vocabularies HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@vocabularies/actions",
"title": "actions"
},
{
"@id": "http://localhost:8000/@vocabularies/behaviors",
"title": "behaviors"
},
{
"@id": "http://localhost:8000/@vocabularies/boolean",
"title": "boolean"
GET VOCABULARY
GET /@vocabularies/actions HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@vocabularies/actions",
"items": [
{
"title": "View",
"token": "view"
},
{
"title": "Edit",
"token": "edit"
},
{
UTILITY CALLS
BREADCRUMBS
GET /events/event-1/@breadcrumbs HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/events/event-1/@breadcrumbs",
"items": [
{
"@id": "http://localhost:8000/events",
"title": "Events"
},
{
"@id": "http://localhost:8000/events/event-1",
"title": "Event 1"
}
]
NAVIGATION
GET /news/@navigation HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/news/@navigation",
"items": [
{
"@id": "http://localhost:8000/events",
"@type": "Folder",
"title": "Events",
"UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41",
"path": "/events",
"Description": null,
"Title": "Events",
"Subject": null,
CONTROLPANELS
nick
└─ src
└─ profiles
└─ core
└─ controlpanels
└─ mail.js
└─ language.js
{
"id": "mail",
"title:i18n": "Mail",
"group": "General",
"schema": {
"fieldsets": [
{
"behavior": "plone",
"fields": [
"host",
"port",
"secure",
"user",
"pass",
CONTROLPANELS
GET /@controlpanels HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"@id": "http://localhost:8000/@controlpanels/language",
"group": "General",
"title": "Language"
},
{
"@id": "http://localhost:8000/@controlpanels/mail",
"group": "General",
"title": "Mail"
}
]
GET CONTROLPANEL
GET /@controlpanels/mail HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@controlpanels/mail",
"group": "General",
"title": "Mail",
"data": {
"host": "localhost",
"pass": "",
"port": 25,
"user": "",
"debug": true,
"secure": true,
"email from name": "Webmaster",
UPDATE CONTROLPANEL
PATCH /@controlpanels/mail HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"host": "mail.someserver.com",
"port": 25
}
HTTP/1.1 204 No Content
MAIL
POST /@email-send HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"name": "John Doe",
"from": "john@doe.com",
"to": "jane@doe.com",
"subject": "Hello!",
"message": "Just want to say hi."
}
HTTP/1.1 204 OK
MAIL WEBMASTER
POST /@email-notification HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"name": "John Doe",
"from": "john@doe.com",
"subject": "Hello!",
"message": "Just want to say hi."
}
HTTP/1.1 204 OK
SYSTEM
GET /@system HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@system",
"nick_version": "2.8.0",
"node_version": "v16.15.0",
"express_version": "4.18.2",
"objection_version": "3.0.1",
"knex_version": "2.3.0",
"postgres_version": "14.4"
}
DATABASE
GET /@database HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8000/@database",
"db_name": "nick",
"db_size": "10 MB",
"pool": {
"min": 2,
"max": 10
},
"blob_size": "10 MB"
}
I18N
nick
└─ locales
└─ en
└─ LC_MESSAGES
└─ nick.po
└─ nl
└─ LC_MESSAGES
└─ nick.po
└─ nick.po
└─ en.js
└─ nl.js
I18N
$ yarn i18n
I18N IN JS
import { i18n } from './middleware';
...
app.use(i18n);
...
req.i18n('Transaction error.')
I18N IN JSON
{
"id": "basic",
"title:i18n": "Basic metadata",
"description:i18n": "Adds title and description fields.",
"schema": {
"fieldsets": [
{
"fields": ["title", "description"],
"id": "default",
"title:i18n": "Default"
}
],
"properties": {
"description": {
"description:i18n": "A description of this item.",
LOGGING (LOG4JS)
log.info(`Server listening on port ${config.port}`)
2022-05-30T22:21:06.317 INFO [server.js:12] Server listening o
DATABASE SCHEMA
DATABASE SCHEMA
ROADMAP
Querystring search
100% test coverage (currently 83%)
Multi-Lingual
Email login
Controlpanels
Developer docs
Websockets
QUESTIONS?
slideshare.net/robgietema/nick-ploneconf-2022

More Related Content

Similar to Nick: A Nearly Headless CMS

Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
Ana-Maria Mihalceanu
 
! Modernizr v2.0.6 httpwww.modernizr.com Copyri.docx
!  Modernizr v2.0.6  httpwww.modernizr.com   Copyri.docx!  Modernizr v2.0.6  httpwww.modernizr.com   Copyri.docx
! Modernizr v2.0.6 httpwww.modernizr.com Copyri.docx
MARRY7
 
Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Ovadiah Myrgorod
 
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docxcase3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
tidwellveronique
 
Stencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrivedStencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrived
Gil Fink
 
Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15
Murat Yener
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
Marco Vito Moscaritolo
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
DrupalDay
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Jeado Ko
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
WebFrameworks
 
Formatting ForThe Masses
Formatting ForThe MassesFormatting ForThe Masses
Formatting ForThe Masses
Holger Schill
 
Intro To Mvc Development In Php
Intro To Mvc Development In PhpIntro To Mvc Development In Php
Intro To Mvc Development In Phpfunkatron
 
Custom gutenberg block development in react
Custom gutenberg block development in reactCustom gutenberg block development in react
Custom gutenberg block development in react
Imran Sayed
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
Giorgio Cefaro
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
eugenio pombi
 
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
Matt Raible
 
Koin Quickstart
Koin QuickstartKoin Quickstart
Koin Quickstart
Matthew Clarke
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
Igor Bronovskyy
 
A Gentle Introduction to Angular Schematics - Angular SF 2019
A Gentle Introduction to Angular Schematics - Angular SF 2019A Gentle Introduction to Angular Schematics - Angular SF 2019
A Gentle Introduction to Angular Schematics - Angular SF 2019
Matt Raible
 

Similar to Nick: A Nearly Headless CMS (20)

Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17Enhancing Productivity and Insight  A Tour of JDK Tools Progress Beyond Java 17
Enhancing Productivity and Insight A Tour of JDK Tools Progress Beyond Java 17
 
! Modernizr v2.0.6 httpwww.modernizr.com Copyri.docx
!  Modernizr v2.0.6  httpwww.modernizr.com   Copyri.docx!  Modernizr v2.0.6  httpwww.modernizr.com   Copyri.docx
! Modernizr v2.0.6 httpwww.modernizr.com Copyri.docx
 
Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8
 
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docxcase3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
 
Stencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrivedStencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrived
 
Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your Code
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
Formatting ForThe Masses
Formatting ForThe MassesFormatting ForThe Masses
Formatting ForThe Masses
 
Intro To Mvc Development In Php
Intro To Mvc Development In PhpIntro To Mvc Development In Php
Intro To Mvc Development In Php
 
Custom gutenberg block development in react
Custom gutenberg block development in reactCustom gutenberg block development in react
Custom gutenberg block development in react
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
 
Datagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and BackgridDatagrids with Symfony 2, Backbone and Backgrid
Datagrids with Symfony 2, Backbone and Backgrid
 
Backbone js-slides
Backbone js-slidesBackbone js-slides
Backbone js-slides
 
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
A Gentle Introduction to Angular Schematics - Devoxx Belgium 2019
 
Koin Quickstart
Koin QuickstartKoin Quickstart
Koin Quickstart
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
 
A Gentle Introduction to Angular Schematics - Angular SF 2019
A Gentle Introduction to Angular Schematics - Angular SF 2019A Gentle Introduction to Angular Schematics - Angular SF 2019
A Gentle Introduction to Angular Schematics - Angular SF 2019
 

More from Rob Gietema

Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024
Rob Gietema
 
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Rob Gietema
 
How to Build a Site Using Nick
How to Build a Site Using NickHow to Build a Site Using Nick
How to Build a Site Using Nick
Rob Gietema
 
Van Klimhal naar Big Wall
Van Klimhal naar Big WallVan Klimhal naar Big Wall
Van Klimhal naar Big Wall
Rob Gietema
 
Van 0 naar 6000+
Van 0 naar 6000+Van 0 naar 6000+
Van 0 naar 6000+
Rob Gietema
 
How to create your own Volto site!
How to create your own Volto site!How to create your own Volto site!
How to create your own Volto site!
Rob Gietema
 
Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018
Rob Gietema
 
Volto: Plone Conference 2018
Volto: Plone Conference 2018Volto: Plone Conference 2018
Volto: Plone Conference 2018
Rob Gietema
 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15
Rob Gietema
 
React Native: React Meetup 3
React Native: React Meetup 3React Native: React Meetup 3
React Native: React Meetup 3
Rob Gietema
 
React Router: React Meetup XXL
React Router: React Meetup XXLReact Router: React Meetup XXL
React Router: React Meetup XXL
Rob Gietema
 
Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014
Rob Gietema
 
Hackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksHackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning Talks
Rob Gietema
 
Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014
Rob Gietema
 
Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013
Rob Gietema
 
Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014
Rob Gietema
 
Projectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemProjectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemRob Gietema
 
Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010
Rob Gietema
 
Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010
Rob Gietema
 
Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010
Rob Gietema
 

More from Rob Gietema (20)

Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024Van klimhal naar Big Wall: Bergsportdag 2024
Van klimhal naar Big Wall: Bergsportdag 2024
 
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
 
How to Build a Site Using Nick
How to Build a Site Using NickHow to Build a Site Using Nick
How to Build a Site Using Nick
 
Van Klimhal naar Big Wall
Van Klimhal naar Big WallVan Klimhal naar Big Wall
Van Klimhal naar Big Wall
 
Van 0 naar 6000+
Van 0 naar 6000+Van 0 naar 6000+
Van 0 naar 6000+
 
How to create your own Volto site!
How to create your own Volto site!How to create your own Volto site!
How to create your own Volto site!
 
Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018Volto Extensibility Story: Plone Conference 2018
Volto Extensibility Story: Plone Conference 2018
 
Volto: Plone Conference 2018
Volto: Plone Conference 2018Volto: Plone Conference 2018
Volto: Plone Conference 2018
 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15
 
React Native: React Meetup 3
React Native: React Meetup 3React Native: React Meetup 3
React Native: React Meetup 3
 
React Router: React Meetup XXL
React Router: React Meetup XXLReact Router: React Meetup XXL
React Router: React Meetup XXL
 
Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014Four o Four: World Plone Day 2014
Four o Four: World Plone Day 2014
 
Hackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksHackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning Talks
 
Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014
 
Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013
 
Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014
 
Projectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks ArnhemProjectgroep Millennium GroenLinks Arnhem
Projectgroep Millennium GroenLinks Arnhem
 
Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010Deco UI: Plone Conference 2010
Deco UI: Plone Conference 2010
 
Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010
 
Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010
 

Recently uploaded

成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
ysasp1
 
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
ufdana
 
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
CIOWomenMagazine
 
Understanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdfUnderstanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdf
SEO Article Boost
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
hackersuli
 
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
eutxy
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
cuobya
 
Bài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docxBài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docx
nhiyenphan2005
 
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
3ipehhoa
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
keoku
 
7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
Danica Gill
 
test test test test testtest test testtest test testtest test testtest test ...
test test  test test testtest test testtest test testtest test testtest test ...test test  test test testtest test testtest test testtest test testtest test ...
test test test test testtest test testtest test testtest test testtest test ...
Arif0071
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Florence Consulting
 
Explore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories SecretlyExplore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories Secretly
Trending Blogers
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC
 
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
Javier Lasa
 
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
cuobya
 
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
fovkoyb
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
zyfovom
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
vmemo1
 

Recently uploaded (20)

成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
成绩单ps(UST毕业证)圣托马斯大学毕业证成绩单快速办理
 
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
一比一原版(CSU毕业证)加利福尼亚州立大学毕业证成绩单专业办理
 
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
Internet of Things in Manufacturing: Revolutionizing Efficiency & Quality | C...
 
Understanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdfUnderstanding User Behavior with Google Analytics.pdf
Understanding User Behavior with Google Analytics.pdf
 
[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024[HUN][hackersuli] Red Teaming alapok 2024
[HUN][hackersuli] Red Teaming alapok 2024
 
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
一比一原版(LBS毕业证)伦敦商学院毕业证成绩单专业办理
 
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
制作毕业证书(ANU毕业证)莫纳什大学毕业证成绩单官方原版办理
 
Bài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docxBài tập unit 1 English in the world.docx
Bài tập unit 1 English in the world.docx
 
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
急速办(bedfordhire毕业证书)英国贝德福特大学毕业证成绩单原版一模一样
 
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
一比一原版(SLU毕业证)圣路易斯大学毕业证成绩单专业办理
 
7 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 20247 Best Cloud Hosting Services to Try Out in 2024
7 Best Cloud Hosting Services to Try Out in 2024
 
test test test test testtest test testtest test testtest test testtest test ...
test test  test test testtest test testtest test testtest test testtest test ...test test  test test testtest test testtest test testtest test testtest test ...
test test test test testtest test testtest test testtest test testtest test ...
 
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdfMeet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
Meet up Milano 14 _ Axpo Italia_ Migration from Mule3 (On-prem) to.pdf
 
Explore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories SecretlyExplore-Insanony: Watch Instagram Stories Secretly
Explore-Insanony: Watch Instagram Stories Secretly
 
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
APNIC Foundation, presented by Ellisha Heppner at the PNG DNS Forum 2024
 
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdfJAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
JAVIER LASA-EXPERIENCIA digital 1986-2024.pdf
 
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
假文凭国外(Adelaide毕业证)澳大利亚国立大学毕业证成绩单办理
 
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
存档可查的(USC毕业证)南加利福尼亚大学毕业证成绩单制做办理
 
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
学位认证网(DU毕业证)迪肯大学毕业证成绩单一比一原版制作
 
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
重新申请毕业证书(RMIT毕业证)皇家墨尔本理工大学毕业证成绩单精仿办理
 

Nick: A Nearly Headless CMS

  • 1. NICK A NEARLY HEADLESS CMS Plone Conference 2022, Namur - Rob Gietema @robgietema
  • 3. What is Nick? Why? Architecture Getting started Documentation Database Contenttypes Security Catalog & Search Vocabularies Security Utility calls i18n Logging Database schema Roadmap WHAT WILL WE COVER?
  • 4. WHAT IS NICK? Headless CMS Build with Node.js RESTfull API compatible with plone.restapi (Volto)
  • 5. WHY? Fun to build! Plone has a great architecture, great way to learn the internals Plone has a great Rest API Started as a proof of concept on Ploneconf 2018 in Tokyo Frontend and backend using the same language
  • 6. ISSUES WITH PLONE Disclaimer: my opinion Lots of legacy code Lot of code to maintain ourself Deployment
  • 7. COMPLEX STACK Python Zope Generic Setup (xml) ZCML Page templates REST Yaml JSON cfg ini Markdown Javascript Webpack CSS / LESS / SASS XSLT Buildout KSS Portal Skins Restricted Python DTML
  • 13. GETTING STARTED CREATE DATABASE nick; CREATE USER nick WITH ENCRYPTED PASSWORD 'nick'; GRANT ALL PRIVILEGES ON DATABASE nick TO nick; $ yarn bootstrap $ yarn start
  • 14. YEOMAN GENERATOR $ npm install -g yo $ npm install -g @robgietema/generator-nick $ yo @robgietema/nick my-project CREATE DATABASE my-project; CREATE USER nick WITH ENCRYPTED PASSWORD 'my-project'; GRANT ALL PRIVILEGES ON DATABASE my-project TO my-project; $ cd my-project $ yarn bootstrap $ yarn start
  • 19. TESTS (GET.REQ) GET /events/event-1/@breadcrumbs HTTP/1.1 Accept: application/json
  • 20. TESTS (GET.RES) HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/events/event-1/@breadcrumbs", "items": [ { "@id": "http://localhost:8000/events", "title": "Events" }, { "@id": "http://localhost:8000/events/event-1", "title": "Event 1" } ]
  • 21. TESTS (BREADCRUMBS.MD) --- nav_order: 14 permalink: /breadcrumbs --- # Breadcrumbs Getting the breadcrumbs for the current page: ``` {% include_relative examples/breadcrumbs/get.req %} ``` Example response:
  • 24. DATABASE Postgres Knex.js ( ) Objection.js ( https://knexjs.org https://vincit.github.io/objection.js/
  • 25. KNEX.JS knex.schema.createTable('group', (table) => { table.string('id').primary(); table.string('title'); table.string('description'); table.string('email').unique(); }
  • 26. MIGRATIONS nick └─ src └─ migration └─ 202203200901_permission.js └─ 202203200902_role.js └─ 202203200903_group.js └─ 202203200904_user.js └─ 202203200905_workflow.js └─ 202203200906_type.js └─ 202203200907_document.js └─ ... $ yarn migrate
  • 27. 202203200901_PERMISSION.JS export const up = async (knex) => { await knex.schema.createTable('permission', (table) => { table.string('id').primary(); table.string('title'); }); }; export const down = async (knex) => { await knex.schema.dropTable('permission'); };
  • 28. OBJECTION.JS (MODELS) /** * Permission Model. * @module models/permission/permission */ import { Model } from '../../models'; /** * A model for Permission. * @class Permission * @extends Model */ export class Permission extends Model { // Set relation mappings static get relationMappings() {
  • 29. OBJECTION.JS (BASE MODEL) /** * Objection Model. * @module helpers/base-model/base-model */ import { mixin, Model as ObjectionModel } from 'objection'; import TableName from 'objection-table-name'; import _, { difference, isArray, isEmpty, isObject, isString, keys, map,
  • 30. OBJECTION.JS (COLLECTIONS) /** * ActionCollection. * @module collection/action/action */ import { Collection } from '../../collections'; import _, { includes, map, omit } from 'lodash'; /** * Action Collection * @class ActionCollection * @extends Collection */ export class ActionCollection extends Collection { /**
  • 31. OBJECTION.JS (BASE COLLECTION) /** * Collection. * @module collection/_collection/_collection */ import { map, omitBy } from 'lodash'; /** * Base collection used to extend collections from. * @class Collection */ export class Collection { /** * Construct a Collection. * @constructs Collection
  • 32. SEEDS / PROFILES nick └─ src └─ profiles └─ core └─ types └─ file.js └─ image.js └─ ... └─ groups.js └─ permissions.js └─ default └─ users.js └─ ... $ yarn seed
  • 33. TRANSACTIONS const trx = await Model.startTransaction(); ... // Get user req.user = await User.fetchById( getUserId(req), { related: '[_roles, _groups._roles]', }, trx, ); ...
  • 34. BLOBS nick └─ var └─ blobstorage └─ 1d2362de-8090-472b-a06a-0e4d23705f3c └─ 2bd8d8f2-6d01-4f39-a799-a521acd17dbf └─ 5e178390-2cf6-498b-9bb3-424c1aa4dea3 └─ ...
  • 36. CONFIG nick/src/config.js export const config = { connection: { port: 5432, host: 'localhost', database: 'nick', user: 'nick', password: 'nick', }, blobsDir: `${__dirname}/var/blobstorage`, port: 8000, secret: 'secret', clientMaxSize: '64mb', systemUsers: ['admin', 'anonymous'], $ yarn create-config
  • 38. CONTENTTYPES (SCHEMA BASED) nick └─ src └─ profiles └─ core └─ types └─ file.json └─ folder.json └─ image.json └─ page.json └─ site.json
  • 39. IMAGE.JSON { "id": "Image", "title:i18n": "Image", "description:i18n": "Images can be referenced in pages or di "global_allow": true, "filter_content_types": true, "allowed_content_types": [], "schema": { "fieldsets": [ { "fields": ["image"], "id": "default", "title:i18n": "Default" } ],
  • 40. BEHAVIORS (SCHEMA BASED) nick └─ src └─ profiles └─ core └─ behaviors └─ basic.json └─ blocks.json └─ categorization.json └─ dates.json └─ dublin_core.json └─ exclude_from_nav.json └─ ownership.json └─ short_name.json └─ versions.json
  • 41. BEHAVIORS (BASIC.JSON) { "id": "basic", "title:i18n": "Basic metadata", "description:i18n": "Adds title and description fields.", "schema": { "fieldsets": [ { "fields": ["title", "description"], "id": "default", "title:i18n": "Default" } ], "properties": { "description": { "description:i18n": "A description of this item.",
  • 42. NESTED BEHAVIORS { "id": "dublin_core", "title:i18n": "Dublin Core metadata", "description:i18n": "Adds standard metadatafields", "schema": { "behaviors": ["basic", "categorization", "ownership"] } }
  • 43. BEHAVIORS (CLASS BASED) nick └─ src └─ behaviors └─ id_from_title └─ id_from_title.js
  • 44. ID_FROM_TITLE.JS /** * Id from title behavior. * @module behaviors/id_from_title/id_from_title */ import slugify from 'slugify'; import { uniqueId } from '../../helpers'; /** * Id from title behavior. * @constant id_from_title */ export const id_from_title = { /**
  • 45. INITIAL CONTENT nick/src/profiles/default/documents/events.event-1.json { "uuid": "405ca717-0c68-43a0-88ac-629a82658675", "type": "Page", "title": "Event 1", "owner": "admin", "workflow_state": "published", "created": "2022-04-02T20:10:00.000Z", "modified": "2022-04-02T20:10:00.000Z", "effective": "2022-04-02T20:10:00.000Z", "subjects": ["event"], "blocks": { "79ba8858-1dd3-4719-b731-5951e32fbf79": {
  • 46. SHARING { "uuid": "80994493-74ca-4b94-9a7c-145a33a6dd80", "type": "Folder", "title": "Users", "owner": "admin", "workflow_state": "published", "created": "2022-04-02T20:24:00.000Z", "modified": "2022-04-02T20:24:00.000Z", "effective": "2022-04-02T20:24:00.000Z", "blocks": { "79ba8858-1dd3-4719-b731-5951e32fbf79": { "@type": "title" } }, "blocks layout": {
  • 47. HISTORY { "uuid": "32215c67-86de-462a-8cc0-eabcd2b39c26", "type": "Folder", "title": "News", "description": "News Items", "owner": "admin", "workflow_state": "published", "created": "2022-04-02T20:22:00.000Z", "modified": "2022-04-02T20:22:00.000Z", "effective": "2022-04-02T20:22:00.000Z", "blocks": { "79ba8858-1dd3-4719-b731-5951e32fbf79": { "@type": "title" } },
  • 48. REDIRECTS { "purge": true, "redirects": [{ "path": "/events/event-2", "document": "79ba8858-1dd3-4719-b731-5951e32fbf79" }] }
  • 50. GET GET /news HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "title": "News", "blocks": { "79ba8858-1dd3-4719-b731-5951e32fbf79": { "@type": "title" } }, "description": "News Items", "blocks_layout": { "items": [ "79ba8858-1dd3-4719-b731-5951e32fbf79" ]
  • 51. POST POST /news HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "@type": "Page", "title": "My News Item", "description": "News Description" } HTTP/1.1 201 Created Content-Type: application/json { "title": "My News Item", "description": "News Description", "@id": "http://localhost:8000/news/my-news-item", "@type": "Page", "id": "my-news-item", "created": "2022-04-08T16:00:00.000Z",
  • 53. PATCH PATCH /news/my-news-item HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "title": "My New News Item" } HTTP/1.1 204 No Content
  • 54. DELETE DELETE /news/my-news-item HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 204 No Content
  • 55. ORDERING PATCH /news HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "ordering": {"obj_id": "my-news-item", "delta": "top"} } HTTP/1.1 204 No Content
  • 56. HISTORY GET /news/@history HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/news/@history/1", "action": "Edited", "actor": { "@id": "http://localhost:8000/@users/admin", "fullname": "Admin", "id": "admin", "username": "admin" }, "comments": "Changed title",
  • 57. GET VERSION GET /news/@history/0 HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "title": "Old News", "blocks": { "79ba8858-1dd3-4719-b731-5951e32fbf79": { "@type": "title" } }, "description": "News Items", "blocks_layout": { "items": [ "79ba8858-1dd3-4719-b731-5951e32fbf79" ]
  • 58. REVERT TO VERSION PATCH /news/my-news-item/@history HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "version": 0 } HTTP/1.1 200 OK Content-Type: application/json { "message": "My News Item has been reverted to revision 0." }
  • 59. COPY POST /news/@copy HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "source": "/events/event-1" } HTTP/1.1 200 OK Content-Type: application/json [ { "source": "http://localhost:8000/events/event-1", "target": "http://localhost:8000/news/event-1" } ]
  • 60. MOVE POST /news/@move HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "source": "/events/event-1" } HTTP/1.1 200 OK Content-Type: application/json [ { "source": "http://localhost:8000/events/event-1", "target": "http://localhost:8000/news/event-1" } ]
  • 61. COPY MULTIPLE POST /news/@copy HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "source": [ "/events", "/users" ] } HTTP/1.1 200 OK Content-Type: application/json [ { "source": "http://localhost:8000/events", "target": "http://localhost:8000/news/events" }, {
  • 63. ACTIONS GET /@actions HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "object": [ { "id": "view", "title": "View" }, { "id": "edit", "title": "Edit" }, { "id": "folderContents",
  • 64. CREATE LOCK POST /news/my-news-item/@lock HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "created": "2022-04-08T16:00:00.000Z", "creator": "admin", "creator_name": "Admin", "creator_url": "http://localhost:8000/@users/admin", "locked": true, "stealable": true, "time": "2022-04-08T16:00:00.000Z", "timeout": 600, "token": "a95388f2-e4b3-4292-98aa-62656cbd5b9c" }
  • 65. GET LOCK GET /news/my-news-item/@lock HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "created": "2022-04-08T16:00:00.000Z", "creator": "admin", "creator_name": "Admin", "creator_url": "http://localhost:8000/@users/admin", "locked": true, "stealable": true, "time": "2022-04-08T16:00:00.000Z", "timeout": 600, "token": "a95388f2-e4b3-4292-98aa-62656cbd5b9c" }
  • 66. UPDATE WITH LOCK PATCH /news/my-news-item HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Lock-Token: a95388f2-e4b3-4292-98aa-62656cbd5b9c Content-Type: application/json { "title": "My New News Item" } HTTP/1.1 204 No Content
  • 67. DELETE LOCK DELETE /news/my-news-item/@lock HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "locked": false, "stealable": true }
  • 69. AUTHENTICATION POST /@login HTTP/1.1 Accept: application/json { "login": "admin", "password": "admin" } HTTP/1.1 200 OK Content-Type: application/json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ }
  • 70. RENEW POST /@login-renew HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ }
  • 71. LOGOUT POST /@logout HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 204 No Content
  • 72. PERMISSION SYSTEM Permissions Roles (have permissions) Groups (have roles) Users (have roles, groups) Local roles (user/group has a role on an object) Local role permissions are inherited from the parent Local role inheritence can be disabled per object Workflows (have states and transitions) States (have permissions per role) Transitions (have permissions)
  • 73. PERMISSIONS.JSON { "purge": true, "permissions": [ { "id": "View", "title:i18n": "View" }, { "id": "Add", "title:i18n": "Add" }, { "id": "Login", "title:i18n": "Login" },
  • 74. ROLES.JSON { "purge": true, "roles": [ { "id": "Anonymous", "title:i18n": "Anonymous", "permissions": ["Login", "Register"] }, { "id": "Authenticated", "title:i18n": "Authenticated", "permissions": ["Logout", "Manage Preferences"] }, { "id": "Owner",
  • 75. USERS.JSON { "purge": true, "users": [ { "id": "admin", "password": "admin", "fullname": "Admin", "email": "admin@example.com", "roles": ["Administrator"] }, { "id": "anonymous", "password": "anonymous", "fullname": "Anonymous", "email": "anonymous@example.com",
  • 76. GROUPS.JSON { "purge": true, "groups": [ { "id": "Administrators", "title:i18n": "Administrators", "description:i18n": "", "email": "", "roles": ["Administrator"] } ] }
  • 77. WORKFLOWS.JSON { "purge": true, "workflows": [ { "id": "simple_publication_workflow", "title:i18n": "Simple Publication Workflow", "description:i18n": "Simple workflow that is useful for "json": { "initial_state": "private", "states": { "private": { "title:i18n": "Private", "description:i18n": "Can only be seen and edited b "transitions": ["publish", "submit"], "permissions": {
  • 79. GET ROLES GET /@roles HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@roles/Anonymous", "@type": "role", "id": "Anonymous", "title": "Anonymous" }, { "@id": "http://localhost:8000/@roles/Authenticated", "@type": "role", "id": "Authenticated", "title": "Authenticated"
  • 80. LIST USERS GET /@users HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@users/admin", "id": "admin", "fullname": "Admin", "email": "admin@example.com", "roles": [ "Administrator" ], "groups": [] }, {
  • 81. LIST WITH A QUERY GET /@users?query=admin HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@users/admin", "id": "admin", "fullname": "Admin", "email": "admin@example.com", "roles": [ "Administrator" ], "groups": [] } ]
  • 82. GET USER GET /@users/admin HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@users/admin", "id": "admin", "fullname": "Admin", "email": "admin@example.com", "roles": [ "Administrator" ], "groups": [] }
  • 83. UPDATE USER PATCH /@users/headlessnick HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "fullname": "Headless Nick" } HTTP/1.1 204 No Content
  • 84. DELETE USER DELETE /@users/headlessnick HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 204 No Content
  • 85. REGISTRATION POST /@users HTTP/1.1 Accept: application/json Content-Type: application/json { "email": "nearly.headless.nick@example.com", "fullname": "Nearly Headless Nick", "username": "headlessnick", "sendPasswordReset": true } HTTP/1.1 201 Created Content-Type: application/json { "@id": "http://localhost:8000/@users/headlessnick", "id": "headlessnick", "fullname": "Nearly Headless Nick", "email": "nearly.headless.nick@example.com", "roles": [],
  • 87. RESET PASSWORD POST /@users/headlessnick/reset-password HTTP/1.1 Accept: application/json HTTP/1.1 200 OK
  • 88. SET RESET PASSWORD POST /@users/headlessnick/reset-password HTTP/1.1 Accept: application/json Content-Type: application/json { "reset_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI "new_password": "headless" } HTTP/1.1 200 OK
  • 89. LIST GROUPS GET /@groups HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@groups/Administrators", "description": "", "email": "", "groupname": "Administrators", "id": "Administrators", "roles": [ "Administrator" ], "title": "Administrators" }
  • 90. GET GROUP GET /@groups/Administrators HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@groups/Administrators", "id": "Administrators", "title": "Administrators", "description": "", "groupname": "Administrators", "email": "", "roles": [ "Administrator" ] }
  • 91. ADD GROUP POST /@groups HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "groupname": "nicks", "title": "Nicks", "description": "Nearly Headless Nicks", "email": "nearly.headless.nicks@example.com", "roles": [ "Contributor" ] } HTTP/1.1 201 Created Content-Type: application/json { "@id": "http://localhost:8000/@groups/nicks", "id": "nicks",
  • 92. "groupname": "nicks", "description": "Nearly Headless Nicks", "email": "nearly.headless.nicks@example.com", "roles": [], "title": "Nicks" }
  • 93. UPDATE GROUP PATCH /@groups/nicks HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "title": "Headless Nicks" } HTTP/1.1 204 No Content
  • 94. DELETE GROUP DELETE /@groups/nicks HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 204 No Content
  • 95. GET WORKFLOW GET /news/@workflow HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/news/@workflow", "history": [], "state": { "id": "published", "title": "Published" }, "transitions": [ { "@id": "http://localhost:8000/news/@workflow/reject", "title": "Send back" },
  • 96. WORKFLOW TRANSITION POST /news/my-news-item/@workflow/publish HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "action": "publish", "actor": "admin", "comments": "", "review_state": "published", "time": "2022-04-08T16:00:00.000Z", "title": "Published" }
  • 97. SHARING GET /@sharing HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "available_roles": [ { "id": "Anonymous", "title": "Anonymous" }, { "id": "Authenticated", "title": "Authenticated" }, { "id": "Owner",
  • 98. ADD SHARING POST /@sharing HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "entries": [ { "id": "Administrators", "roles": { "Contributor": true, "Reader": true }, "type": "user" } HTTP/1.1 204 No Content
  • 100. CATALOG Indexes type (path, uuid, integer, date, text, string, boolean, string[]) operators Metadata name type attribute
  • 101. CATALOG.JSON { "indexes": [ { "name": "path", "type": "path", "attr": "path", "title:i18n": "Location", "description:i18n": "The location of an item", "group": "Metadata", "enabled": true, "sortable": false, "operators": { "string.absolutePath": { "title:i18n": "Absolute path", "description:i18n": "Location in the site structure"
  • 102. QUERYSTRING GET /@querystring HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@querystring", "indexes": { "created": { "title": "Creation date", "description": "The date an item was created", "group": "Dates", "enabled": true, "sortable": true, "values": {}, "vocabulary": null, "operations": [
  • 103. SEARCH GET /@search?SearchableText=news* HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@search", "items": [ { "@id": "http://localhost:8000/news", "@type": "Folder", "title": "News", "UID": "32215c67-86de-462a-8cc0-eabcd2b39c26", "path": "/news", "Description": "News Items", "Title": "News", "Subject": null,
  • 104. BATCHING GET /@search?b_size=2 HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@search", "items": [ { "@id": "http://localhost:8000/events", "@type": "Folder", "title": "Events", "UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41", "path": "/events", "Description": null, "Title": "Events", "Subject": null,
  • 105. DEPTH GET /@search?path.depth=1 HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@search", "items": [ { "@id": "http://localhost:8000/events", "@type": "Folder", "title": "Events", "UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41", "path": "/events", "Description": null, "Title": "Events", "Subject": null,
  • 106. SORT GET /@search?sort_on=effective HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@search", "items": [ { "@id": "http://localhost:8000/", "@type": "Site", "title": "Welcome to Nick!", "UID": "92a80817-f5b7-400d-8f58-b08126f0f09b", "path": "/", "Description": "Congratulations! You have successfully i "Title": "Welcome to Nick!", "Subject": null,
  • 108. VOCABULARIES nick └─ src └─ vocabularies └─ actions └─ actions.js └─ behaviors └─ behaviors.js └─ groups └─ groups.js └─ image-scales └─ image-scales.js ... └─ index.js
  • 109. CREATE VOCABULARY /** * Actions vocabulary. * @module vocabularies/actions/actions */ import { Action } from '../../models'; /** * Returns the acions vocabulary. * @method actions * @returns {Array} Array of terms. */ export async function actions(req, trx) { const actions = await Action.fetchAll( {},
  • 110. PROFILE VOCABULARIES nick └─ src └─ profiles └─ core └─ vocabularies └─ boolean.json
  • 111. PROFILE VOCABULARY { "id": "boolean", "title:i18n": "boolean", "items": [ { "title:i18n": "Yes", "token": "Yes" }, { "title:i18n": "No", "token": "No" } ] }
  • 112. LIST VOCABULARIES GET /@vocabularies HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@vocabularies/actions", "title": "actions" }, { "@id": "http://localhost:8000/@vocabularies/behaviors", "title": "behaviors" }, { "@id": "http://localhost:8000/@vocabularies/boolean", "title": "boolean"
  • 113. GET VOCABULARY GET /@vocabularies/actions HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@vocabularies/actions", "items": [ { "title": "View", "token": "view" }, { "title": "Edit", "token": "edit" }, {
  • 115. BREADCRUMBS GET /events/event-1/@breadcrumbs HTTP/1.1 Accept: application/json HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/events/event-1/@breadcrumbs", "items": [ { "@id": "http://localhost:8000/events", "title": "Events" }, { "@id": "http://localhost:8000/events/event-1", "title": "Event 1" } ]
  • 116. NAVIGATION GET /news/@navigation HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/news/@navigation", "items": [ { "@id": "http://localhost:8000/events", "@type": "Folder", "title": "Events", "UID": "1a2123ba-14e8-4910-8e6b-c04a40d72a41", "path": "/events", "Description": null, "Title": "Events", "Subject": null,
  • 117. CONTROLPANELS nick └─ src └─ profiles └─ core └─ controlpanels └─ mail.js └─ language.js { "id": "mail", "title:i18n": "Mail", "group": "General", "schema": { "fieldsets": [ { "behavior": "plone", "fields": [ "host", "port", "secure", "user",
  • 119. CONTROLPANELS GET /@controlpanels HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json [ { "@id": "http://localhost:8000/@controlpanels/language", "group": "General", "title": "Language" }, { "@id": "http://localhost:8000/@controlpanels/mail", "group": "General", "title": "Mail" } ]
  • 120. GET CONTROLPANEL GET /@controlpanels/mail HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@controlpanels/mail", "group": "General", "title": "Mail", "data": { "host": "localhost", "pass": "", "port": 25, "user": "", "debug": true, "secure": true, "email from name": "Webmaster",
  • 121. UPDATE CONTROLPANEL PATCH /@controlpanels/mail HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "host": "mail.someserver.com", "port": 25 } HTTP/1.1 204 No Content
  • 122. MAIL POST /@email-send HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ Content-Type: application/json { "name": "John Doe", "from": "john@doe.com", "to": "jane@doe.com", "subject": "Hello!", "message": "Just want to say hi." } HTTP/1.1 204 OK
  • 123. MAIL WEBMASTER POST /@email-notification HTTP/1.1 Accept: application/json Content-Type: application/json { "name": "John Doe", "from": "john@doe.com", "subject": "Hello!", "message": "Just want to say hi." } HTTP/1.1 204 OK
  • 124. SYSTEM GET /@system HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@system", "nick_version": "2.8.0", "node_version": "v16.15.0", "express_version": "4.18.2", "objection_version": "3.0.1", "knex_version": "2.3.0", "postgres_version": "14.4" }
  • 125. DATABASE GET /@database HTTP/1.1 Accept: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ HTTP/1.1 200 OK Content-Type: application/json { "@id": "http://localhost:8000/@database", "db_name": "nick", "db_size": "10 MB", "pool": { "min": 2, "max": 10 }, "blob_size": "10 MB" }
  • 126. I18N nick └─ locales └─ en └─ LC_MESSAGES └─ nick.po └─ nl └─ LC_MESSAGES └─ nick.po └─ nick.po └─ en.js └─ nl.js
  • 128. I18N IN JS import { i18n } from './middleware'; ... app.use(i18n); ... req.i18n('Transaction error.')
  • 129. I18N IN JSON { "id": "basic", "title:i18n": "Basic metadata", "description:i18n": "Adds title and description fields.", "schema": { "fieldsets": [ { "fields": ["title", "description"], "id": "default", "title:i18n": "Default" } ], "properties": { "description": { "description:i18n": "A description of this item.",
  • 130. LOGGING (LOG4JS) log.info(`Server listening on port ${config.port}`) 2022-05-30T22:21:06.317 INFO [server.js:12] Server listening o
  • 133. ROADMAP Querystring search 100% test coverage (currently 83%) Multi-Lingual Email login Controlpanels Developer docs Websockets