@cloudimage-strapi/content-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/admin/src/components/Initializer/index.js +26 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/index.js +63 -0
- package/admin/src/pages/App/index.js +25 -0
- package/admin/src/pages/HomePage/index.js +199 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/translations/en.json +1 -0
- package/admin/src/translations/fr.json +1 -0
- package/admin/src/utils/axiosInstance.js +40 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/package.json +35 -0
- package/server/bootstrap.js +5 -0
- package/server/config/index.js +6 -0
- package/server/content-types/index.js +3 -0
- package/server/controllers/index.js +7 -0
- package/server/controllers/scaleflex-cloudimage.js +40 -0
- package/server/destroy.js +5 -0
- package/server/index.js +25 -0
- package/server/middlewares/index.js +3 -0
- package/server/policies/index.js +3 -0
- package/server/register.js +65 -0
- package/server/routes/index.js +56 -0
- package/server/services/index.js +7 -0
- package/server/services/scaleflex-cloudimage.js +187 -0
- package/strapi-admin.js +3 -0
- package/strapi-server.js +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 scaleflex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Cloudimage plugin from Scaleflex for Strapi v4
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
`npm install @cloudimage-strapi/content-plugin`
|
|
6
|
+
|
|
7
|
+
## Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
`config/middlewares.js`
|
|
11
|
+
|
|
12
|
+
Replace `'strapi::security',` with:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
{
|
|
16
|
+
name: 'strapi::security',
|
|
17
|
+
config: {
|
|
18
|
+
contentSecurityPolicy: {
|
|
19
|
+
useDefaults: true,
|
|
20
|
+
directives: {
|
|
21
|
+
'connect-src': ["'self'", 'https:'],
|
|
22
|
+
'img-src': ["'self'", 'data:', 'blob:', 'scaleflex.cloudimg.io', 'assets.scaleflex.com', '*.cloudimg.io'],
|
|
23
|
+
'media-src': ["'self'", 'data:', 'blob:', 'scaleflex.cloudimg.io', 'assets.scaleflex.com', '*.cloudimg.io'],
|
|
24
|
+
upgradeInsecureRequests: null,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`config/server.js`
|
|
32
|
+
|
|
33
|
+
Append `url: 'domain (including the http/https:// part)'`
|
|
34
|
+
|
|
35
|
+
Eg: if you website is called `mywebsite.com`, then write like this:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
module.exports = ({ env }) => ({
|
|
39
|
+
host: env('HOST', '0.0.0.0'),
|
|
40
|
+
port: env.int('PORT', 1337),
|
|
41
|
+
app: {
|
|
42
|
+
keys: env.array('APP_KEYS'),
|
|
43
|
+
},
|
|
44
|
+
url: 'https://www.mywebsite.com',
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**It’s very important that you don’t forget to do this**
|
|
49
|
+
|
|
50
|
+
## Configure
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
- You can enter either token or domain.
|
|
55
|
+
- Whether your Cloudimage token is v7 or not, it will be auto-detected and set.
|
|
56
|
+
|
|
57
|
+
If you enter nothing or an invalid token, you will get this error.
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
In the rare case that we can’t auto-detect whether token is v7 or not, you will get the option to manually set it.
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
## What the plugin brings
|
|
66
|
+
|
|
67
|
+
1. This plugin simple gives client an interface to save the configuration settings.
|
|
68
|
+
2. It also allows clients to Cloudimage-ize all previous images:
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
- It will NOT Cloudimage-ize images that are already hosted on Filerobot.
|
|
73
|
+
- It will only sync images, no videos nor audios.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Initializer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useRef } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import pluginId from '../../pluginId';
|
|
10
|
+
|
|
11
|
+
const Initializer = ({ setPlugin }) => {
|
|
12
|
+
const ref = useRef();
|
|
13
|
+
ref.current = setPlugin;
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
ref.current(pluginId);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Initializer.propTypes = {
|
|
23
|
+
setPlugin: PropTypes.func.isRequired,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Initializer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* PluginIcon
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import Puzzle from '@strapi/icons/Puzzle';
|
|
9
|
+
|
|
10
|
+
const PluginIcon = () => <img src="https://assets.scaleflex.com/Marketing/Logos/Cloudimage+Logos/Favicon/CLOUDIMAGE+favicon.ico" width="16" height="16" />;
|
|
11
|
+
|
|
12
|
+
export default PluginIcon;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|
2
|
+
import pluginPkg from '../../package.json';
|
|
3
|
+
import pluginId from './pluginId';
|
|
4
|
+
import Initializer from './components/Initializer';
|
|
5
|
+
import PluginIcon from './components/PluginIcon';
|
|
6
|
+
|
|
7
|
+
const name = "scaleflex-cloudimage";
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
register(app) {
|
|
11
|
+
app.addMenuLink({
|
|
12
|
+
to: `/plugins/${pluginId}`,
|
|
13
|
+
icon: PluginIcon,
|
|
14
|
+
intlLabel: {
|
|
15
|
+
id: `${pluginId}.plugin.name`,
|
|
16
|
+
defaultMessage: 'Cloudimage by Scaleflex',
|
|
17
|
+
},
|
|
18
|
+
Component: async () => {
|
|
19
|
+
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
|
|
20
|
+
|
|
21
|
+
return component;
|
|
22
|
+
},
|
|
23
|
+
permissions: [
|
|
24
|
+
// Uncomment to set the permissions of the plugin here
|
|
25
|
+
// {
|
|
26
|
+
// action: '', // the action name should be plugin::plugin-name.actionType
|
|
27
|
+
// subject: null,
|
|
28
|
+
// },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
app.registerPlugin({
|
|
32
|
+
id: pluginId,
|
|
33
|
+
initializer: Initializer,
|
|
34
|
+
isReady: false,
|
|
35
|
+
name,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
bootstrap(app) {},
|
|
40
|
+
async registerTrads({ locales }) {
|
|
41
|
+
const importedTrads = await Promise.all(
|
|
42
|
+
locales.map(locale => {
|
|
43
|
+
return import(
|
|
44
|
+
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
|
|
45
|
+
)
|
|
46
|
+
.then(({ default: data }) => {
|
|
47
|
+
return {
|
|
48
|
+
data: prefixPluginTranslations(data, pluginId),
|
|
49
|
+
locale,
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {
|
|
53
|
+
return {
|
|
54
|
+
data: {},
|
|
55
|
+
locale,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return Promise.resolve(importedTrads);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* This component is the skeleton around the actual pages, and should only
|
|
4
|
+
* contain code that should be seen on all pages. (e.g. navigation bar)
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Switch, Route } from 'react-router-dom';
|
|
10
|
+
import { NotFound } from '@strapi/helper-plugin';
|
|
11
|
+
import pluginId from '../../pluginId';
|
|
12
|
+
import HomePage from '../HomePage';
|
|
13
|
+
|
|
14
|
+
const App = () => {
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<Switch>
|
|
18
|
+
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
|
19
|
+
<Route component={NotFound} />
|
|
20
|
+
</Switch>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default App;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* HomePage
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, {memo, useState, useEffect} from 'react';
|
|
8
|
+
import { Stack, Box, Typography, Field, FieldLabel, FieldInput, Button, Alert, Radio, RadioGroup } from '@strapi/design-system';
|
|
9
|
+
import {request} from "@strapi/helper-plugin";
|
|
10
|
+
import pluginId from '../../pluginId';
|
|
11
|
+
|
|
12
|
+
const HomePage = () => {
|
|
13
|
+
const [config, setConfig] = useState(null);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [domain, setDomain] = useState('');
|
|
16
|
+
const [isV7, setIsV7] = useState(false);
|
|
17
|
+
const [isV7CheckSuccessful, setIsV7CheckSuccessful] = useState(true);
|
|
18
|
+
const [manualV7, setManualV7] = useState(false);
|
|
19
|
+
const [disabledAllButtons, setDisabledAllButtons] = useState(false);
|
|
20
|
+
const [success, setSuccess] = useState(false);
|
|
21
|
+
const [error, setError] = useState(false);
|
|
22
|
+
const [disableUpdateButtons, setDisableUpdateButtons] = useState(false);
|
|
23
|
+
const [updateCount, setUpdateCount] = useState(-1);
|
|
24
|
+
const [displayUpdateResult, setDisplayUpdateResult] = useState(false);
|
|
25
|
+
const [updateMessage, setUpdateMessage] = useState('');
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
request(`/${pluginId}/config`, {method: 'GET'}).then(config => {
|
|
29
|
+
setConfig(config);
|
|
30
|
+
setDomain(config.domain);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
|
|
33
|
+
if (!config.domain || !config.hasOwnProperty('isV7'))
|
|
34
|
+
{
|
|
35
|
+
setDisableUpdateButtons(true);
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
{
|
|
39
|
+
setDisableUpdateButtons(false);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
if (loading)
|
|
45
|
+
{
|
|
46
|
+
return (
|
|
47
|
+
<Typography>fetching configuration...</Typography>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const saveConfiguration = async () => {
|
|
52
|
+
if (!domain)
|
|
53
|
+
{
|
|
54
|
+
setError(true);
|
|
55
|
+
setSuccess(false);
|
|
56
|
+
setDisabledAllButtons(false);
|
|
57
|
+
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
{
|
|
62
|
+
setError(false);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setDisabledAllButtons(true);
|
|
66
|
+
|
|
67
|
+
// Check whether input is token or domain
|
|
68
|
+
let tokenOrDomain = '';
|
|
69
|
+
|
|
70
|
+
if (domain && domain.indexOf('.') === -1)
|
|
71
|
+
{
|
|
72
|
+
tokenOrDomain = `${domain}.cloudimg.io`.trim();
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
{
|
|
76
|
+
tokenOrDomain = domain.trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check whether v7 or not
|
|
80
|
+
let v7Check = await request(`/${pluginId}/is-v7?domain=${tokenOrDomain}`, {method: 'GET'});
|
|
81
|
+
|
|
82
|
+
if (!v7Check.domainExists)
|
|
83
|
+
{
|
|
84
|
+
setError(true);
|
|
85
|
+
setSuccess(false);
|
|
86
|
+
setDisabledAllButtons(false);
|
|
87
|
+
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
setError(false);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setIsV7CheckSuccessful(v7Check.isSuccess);
|
|
96
|
+
|
|
97
|
+
let isV7Overall = v7Check.isSuccess ? v7Check.isV7 : manualV7;
|
|
98
|
+
setIsV7(isV7Overall);
|
|
99
|
+
|
|
100
|
+
// POST to backend to set config
|
|
101
|
+
let config = {
|
|
102
|
+
domain: tokenOrDomain,
|
|
103
|
+
isV7: isV7Overall
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let updatedConfigs = await request(`/${pluginId}/update-config`, {method: 'PUT', body: config});
|
|
107
|
+
|
|
108
|
+
setDomain(updatedConfigs.domain);
|
|
109
|
+
setSuccess(true);
|
|
110
|
+
setDisabledAllButtons(false);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const countUpdate = async () => {
|
|
114
|
+
setUpdateCount(-1);
|
|
115
|
+
setDisabledAllButtons(true);
|
|
116
|
+
let count = await request(`/${pluginId}/count-update`, {method: 'GET'});
|
|
117
|
+
setUpdateCount(count);
|
|
118
|
+
setDisabledAllButtons(false);
|
|
119
|
+
}
|
|
120
|
+
const updateMedia = async () => {
|
|
121
|
+
setDisplayUpdateResult(false);
|
|
122
|
+
setDisabledAllButtons(true);
|
|
123
|
+
|
|
124
|
+
let result = await request(`/${pluginId}/update-media`, {method: 'PUT'});
|
|
125
|
+
|
|
126
|
+
if (result.response === false)
|
|
127
|
+
{
|
|
128
|
+
setUpdateMessage('Update was not successful. Please try again later.');
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
{
|
|
132
|
+
setUpdateMessage(`${result.response} updates was successful.`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setDisplayUpdateResult(true);
|
|
136
|
+
setDisabledAllButtons(false);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<>
|
|
141
|
+
<Stack spacing={4} padding={3}>
|
|
142
|
+
{success && isV7CheckSuccessful && (
|
|
143
|
+
<Alert title="Successful" onClose={() => setSuccess(false)} closeLabel="Close alert" variant={'success'}>
|
|
144
|
+
Configuration updated. Your domain is {isV7 ? '' : 'not'} of Cloudimage version 7.
|
|
145
|
+
</Alert>
|
|
146
|
+
)}
|
|
147
|
+
{success && !isV7CheckSuccessful && (
|
|
148
|
+
<Alert title="Failed" onClose={() => setError(false)} closeLabel="Close alert" variant={'danger'}>
|
|
149
|
+
Cannot auto-determine whether it's Cloudimage version 7 or not. Please mannually set Cloudimage version.
|
|
150
|
+
</Alert>
|
|
151
|
+
)}
|
|
152
|
+
{error && (
|
|
153
|
+
<Alert title="Failed" onClose={() => setError(false)} closeLabel="Close alert" variant={'danger'}>
|
|
154
|
+
Please check your configuration inputs. Ensure you entered valid inputs for all required fields.
|
|
155
|
+
</Alert>
|
|
156
|
+
)}
|
|
157
|
+
{updateCount > -1 && (
|
|
158
|
+
<Alert onClose={() => setUpdateCount(-1)} closeLabel="Close alert" variant={'success'}>
|
|
159
|
+
There are {updateCount} image URLs to be updated.
|
|
160
|
+
</Alert>
|
|
161
|
+
)}
|
|
162
|
+
{displayUpdateResult && (
|
|
163
|
+
<Alert onClose={() => setDisplayUpdateResult(false)} closeLabel="Close alert" variant={'success'}>
|
|
164
|
+
{(updateCount <= 0) ? 'None are to be updated.' : updateMessage}
|
|
165
|
+
</Alert>
|
|
166
|
+
)}
|
|
167
|
+
<Box paddingLeft={8} paddingTop={5} paddingRight={8}>
|
|
168
|
+
<Typography variant={'alpha'}>Cloudimage by Scaleflex</Typography>
|
|
169
|
+
</Box>
|
|
170
|
+
<Field name="domain">
|
|
171
|
+
<Stack spacing={1}>
|
|
172
|
+
<FieldLabel>Token or Domain *</FieldLabel>
|
|
173
|
+
<FieldInput type="text" placeholder="Token/Domain" value={domain} onChange={(e) => {
|
|
174
|
+
setDomain(e.target.value)
|
|
175
|
+
}}/>
|
|
176
|
+
</Stack>
|
|
177
|
+
</Field>
|
|
178
|
+
{!isV7CheckSuccessful && (
|
|
179
|
+
<Stack spacing={1}>
|
|
180
|
+
<FieldLabel>Is Cloudimage version 7 ?</FieldLabel>
|
|
181
|
+
<RadioGroup onChange={e => setManualV7(e.target.value)} value={manualV7} name="manualV7">
|
|
182
|
+
<Radio value="true">Version 7</Radio>
|
|
183
|
+
<Radio value="false">Not version 7</Radio>
|
|
184
|
+
</RadioGroup>
|
|
185
|
+
</Stack>
|
|
186
|
+
)}
|
|
187
|
+
<Box width={200}>
|
|
188
|
+
<Button disabled={disabledAllButtons} onClick={() => saveConfiguration()}>Save configuration</Button>
|
|
189
|
+
</Box>
|
|
190
|
+
<Stack horizontal spacing={4}>
|
|
191
|
+
<Button disabled={disabledAllButtons || disableUpdateButtons} onClick={() => countUpdate()}>Count updatable images</Button>
|
|
192
|
+
<Button disabled={disabledAllButtons || disableUpdateButtons} onClick={() => updateMedia()}>Update old images</Button>
|
|
193
|
+
</Stack>
|
|
194
|
+
</Stack>
|
|
195
|
+
</>
|
|
196
|
+
)
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export default memo(HomePage);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axios with a custom config.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { auth } from '@strapi/helper-plugin';
|
|
7
|
+
|
|
8
|
+
const instance = axios.create({
|
|
9
|
+
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
instance.interceptors.request.use(
|
|
13
|
+
async config => {
|
|
14
|
+
config.headers = {
|
|
15
|
+
Authorization: `Bearer ${auth.getToken()}`,
|
|
16
|
+
Accept: 'application/json',
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return config;
|
|
21
|
+
},
|
|
22
|
+
error => {
|
|
23
|
+
Promise.reject(error);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
instance.interceptors.response.use(
|
|
28
|
+
response => response,
|
|
29
|
+
error => {
|
|
30
|
+
// whatever you want to do with the error
|
|
31
|
+
if (error.response?.status === 401) {
|
|
32
|
+
auth.clearAppStorage();
|
|
33
|
+
window.location.reload();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default instance;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloudimage-strapi/content-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Strapi v4 plugin for Filerobot by Scaleflex",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"name": "scaleflex-cloudimage",
|
|
7
|
+
"description": "Scaleflex Cloudimage",
|
|
8
|
+
"kind": "plugin",
|
|
9
|
+
"displayName": "Scaleflex Cloudimage"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@strapi/design-system": "^1.4.1"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@strapi/strapi": "^4.3.0"
|
|
16
|
+
},
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Scaleflex"
|
|
19
|
+
},
|
|
20
|
+
"maintainers": [
|
|
21
|
+
{
|
|
22
|
+
"name": "Ruslan Aliyev"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=12.x. <=16.x.x",
|
|
27
|
+
"npm": ">=6.0.0"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"scaleflex",
|
|
32
|
+
"filerobot",
|
|
33
|
+
"strapi"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = ({ strapi }) => ({
|
|
4
|
+
async index(ctx) {
|
|
5
|
+
ctx.body = await strapi
|
|
6
|
+
.plugin('scaleflex-cloudimage')
|
|
7
|
+
.service('scaleflexCloudimage')
|
|
8
|
+
.getWelcomeMessage();
|
|
9
|
+
},
|
|
10
|
+
async getConfig(ctx) {
|
|
11
|
+
ctx.body = await strapi
|
|
12
|
+
.plugin('scaleflex-cloudimage')
|
|
13
|
+
.service('scaleflexCloudimage')
|
|
14
|
+
.getConfig();
|
|
15
|
+
},
|
|
16
|
+
async updateConfig(ctx) {
|
|
17
|
+
ctx.body = await strapi
|
|
18
|
+
.plugin('scaleflex-cloudimage')
|
|
19
|
+
.service('scaleflexCloudimage')
|
|
20
|
+
.updateConfig(ctx);
|
|
21
|
+
},
|
|
22
|
+
async checkV7(ctx) {
|
|
23
|
+
ctx.body = await strapi
|
|
24
|
+
.plugin('scaleflex-cloudimage')
|
|
25
|
+
.service('scaleflexCloudimage')
|
|
26
|
+
.checkV7(ctx);
|
|
27
|
+
},
|
|
28
|
+
async countUpdate(ctx) {
|
|
29
|
+
ctx.body = await strapi
|
|
30
|
+
.plugin('scaleflex-cloudimage')
|
|
31
|
+
.service('scaleflexCloudimage')
|
|
32
|
+
.countUpdate(ctx);
|
|
33
|
+
},
|
|
34
|
+
async updateMedia(ctx) {
|
|
35
|
+
ctx.body = await strapi
|
|
36
|
+
.plugin('scaleflex-cloudimage')
|
|
37
|
+
.service('scaleflexCloudimage')
|
|
38
|
+
.updateMedia(ctx);
|
|
39
|
+
},
|
|
40
|
+
});
|
package/server/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const register = require('./register');
|
|
4
|
+
const bootstrap = require('./bootstrap');
|
|
5
|
+
const destroy = require('./destroy');
|
|
6
|
+
const config = require('./config');
|
|
7
|
+
const contentTypes = require('./content-types');
|
|
8
|
+
const controllers = require('./controllers');
|
|
9
|
+
const routes = require('./routes');
|
|
10
|
+
const middlewares = require('./middlewares');
|
|
11
|
+
const policies = require('./policies');
|
|
12
|
+
const services = require('./services');
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
register,
|
|
16
|
+
bootstrap,
|
|
17
|
+
destroy,
|
|
18
|
+
config,
|
|
19
|
+
controllers,
|
|
20
|
+
routes,
|
|
21
|
+
services,
|
|
22
|
+
contentTypes,
|
|
23
|
+
policies,
|
|
24
|
+
middlewares,
|
|
25
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//@Todo: Think about how this can be done in a cluster of may servers. Maybe persist the plugin's middleware settings in DB, then load it into that memory object upon start
|
|
3
|
+
module.exports = ({ strapi }) => {
|
|
4
|
+
// const pluginMiddleware = {
|
|
5
|
+
// name: 'strapi::security',
|
|
6
|
+
// config: {
|
|
7
|
+
// contentSecurityPolicy: {
|
|
8
|
+
// useDefaults: true,
|
|
9
|
+
// directives: {
|
|
10
|
+
// 'connect-src': ["'self'", 'https:'],
|
|
11
|
+
// 'img-src': ["'self'", 'data:', 'blob:', 'scaleflex.cloudimg.io', 'assets.scaleflex.com', '*.cloudimg.io'],
|
|
12
|
+
// 'media-src': ["'self'", 'data:', 'blob:', 'scaleflex.cloudimg.io', 'assets.scaleflex.com', '*.cloudimg.io'],
|
|
13
|
+
// upgradeInsecureRequests: null,
|
|
14
|
+
// },
|
|
15
|
+
// },
|
|
16
|
+
// },
|
|
17
|
+
// };
|
|
18
|
+
|
|
19
|
+
// // Merge a `source` object to a `target` recursively
|
|
20
|
+
// const merge = (target, source) => {
|
|
21
|
+
// // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
|
22
|
+
// for (const key of Object.keys(source))
|
|
23
|
+
// {
|
|
24
|
+
// if (Array.isArray(source[key]))
|
|
25
|
+
// {
|
|
26
|
+
// Object.assign(source[key], [...new Set(target[key].concat(source[key]))]);
|
|
27
|
+
// }
|
|
28
|
+
|
|
29
|
+
// if (source[key] instanceof Object && !Array.isArray(source[key]))
|
|
30
|
+
// {
|
|
31
|
+
// Object.assign(source[key], merge(target[key], source[key]));
|
|
32
|
+
// }
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// // Join `target` and modified `source`
|
|
36
|
+
// Object.assign(target || {}, source);
|
|
37
|
+
|
|
38
|
+
// return target;
|
|
39
|
+
// };
|
|
40
|
+
|
|
41
|
+
// for (let index = 1; index < strapi.config.middlewares.length; index++)
|
|
42
|
+
// {
|
|
43
|
+
// let item = strapi.config.middlewares[index];
|
|
44
|
+
|
|
45
|
+
// if (typeof item === 'object' && item.name === 'strapi::security')
|
|
46
|
+
// {
|
|
47
|
+
// let mergedSettings = merge(item.config, pluginMiddleware.config);
|
|
48
|
+
// strapi.config.middlewares.splice(index, 1, {name: 'strapi::security', config: mergedSettings});
|
|
49
|
+
|
|
50
|
+
// break;
|
|
51
|
+
// }
|
|
52
|
+
// else if (typeof item === 'string' && item === 'strapi::security')
|
|
53
|
+
// {
|
|
54
|
+
// strapi.config.middlewares.splice(index, 1, pluginMiddleware);
|
|
55
|
+
|
|
56
|
+
// break;
|
|
57
|
+
// }
|
|
58
|
+
// else
|
|
59
|
+
// {
|
|
60
|
+
// strapi.config.middlewares.push(pluginMiddleware);
|
|
61
|
+
|
|
62
|
+
// break;
|
|
63
|
+
// }
|
|
64
|
+
// }
|
|
65
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module.exports = [
|
|
2
|
+
{
|
|
3
|
+
method: 'GET',
|
|
4
|
+
path: '/',
|
|
5
|
+
handler: 'scaleflexCloudimage.index',
|
|
6
|
+
config: {
|
|
7
|
+
policies: [],
|
|
8
|
+
auth: false,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
method: 'GET',
|
|
13
|
+
path: '/config',
|
|
14
|
+
handler: 'scaleflexCloudimage.getConfig',
|
|
15
|
+
config: {
|
|
16
|
+
policies: [],
|
|
17
|
+
auth: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
method: 'PUT',
|
|
22
|
+
path: '/update-config',
|
|
23
|
+
handler: 'scaleflexCloudimage.updateConfig',
|
|
24
|
+
config: {
|
|
25
|
+
policies: [],
|
|
26
|
+
auth: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
method: 'GET',
|
|
31
|
+
path: '/is-v7',
|
|
32
|
+
handler: 'scaleflexCloudimage.checkV7',
|
|
33
|
+
config: {
|
|
34
|
+
policies: [],
|
|
35
|
+
auth: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
method: 'GET',
|
|
40
|
+
path: '/count-update',
|
|
41
|
+
handler: 'scaleflexCloudimage.countUpdate',
|
|
42
|
+
config: {
|
|
43
|
+
policies: [],
|
|
44
|
+
auth: false,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
method: 'PUT',
|
|
49
|
+
path: '/update-media',
|
|
50
|
+
handler: 'scaleflexCloudimage.updateMedia',
|
|
51
|
+
config: {
|
|
52
|
+
policies: [],
|
|
53
|
+
auth: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fetch = require("node-fetch");
|
|
6
|
+
|
|
7
|
+
module.exports = ({ strapi }) => ({
|
|
8
|
+
getWelcomeMessage() {
|
|
9
|
+
return 'Thank you for using Scaleflex Filerobot';
|
|
10
|
+
},
|
|
11
|
+
getPluginStore() {
|
|
12
|
+
return strapi.store({
|
|
13
|
+
environment: strapi.config.environment,
|
|
14
|
+
type: 'plugin',
|
|
15
|
+
name: 'cloudimage',
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
async getConfig() {
|
|
19
|
+
const pluginStore = this.getPluginStore();
|
|
20
|
+
|
|
21
|
+
let config = {
|
|
22
|
+
domain: '',
|
|
23
|
+
isV7: ''
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const storedConfig = await pluginStore.get({key: 'options'})
|
|
27
|
+
if (storedConfig) {
|
|
28
|
+
config = storedConfig;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return config
|
|
32
|
+
},
|
|
33
|
+
async updateConfig(ctx) {
|
|
34
|
+
const pluginStore = this.getPluginStore();
|
|
35
|
+
|
|
36
|
+
await pluginStore.set({
|
|
37
|
+
key: 'options',
|
|
38
|
+
value: ctx.request.body
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const config = await pluginStore.get({key: 'options'});
|
|
42
|
+
|
|
43
|
+
// if (config.domain)
|
|
44
|
+
// {
|
|
45
|
+
// for (let index = 1; index < strapi.config.middlewares.length; index++)
|
|
46
|
+
// {
|
|
47
|
+
// let item = strapi.config.middlewares[index];
|
|
48
|
+
|
|
49
|
+
// if (typeof item === 'object' && item.name === 'strapi::security'
|
|
50
|
+
// && !item.config.contentSecurityPolicy.directives['img-src'].includes(config.domain))
|
|
51
|
+
// {
|
|
52
|
+
// strapi.config.middlewares[index].config.contentSecurityPolicy.directives['img-src'].push(config.domain);
|
|
53
|
+
// strapi.config.middlewares[index].config.contentSecurityPolicy.directives['media-src'].push(config.domain);
|
|
54
|
+
// }
|
|
55
|
+
// }
|
|
56
|
+
// }
|
|
57
|
+
|
|
58
|
+
return config;
|
|
59
|
+
},
|
|
60
|
+
async checkV7(ctx) {
|
|
61
|
+
let domain = ctx.request.query.domain;
|
|
62
|
+
|
|
63
|
+
let response = {};
|
|
64
|
+
let responseV7 = {};
|
|
65
|
+
|
|
66
|
+
try
|
|
67
|
+
{
|
|
68
|
+
response = await fetch(`https://${domain}/http://sample.li/blank.png`);
|
|
69
|
+
responseV7 = await fetch(`https://${domain}/v7/http://sample.li/blank.png`);
|
|
70
|
+
}
|
|
71
|
+
catch (error)
|
|
72
|
+
{
|
|
73
|
+
console.error(error);
|
|
74
|
+
|
|
75
|
+
return {domainExists: false};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let isValid = response.status === 200 && !response.headers.get('x-hexa-missingbehavior');
|
|
79
|
+
let isV7Valid = responseV7.status === 200 && !responseV7.headers.get('x-hexa-missingbehavior');
|
|
80
|
+
|
|
81
|
+
if (isValid && !isV7Valid)
|
|
82
|
+
{
|
|
83
|
+
return {isSuccess: true, domainExists: true, isV7: false};
|
|
84
|
+
}
|
|
85
|
+
else if (!isValid && isV7Valid)
|
|
86
|
+
{
|
|
87
|
+
return {isSuccess: true, domainExists: true, isV7: true};
|
|
88
|
+
}
|
|
89
|
+
else
|
|
90
|
+
{
|
|
91
|
+
return {isSuccess: false, domainExists: true};
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
async countUpdate(ctx) {
|
|
95
|
+
let pluginStore = this.getPluginStore();
|
|
96
|
+
let pluginConfig = await pluginStore.get({key: 'options'});
|
|
97
|
+
let domain = pluginConfig.domain;
|
|
98
|
+
|
|
99
|
+
let media = await strapi.entityService.findMany('plugin::upload.file', {
|
|
100
|
+
populate: {category: true},
|
|
101
|
+
filters: { // https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/filter.html#and
|
|
102
|
+
$and: [
|
|
103
|
+
{
|
|
104
|
+
url: { $notContains: domain, },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
mime: { $contains: 'image', },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
provider: { $notContains: 'filerobot', },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return media.length;
|
|
117
|
+
},
|
|
118
|
+
async updateMedia(ctx) {
|
|
119
|
+
let baseUrl = strapi.config.get('server.url');
|
|
120
|
+
let pluginStore = this.getPluginStore();
|
|
121
|
+
let pluginConfig = await pluginStore.get({key: 'options'});
|
|
122
|
+
let domain = pluginConfig.domain;
|
|
123
|
+
let isV7 = pluginConfig.isV7;
|
|
124
|
+
|
|
125
|
+
let media = await strapi.entityService.findMany('plugin::upload.file', {
|
|
126
|
+
populate: {category: true},
|
|
127
|
+
filters: {
|
|
128
|
+
$and: [
|
|
129
|
+
{
|
|
130
|
+
url: { $notContains: domain, },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
mime: { $contains: 'image', },
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
provider: { $notContains: 'filerobot', },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return Promise.all(media.map((item, index) => {
|
|
143
|
+
let prepUrl = '';
|
|
144
|
+
|
|
145
|
+
if (/^https?:\/\//.test(item.url))
|
|
146
|
+
{
|
|
147
|
+
prepUrl = item.url.replace(/^https?:\/\//, '');
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
{
|
|
151
|
+
prepUrl = `${baseUrl}${item.url}`.replace(/^https?:\/\//, '');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let ciUrl = `https://${pluginConfig.domain}${pluginConfig.isV7 ? '/v7' : ''}/${prepUrl}`;
|
|
155
|
+
|
|
156
|
+
return strapi.entityService.update('plugin::upload.file', item.id, {
|
|
157
|
+
data: {
|
|
158
|
+
url: ciUrl,
|
|
159
|
+
formats: null
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
.then(function(result) {
|
|
163
|
+
return {success: true, result: result};
|
|
164
|
+
})
|
|
165
|
+
.catch(function(error) {
|
|
166
|
+
return {success: false, error: error.message};
|
|
167
|
+
});
|
|
168
|
+
}))
|
|
169
|
+
.then(function(results) {
|
|
170
|
+
let successCount = 0;
|
|
171
|
+
|
|
172
|
+
results.forEach((result, index) => {
|
|
173
|
+
if (result.success)
|
|
174
|
+
{
|
|
175
|
+
successCount++;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return {response: `${successCount} / ${results.length}`};
|
|
180
|
+
})
|
|
181
|
+
.catch(function(error) {
|
|
182
|
+
console.dir(error.message);
|
|
183
|
+
|
|
184
|
+
return {response: false};
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
package/strapi-admin.js
ADDED
package/strapi-server.js
ADDED