mirror of
https://github.com/ComfortablyCoding/strapi-plugin-website-builder.git
synced 2025-06-25 00:03:29 -04:00
Compare commits
No commits in common. "master" and "2.0.1" have entirely different histories.
@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
files: ['./server/**/*'],
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'],
|
||||
globals: {
|
||||
strapi: 'readonly',
|
||||
env: {
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:node/recommended', 'prettier'],
|
||||
};
|
||||
|
@ -1,15 +1,23 @@
|
||||
module.exports = {
|
||||
files: ['./admin/**/*'],
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
parser: '@babel/eslint-parser',
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ['react'],
|
||||
extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
requireConfigFile: false,
|
||||
ecmaVersion: 2018,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
sourceType: 'module',
|
||||
babelOptions: {
|
||||
presets: ['@babel/preset-react'],
|
||||
},
|
||||
},
|
||||
env: { browser: true, es6: true },
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
|
27
.eslintrc.js
27
.eslintrc.js
@ -1,5 +1,28 @@
|
||||
const frontendESLint = require('./.eslintrc.frontend.js');
|
||||
const backendESLint = require('./.eslintrc.backend.js');
|
||||
|
||||
module.exports = {
|
||||
$schema: 'https://json.schemastore.org/eslintrc',
|
||||
root: true,
|
||||
overrides: [require('./.eslintrc.backend.js'), require('./.eslintrc.frontend.js')],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
indent: ['error', 'tab'],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
},
|
||||
globals: {
|
||||
strapi: 'readonly',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['server/**/*'],
|
||||
...backendESLint,
|
||||
},
|
||||
{
|
||||
files: ['admin/**/*'],
|
||||
...frontendESLint,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
9
.github/workflows/npm-publish.yml
vendored
9
.github/workflows/npm-publish.yml
vendored
@ -13,14 +13,17 @@ jobs:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node v18
|
||||
- name: Install Node v14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18.x'
|
||||
node-version: '14.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Clean install deps
|
||||
run: npm ci
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Publish to NPM
|
||||
run: npm publish
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,7 +7,3 @@ stats.json
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
.idea
|
||||
|
||||
# docs
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
@ -20,6 +20,3 @@ npm-debug.log
|
||||
|
||||
# github
|
||||
.github
|
||||
|
||||
# docs
|
||||
docs
|
||||
|
@ -1,3 +1 @@
|
||||
package-lock.json
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
@ -7,5 +7,5 @@
|
||||
"useTabs": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 120
|
||||
"printWidth": 100
|
||||
}
|
||||
|
100
README.md
100
README.md
@ -2,10 +2,6 @@
|
||||
|
||||
A plugin for [Strapi](https://github.com/strapi/strapi) that provides the ability to trigger website builds manually, periodically or through model events.
|
||||
|
||||
[](https://img.shields.io/npm/dm/strapi-plugin-website-builder?style=for-the-badge)
|
||||
[](https://img.shields.io/npm/l/strapi-plugin-website-builder?style=for-the-badge)
|
||||
[](https://img.shields.io/github/v/release/ComfortablyCoding/strapi-plugin-website-builder?style=for-the-badge)
|
||||
|
||||
## Requirements
|
||||
|
||||
The installation requirements are the same as Strapi itself and can be found in the documentation on the [Quick Start](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html) page in the Prerequisites info card.
|
||||
@ -16,10 +12,100 @@ The installation requirements are the same as Strapi itself and can be found in
|
||||
|
||||
**NOTE**: While this plugin may work with the older Strapi versions, they are not supported, it is always recommended to use the latest version of Strapi.
|
||||
|
||||
## Documentation
|
||||
## Installation
|
||||
|
||||
The documentation for this plugin can be viewed [here](https://strapi-plugin-website-builder.netlify.app/)
|
||||
```sh
|
||||
npm install strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
**or**
|
||||
|
||||
```sh
|
||||
yarn add strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The plugin configuration is stored in a config file located at `./config/plugins.js`.
|
||||
|
||||
The plugin has different structures depending on the type of trigger for the build. Each of the following sample configurations is the minimum needed for their respective trigger type.
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual',
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Cron/Periodic Configuration
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'cron',
|
||||
cron: '* * 1 * * *',
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Event Configuration
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'event',
|
||||
events: [
|
||||
{
|
||||
model: 'recipes',
|
||||
types: ['create', 'delete'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**IMPORTANT NOTE**: Make sure any sensitive data is stored in env files.
|
||||
|
||||
#### The Complete Plugin Configuration Object
|
||||
|
||||
| Property | Description | Type | Required |
|
||||
| -------- | ----------- | ---- | -------- |
|
||||
| url | The trigger URL for the website build. | String | Yes |
|
||||
| headers | Any headers to send along with the request. | Object | No |
|
||||
| body | Any body data to send along with the request. | Object | No |
|
||||
| trigger | The trigger conditions for the build. | Object | Yes |
|
||||
| trigger.type | The type of trigger. The current supported options are `manual`,`cron` and `event` | String | Yes |
|
||||
| trigger.cron | The cron expression to use for cron trigger. The supported expressions are the same as in the [strapi docs](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/cronjobs.html#cron-jobs) | String | Only if the type is cron |
|
||||
| trigger.events | The events to use for the event trigger. | Array | Only if the type is event |
|
||||
| trigger.events.model | The model to listen for events on. | String | Yes |
|
||||
| trigger.events.types | The model events to trigger on. The current supported events are `create`, `update` and `delete` | Array | Yes |
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin has been installed and configured, it will show in the sidebar as `Website Builder`.
|
||||
To trigger a manual build select the `Website Builder` menu item in the sidebar and click
|
||||
the `Trigger Build` button to start a build process.
|
||||
|
||||
## Bugs
|
||||
|
||||
If any bugs are found please report them as a [Github Issue](https://github.com/ComfortablyCoding/strapi-plugin-website-builder/issues)
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* Initializer
|
||||
*
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { PLUGIN_ID } from '../../utils/constants';
|
||||
|
||||
const Initializer = ({ setPlugin }) => {
|
||||
const ref = useRef();
|
||||
ref.current = setPlugin;
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(PLUGIN_ID);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Initializer.propTypes = {
|
||||
setPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Initializer;
|
@ -1 +1,26 @@
|
||||
export { default } from './Initializer';
|
||||
/**
|
||||
*
|
||||
* Initializer
|
||||
*
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { pluginId } from '../../pluginId';
|
||||
|
||||
const Initializer = ({ setPlugin }) => {
|
||||
const ref = useRef();
|
||||
ref.current = setPlugin;
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(pluginId);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Initializer.propTypes = {
|
||||
setPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Initializer;
|
||||
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* PluginIcon
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Play from '@strapi/icons/Play';
|
||||
|
||||
const PluginIcon = () => {
|
||||
return <Play />;
|
||||
};
|
||||
|
||||
export default PluginIcon;
|
@ -1 +1,12 @@
|
||||
export { default } from './PluginIcon';
|
||||
/**
|
||||
*
|
||||
* PluginIcon
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Play from '@strapi/icons/Play';
|
||||
|
||||
const PluginIcon = () => <Play />;
|
||||
|
||||
export default PluginIcon;
|
||||
|
@ -1,45 +0,0 @@
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
|
||||
import { PLUGIN_ID } from '../utils/constants';
|
||||
import { getTrad } from '../utils/common';
|
||||
|
||||
export const useBuild = () => {
|
||||
const { post, get } = useFetchClient();
|
||||
const toggleNotification = useNotification();
|
||||
|
||||
function getBuilds() {
|
||||
return useQuery({
|
||||
queryKey: [PLUGIN_ID, 'builds'],
|
||||
queryFn: function () {
|
||||
return get(`/${PLUGIN_ID}/builds`);
|
||||
},
|
||||
select: function ({ data }) {
|
||||
return data.data || false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { mutateAsync: triggerBuild } = useMutation({
|
||||
mutationFn: function (data) {
|
||||
return post(`/${PLUGIN_ID}/builds`, { data });
|
||||
},
|
||||
onSuccess: () => {
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: getTrad('build.notification.trigger.success') },
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: error.response?.error?.message || error.message || { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
triggerBuild,
|
||||
getBuilds,
|
||||
};
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { useFetchClient, useNotification } from '@strapi/helper-plugin';
|
||||
|
||||
import { PLUGIN_ID } from '../utils/constants';
|
||||
import { getTrad } from '../utils/common';
|
||||
|
||||
export const useLogs = () => {
|
||||
const { get, del } = useFetchClient();
|
||||
const toggleNotification = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
function getLogs({ page }) {
|
||||
return useQuery({
|
||||
queryKey: [PLUGIN_ID, 'logs'],
|
||||
queryFn: function () {
|
||||
return get(`/${PLUGIN_ID}/logs`, { params: { sort: ['id:desc'], pagination: { page } } });
|
||||
},
|
||||
select: function ({ data }) {
|
||||
return { ...data } || false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { mutateAsync: deleteLog } = useMutation({
|
||||
mutationFn: function (id) {
|
||||
return del(`/${PLUGIN_ID}/logs/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([PLUGIN_ID, 'logs']);
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: getTrad('log.notification.delete.success') },
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: error.response?.error?.message || error.message || { id: 'notification.error' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
getLogs,
|
||||
deleteLog,
|
||||
};
|
||||
};
|
@ -1,51 +1,30 @@
|
||||
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from './utils/constants';
|
||||
import { pluginId } from './pluginId';
|
||||
import Initializer from './components/Initializer';
|
||||
import PluginIcon from './components/PluginIcon';
|
||||
import PluginPkg from '../../package.json';
|
||||
|
||||
const name = 'Website Builder';
|
||||
const name = PluginPkg.strapi.displayName;
|
||||
|
||||
export default {
|
||||
register(app) {
|
||||
app.addMenuLink({
|
||||
to: `/plugins/${PLUGIN_ID}`,
|
||||
to: `/plugins/${pluginId}`,
|
||||
icon: PluginIcon,
|
||||
intlLabel: {
|
||||
id: `${PLUGIN_ID}.plugin.name`,
|
||||
id: `${pluginId}.plugin.name`,
|
||||
defaultMessage: name,
|
||||
},
|
||||
Component: async () => {
|
||||
const component = await import(/* webpackChunkName: "[website-builder-request]" */ './pages/App');
|
||||
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
|
||||
|
||||
return component;
|
||||
},
|
||||
});
|
||||
app.registerPlugin({
|
||||
id: PLUGIN_ID,
|
||||
id: pluginId,
|
||||
initializer: Initializer,
|
||||
isReady: false,
|
||||
name,
|
||||
});
|
||||
},
|
||||
async registerTrads({ locales }) {
|
||||
const importedTrads = await Promise.all(
|
||||
locales.map((locale) => {
|
||||
return import(/* webpackChunkName: "translation-[website-builder-request]" */ `./translations/${locale}.json`)
|
||||
.then(({ default: data }) => {
|
||||
return {
|
||||
data: prefixPluginTranslations(data, PLUGIN_ID),
|
||||
locale,
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
data: {},
|
||||
locale,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return Promise.resolve(importedTrads);
|
||||
},
|
||||
};
|
||||
|
@ -8,17 +8,17 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { NotFound } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from '../../utils/constants';
|
||||
import Builds from '../Builds/ListView';
|
||||
import BuildLogs from '../Logs/ListView';
|
||||
import { pluginId } from '../../pluginId';
|
||||
import HomePage from '../HomePage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`/plugins/${PLUGIN_ID}`} component={Builds} exact />
|
||||
<Route path={`/plugins/${PLUGIN_ID}/logs`} component={BuildLogs} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
<div>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* BuildPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Layout,
|
||||
Main,
|
||||
HeaderLayout,
|
||||
ContentLayout,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Th,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
EmptyStateLayout,
|
||||
Typography,
|
||||
Switch,
|
||||
LinkButton,
|
||||
Button,
|
||||
Flex,
|
||||
} from '@strapi/design-system';
|
||||
import { EmptyDocuments, Stack, Play } from '@strapi/icons';
|
||||
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
||||
import { PLUGIN_ID } from '../../../utils/constants';
|
||||
import { useBuild } from '../../../hooks/useBuild';
|
||||
import { getTrad } from '../../../utils/common';
|
||||
|
||||
const BuildPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [builds, setBuilds] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
const { triggerBuild, getBuilds } = useBuild();
|
||||
|
||||
const { isLoading: isLoadingBuilds, data, isRefetching: isRefetchingBuilds } = getBuilds();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!isLoadingBuilds && !isRefetchingBuilds) {
|
||||
if (data) {
|
||||
setBuilds(data);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isLoadingBuilds, isRefetchingBuilds]);
|
||||
|
||||
function handleTriggerBuild(name) {
|
||||
triggerBuild({ name });
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Main aria-busy={isLoading}>
|
||||
<HeaderLayout
|
||||
title={formatMessage({ id: getTrad('builds.header.title'), defaultMessage: 'Builds' })}
|
||||
primaryAction={
|
||||
<LinkButton variant="secondary" size="s" endIcon={<Stack />} to={`/plugins/${PLUGIN_ID}/logs`}>
|
||||
Logs
|
||||
</LinkButton>
|
||||
}
|
||||
/>
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<LoadingIndicatorPage />
|
||||
</Box>
|
||||
) : builds.length > 0 ? (
|
||||
<Table colCount={5} rowCount={builds.length + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th width="5%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.enabled'),
|
||||
defaultMessage: 'Enabled',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="5%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.trigger'),
|
||||
defaultMessage: 'Trigger',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="20%">
|
||||
<Typography variant="sigma" fontWeight="semiBold" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.name'),
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="60%">
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('builds.table.header.url'),
|
||||
defaultMessage: 'URL',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th width="10%">
|
||||
{formatMessage({
|
||||
id: getTrad('table.header.actions'),
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{builds.map((build) => (
|
||||
<Tr key={btoa(build.name)}>
|
||||
<Td>
|
||||
<Switch label="Build Enabled" selected={build.enabled} />
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{build.trigger.type}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography fontWeight="semiBold" textColor="neutral800">
|
||||
{build.name}
|
||||
</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{build.url}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex gap={2}>
|
||||
{build.trigger.type === 'manual' && (
|
||||
<Button
|
||||
variant="default"
|
||||
size="S"
|
||||
disabled={build.enabled === false}
|
||||
endIcon={<Play />}
|
||||
onClick={() => handleTriggerBuild(build.name)}
|
||||
>
|
||||
Trigger
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyDocuments width="160px" />}
|
||||
content={formatMessage({
|
||||
id: getTrad('builds.empty'),
|
||||
defaultMessage: 'No builds found',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(BuildPage);
|
@ -1 +0,0 @@
|
||||
export { default } from './ListView';
|
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { ContentLayout } from '@strapi/design-system/Layout';
|
||||
import { Stack } from '@strapi/design-system/Stack';
|
||||
import { LogTable } from '../LogTable';
|
||||
|
||||
export const HomeContentLayout = () => (
|
||||
<ContentLayout>
|
||||
<Stack size={4}>
|
||||
<LogTable />
|
||||
</Stack>
|
||||
</ContentLayout>
|
||||
);
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import Publish from '@strapi/icons/Play';
|
||||
import { HeaderLayout } from '@strapi/design-system/Layout';
|
||||
import { Button } from '@strapi/design-system/Button';
|
||||
import { requestPluginEndpoint } from '../../../../utils/requestPluginEndpoint';
|
||||
|
||||
const triggerBuild = () =>
|
||||
requestPluginEndpoint('build', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
export const HomeHeaderLayout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation(triggerBuild, {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('build-logs');
|
||||
},
|
||||
});
|
||||
|
||||
const handleTrigger = async () => mutation.mutate();
|
||||
return (
|
||||
<HeaderLayout
|
||||
primaryAction={
|
||||
<Button onClick={handleTrigger} variant="primary" startIcon={<Publish />} size="L">
|
||||
Trigger Build
|
||||
</Button>
|
||||
}
|
||||
title="Website Builder"
|
||||
subtitle="The right way to build websites."
|
||||
/>
|
||||
);
|
||||
};
|
49
admin/src/pages/HomePage/components/LogTable/index.js
Normal file
49
admin/src/pages/HomePage/components/LogTable/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { Table, Tbody } from '@strapi/design-system/Table';
|
||||
import { LogTableHeaders } from '../LogTableHeaders';
|
||||
import { LogTableRow } from '../LogTableRow';
|
||||
import { EmptyStateLayout } from '@strapi/design-system/EmptyStateLayout';
|
||||
import { requestPluginEndpoint } from '../../../../utils/requestPluginEndpoint';
|
||||
|
||||
const deleteLog = async (id) =>
|
||||
requestPluginEndpoint(`logs/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
const fetchLogs = () => requestPluginEndpoint('logs');
|
||||
|
||||
export const LogTable = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery('build-logs', () => fetchLogs());
|
||||
|
||||
const mutation = useMutation(deleteLog, {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('build-logs');
|
||||
},
|
||||
});
|
||||
|
||||
const handleLogDelete = (id) => {
|
||||
mutation.mutate(id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLogs();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<LogTableHeaders />
|
||||
<Tbody>
|
||||
{!query.isLoading && query.data.data.logs.length ? (
|
||||
query.data.data.logs.map((log) => (
|
||||
<LogTableRow key={log.id} log={log} handleDelete={handleLogDelete} />
|
||||
))
|
||||
) : (
|
||||
<EmptyStateLayout />
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
19
admin/src/pages/HomePage/components/LogTableHeaders/index.js
Normal file
19
admin/src/pages/HomePage/components/LogTableHeaders/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Thead, Tr, Th } from '@strapi/design-system/Table';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
|
||||
const headers = ['ID', 'Status', 'Trigger', 'Timestamp', 'Actions'];
|
||||
|
||||
export const LogTableHeaders = () => {
|
||||
return (
|
||||
<Thead>
|
||||
<Tr>
|
||||
{headers.map((header, i) => (
|
||||
<Th key={i}>
|
||||
<Typography variant="sigma">{header}</Typography>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
);
|
||||
};
|
40
admin/src/pages/HomePage/components/LogTableRow/index.js
Normal file
40
admin/src/pages/HomePage/components/LogTableRow/index.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tr, Td } from '@strapi/design-system/Table';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
import { IconButton } from '@strapi/design-system/IconButton';
|
||||
import Trash from '@strapi/icons/Trash';
|
||||
|
||||
const LogTableRow = ({ log, handleDelete }) => {
|
||||
return (
|
||||
<Tr key={log.id}>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.id}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.status}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.trigger}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.createdAt}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton onClick={() => handleDelete(log.id)} label="Delete" noBorder icon={<Trash />} />
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
LogTableRow.propTypes = {
|
||||
log: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
status: PropTypes.number.isRequired,
|
||||
trigger: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
handleDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { LogTableRow };
|
32
admin/src/pages/HomePage/index.js
Normal file
32
admin/src/pages/HomePage/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
*
|
||||
* HomePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Box } from '@strapi/design-system/Box';
|
||||
import { HomeHeaderLayout } from './components/HomeHeaderLayout';
|
||||
import { HomeContentLayout } from './components/HomeContentLayout';
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<Box>
|
||||
<HomeHeaderLayout />
|
||||
<HomeContentLayout />
|
||||
</Box>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HomePage);
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* LogsPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Layout,
|
||||
Main,
|
||||
HeaderLayout,
|
||||
ContentLayout,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Th,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
EmptyStateLayout,
|
||||
Typography,
|
||||
VisuallyHidden,
|
||||
IconButton,
|
||||
} from '@strapi/design-system';
|
||||
import { EmptyDocuments, Trash } from '@strapi/icons';
|
||||
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
|
||||
import { useLogs } from '../../../hooks/useLogs';
|
||||
import { getTrad } from '../../../utils/common';
|
||||
|
||||
const LogsPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [logs, setLogs] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
const { getLogs, deleteLog } = useLogs();
|
||||
|
||||
const { isLoading: isLoadingLogs, data: response, isRefetching: isRefetchingLogs } = getLogs({ page: 1 });
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!isLoadingLogs && !isRefetchingLogs) {
|
||||
if (response && !response.error) {
|
||||
setLogs(response.data);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isLoadingLogs, isRefetchingLogs]);
|
||||
|
||||
function isSuccessStatus(status) {
|
||||
return status >= 200 && 400 > status;
|
||||
}
|
||||
|
||||
function handleLogDelete(id) {
|
||||
deleteLog(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Main aria-busy={isLoading}>
|
||||
<HeaderLayout title={formatMessage({ id: getTrad('logs.header.title'), defaultMessage: 'Build Logs' })} />
|
||||
<ContentLayout>
|
||||
{isLoading ? (
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<LoadingIndicatorPage />
|
||||
</Box>
|
||||
) : logs.length > 0 ? (
|
||||
<>
|
||||
<Table colCount={5} rowCount={logs.length + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.id'),
|
||||
defaultMessage: 'ID',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.build'),
|
||||
defaultMessage: 'Build',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.trigger'),
|
||||
defaultMessage: 'Trigger',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.status'),
|
||||
defaultMessage: 'Status',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma" fontWeight="semiBold" textColor="neutral600">
|
||||
{formatMessage({
|
||||
id: getTrad('logs.table.header.timestamp'),
|
||||
defaultMessage: 'Timestamp',
|
||||
})}
|
||||
</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<VisuallyHidden>
|
||||
{formatMessage({
|
||||
id: getTrad('table.header.actions'),
|
||||
defaultMessage: 'Actions',
|
||||
})}
|
||||
</VisuallyHidden>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{logs.map((log) => (
|
||||
<Tr key={log.id}>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.id}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.trigger}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.build || 'unknown'}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography
|
||||
fontWeight="semiBold"
|
||||
textColor={isSuccessStatus(log.attributes.status) ? 'success500' : 'danger500'}
|
||||
>
|
||||
{log.attributes.status}
|
||||
</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Typography textColor="neutral800">{log.attributes.createdAt}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton onClick={() => handleLogDelete(log.id)} label="Delete" noBorder icon={<Trash />} />
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</>
|
||||
) : (
|
||||
<EmptyStateLayout
|
||||
icon={<EmptyDocuments width="160px" />}
|
||||
content={formatMessage({
|
||||
id: getTrad('logs.empty'),
|
||||
defaultMessage: 'No logs found',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LogsPage);
|
@ -1 +0,0 @@
|
||||
export { default } from './ListView';
|
3
admin/src/pluginId.js
Normal file
3
admin/src/pluginId.js
Normal file
@ -0,0 +1,3 @@
|
||||
import pluginPkg from '../../package.json';
|
||||
|
||||
export const pluginId = pluginPkg.strapi.name;
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"table.header.actions": "Actions",
|
||||
"builds.header.title": "Builds",
|
||||
"builds.table.header.enabled": "Enabled",
|
||||
"builds.table.header.trigger": "Trigger",
|
||||
"builds.table.header.name": "Timestamp",
|
||||
"builds.table.header.url": "URL",
|
||||
"builds.empty": "No builds found",
|
||||
"build.notification.trigger.success": "Build has been triggered successfully",
|
||||
"logs.header.title": "Build Logs",
|
||||
"logs.table.header.id": "ID",
|
||||
"logs.table.header.trigger": "Trigger",
|
||||
"logs.table.header.status": "Status",
|
||||
"logs.table.header.timestamp": "Timestamp",
|
||||
"logs.empty": "No logs found",
|
||||
"log.notification.delete.success": "Build log deleted successfully",
|
||||
"plugin.name": "Website Builder"
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { PLUGIN_ID } from './constants';
|
||||
|
||||
export function getTrad(id) {
|
||||
return `${PLUGIN_ID}.${id}`;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export const PLUGIN_ID = 'website-builder';
|
9
admin/src/utils/getPluginEndpointURL.js
Normal file
9
admin/src/utils/getPluginEndpointURL.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { pluginId } from '../pluginId';
|
||||
|
||||
/**
|
||||
* Auto prefix URLs with the plugin id
|
||||
*
|
||||
* @param {String} endpoint plugin specific endpoint
|
||||
* @returns {String} plugin id prefixed endpoint
|
||||
*/
|
||||
export const getPluginEndpointURL = (endpoint) => `/${pluginId}/${endpoint}`;
|
7
admin/src/utils/requestPluginEndpoint.js
Normal file
7
admin/src/utils/requestPluginEndpoint.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { request } from '@strapi/helper-plugin';
|
||||
import { getPluginEndpointURL } from './getPluginEndpointURL';
|
||||
|
||||
export const requestPluginEndpoint = (endpoint, data) => {
|
||||
const url = getPluginEndpointURL(endpoint);
|
||||
return request(url, data);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: 'Strapi Plugin Website Builder',
|
||||
description:
|
||||
'A plugin for Strapi Headless CMS that provides the ability to trigger website builds manually, periodically or through model events.',
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Quick Start', link: '/quick-start' },
|
||||
{ text: 'Configuration API', link: '/config-api' },
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
items: [
|
||||
{ text: 'Quick Start', link: '/quick-start' },
|
||||
{ text: 'Build Triggers', link: '/build-triggers' },
|
||||
{ text: 'Configuration API', link: '/config-api' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/ComfortablyCoding/strapi-plugin-website-builder' }],
|
||||
},
|
||||
});
|
@ -1,145 +0,0 @@
|
||||
# Builds
|
||||
|
||||
The plugin supports multiple builds and build configurations to cover as many use cases as possible. Each build at minimum must specify a name,url and trigger type to be valid.
|
||||
|
||||
Their are currently three supported trigger types `manual`,`cron`, and `event`.
|
||||
|
||||
## Manual Trigger
|
||||
|
||||
The manual trigger will start a build whenever the trigger button in the admin panel is pressed for the respective build.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Periodic Trigger
|
||||
|
||||
The periodic trigger will start a build at the interval specified by the cron expressions. The following example triggers a new build every hour.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'cron',
|
||||
expression: '0 */1 * * *'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Event Trigger
|
||||
|
||||
The event trigger wull start a build whenever one of the specific actions of the defined uid is emitted. The following example triggers a build every time a new article is created.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'event',
|
||||
events: [
|
||||
{
|
||||
uid: 'api::articles.articles',
|
||||
actions: ['create'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
::: info
|
||||
To trigger builds for image mutations use the uid `plugin::upload.file`.
|
||||
:::
|
||||
|
||||
## Multiple Builds
|
||||
|
||||
The plugin supports as many build configurations as you wish to use.
|
||||
|
||||
```javascript
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'production-manual'
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'development',
|
||||
enabled: env('NODE_ENV') !== 'production',
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'production-edge'
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'cron',
|
||||
expression: '0 */1 * * *'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'production-automated'
|
||||
url: 'https://link-to-hit-on-trigger.com'
|
||||
trigger: {
|
||||
type: 'event',
|
||||
events: [
|
||||
{
|
||||
uid: 'api::articles.articles',
|
||||
actions: ['create'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
@ -1,196 +0,0 @@
|
||||
# Configuration API
|
||||
|
||||
- default
|
||||
|
||||
```json
|
||||
{
|
||||
"shared": {},
|
||||
"builds": [],
|
||||
"hooks": {}
|
||||
}
|
||||
```
|
||||
|
||||
## shared
|
||||
|
||||
- type: `object`
|
||||
|
||||
> default values between all builds
|
||||
|
||||
- example
|
||||
|
||||
```json
|
||||
{
|
||||
"headers": {
|
||||
"X-Powered-By": "Strapi CMS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### headers
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The default headers to be sent with the request for all builds. Any build specific headers with the same nproperty ame will override these. A record is only provided to the function for event triggers.
|
||||
|
||||
### params
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The default parameters to be sent with the request for all builds. Any build specific params with the same property name will override these. A record is only provided to the function for event triggers.
|
||||
|
||||
## build
|
||||
|
||||
- type `array`
|
||||
|
||||
> build configurations
|
||||
|
||||
### name
|
||||
|
||||
- type: `string`
|
||||
- description: The name for the build. **MUST BE UNIQUE**.
|
||||
|
||||
### url
|
||||
|
||||
- type: `string`
|
||||
- description: The url for the build.
|
||||
|
||||
### trigger
|
||||
|
||||
- type: `object`
|
||||
- description: The trigger configuration for the build.
|
||||
|
||||
- #### type
|
||||
|
||||
- type: `string`
|
||||
- description: The trigger type for the build. It can be `manual`,`cron`, or `event`.
|
||||
|
||||
- #### expression
|
||||
|
||||
- type: `string`
|
||||
- description: The cron expression to use for the `cron` tigger type. Required for the `cron` trigger type only.
|
||||
|
||||
- #### events
|
||||
|
||||
- type: `array`
|
||||
- description: The events to trigger new build on. Required for the `event` trigger type only.
|
||||
|
||||
- ##### uid
|
||||
|
||||
- type: `string`
|
||||
- description: The uid of the content type to watch mutations on.
|
||||
|
||||
- ##### actions
|
||||
|
||||
- type: `actions`
|
||||
- description: The actions/mutations to listen for on the content type. Possible values are `create`,`update`,`delete`,`publish`,`unpublish`. `publish` and `unpublish` are only available on content types, not media mutations.
|
||||
|
||||
### method
|
||||
|
||||
- type: `string`
|
||||
- default: `POST`
|
||||
- description: The method to use for the request that triggers the build. Supported methods are `GET` and `POST`.
|
||||
|
||||
### headers
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The headers to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
headers: () => ({
|
||||
'X-Powered-By': 'Lorem Ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
headers: {
|
||||
'X-Powered-By': 'Lorem Ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
### params
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The parameters to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
params: () => ({
|
||||
lorem: 'ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
params: {
|
||||
'lorem': 'ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
### body
|
||||
|
||||
- type: `function` or `object`
|
||||
- arguments: `{ record }`
|
||||
- description: The parameters to be sent with the request. A record is only provided to the function for event triggers.
|
||||
- example
|
||||
|
||||
:::: code-group
|
||||
|
||||
```js [function]
|
||||
{
|
||||
body: () => ({
|
||||
lorem: 'ipsum',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```js [object]
|
||||
{
|
||||
body: {
|
||||
'lorem': 'ipsum',
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
## hooks
|
||||
|
||||
- type: `object`
|
||||
|
||||
> hook into specific areas to extend functionality
|
||||
|
||||
### beforeRequest
|
||||
|
||||
- type: `function`
|
||||
- arguments: `requestConfig`
|
||||
- description: Mutate the request before it is sent out
|
||||
- example
|
||||
|
||||
```js
|
||||
{
|
||||
hooks: {
|
||||
beforeRequest: (requestConfig) => {
|
||||
requestConfig.headers.custom = 'custom_value';
|
||||
return config;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: 'Strapi Plugin Website Builder'
|
||||
tagline: 'The only builder you will ever need'
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Quick Start
|
||||
link: /quick-start
|
||||
- theme: alt
|
||||
text: Configuration API
|
||||
link: /config-api
|
||||
|
||||
features:
|
||||
- title: Manual Builds
|
||||
details: Trigger builds manually via the strapi admin.
|
||||
- title: Periodic Builds
|
||||
details: Trigger builds periodically through cron jobs.
|
||||
- title: Events Builds
|
||||
details: Trigger builds based on content type actions.
|
||||
---
|
@ -1,52 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
## Installation & Configuration
|
||||
|
||||
::: info
|
||||
This plugin is compatible with Strapi v4.x only. While it may work with the older Strapi versions, they are not supported. It is recommended to always use the latest version of Strapi.
|
||||
:::
|
||||
|
||||
1. Install the plugin in the root directory of your strapi project.
|
||||
|
||||
:::: code-group
|
||||
|
||||
```bash [npm]
|
||||
npm i strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
```bash [yarn]
|
||||
yarn add strapi-plugin-website-builder
|
||||
```
|
||||
|
||||
::::
|
||||
|
||||
2. Enable the plugin at `./config/plugins.js`.
|
||||
|
||||
```js
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'website-builder': {
|
||||
enabled: true,
|
||||
config: {
|
||||
builds: [
|
||||
{
|
||||
name: 'manual-build',
|
||||
url: 'https://link-to-hit-on-trigger.com',
|
||||
trigger: {
|
||||
type: 'manual',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin has been installed and configured, it should show in the sidebar as `Website Builder`. To trigger a manual build select the `Website Builder` menu item in the sidebar and click the `Trigger` button fr the build process you wish to start.
|
||||
|
||||
::: warning
|
||||
If the plugin does not show in the admin sidebar after the plugin is enabled then a clean rebuild of the admin is required. This can be done by deleting the generated `.cache` and `build` folders and re-running the develop command.
|
||||
:::
|
18297
package-lock.json
generated
18297
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@ -1,14 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "strapi-plugin-website-builder",
|
||||
"version": "3.0.1",
|
||||
"version": "2.0.1",
|
||||
"description": "A plugin for Strapi Headless CMS that provides the ability to trigger website builds manually, periodically or through model events.",
|
||||
"scripts": {
|
||||
"lint:fix": "eslint --fix \"./**/*.{js,yml}\"",
|
||||
"format:fix": "prettier --write \"./**/*.{js,json,yml,md}\"",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write **/*.{ts,js,json,yml}"
|
||||
},
|
||||
"author": {
|
||||
"name": "@ComfortablyCoding",
|
||||
@ -28,24 +25,22 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ComfortablyCoding/strapi-plugin-website-builder/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.5.0",
|
||||
"defu": "^6.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.2",
|
||||
"@babel/eslint-parser": "^7.17.0",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"prettier": "^2.5.1",
|
||||
"vitepress": "^1.0.0-rc.14"
|
||||
"prettier": "^2.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@strapi/strapi": "^4.0.7"
|
||||
"@strapi/strapi": "^4.0.7",
|
||||
"axios": "^0.24.0",
|
||||
"react-query": "^3.24.3",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"strapi": {
|
||||
"displayName": "Website Builder",
|
||||
@ -53,6 +48,10 @@
|
||||
"name": "website-builder",
|
||||
"kind": "plugin"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"strapi",
|
||||
"strapi-plugin",
|
||||
|
16
server/bootstrap.js
vendored
Normal file
16
server/bootstrap.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const { getPluginService } = require('./utils/getPluginService');
|
||||
const { setupCronWebhook } = require('./utils/setupCronWebhook');
|
||||
const { setupEventWebhook } = require('./utils/setupEventWebhook');
|
||||
|
||||
module.exports = async ({ strapi }) => {
|
||||
const settings = getPluginService(strapi, 'settingsService').get();
|
||||
|
||||
// complete any required webhook setup
|
||||
if (settings.trigger.type === 'cron') {
|
||||
setupCronWebhook(strapi, settings);
|
||||
} else if (settings.trigger.type === 'event') {
|
||||
setupEventWebhook(strapi, settings);
|
||||
}
|
||||
};
|
20
server/bootstrap/bootstrap.js
vendored
20
server/bootstrap/bootstrap.js
vendored
@ -1,20 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
const { bootstrapCron } = require('./bootstrapCron');
|
||||
const { bootstrapEvents } = require('./bootstrapEvents');
|
||||
|
||||
module.exports = async ({ strapi }) => {
|
||||
const builds = getService({ strapi, name: 'settings' }).get({ path: 'builds' });
|
||||
|
||||
builds
|
||||
.filter((b) => b.enabled || typeof b.enabled === 'undefined')
|
||||
.forEach((build) => {
|
||||
if (build.trigger.type === 'cron') {
|
||||
bootstrapCron({ strapi, build });
|
||||
} else if (build.trigger.type === 'event') {
|
||||
bootstrapEvents({ strapi, build });
|
||||
}
|
||||
strapi.log.info(`[website builder] ${build.trigger.type} trigger enabled for ${build.name} build`);
|
||||
});
|
||||
};
|
21
server/bootstrap/bootstrapCron.js
vendored
21
server/bootstrap/bootstrapCron.js
vendored
@ -1,21 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
|
||||
function bootstrapCron({ strapi, build }) {
|
||||
// create cron check
|
||||
strapi.cron.add({
|
||||
[build.name]: {
|
||||
options: {
|
||||
rule: build.trigger.expression,
|
||||
},
|
||||
task: () => {
|
||||
getService({ strapi, name: 'build' }).trigger({ name: build.name, trigger: { type: 'cron' } });
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bootstrapCron,
|
||||
};
|
46
server/bootstrap/bootstrapEvents.js
vendored
46
server/bootstrap/bootstrapEvents.js
vendored
@ -1,46 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
const { FILE_UID, EMIT_ACTIONS } = require('../utils/constants');
|
||||
|
||||
function bootstrapEvents({ strapi, build }) {
|
||||
// build valid events
|
||||
const events = new Map();
|
||||
build.trigger.events.forEach((event) => {
|
||||
// setup actions
|
||||
let actions = new Set();
|
||||
if (event.actions === '*') {
|
||||
EMIT_ACTIONS.forEach((action) => actions.add(action));
|
||||
} else {
|
||||
event.actions.forEach((action) => actions.add(action));
|
||||
}
|
||||
|
||||
// setup events
|
||||
if (event.uid === '*') {
|
||||
Object.keys(strapi.contentTypes)
|
||||
.filter((uid) => /^api::/.test(uid) || uid === FILE_UID)
|
||||
.forEach((uid) => {
|
||||
events.set(uid, actions);
|
||||
});
|
||||
} else {
|
||||
events.set(event.uid, actions);
|
||||
}
|
||||
});
|
||||
|
||||
EMIT_ACTIONS.forEach((action) => {
|
||||
strapi.eventHub.on(`entry.${action}`, ({ uid, entry: record }) => {
|
||||
const entry = events.get(uid);
|
||||
if (entry && entry.has(action)) {
|
||||
getService({ strapi, name: 'build' }).trigger({
|
||||
name: build.name,
|
||||
record,
|
||||
trigger: { type: 'event' },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bootstrapEvents,
|
||||
};
|
@ -1 +0,0 @@
|
||||
module.exports = require('./bootstrap');
|
@ -3,10 +3,7 @@
|
||||
const { pluginConfigSchema } = require('./schema.js');
|
||||
|
||||
module.exports = {
|
||||
default: () => ({
|
||||
shared: {},
|
||||
builds: [],
|
||||
hooks: {},
|
||||
}),
|
||||
validator: async (config) => await pluginConfigSchema.validate(config),
|
||||
validator: async (config) => {
|
||||
await pluginConfigSchema.validate(config);
|
||||
},
|
||||
};
|
||||
|
@ -5,68 +5,37 @@ const yup = require('yup');
|
||||
const pluginConfigSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
shared: yup.object(),
|
||||
hooks: yup.object(),
|
||||
builds: yup.array().of(
|
||||
yup.object().shape({
|
||||
enabled: yup.bool(),
|
||||
name: yup.string().required('A build name must be provided'),
|
||||
url: yup.string().required('A URL is required'),
|
||||
trigger: yup
|
||||
.object()
|
||||
.shape({
|
||||
type: yup.string().oneOf(['manual', 'cron', 'event']).required('A trigger type is required'),
|
||||
expression: yup.string().when('type', {
|
||||
is: 'cron',
|
||||
then: yup.string().required('A cron expression must be entered'),
|
||||
}),
|
||||
events: yup.array().when('type', {
|
||||
is: 'event',
|
||||
then: yup
|
||||
url: yup.string().url().required('url is required'),
|
||||
headers: yup.object(),
|
||||
body: yup.object(),
|
||||
trigger: yup
|
||||
.object()
|
||||
.shape({
|
||||
type: yup.string().oneOf(['manual', 'cron', 'event']),
|
||||
cron: yup.string().when('type', {
|
||||
is: 'cron',
|
||||
then: yup.string().required('A cron expression must be entered'),
|
||||
}),
|
||||
events: yup
|
||||
.array()
|
||||
.of(
|
||||
yup.object().shape({
|
||||
model: yup.string().required('A model name is required'),
|
||||
types: yup
|
||||
.array()
|
||||
.of(
|
||||
yup.object().shape({
|
||||
uid: yup.string().required('A uid is required'),
|
||||
actions: yup.mixed().test({
|
||||
name: 'actions',
|
||||
exclusive: true,
|
||||
message: '${path} must be an string or valid actions',
|
||||
test: (value) =>
|
||||
typeof value === 'string' ||
|
||||
yup
|
||||
.array()
|
||||
.of(yup.string().oneOf(['create', 'update', 'delete', 'publish', 'unpublish']))
|
||||
.required('uid actions are required')
|
||||
.isValid(value),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.min(1, 'At least one event must be provided')
|
||||
.required('events is required'),
|
||||
}),
|
||||
})
|
||||
.required('A trigger is required'),
|
||||
method: yup.mixed().oneOf(['GET', 'POST']).optional(),
|
||||
params: yup.mixed().test({
|
||||
name: 'params',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
headers: yup.mixed().test({
|
||||
name: 'headers',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
body: yup.mixed().test({
|
||||
name: 'body',
|
||||
exclusive: true,
|
||||
message: '${path} must be an object or function',
|
||||
test: (value) => typeof value === 'function' || yup.object().isValid(value),
|
||||
}),
|
||||
.of(yup.string().oneOf(['create', 'update', 'delete']))
|
||||
.required('types is required'),
|
||||
})
|
||||
)
|
||||
.when('type', {
|
||||
is: 'event',
|
||||
then: yup
|
||||
.array()
|
||||
.min(1, 'At least one event must be provided')
|
||||
.required('events is required'),
|
||||
}),
|
||||
})
|
||||
),
|
||||
.required('trigger is required'),
|
||||
})
|
||||
.required('A config is required');
|
||||
|
||||
|
@ -18,22 +18,16 @@ module.exports = {
|
||||
},
|
||||
options: {
|
||||
draftAndPublish: false,
|
||||
comment: '',
|
||||
},
|
||||
attributes: {
|
||||
status: {
|
||||
type: 'integer',
|
||||
},
|
||||
build: {
|
||||
type: 'string',
|
||||
},
|
||||
trigger: {
|
||||
type: 'string',
|
||||
},
|
||||
method: {
|
||||
type: 'string',
|
||||
},
|
||||
response: {
|
||||
type: 'json',
|
||||
type: 'enumeration',
|
||||
enum: ['manual', 'cron', 'event'],
|
||||
default: 'manual',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,32 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
|
||||
const { getPluginService } = require('../utils/getPluginService');
|
||||
module.exports = ({ strapi }) => ({
|
||||
/**
|
||||
* Trigger a website build
|
||||
* Trigger a website rebuild
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
async trigger(ctx) {
|
||||
async build(ctx) {
|
||||
try {
|
||||
const { status } = await getService({ strapi, name: 'build' }).trigger({
|
||||
name: ctx.request.body.data.name,
|
||||
trigger: { type: 'manual' },
|
||||
const settings = await getPluginService(strapi, 'settingsService').get();
|
||||
|
||||
const buildStatus = await getPluginService(strapi, 'buildService').build({
|
||||
settings,
|
||||
trigger: 'manual',
|
||||
});
|
||||
|
||||
ctx.send({ data: { status } });
|
||||
ctx.send({ data: { status: buildStatus } });
|
||||
} catch (error) {
|
||||
ctx.badRequest();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all builds
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
async find(ctx) {
|
||||
ctx.send({ data: getService({ strapi, name: 'settings' }).get({ path: 'builds' }) });
|
||||
},
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const log = require('./log-controller');
|
||||
const build = require('./build-controller');
|
||||
const logController = require('./log-controller');
|
||||
const buildController = require('./build-controller');
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
build,
|
||||
logController,
|
||||
buildController,
|
||||
};
|
||||
|
@ -1,9 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* controller
|
||||
*/
|
||||
const { getPluginService } = require('../utils/getPluginService');
|
||||
|
||||
const { createCoreController } = require('@strapi/strapi').factories;
|
||||
module.exports = ({ strapi }) => ({
|
||||
/**
|
||||
* Fetch the current logs
|
||||
*
|
||||
* @return {Array} logs
|
||||
*/
|
||||
async find(ctx) {
|
||||
const logs = await getPluginService(strapi, 'logService').find({
|
||||
sort: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
module.exports = createCoreController('plugin::website-builder.log');
|
||||
ctx.send({ data: { logs } });
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a log
|
||||
*
|
||||
* @return {Object} log
|
||||
*/
|
||||
async create(ctx) {
|
||||
const { body } = ctx.request;
|
||||
const createdLog = await getPluginService(strapi, 'logService').create(body);
|
||||
|
||||
ctx.send({ data: { log: createdLog } });
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a log
|
||||
*
|
||||
* @return {Object} log
|
||||
*/
|
||||
async delete(ctx) {
|
||||
const { id } = ctx.params;
|
||||
const log = await getPluginService(strapi, 'logService').findOne(id);
|
||||
|
||||
if (!log) {
|
||||
return ctx.notFound('log not found');
|
||||
}
|
||||
|
||||
const deletedLog = await getPluginService(strapi, 'logService').delete(id);
|
||||
|
||||
ctx.send({ data: { log: deletedLog } });
|
||||
},
|
||||
});
|
||||
|
@ -3,12 +3,7 @@
|
||||
module.exports = [
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/builds',
|
||||
handler: 'build.trigger',
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/builds',
|
||||
handler: 'build.find',
|
||||
path: '/build',
|
||||
handler: 'buildController.build',
|
||||
},
|
||||
];
|
||||
|
@ -4,11 +4,16 @@ module.exports = [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/logs',
|
||||
handler: 'log.find',
|
||||
handler: 'logController.find',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/logs',
|
||||
handler: 'logController.create',
|
||||
},
|
||||
{
|
||||
method: 'DELETE',
|
||||
path: '/logs/:id',
|
||||
handler: 'log.delete',
|
||||
handler: 'logController.delete',
|
||||
},
|
||||
];
|
||||
|
@ -1,32 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils/common');
|
||||
const axios = require('axios').default;
|
||||
const { getPluginService } = require('../utils/getPluginService');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
async trigger({ name, record, trigger }) {
|
||||
let log = { trigger: trigger.type, status: 500, build: name };
|
||||
|
||||
/**
|
||||
* Makes a request to the url specified in the plugin config
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {object} options.settings The plugin setting
|
||||
* @param {string} options.trigger The type of trigger that started the build
|
||||
*
|
||||
* @return {Promise<object>} response The response data from the url
|
||||
*/
|
||||
build: async ({ settings, trigger }) => {
|
||||
let status = 500;
|
||||
try {
|
||||
const request = await getService({ strapi, name: 'request' }).build({
|
||||
name,
|
||||
record,
|
||||
trigger,
|
||||
});
|
||||
const response = await getService({ strapi, name: 'request' }).execute(request);
|
||||
log.status = response.status;
|
||||
log.response = response.data;
|
||||
let requestConfig = { method: 'POST', data: {}, url: settings.url };
|
||||
if (settings.headers) {
|
||||
requestConfig.headers = settings.header;
|
||||
}
|
||||
|
||||
if (settings.body) {
|
||||
requestConfig.data = settings.body;
|
||||
}
|
||||
const buildResponse = await axios(requestConfig);
|
||||
status = buildResponse.status;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
log.status = error.response.status;
|
||||
} else if (error.request) {
|
||||
log.response = {};
|
||||
} else {
|
||||
log.response = error.message;
|
||||
status = error.response.status;
|
||||
}
|
||||
} finally {
|
||||
getPluginService(strapi, 'logService').create({
|
||||
trigger,
|
||||
status,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
getService({ strapi, name: 'log' }).create({ data: log });
|
||||
|
||||
return { status: log.status };
|
||||
return { status };
|
||||
},
|
||||
});
|
||||
|
@ -1,13 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const log = require('./log-service');
|
||||
const build = require('./build-service');
|
||||
const settings = require('./settings-service');
|
||||
const request = require('./request-service');
|
||||
const logService = require('./log-service');
|
||||
const buildService = require('./build-service');
|
||||
const settingsService = require('./settings-service');
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
build,
|
||||
settings,
|
||||
request,
|
||||
logService,
|
||||
buildService,
|
||||
settingsService,
|
||||
};
|
||||
|
@ -1,9 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* service
|
||||
*/
|
||||
const { pluginId } = require('../utils/pluginId');
|
||||
|
||||
const { createCoreService } = require('@strapi/strapi').factories;
|
||||
const uid = `plugin::${pluginId}.log`;
|
||||
|
||||
module.exports = createCoreService('plugin::website-builder.log');
|
||||
module.exports = ({ strapi }) => ({
|
||||
/**
|
||||
* Returns the currently stored build logs
|
||||
*
|
||||
* @return {Promise<array>} logs
|
||||
*/
|
||||
find(options = {}) {
|
||||
return strapi.entityService.findMany(uid, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the a specific stored build log
|
||||
*
|
||||
* @return {Promise<Object>} log
|
||||
*/
|
||||
findOne(id, options = {}) {
|
||||
return strapi.entityService.findOne(uid, id, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a build log
|
||||
*
|
||||
* @return {Promise<Object>} log
|
||||
*/
|
||||
create(log) {
|
||||
return strapi.entityService.create(uid, { data: log });
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a build log
|
||||
*
|
||||
* @return {Promise<Object>} log
|
||||
*/
|
||||
delete(id) {
|
||||
return strapi.entityService.delete(uid, id);
|
||||
},
|
||||
});
|
||||
|
@ -1,61 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios').default;
|
||||
const { defu } = require('defu');
|
||||
const { getService, resolveValue } = require('../utils/common');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
async build({ name, record, trigger }) {
|
||||
let request = {};
|
||||
const { shared, builds } = getService({ strapi, name: 'settings' }).get();
|
||||
|
||||
const build = builds.find((b) => b.name === name);
|
||||
|
||||
if (!build) {
|
||||
return;
|
||||
}
|
||||
|
||||
// method
|
||||
request.method = build.method || 'POST';
|
||||
|
||||
// url
|
||||
request.url = build.url;
|
||||
|
||||
// body
|
||||
if (build.body) {
|
||||
request.data = await resolveValue({ value: build.body, args: { record, trigger } });
|
||||
}
|
||||
|
||||
// params
|
||||
if (shared.params) {
|
||||
request.params = await resolveValue({ value: shared.params, args: { record, trigger } });
|
||||
}
|
||||
|
||||
if (build.params) {
|
||||
const paramsValue = await resolveValue({ value: build.params, args: { record, trigger } });
|
||||
|
||||
request.params = defu(paramsValue, request.params || {});
|
||||
}
|
||||
|
||||
// headers
|
||||
if (shared.headers) {
|
||||
request.headers = await resolveValue({ value: shared.headers, args: { record, trigger } });
|
||||
}
|
||||
|
||||
if (build.headers) {
|
||||
const headerValue = await resolveValue({ value: build.headers, args: { record, trigger } });
|
||||
request.headers = defu(headerValue, request.headers || {});
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
async execute(request) {
|
||||
const hooks = getService({ strapi, name: 'settings' }).get({ path: 'hooks' });
|
||||
|
||||
if (hooks.beforeRequest) {
|
||||
axios.interceptors.request.use(hooks.beforeRequest);
|
||||
}
|
||||
|
||||
return axios(request);
|
||||
},
|
||||
});
|
@ -1,16 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const { PLUGIN_ID } = require('../utils/constants');
|
||||
const { pluginId } = require('../utils/pluginId');
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
get({ path = '', defaultValue } = {}) {
|
||||
if (path.length) {
|
||||
path = `.${path}`;
|
||||
}
|
||||
|
||||
return strapi.config.get(`plugin.${PLUGIN_ID}${path}`, defaultValue);
|
||||
},
|
||||
set({ path = '', value }) {
|
||||
return strapi.config.set(`plugin.website-builder${path}`, value);
|
||||
/**
|
||||
* Returns the current plugin settings
|
||||
*
|
||||
* @return {Promise<Object>} settings
|
||||
*/
|
||||
get() {
|
||||
return strapi.config.get(`plugin.${pluginId}`);
|
||||
},
|
||||
});
|
||||
|
@ -1,18 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
function getService({ strapi, name, plugin = 'website-builder' }) {
|
||||
return strapi.plugin(plugin).service(name);
|
||||
}
|
||||
|
||||
async function resolveValue({ value, args }) {
|
||||
if (typeof value === 'function') {
|
||||
return await value(args);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getService,
|
||||
resolveValue,
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
const PLUGIN_ID = 'website-builder';
|
||||
|
||||
const FILE_UID = 'plugin::upload.file';
|
||||
|
||||
const EMIT_ACTIONS = ['create', 'update', 'delete', 'publish', 'unpublish'];
|
||||
|
||||
module.exports = {
|
||||
PLUGIN_ID,
|
||||
FILE_UID,
|
||||
EMIT_ACTIONS,
|
||||
};
|
12
server/utils/getPluginService.js
Normal file
12
server/utils/getPluginService.js
Normal file
@ -0,0 +1,12 @@
|
||||
const { pluginId } = require('./pluginId');
|
||||
|
||||
/**
|
||||
* A helper function to obtain a plugin service
|
||||
*
|
||||
* @return service
|
||||
*/
|
||||
const getPluginService = (strapi, name) => strapi.plugin(pluginId).service(name);
|
||||
|
||||
module.exports = {
|
||||
getPluginService,
|
||||
};
|
10
server/utils/pluginId.js
Normal file
10
server/utils/pluginId.js
Normal file
@ -0,0 +1,10 @@
|
||||
const pluginPkg = require('../../package.json');
|
||||
|
||||
/**
|
||||
* Returns the plugin id
|
||||
*
|
||||
* @return plugin id
|
||||
*/
|
||||
const pluginId = pluginPkg.strapi.name;
|
||||
|
||||
module.exports = { pluginId };
|
17
server/utils/setupCronWebhook.js
Normal file
17
server/utils/setupCronWebhook.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { getPluginService } = require('./getPluginService');
|
||||
|
||||
/**
|
||||
* Setup function for cron type trigger
|
||||
*
|
||||
*/
|
||||
const setupCronWebhook = (strapi, settings) => {
|
||||
strapi.cron.add({
|
||||
[settings.trigger.cron]: ({ strapi }) => {
|
||||
getPluginService(strapi, 'buildService').build({ settings, trigger: 'cron' });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setupCronWebhook,
|
||||
};
|
45
server/utils/setupEventWebhook.js
Normal file
45
server/utils/setupEventWebhook.js
Normal file
@ -0,0 +1,45 @@
|
||||
const { getPluginService } = require('./getPluginService');
|
||||
|
||||
const normalizeEvents = (events) => {
|
||||
let eventModels = {};
|
||||
|
||||
for (const { model, types } of events) {
|
||||
let eventPrefix = 'entry';
|
||||
|
||||
// media model uses the media prefix for events
|
||||
if (model === 'media') {
|
||||
eventPrefix = 'media';
|
||||
}
|
||||
|
||||
// add each model to their respective event type
|
||||
for (const eventType of types) {
|
||||
const eventKey = `${eventPrefix}.${eventType}`;
|
||||
if (!eventModels[eventKey]) {
|
||||
eventModels[eventKey] = [];
|
||||
}
|
||||
eventModels[eventKey].push(model);
|
||||
}
|
||||
}
|
||||
|
||||
return eventModels;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup function for event type trigger
|
||||
*
|
||||
*/
|
||||
const setupEventWebhook = (strapi, settings) => {
|
||||
const events = normalizeEvents(settings.trigger.events);
|
||||
|
||||
for (const [event, eventModels] of Object.entries(events)) {
|
||||
strapi.eventHub.on(event, ({ model }) => {
|
||||
if (eventModels.includes(model)) {
|
||||
getPluginService(strapi, 'buildService').build({ settings, trigger: 'event' });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setupEventWebhook,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user