SlideShare a Scribd company logo
1 of 134
Download to read offline
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 17Ana-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.docxMARRY7
ย 
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.docxtidwellveronique
ย 
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 arrivedGil 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'15Murat Yener
ย 
Your Entity, Your Code
Your Entity, Your CodeYour Entity, Your Code
Your Entity, Your CodeDrupalDay
ย 
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 MassesHolger 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 reactImran 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 BackgridGiorgio 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 Backgrideugenio 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 2019Matt Raible
ย 
Koin Quickstart
Koin QuickstartKoin Quickstart
Koin QuickstartMatthew 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 2019Matt 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 2024Rob Gietema
ย 
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024
Alpiene Cursussen van Bergsportreizen: Bergsportdag 2024Rob 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 NickRob Gietema
ย 
Van Klimhal naar Big Wall
Van Klimhal naar Big WallVan Klimhal naar Big Wall
Van Klimhal naar Big WallRob 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 2018Rob Gietema
ย 
Volto: Plone Conference 2018
Volto: Plone Conference 2018Volto: Plone Conference 2018
Volto: Plone Conference 2018Rob Gietema
ย 
React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15React Native: JS MVC Meetup #15
React Native: JS MVC Meetup #15Rob Gietema
ย 
React Native: React Meetup 3
React Native: React Meetup 3React Native: React Meetup 3
React Native: React Meetup 3Rob Gietema
ย 
React Router: React Meetup XXL
React Router: React Meetup XXLReact Router: React Meetup XXL
React Router: React Meetup XXLRob 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 2014Rob Gietema
ย 
Hackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksHackathon: Silicon Alley Lightning Talks
Hackathon: Silicon Alley Lightning TalksRob Gietema
ย 
Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Resource Registries: Plone Conference 2014
Resource Registries: Plone Conference 2014Rob Gietema
ย 
Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Arnhem Sprint 2013: Plone Conference 2013
Arnhem Sprint 2013: Plone Conference 2013Rob Gietema
ย 
Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Plone 5: Nederlandse Plone Gebruikersdag 2014
Plone 5: Nederlandse Plone Gebruikersdag 2014Rob 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 2010Rob Gietema
ย 
Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Deco UI: DZUG Tagung 2010
Deco UI: DZUG Tagung 2010Rob Gietema
ย 
Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Deco UI: Nederlandse Plone Gebruikesdag 2010
Deco UI: Nederlandse Plone Gebruikesdag 2010Rob 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

Low Rate Call Girls Kolkata Avani ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkataanamikaraghav4
ย 
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdf
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdfThe Intriguing World of CDR Analysis by Police: What You Need to Know.pdf
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdfMilind Agarwal
ย 
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Delivery
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on DeliveryCall Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Delivery
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Deliverybabeytanya
ย 
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012rehmti665
ย 
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130 Available With Roomdivyansh0kumar0
ย 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITMgdsc13
ย 
Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhimiss dipika
ย 
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€Fs
ย 
VIP Call Girls Kolkata Ananya ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataVIP Call Girls Kolkata Ananya ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkataanamikaraghav4
ย 
Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Paul Calvano
ย 
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”soniya singh
ย 
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...akbard9823
ย 
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja Vip
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja VipCall Girls Service Adil Nagar 7001305949 Need escorts Service Pooja Vip
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja VipCall Girls Lucknow
ย 
Magic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMagic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMartaLoveguard
ย 
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130 Available With Room
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130  Available With RoomVIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130  Available With Room
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130 Available With Roomdivyansh0kumar0
ย 
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€Fs
ย 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)Christopher H Felton
ย 
Russian Call Girls in Kolkata Samaira ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataRussian Call Girls in Kolkata Samaira ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkataanamikaraghav4
ย 
Complet Documnetation for Smart Assistant Application for Disabled Person
Complet Documnetation   for Smart Assistant Application for Disabled PersonComplet Documnetation   for Smart Assistant Application for Disabled Person
Complet Documnetation for Smart Assistant Application for Disabled Personfurqan222004
ย 

Recently uploaded (20)

Low Rate Call Girls Kolkata Avani ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
ย 
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdf
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdfThe Intriguing World of CDR Analysis by Police: What You Need to Know.pdf
The Intriguing World of CDR Analysis by Police: What You Need to Know.pdf
ย 
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Delivery
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on DeliveryCall Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Delivery
Call Girls In Mumbai Central Mumbai โค๏ธ 9920874524 ๐Ÿ‘ˆ Cash on Delivery
ย 
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012
Call Girls South Delhi Delhi reach out to us at โ˜Ž 9711199012
ย 
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur ๐Ÿ‘‰ 8250192130 Available With Room
ย 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITM
ย 
Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhi
ย 
young call girls in Uttam Nagar๐Ÿ” 9953056974 ๐Ÿ” Delhi escort Service
young call girls in Uttam Nagar๐Ÿ” 9953056974 ๐Ÿ” Delhi escort Serviceyoung call girls in Uttam Nagar๐Ÿ” 9953056974 ๐Ÿ” Delhi escort Service
young call girls in Uttam Nagar๐Ÿ” 9953056974 ๐Ÿ” Delhi escort Service
ย 
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(AUTๆฏ•ไธš่ฏไนฆ)ๆ–ฐ่ฅฟๅ…ฐๅฅฅๅ…‹ๅ…ฐ็†ๅทฅๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ย 
VIP Call Girls Kolkata Ananya ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataVIP Call Girls Kolkata Ananya ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
ย 
Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24
ย 
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”
Call Girls in Uttam Nagar Delhi ๐Ÿ’ฏCall Us ๐Ÿ”8264348440๐Ÿ”
ย 
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
ย 
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja Vip
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja VipCall Girls Service Adil Nagar 7001305949 Need escorts Service Pooja Vip
Call Girls Service Adil Nagar 7001305949 Need escorts Service Pooja Vip
ย 
Magic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptxMagic exist by Marta Loveguard - presentation.pptx
Magic exist by Marta Loveguard - presentation.pptx
ย 
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130 Available With Room
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130  Available With RoomVIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130  Available With Room
VIP Kolkata Call Girl Alambazar ๐Ÿ‘‰ 8250192130 Available With Room
ย 
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ๅฎšๅˆถ(UALๅญฆไฝ่ฏ)่‹ฑๅ›ฝไผฆๆ•ฆ่‰บๆœฏๅคงๅญฆๆฏ•ไธš่ฏๆˆ็ปฉๅ•ๅŽŸ็‰ˆไธ€ๆฏ”ไธ€
ย 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
ย 
Russian Call Girls in Kolkata Samaira ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls KolkataRussian Call Girls in Kolkata Samaira ๐ŸคŒ  8250192130 ๐Ÿš€ Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira ๐ŸคŒ 8250192130 ๐Ÿš€ Vip Call Girls Kolkata
ย 
Complet Documnetation for Smart Assistant Application for Disabled Person
Complet Documnetation   for Smart Assistant Application for Disabled PersonComplet Documnetation   for Smart Assistant Application for Disabled Person
Complet Documnetation for Smart Assistant Application for Disabled Person
ย 

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