Sitemap

Strapi — repeatable component recipe

4 min readSep 2, 2021

Appending a Component Entry in Strapi

When using Strapi there is a limitation in having to read all of the attached component entries out of a content object and then write them back in. This is problematic when you need a log style entry in a content object, such as a location that changes or adding a photo, event record, etc.

The solution outlined hopes to overcome the limitation by creating a new endpoint on the API that will take a POST (i.e. create a new entry) on a content resource (e.g. hub), saving a new entry in the component (e.g. event)

This would need specific knowledge of the component structure and is not that flexible. However if you have a fairly mature scheme, then this can work.

Setup

The Strapi setup is based on MariaDB on Ubuntu with Strapi. See other documents on how to set these up.

mysql  Ver 15.1 Distrib 10.1.48-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2
Server: MariaDB
Server version: 10.1.48-MariaDB-0ubuntu0.18.04.1 Ubuntu 18.04
Protocol version: 10

STRAPI VERSION v3.6.3
Community Edition
NODE VERSION v14.16.0

Sample Object

The example is a Hub content-type that has an Event repeatable component. The goal is to put log events in the Event component on state changes of the Hub object.

{
"id": 1,
"site": {
"id": 18,
"sitename": "Hildebrand Office",
"published_at": "2021-06-28T09:54:14.000Z",
"created_at": "2021-06-28T08:54:14.000Z",
"updated_at": "2021-07-02T16:31:13.000Z",
"reference": null,
"location": {
"id": 19,
"name": "Hildebrand Technology Limited",
"address1": "56 Conduit Street",
"address2": null,
"address3": null,
"city": "London",
"country": "United Kingdom",
"postcode": "W1S 2YZ",
"reference": "OFFICETEST",
"geopoint": {
"id": 15,
"lat": 51.512425,
"lng": -0.141339
}
},
"contacts": [],
"locationimages": [],
"logo": null,
"documents": []
},
"published_at": "2021-06-21T16:49:02.000Z",
"created_at": "2021-06-19T14:41:29.000Z",
"updated_at": "2021-07-04T14:04:57.000Z",
"mac": "ABCDEF1234",
"serial": "333333",
"type": "hub",
"make": "xxx",
"model": null,
"hardwareid": null,
"events": [
{
"id": 1,
"name": "changed location",
"type": "location",
"timestamp": "2021-07-04T14:04:57.000Z",
"data": {}
}
],
"location": [
{
"id": 2,
"name": "7th Floor",
"address1": null,
"address2": null,
"address3": null,
"city": null,
"country": null,
"postcode": null,
"reference": null,
"geopoint": null
},
{
"id": 16,
"name": "Warehouse",
"address1": null,
"address2": null,
"address3": null,
"city": null,
"country": null,
"postcode": null,
"reference": null,
"geopoint": null
},
{
"id": 17,
"name": "Waters Stores",
"address1": null,
"address2": null,
"address3": null,
"city": null,
"country": null,
"postcode": null,
"reference": null,
"geopoint": null
},
{
"id": 18,
"name": "Room 11",
"address1": null,
"address2": null,
"address3": null,
"city": null,
"country": null,
"postcode": null,
"reference": null,
"geopoint": null
}
]
}

Database Support

To make a transaction safe entry, a stored procedure is used in the database. This keeps the IDs consistent and makes the call very simple from the Strapi controller.

Create a stored procedure in MariaDB

DELIMITER $$

CREATE PROCEDURE AddEvent(
phubid int(10) unsigned,
pname VARCHAR(255),
ptype VARCHAR(255),
pdata longtext
)
BEGIN
DECLARE l_event_id INT DEFAULT 0;
DECLARE last_event_order INT DEFAULT 0;

START TRANSACTION;
-- Insert event data
INSERT INTO components_global_events (`name`, `type`, `data`, `timestamp`)
VALUES (pname, ptype, pdata, now());

-- get event id
SET l_event_id = LAST_INSERT_ID();

-- insert relation to hub for the event
IF l_event_id > 0 THEN

SET last_event_order = (SELECT max(`order`) FROM hubs_components WHERE component_type = "components_global_events" AND hub_id = phubid) + 1;
IF last_event_order IS NULL THEN
SET last_event_order = 1;
END IF;

INSERT INTO hubs_components(`field`, `order`, `component_type`, `component_id`, `hub_id`)
VALUES("events", last_event_order, "components_global_events", l_event_id, phubid);
-- commit
COMMIT;
ELSE
ROLLBACK;
END IF;
END$$

DELIMITER ;

You should be able to test with a mysql command line, but you will need a hub object with an ID already create. This will just append an event.

mysql> call AddEvent(1, "test 1", "test", NULL);

This should work if there are no values in the Events component.

Strapi Modifications

So within Strapi we are using a POST on /hubs/:id with a body object to insert. I started by creating a route in /api/hub/config/routes.json

{ 
"method": "POST",
"path": "/hubs/:id",
"handler": "hub.addentry",
"config": {
"policies": []
}
}

The POST is not being used with an ID, so should be OK. Check your entries to make sure that it is available.

In the hub controller (api/hub/controllers/hub.js) you will need


module.exports = {

async addentry(ctx) {

const { id } = ctx.params;
let name = "";
let type = "";
let data = {};
let result;

if(ctx.request.body.events) {
name = ctx.request.body.events.name;
type = ctx.request.body.events.type;
data = ctx.request.body.events.data;

if(data !== Object(data)) {
data = {};
}

let sql = `CALL AddEvent(${id}, "${name}", "${type}", '${JSON.stringify(data)}');`;

const db = strapi.connections.default.raw(sql);
result = await db.then();

return result;

}

return false;

}
}

Once the route is in place you will need to go to the Admin panel, select the Role -> Authenticated (or whatever your security model looks like) and check the addentry method to be allowed. I have mine for authenticated only.

The Call

POST /hubs/2 HTTP/1.1
Host: 192.168.22.231:1337
Authorization: Bearer xxxxx
Content-Type: application/json
Content-Length: 135

{
"events": {
"name": "Test event 2",
"type": "test",
"data": {
"somedata": 4
}
}
}

And then go and check in your Admin panel to see the new entry.

--

--

No responses yet