ghost 4.22.1 → 4.23.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/.c8rc.json +24 -0
- package/.eslintrc.js +6 -0
- package/Gruntfile.js +1 -1
- package/content/public/README.md +3 -0
- package/core/boot.js +20 -12
- package/core/built/assets/{chunk.3.1148677ff3b78e5aeaee.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
- package/core/built/assets/{ghost-dark-684ad238e1a858c7cb5be6988de7c6f5.css → ghost-dark-42cf6e0c730578940ec069bda45aea41.css} +1 -1
- package/core/built/assets/{ghost.min-f7037eca328f4d4eb99f0309c19c9bae.js → ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js} +189 -143
- package/core/built/assets/{ghost.min-66e08535f8bb797a8c40e0a2b31f1e9e.css → ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css} +1 -1
- package/core/built/assets/icons/powered-by-tenor.svg +35 -0
- package/core/built/assets/icons/tenor.svg +7 -0
- package/core/built/assets/{vendor.min-7c8fdd90f7ecd2e94328a07ea3b64608.js → vendor.min-c9002845b6c30ac978abdadde9f33d7c.js} +8189 -7601
- package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/services/card-assets/service.js +21 -13
- package/core/frontend/services/routing/CollectionRouter.js +4 -5
- package/core/frontend/services/routing/EmailRouter.js +1 -1
- package/core/frontend/services/routing/ParentRouter.js +0 -8
- package/core/frontend/services/routing/PreviewRouter.js +1 -1
- package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
- package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
- package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
- package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
- package/core/frontend/services/routing/router-manager.js +7 -2
- package/core/frontend/services/rss/generate-feed.js +2 -1
- package/core/frontend/src/cards/css/bookmark.css +66 -48
- package/core/frontend/src/cards/css/button.css +30 -0
- package/core/frontend/src/cards/css/callout.css +50 -0
- package/core/frontend/src/cards/css/gallery.css +8 -13
- package/core/frontend/src/cards/css/nft.css +94 -0
- package/core/frontend/src/cards/css/toggle.css +47 -0
- package/core/frontend/src/cards/js/toggle.js +16 -0
- package/core/frontend/web/middleware/serve-public-file.js +14 -8
- package/core/frontend/web/routes.js +0 -1
- package/core/frontend/web/site.js +15 -12
- package/core/server/adapters/storage/LocalFilesStorage.js +17 -0
- package/core/server/adapters/storage/LocalImagesStorage.js +1 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +2 -1
- package/core/server/adapters/storage/LocalStorageBase.js +30 -5
- package/core/server/api/canary/authentication.js +1 -1
- package/core/server/api/canary/files.js +19 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/media.js +25 -5
- package/core/server/api/canary/oembed.js +3 -0
- package/core/server/api/canary/utils/serializers/input/index.js +4 -0
- package/core/server/api/canary/utils/serializers/input/media.js +8 -0
- package/core/server/api/canary/utils/serializers/output/config.js +21 -14
- package/core/server/api/canary/utils/serializers/output/files.js +27 -0
- package/core/server/api/canary/utils/serializers/output/index.js +4 -0
- package/core/server/api/canary/utils/serializers/output/media.js +9 -0
- package/core/server/api/canary/utils/validators/input/files.js +7 -0
- package/core/server/api/canary/utils/validators/input/index.js +4 -0
- package/core/server/api/canary/utils/validators/input/media.js +4 -0
- package/core/server/api/v2/authentication.js +1 -1
- package/core/server/api/v3/authentication.js +1 -1
- package/core/server/data/db/connection.js +7 -0
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
- package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
- package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
- package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
- package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
- package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
- package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
- package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
- package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
- package/core/server/data/schema/fixtures/index.js +8 -2
- package/core/server/services/mega/post-email-serializer.js +5 -1
- package/core/server/services/mega/segment-parser.js +1 -2
- package/core/server/services/mega/template.js +69 -1
- package/core/server/services/nft-oembed.js +57 -0
- package/core/server/services/oembed.js +161 -126
- package/core/server/services/public-config/config.js +2 -1
- package/core/server/services/stripe/index.js +4 -2
- package/core/server/services/url/Resource.js +1 -1
- package/core/server/services/url/Resources.js +36 -23
- package/core/server/services/url/UrlGenerator.js +23 -20
- package/core/server/services/url/UrlService.js +123 -21
- package/core/server/services/url/Urls.js +7 -2
- package/core/server/services/url/index.js +9 -1
- package/core/server/web/admin/app.js +6 -6
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +1 -1
- package/core/server/web/api/canary/admin/app.js +4 -4
- package/core/server/web/api/canary/admin/middleware.js +6 -6
- package/core/server/web/api/canary/admin/routes.js +20 -5
- package/core/server/web/api/canary/content/app.js +4 -4
- package/core/server/web/api/canary/content/middleware.js +3 -3
- package/core/server/web/api/middleware/cors.js +7 -7
- package/core/server/web/api/v2/admin/app.js +4 -4
- package/core/server/web/api/v2/admin/middleware.js +6 -6
- package/core/server/web/api/v2/admin/routes.js +5 -5
- package/core/server/web/api/v2/content/app.js +4 -4
- package/core/server/web/api/v2/content/middleware.js +3 -3
- package/core/server/web/api/v3/admin/app.js +4 -4
- package/core/server/web/api/v3/admin/middleware.js +6 -6
- package/core/server/web/api/v3/admin/routes.js +5 -5
- package/core/server/web/api/v3/content/app.js +4 -4
- package/core/server/web/api/v3/content/middleware.js +3 -3
- package/core/server/web/members/app.js +7 -7
- package/core/server/web/oauth/app.js +1 -1
- package/core/server/web/parent/app.js +2 -3
- package/core/server/web/parent/frontend.js +1 -1
- package/core/server/web/shared/index.js +2 -2
- package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/error-handler.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/maintenance.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
- package/core/shared/config/defaults.json +10 -2
- package/core/shared/config/helpers.js +44 -0
- package/core/shared/config/loader.js +1 -1
- package/core/shared/config/overrides.json +2 -2
- package/core/shared/labs.js +8 -1
- package/loggingrc.js +19 -20
- package/package.json +35 -35
- package/urls.json +597 -0
- package/yarn.lock +655 -339
- package/core/server/data/schema/fixtures/utils.js +0 -321
- package/core/server/web/parent/vhost-utils.js +0 -39
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
.kg-nft-card {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.kg-nft-card-container {
|
|
9
|
+
position: static;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex: auto;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont,
|
|
15
|
+
'avenir next', avenir,
|
|
16
|
+
'helvetica neue', helvetica,
|
|
17
|
+
ubuntu,
|
|
18
|
+
roboto, noto,
|
|
19
|
+
'segoe ui', arial,
|
|
20
|
+
sans-serif;
|
|
21
|
+
font-size: 1.4rem;
|
|
22
|
+
font-weight: 400;
|
|
23
|
+
box-shadow: 0 2px 6px -2px rgb(0 0 0 / 10%), 0 0 1px rgb(0 0 0 / 40%);
|
|
24
|
+
width: 100%;
|
|
25
|
+
max-width: 512px;
|
|
26
|
+
color: #222;
|
|
27
|
+
background: #fff;
|
|
28
|
+
border-radius: 5px;
|
|
29
|
+
transition: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.kg-nft-card:hover {
|
|
33
|
+
color: #333;
|
|
34
|
+
opacity: 1.0;
|
|
35
|
+
transition: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.kg-nft-card * {
|
|
39
|
+
position: static;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.kg-nft-metadata {
|
|
43
|
+
padding: 2.0rem;
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.kg-nft-image {
|
|
48
|
+
border-radius: 5px 5px 0 0;
|
|
49
|
+
width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.kg-nft-header {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
align-items: flex-start;
|
|
56
|
+
gap: 20px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.kg-nft-header h4.kg-nft-title {
|
|
60
|
+
font-family: inherit;
|
|
61
|
+
font-size: 1.9rem;
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
line-height: 1.3em;
|
|
64
|
+
min-width: unset;
|
|
65
|
+
max-width: unset;
|
|
66
|
+
margin: 0;
|
|
67
|
+
color: #222;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.kg-nft-opensea-logo {
|
|
71
|
+
margin-top: 2px;
|
|
72
|
+
width: 100px;
|
|
73
|
+
object-fit: scale-down;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.kg-nft-creator {
|
|
77
|
+
font-family: inherit;
|
|
78
|
+
line-height: 1.4em;
|
|
79
|
+
margin: 0.4rem 0 0;
|
|
80
|
+
color: #ababab;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.kg-nft-creator span {
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
color: #222;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.kg-nft-card p.kg-nft-description {
|
|
89
|
+
font-family: inherit;
|
|
90
|
+
font-size: 1.4rem;
|
|
91
|
+
line-height: 1.4em;
|
|
92
|
+
margin: 2.0rem 0 0;
|
|
93
|
+
color: #222;
|
|
94
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.kg-toggle-card[data-kg-toggle-state="close"] .kg-toggle-content{
|
|
2
|
+
visibility: hidden;
|
|
3
|
+
opacity: 0;
|
|
4
|
+
height: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.kg-toggle-card[data-kg-toggle-state="close"] svg {
|
|
9
|
+
transform: unset;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.kg-toggle-card {
|
|
13
|
+
border: 1px solid rgba(127, 127, 127, 0.15);
|
|
14
|
+
border-radius: 4px;
|
|
15
|
+
padding: 20px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.kg-toggle-heading {
|
|
19
|
+
font-size: 2rem;
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.kg-toggle-card-icon {
|
|
28
|
+
height: 24px;
|
|
29
|
+
width: 24px;
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
align-items: center;
|
|
33
|
+
margin-left: 16px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.kg-toggle-heading svg {
|
|
37
|
+
width: 14px;
|
|
38
|
+
color: rgba(127, 127, 127, 0.4);
|
|
39
|
+
transition: transform 0.3s;
|
|
40
|
+
transform: rotate(180deg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.kg-toggle-content {
|
|
44
|
+
display: flex;
|
|
45
|
+
transition: opacity 0.3s;
|
|
46
|
+
padding-top: 8px;
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const toggleHeadingElements = document.getElementsByClassName("kg-toggle-heading");
|
|
2
|
+
|
|
3
|
+
const toggleFn = function(event) {
|
|
4
|
+
const targetElement = event.target;
|
|
5
|
+
const parentElement = targetElement.closest('.kg-toggle-card');
|
|
6
|
+
var toggleState = parentElement.getAttribute("data-kg-toggle-state");
|
|
7
|
+
if (toggleState === 'close') {
|
|
8
|
+
parentElement.setAttribute('data-kg-toggle-state', 'open');
|
|
9
|
+
} else {
|
|
10
|
+
parentElement.setAttribute('data-kg-toggle-state', 'close');
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < toggleHeadingElements.length; i++) {
|
|
15
|
+
toggleHeadingElements[i].addEventListener('click', toggleFn, false);
|
|
16
|
+
}
|
|
@@ -11,10 +11,16 @@ const messages = {
|
|
|
11
11
|
fileNotFound: 'File not found'
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
function createPublicFileMiddleware(file,
|
|
14
|
+
function createPublicFileMiddleware(location, file, mime, maxAge) {
|
|
15
15
|
let content;
|
|
16
|
-
|
|
17
|
-
const
|
|
16
|
+
// These files are provided by Ghost, and therefore live inside of the core folder
|
|
17
|
+
const staticFilePath = config.get('paths').publicFilePath;
|
|
18
|
+
// These files are built on the fly, and must be saved in the content folder
|
|
19
|
+
const builtFilePath = config.getContentPath('public');
|
|
20
|
+
|
|
21
|
+
let locationPath = location === 'static' ? staticFilePath : builtFilePath;
|
|
22
|
+
|
|
23
|
+
const filePath = file.match(/^public/) ? path.join(locationPath, file.replace(/^public/, '')) : path.join(locationPath, file);
|
|
18
24
|
const blogRegex = /(\{\{blog-url\}\})/g;
|
|
19
25
|
|
|
20
26
|
return function servePublicFileMiddleware(req, res, next) {
|
|
@@ -24,7 +30,7 @@ function createPublicFileMiddleware(file, type, maxAge) {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
// send image files directly and let express handle content-length, etag, etc
|
|
27
|
-
if (
|
|
33
|
+
if (mime.match(/^image/)) {
|
|
28
34
|
return res.sendFile(filePath, (err) => {
|
|
29
35
|
if (err && err.status === 404) {
|
|
30
36
|
// ensure we're triggering basic asset 404 and not a templated 404
|
|
@@ -57,13 +63,13 @@ function createPublicFileMiddleware(file, type, maxAge) {
|
|
|
57
63
|
|
|
58
64
|
let str = buf.toString();
|
|
59
65
|
|
|
60
|
-
if (
|
|
66
|
+
if (mime === 'text/xsl' || mime === 'text/plain' || mime === 'application/javascript') {
|
|
61
67
|
str = str.replace(blogRegex, urlUtils.urlFor('home', true).replace(/\/$/, ''));
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
content = {
|
|
65
71
|
headers: {
|
|
66
|
-
'Content-Type':
|
|
72
|
+
'Content-Type': mime,
|
|
67
73
|
'Content-Length': Buffer.from(str).length,
|
|
68
74
|
ETag: `"${crypto.createHash('md5').update(str, 'utf8').digest('hex')}"`,
|
|
69
75
|
'Cache-Control': `public, max-age=${maxAge}`
|
|
@@ -78,8 +84,8 @@ function createPublicFileMiddleware(file, type, maxAge) {
|
|
|
78
84
|
|
|
79
85
|
// ### servePublicFile Middleware
|
|
80
86
|
// Handles requests to robots.txt and favicon.ico (and caches them)
|
|
81
|
-
function servePublicFile(file, type, maxAge) {
|
|
82
|
-
const publicFileMiddleware = createPublicFileMiddleware(file, type, maxAge);
|
|
87
|
+
function servePublicFile(location, file, type, maxAge) {
|
|
88
|
+
const publicFileMiddleware = createPublicFileMiddleware(location, file, type, maxAge);
|
|
83
89
|
|
|
84
90
|
return function servePublicFileMiddleware(req, res, next) {
|
|
85
91
|
if (req.path === '/' + file) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const debug = require('@tryghost/debug')('routing');
|
|
2
2
|
|
|
3
3
|
const routing = require('../services/routing');
|
|
4
|
-
// NOTE: temporary import from the frontend, will become a backend service soon
|
|
5
4
|
const urlService = require('../../server/services/url');
|
|
6
5
|
const routeSettings = require('../../server/services/route-settings');
|
|
7
6
|
|
|
@@ -25,6 +25,7 @@ const labs = require('../../shared/labs');
|
|
|
25
25
|
|
|
26
26
|
const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
|
|
27
27
|
const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
|
|
28
|
+
const STATIC_FILES_URL_PREFIX = `/${constants.STATIC_FILES_URL_PREFIX}`;
|
|
28
29
|
|
|
29
30
|
let router;
|
|
30
31
|
|
|
@@ -103,20 +104,22 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
103
104
|
siteApp.use(mw.serveFavicon());
|
|
104
105
|
|
|
105
106
|
// Serve sitemap.xsl file
|
|
106
|
-
siteApp.use(mw.servePublicFile('sitemap.xsl', 'text/xsl', constants.ONE_DAY_S));
|
|
107
|
+
siteApp.use(mw.servePublicFile('static', 'sitemap.xsl', 'text/xsl', constants.ONE_DAY_S));
|
|
107
108
|
|
|
108
109
|
// Serve stylesheets for default templates
|
|
109
|
-
siteApp.use(mw.servePublicFile('public/ghost.css', 'text/css', constants.ONE_HOUR_S));
|
|
110
|
-
siteApp.use(mw.servePublicFile('public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));
|
|
110
|
+
siteApp.use(mw.servePublicFile('static', 'public/ghost.css', 'text/css', constants.ONE_HOUR_S));
|
|
111
|
+
siteApp.use(mw.servePublicFile('static', 'public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));
|
|
111
112
|
|
|
112
113
|
// Card assets
|
|
113
|
-
siteApp.use(mw.servePublicFile('public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
|
|
114
|
-
siteApp.use(mw.servePublicFile('public/cards.min.js', 'text/js', constants.ONE_YEAR_S));
|
|
114
|
+
siteApp.use(mw.servePublicFile('built', 'public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
|
|
115
|
+
siteApp.use(mw.servePublicFile('built', 'public/cards.min.js', 'text/js', constants.ONE_YEAR_S));
|
|
115
116
|
|
|
116
117
|
// Serve blog images using the storage adapter
|
|
117
118
|
siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
|
|
118
119
|
// Serve blog media using the storage adapter
|
|
119
120
|
siteApp.use(STATIC_MEDIA_URL_PREFIX, labs.enabledMiddleware('mediaAPI'), storage.getStorage('media').serve());
|
|
121
|
+
// Serve blog files using the storage adapter
|
|
122
|
+
siteApp.use(STATIC_FILES_URL_PREFIX, labs.enabledMiddleware('filesAPI'), storage.getStorage('files').serve());
|
|
120
123
|
|
|
121
124
|
// Global handling for member session, ensures a member is logged in to the frontend
|
|
122
125
|
siteApp.use(membersService.middleware.loadMemberSession);
|
|
@@ -144,26 +147,26 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
144
147
|
debug('Themes done');
|
|
145
148
|
|
|
146
149
|
// Serve robots.txt if not found in theme
|
|
147
|
-
siteApp.use(mw.servePublicFile('robots.txt', 'text/plain', constants.ONE_HOUR_S));
|
|
150
|
+
siteApp.use(mw.servePublicFile('static', 'robots.txt', 'text/plain', constants.ONE_HOUR_S));
|
|
148
151
|
|
|
149
152
|
// site map - this should probably be refactored to be an internal app
|
|
150
153
|
sitemapHandler(siteApp);
|
|
151
154
|
debug('Internal apps done');
|
|
152
155
|
|
|
153
156
|
// send 503 error page in case of maintenance
|
|
154
|
-
siteApp.use(shared.
|
|
157
|
+
siteApp.use(shared.middleware.maintenance);
|
|
155
158
|
|
|
156
159
|
// Add in all trailing slashes & remove uppercase
|
|
157
160
|
// must happen AFTER asset loading and BEFORE routing
|
|
158
|
-
siteApp.use(shared.
|
|
161
|
+
siteApp.use(shared.middleware.prettyUrls);
|
|
159
162
|
|
|
160
163
|
// ### Caching
|
|
161
164
|
siteApp.use(function (req, res, next) {
|
|
162
165
|
// Site frontend is cacheable UNLESS request made by a member or blog is in private mode
|
|
163
166
|
if (req.member || res.isPrivateBlog) {
|
|
164
|
-
return shared.
|
|
167
|
+
return shared.middleware.cacheControl('private')(req, res, next);
|
|
165
168
|
} else {
|
|
166
|
-
return shared.
|
|
169
|
+
return shared.middleware.cacheControl('public', {maxAge: config.get('caching:frontend:maxAge')})(req, res, next);
|
|
167
170
|
}
|
|
168
171
|
});
|
|
169
172
|
|
|
@@ -176,7 +179,7 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
176
179
|
siteApp.use(SiteRouter);
|
|
177
180
|
|
|
178
181
|
// ### Error handlers
|
|
179
|
-
siteApp.use(shared.
|
|
182
|
+
siteApp.use(shared.middleware.errorHandler.pageNotFound);
|
|
180
183
|
config.get('apps:internal').forEach((appName) => {
|
|
181
184
|
const app = require(path.join(config.get('paths').internalAppPath, appName));
|
|
182
185
|
|
|
@@ -184,7 +187,7 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
184
187
|
app.setupErrorHandling(siteApp);
|
|
185
188
|
}
|
|
186
189
|
});
|
|
187
|
-
siteApp.use(shared.
|
|
190
|
+
siteApp.use(shared.middleware.errorHandler.handleThemeResponse);
|
|
188
191
|
|
|
189
192
|
debug('Site setup end');
|
|
190
193
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// # Local File System Storage module
|
|
2
|
+
// The (default) module for storing media, using the local file system
|
|
3
|
+
const config = require('../../../shared/config');
|
|
4
|
+
const constants = require('@tryghost/constants');
|
|
5
|
+
const LocalStorageBase = require('./LocalStorageBase');
|
|
6
|
+
|
|
7
|
+
class LocalFilesStorage extends LocalStorageBase {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({
|
|
10
|
+
storagePath: config.getContentPath('files'),
|
|
11
|
+
siteUrl: config.getSiteUrl(),
|
|
12
|
+
staticFileURLPrefix: constants.STATIC_FILES_URL_PREFIX
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = LocalFilesStorage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// # Local File System
|
|
1
|
+
// # Local File System Media Storage module
|
|
2
2
|
// The (default) module for storing media, using the local file system
|
|
3
3
|
const config = require('../../../shared/config');
|
|
4
4
|
const constants = require('@tryghost/constants');
|
|
@@ -15,6 +15,7 @@ class LocalMediaStore extends LocalStorageBase {
|
|
|
15
15
|
super({
|
|
16
16
|
storagePath: config.getContentPath('media'),
|
|
17
17
|
staticFileURLPrefix: constants.STATIC_MEDIA_URL_PREFIX,
|
|
18
|
+
siteUrl: config.getSiteUrl(),
|
|
18
19
|
errorMessages: messages
|
|
19
20
|
});
|
|
20
21
|
}
|
|
@@ -16,7 +16,8 @@ const StorageBase = require('ghost-storage-base');
|
|
|
16
16
|
const messages = {
|
|
17
17
|
notFound: 'File not found',
|
|
18
18
|
notFoundWithRef: 'File not found: {file}',
|
|
19
|
-
cannotRead: 'Could not read file: {file}'
|
|
19
|
+
cannotRead: 'Could not read file: {file}',
|
|
20
|
+
invalidUrlParameter: `The URL "{url}" is not a valid URL for this site.`
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
class LocalStorageBase extends StorageBase {
|
|
@@ -24,17 +25,20 @@ class LocalStorageBase extends StorageBase {
|
|
|
24
25
|
*
|
|
25
26
|
* @param {Object} options
|
|
26
27
|
* @param {String} options.storagePath
|
|
28
|
+
* @param {String} options.siteUrl
|
|
27
29
|
* @param {String} [options.staticFileURLPrefix]
|
|
28
30
|
* @param {Object} [options.errorMessages]
|
|
29
31
|
* @param {String} [options.errorMessages.notFound]
|
|
30
32
|
* @param {String} [options.errorMessages.notFoundWithRef]
|
|
31
33
|
* @param {String} [options.errorMessages.cannotRead]
|
|
32
34
|
*/
|
|
33
|
-
constructor({storagePath, staticFileURLPrefix, errorMessages}) {
|
|
35
|
+
constructor({storagePath, staticFileURLPrefix, siteUrl, errorMessages}) {
|
|
34
36
|
super();
|
|
35
37
|
|
|
36
38
|
this.storagePath = storagePath;
|
|
37
39
|
this.staticFileURLPrefix = staticFileURLPrefix;
|
|
40
|
+
this.siteUrl = siteUrl;
|
|
41
|
+
this.staticFileUrl = `${siteUrl}${staticFileURLPrefix}`;
|
|
38
42
|
this.errorMessages = errorMessages || messages;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -71,6 +75,26 @@ class LocalStorageBase extends StorageBase {
|
|
|
71
75
|
return fullUrl;
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
* @param {String} url full url under which the stored content is served, result of save method
|
|
81
|
+
* @returns {String} path under which the content is stored
|
|
82
|
+
*/
|
|
83
|
+
urlToPath(url) {
|
|
84
|
+
let filePath;
|
|
85
|
+
|
|
86
|
+
if (url.match(this.staticFileUrl)) {
|
|
87
|
+
filePath = url.replace(this.staticFileUrl, '');
|
|
88
|
+
filePath = path.join(this.storagePath, filePath);
|
|
89
|
+
} else {
|
|
90
|
+
throw new errors.IncorrectUsageError({
|
|
91
|
+
message: tpl(messages.invalidUrlParameter, {url})
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return filePath;
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
exists(fileName, targetDir) {
|
|
75
99
|
const filePath = path.join(targetDir || this.storagePath, fileName);
|
|
76
100
|
|
|
@@ -132,11 +156,12 @@ class LocalStorageBase extends StorageBase {
|
|
|
132
156
|
}
|
|
133
157
|
|
|
134
158
|
/**
|
|
135
|
-
*
|
|
159
|
+
* @param {String} filePath
|
|
136
160
|
* @returns {Promise.<*>}
|
|
137
161
|
*/
|
|
138
|
-
delete() {
|
|
139
|
-
|
|
162
|
+
async delete(fileName, targetDir) {
|
|
163
|
+
const filePath = path.join(targetDir, fileName);
|
|
164
|
+
return await fs.remove(filePath);
|
|
140
165
|
}
|
|
141
166
|
|
|
142
167
|
/**
|
|
@@ -163,7 +163,7 @@ module.exports = {
|
|
|
163
163
|
options = Object.assign(options, {context: {internal: true}});
|
|
164
164
|
return auth.passwordreset.doReset(options, tokenParts, api.settings)
|
|
165
165
|
.then((params) => {
|
|
166
|
-
web.shared.
|
|
166
|
+
web.shared.middleware.api.spamPrevention.userLogin().reset(frame.options.ip, `${tokenParts.email}login`);
|
|
167
167
|
return params;
|
|
168
168
|
});
|
|
169
169
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const storage = require('../../adapters/storage');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
docName: 'files',
|
|
5
|
+
upload: {
|
|
6
|
+
statusCode: 201,
|
|
7
|
+
permissions: false,
|
|
8
|
+
async query(frame) {
|
|
9
|
+
const filePath = await storage.getStorage('files').save({
|
|
10
|
+
name: frame.file.originalname,
|
|
11
|
+
path: frame.file.path
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
filePath
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -109,6 +109,10 @@ module.exports = {
|
|
|
109
109
|
return shared.pipeline(require('./media'), localUtils);
|
|
110
110
|
},
|
|
111
111
|
|
|
112
|
+
get files() {
|
|
113
|
+
return shared.pipeline(require('./files'), localUtils);
|
|
114
|
+
},
|
|
115
|
+
|
|
112
116
|
get tags() {
|
|
113
117
|
return shared.pipeline(require('./tags'), localUtils);
|
|
114
118
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const storage = require('../../adapters/storage');
|
|
2
3
|
|
|
3
4
|
module.exports = {
|
|
@@ -6,17 +7,36 @@ module.exports = {
|
|
|
6
7
|
statusCode: 201,
|
|
7
8
|
permissions: false,
|
|
8
9
|
async query(frame) {
|
|
9
|
-
let
|
|
10
|
+
let thumbnailPath = null;
|
|
10
11
|
if (frame.files.thumbnail && frame.files.thumbnail[0]) {
|
|
11
|
-
|
|
12
|
+
thumbnailPath = await storage.getStorage('media').save(frame.files.thumbnail[0]);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const filePath = await storage.getStorage('media').save(frame.files.file[0]);
|
|
15
16
|
|
|
16
17
|
return {
|
|
17
|
-
filePath
|
|
18
|
-
thumbnailPath
|
|
18
|
+
filePath,
|
|
19
|
+
thumbnailPath
|
|
19
20
|
};
|
|
20
21
|
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
uploadThumbnail: {
|
|
25
|
+
permissions: false,
|
|
26
|
+
options: [
|
|
27
|
+
'url'
|
|
28
|
+
],
|
|
29
|
+
async query(frame) {
|
|
30
|
+
const mediaStorage = storage.getStorage('media');
|
|
31
|
+
const targetDir = path.dirname(mediaStorage.urlToPath(frame.data.url));
|
|
32
|
+
|
|
33
|
+
// NOTE: need to cleanup otherwise the parent media name won't match thumb name
|
|
34
|
+
// due to "unique name" generation during save
|
|
35
|
+
if (mediaStorage.exists(frame.file.name, targetDir)) {
|
|
36
|
+
await mediaStorage.delete(frame.file.name, targetDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return await mediaStorage.save(frame.file, targetDir);
|
|
40
|
+
}
|
|
21
41
|
}
|
|
22
42
|
};
|
|
@@ -3,6 +3,9 @@ const externalRequest = require('../../lib/request-external');
|
|
|
3
3
|
|
|
4
4
|
const OEmbed = require('../../services/oembed');
|
|
5
5
|
const oembed = new OEmbed({config, externalRequest});
|
|
6
|
+
const NFT = require('../../services/nft-oembed');
|
|
7
|
+
const nft = new NFT();
|
|
8
|
+
oembed.registerProvider(nft);
|
|
6
9
|
|
|
7
10
|
module.exports = {
|
|
8
11
|
docName: 'oembed',
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
+
const labs = require('../../../../../../shared/labs');
|
|
2
3
|
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:config');
|
|
3
4
|
|
|
4
5
|
module.exports = {
|
|
5
6
|
all(data, apiConfig, frame) {
|
|
6
7
|
debug('all');
|
|
7
8
|
|
|
9
|
+
const keys = [
|
|
10
|
+
'version',
|
|
11
|
+
'environment',
|
|
12
|
+
'database',
|
|
13
|
+
'mail',
|
|
14
|
+
'useGravatar',
|
|
15
|
+
'labs',
|
|
16
|
+
'clientExtensions',
|
|
17
|
+
'enableDeveloperExperiments',
|
|
18
|
+
'stripeDirect',
|
|
19
|
+
'mailgunIsConfigured',
|
|
20
|
+
'emailAnalytics',
|
|
21
|
+
'hostSettings'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
if (labs.isSet('gifsCard')) {
|
|
25
|
+
keys.push('tenor');
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
frame.response = {
|
|
9
|
-
config: _.pick(data,
|
|
10
|
-
'version',
|
|
11
|
-
'environment',
|
|
12
|
-
'database',
|
|
13
|
-
'mail',
|
|
14
|
-
'useGravatar',
|
|
15
|
-
'labs',
|
|
16
|
-
'clientExtensions',
|
|
17
|
-
'enableDeveloperExperiments',
|
|
18
|
-
'stripeDirect',
|
|
19
|
-
'mailgunIsConfigured',
|
|
20
|
-
'emailAnalytics',
|
|
21
|
-
'hostSettings'
|
|
22
|
-
])
|
|
29
|
+
config: _.pick(data, keys)
|
|
23
30
|
};
|
|
24
31
|
}
|
|
25
32
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const config = require('../../../../../../shared/config');
|
|
2
|
+
const {STATIC_FILES_URL_PREFIX} = require('@tryghost/constants');
|
|
3
|
+
|
|
4
|
+
function getURL(urlPath) {
|
|
5
|
+
const media = new RegExp('^' + config.getSubdir() + '/' + STATIC_FILES_URL_PREFIX);
|
|
6
|
+
const absolute = media.test(urlPath) ? true : false;
|
|
7
|
+
|
|
8
|
+
if (absolute) {
|
|
9
|
+
// Remove the sub-directory from the URL because ghostConfig will add it back.
|
|
10
|
+
urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
|
|
11
|
+
const baseUrl = config.getSiteUrl().replace(/\/$/, '');
|
|
12
|
+
urlPath = baseUrl + urlPath;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return urlPath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
upload({filePath}, apiConfig, frame) {
|
|
20
|
+
return frame.response = {
|
|
21
|
+
files: [{
|
|
22
|
+
url: getURL(filePath),
|
|
23
|
+
ref: frame.data.ref || null
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|