diff --git a/apps/admin/src/routes.tsx b/apps/admin/src/routes.tsx
index d92719dd621..38c398414b6 100644
--- a/apps/admin/src/routes.tsx
+++ b/apps/admin/src/routes.tsx
@@ -36,7 +36,6 @@ const EMBER_ROUTES: string[] = [
"/posts/analytics/:postId/debug",
"/restore",
"/editor/*",
- "/explore/*",
"/migrate/*",
"/members-activity",
];
diff --git a/apps/ember-admin/app/components/gh-explore-iframe.hbs b/apps/ember-admin/app/components/gh-explore-iframe.hbs
deleted file mode 100644
index 25f3309c64b..00000000000
--- a/apps/ember-admin/app/components/gh-explore-iframe.hbs
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/ember-admin/app/components/gh-explore-iframe.js b/apps/ember-admin/app/components/gh-explore-iframe.js
deleted file mode 100644
index eec811159a7..00000000000
--- a/apps/ember-admin/app/components/gh-explore-iframe.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import Component from '@glimmer/component';
-import {action} from '@ember/object';
-import {inject as service} from '@ember/service';
-
-export default class GhExploreIframe extends Component {
- @service explore;
- @service router;
- @service feature;
-
- constructor() {
- super(...arguments);
- window.addEventListener('message', this.handleIframeMessage);
- }
-
- willDestroy() {
- super.willDestroy(...arguments);
- window.removeEventListener('message', this.handleIframeMessage);
- }
-
- @action
- setup() {
- // Only begin setup when Explore window is toggled open
- // to avoid unnecessary loading of assets
- if (this.explore.exploreWindowOpen) {
- this.explore.getExploreIframe().src = this.explore.iframeURL;
- }
- }
-
- @action
- async handleIframeMessage(event) {
- if (this.isDestroyed || this.isDestroying) {
- return;
- }
-
- // only process messages coming from the explore iframe
- if (event?.data && this.explore.iframeURL.includes(event?.origin)) {
- if (event.data?.request === 'apiUrl') {
- this._handleUrlRequest();
- }
-
- if (event.data?.route) {
- this._handleRouteUpdate(event.data);
- }
-
- if (event.data?.siteData) {
- this._handleSiteDataUpdate(event.data);
- }
- }
- }
-
- @action
- async handleDarkModeChange() {
- if (this.explore.exploreWindowOpen) {
- this.explore.sendUIUpdate({darkMode: this.feature.nightShift});
- }
- }
-
- // The iframe can send route updates to navigate to within Admin, as some routes
- // have to be rendered within the iframe and others require to break out of it.
- _handleRouteUpdate(data) {
- const route = data.route;
- this.explore.isIframeTransition = route?.includes('/explore');
- this.explore.toggleExploreWindow(this.explore.isIframeTransition);
- this.router.transitionTo(route);
- }
-
- _handleUrlRequest() {
- this.explore.getExploreIframe().contentWindow.postMessage({
- request: 'apiUrl',
- response: {apiUrl: this.explore.apiUrl, darkMode: this.feature.nightShift}
- }, '*');
- }
-
- _handleSiteDataUpdate(data) {
- this.explore.siteData = data?.siteData ?? {};
- }
-}
diff --git a/apps/ember-admin/app/components/gh-explore-modal.hbs b/apps/ember-admin/app/components/gh-explore-modal.hbs
deleted file mode 100644
index e05dcbcfde9..00000000000
--- a/apps/ember-admin/app/components/gh-explore-modal.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/ember-admin/app/components/gh-explore-modal.js b/apps/ember-admin/app/components/gh-explore-modal.js
deleted file mode 100644
index 90b5bf04b6d..00000000000
--- a/apps/ember-admin/app/components/gh-explore-modal.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Component from '@glimmer/component';
-import {inject as service} from '@ember/service';
-
-export default class GhExploreModal extends Component {
- @service explore;
-
- get visibilityClass() {
- return this.explore.exploreWindowOpen ? 'gh-explore' : 'gh-explore closed';
- }
-}
diff --git a/apps/ember-admin/app/controllers/application.js b/apps/ember-admin/app/controllers/application.js
index 820b40f2a47..212cb4c908e 100644
--- a/apps/ember-admin/app/controllers/application.js
+++ b/apps/ember-admin/app/controllers/application.js
@@ -6,7 +6,6 @@ import {inject as service} from '@ember/service';
export default class ApplicationController extends Controller {
@service billing;
- @service explore;
@service router;
@service session;
@service settings;
diff --git a/apps/ember-admin/app/controllers/explore.js b/apps/ember-admin/app/controllers/explore.js
deleted file mode 100644
index 207b0e134d5..00000000000
--- a/apps/ember-admin/app/controllers/explore.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import Controller from '@ember/controller';
-import {action} from '@ember/object';
-import {inject} from 'ghost-admin/decorators/inject';
-import {inject as service} from '@ember/service';
-
-export default class ExploreController extends Controller {
- @inject config;
- @service explore;
- @service router;
-
- get exploreCredentials() {
- const explore = this.model.findBy('slug', 'ghost-explore');
- const adminKey = explore.adminKey;
-
- return adminKey.secret;
- }
-
- get visibilityClass() {
- return this.explore.isIframeTransition ? 'explore iframe-explore-container' : ' explore fullscreen-explore-container';
- }
-
- @action
- closeConnect() {
- if (this.explore.isIframeTransition) {
- this.explore.sendRouteUpdate({path: '/explore'});
- this.router.transitionTo('/explore');
- } else {
- this.router.transitionTo('/analytics');
- }
- }
-
- @action
- submitExploreSite() {
- const token = this.exploreCredentials;
- const apiUrl = this.explore.apiUrl;
-
- const query = new URLSearchParams();
-
- query.append('token', token);
- query.append('url', apiUrl);
-
- if (this.explore.isIframeTransition) {
- this.explore.sendRouteUpdate({path: this.explore.submitRoute, queryParams: query.toString()});
-
- // Set a short timeout to give Explore enough time to navigate
- // to the submit page and fetch the required site data
- setTimeout(() => {
- this.explore.toggleExploreWindow(true);
- }, 500);
- } else {
- // Ghost Explore URL to submit a new site
- const destination = new URL(`${this.explore.exploreUrl}${this.explore.submitRoute}`);
- destination.search = query;
-
- window.location = destination.toString();
- }
- }
-}
diff --git a/apps/ember-admin/app/router.js b/apps/ember-admin/app/router.js
index 25ff8234910..8db9966b0d2 100644
--- a/apps/ember-admin/app/router.js
+++ b/apps/ember-admin/app/router.js
@@ -37,18 +37,6 @@ Router.map(function () {
this.route('tag.new', {path: '/tags/new'});
this.route('tag', {path: '/tags/:tag_slug'});
- this.route('explore', function () {
- // actual Ember route, not rendered in iframe
- this.route('connect');
- // iframe sub pages, used for categories
- this.route('explore-sub', {path: '/*sub'}, function () {
- // needed to allow search to work, as it uses URL
- // params for search queries. They don't need to
- // be visible, but may not be cut off.
- this.route('explore-query', {path: '/*query'});
- });
- });
-
this.route('migrate', function () {
this.route('migrate', {path: '/*platform'});
});
diff --git a/apps/ember-admin/app/routes/explore.js b/apps/ember-admin/app/routes/explore.js
deleted file mode 100644
index 0b34b48e762..00000000000
--- a/apps/ember-admin/app/routes/explore.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
-import {inject as service} from '@ember/service';
-
-export default class ExploreRoute extends AuthenticatedRoute {
- @service store;
-
- model() {
- return this.store.findAll('integration');
- }
-}
diff --git a/apps/ember-admin/app/routes/explore/connect.js b/apps/ember-admin/app/routes/explore/connect.js
deleted file mode 100644
index 6ab296e8b3d..00000000000
--- a/apps/ember-admin/app/routes/explore/connect.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import ExploreRoute from './index';
-
-export default class ExploreConnectRoute extends ExploreRoute {
- controllerName = 'explore';
-
- // Ensure to always close the iframe, as we're now on an Ember route
- beforeModel() {
- this.explore.toggleExploreWindow(false);
- }
-}
diff --git a/apps/ember-admin/app/routes/explore/explore-sub.js b/apps/ember-admin/app/routes/explore/explore-sub.js
deleted file mode 100644
index f504100aabe..00000000000
--- a/apps/ember-admin/app/routes/explore/explore-sub.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import ExploreRoute from './index';
-
-export default class ExploreSubRoute extends ExploreRoute {
- controllerName = 'explore';
-}
diff --git a/apps/ember-admin/app/routes/explore/index.js b/apps/ember-admin/app/routes/explore/index.js
deleted file mode 100644
index 43a8e43135e..00000000000
--- a/apps/ember-admin/app/routes/explore/index.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
-import {action} from '@ember/object';
-import {inject as service} from '@ember/service';
-
-export default class ExploreIndexRoute extends AuthenticatedRoute {
- @service explore;
- @service store;
- @service router;
- @service feature;
-
- beforeModel(transition) {
- super.beforeModel(...arguments);
-
- // Usage of query param to ensure that sites can be submitted across
- // older versions of Ghost where the `connect` part lives in the
- // explore route directly. By using the query param, we avoid causing
- // a 404 and handle the redirect here.
- if (transition.to?.queryParams?.new === 'true') {
- this.explore.isIframeTransition = false;
- return this.router.transitionTo('explore.connect');
- }
-
- // Ensure the explore window is set to open
- if (transition.to?.localName === 'index') {
- this.explore.isIframeTransition = true;
- this.explore.openExploreWindow();
- }
- }
-
- model() {
- return this.store.findAll('integration');
- }
-
- @action
- willTransition(transition) {
- let isExploreTransition = false;
-
- if (transition) {
- let destinationUrl = (typeof transition.to === 'string')
- ? transition.to
- : (transition.intent
- ? transition.intent.url
- : '');
-
- if (destinationUrl?.includes('/explore')) {
- isExploreTransition = true;
- this.explore.isIframeTransition = isExploreTransition;
-
- if (destinationUrl?.includes('/explore/submit')) {
- // only show the submit page if the site is already submitted
- // and redirect to the connect page if not.
- if (Object.keys(this?.explore?.siteData).length >= 1) {
- this.controllerFor('explore').submitExploreSite();
- } else {
- transition.abort();
- return this.router.transitionTo('explore.connect');
- }
- } else {
- let path = destinationUrl.replace(/explore\//, '');
- path = path === '/' ? '/explore/' : path;
-
- if (destinationUrl?.includes('/explore/about')) {
- window.open(`${this.explore.exploreUrl}about/`, '_blank').focus();
- path = '/explore/';
- }
- // Send the updated route to the iframe
- this.explore.sendRouteUpdate({path});
- }
- }
- }
-
- this.explore.toggleExploreWindow(isExploreTransition);
- }
-
- buildRouteInfoMetadata() {
- return {
- titleToken: 'Explore'
- };
- }
-}
diff --git a/apps/ember-admin/app/services/explore.js b/apps/ember-admin/app/services/explore.js
deleted file mode 100644
index 5efaa2b4628..00000000000
--- a/apps/ember-admin/app/services/explore.js
+++ /dev/null
@@ -1,136 +0,0 @@
-import Service, {inject as service} from '@ember/service';
-import {inject} from 'ghost-admin/decorators/inject';
-import {tracked} from '@glimmer/tracking';
-
-export default class ExploreService extends Service {
- @service router;
- @service feature;
- @service ghostPaths;
-
- @inject config;
-
- exploreUrl = 'https://ghost.org/explore/';
- exploreRouteRoot = '#/explore';
- submitRoute = 'submit';
-
- @tracked exploreWindowOpen = false;
- @tracked siteData = null;
- @tracked isIframeTransition = false;
-
- get apiUrl() {
- const origin = new URL(window.location.origin);
- const subdir = this.ghostPaths.subdir;
- // We want the API URL without protocol
- let url = this.ghostPaths.url.join(origin.host, subdir);
-
- return url.replace(/\/$/, '');
- }
-
- get iframeURL() {
- let url = this.exploreUrl;
-
- if (window.location.hash && window.location.hash.includes(this.exploreRouteRoot)) {
- let destinationRoute = window.location.hash.replace(this.exploreRouteRoot, '');
-
- // Connect is an Ember route, do not use it as iframe src
- if (destinationRoute && !destinationRoute.includes('connect')) {
- url += destinationRoute.replace(/^\//, '');
- }
- }
-
- return url;
- }
-
- constructor() {
- super(...arguments);
-
- if (this.exploreUrl) {
- window.addEventListener('message', (event) => {
- if (event && event.data && event.data.route) {
- this.handleRouteChangeInIframe(event.data.route);
- }
- });
- }
- }
-
- handleRouteChangeInIframe(destinationRoute) {
- if (this.exploreWindowOpen) {
- let exploreRoute = this.exploreRouteRoot;
-
- if (destinationRoute.match(/^\/explore(\/.*)?/)) {
- destinationRoute = destinationRoute.replace(/\/explore/, '');
- }
-
- if (destinationRoute !== '/') {
- exploreRoute += destinationRoute;
- }
-
- if (window.location.hash !== exploreRoute) {
- window.history.replaceState(window.history.state, '', exploreRoute);
- }
- }
- }
-
- // Sends a route update to a child route in the BMA, because we can't control
- // navigating to it otherwise
- sendRouteUpdate(route) {
- this.getExploreIframe().contentWindow.postMessage({
- query: 'routeUpdate',
- response: route
- }, '*');
- }
-
- sendUIUpdate(data) {
- this.getExploreIframe().contentWindow.postMessage({
- query: 'uiUpdate',
- response: data
- }, '*');
- }
-
- // Controls explore window modal visibility and sync of the URL visible in browser
- // and the URL opened on the iframe. It is responsible to non user triggered iframe opening,
- // for example: by entering "/explore" route in the URL or using history navigation (back and forward)
- toggleExploreWindow(value) {
- if (this.config.hostSettings?.forceUpgrade && value) {
- // don't attempt to open Explore iframe when in Force Upgrade state
- return;
- }
-
- if (this.exploreWindowOpen && value) {
- // don't attempt to open again
- return;
- }
- this.exploreWindowOpen = value;
- }
-
- openExploreWindow() {
- if (this.config.hostSettings?.forceUpgrade) {
- // don't attempt to open Explore iframe when in Force Upgrade state
- return;
- }
- if (this.exploreWindowOpen) {
- // don't attempt to open again
- return;
- }
-
- // Begin loading the iframe and setting the src if it's not already set
- this.ensureIframeIsLoaded();
-
- // Ensures correct iframe URL calculation when syncing iframe location
- // in toggleExploreWindow
- window.location.hash = '/explore';
-
- this.router.transitionTo('/explore');
- this.toggleExploreWindow(true);
- }
-
- ensureIframeIsLoaded() {
- if (this.getExploreIframe() && !this.getExploreIframe()?.src) {
- this.getExploreIframe().src = this.iframeURL;
- }
- }
-
- getExploreIframe() {
- return document.getElementById('explore-frame');
- }
-}
diff --git a/apps/ember-admin/app/styles/app-dark.css b/apps/ember-admin/app/styles/app-dark.css
index 06548b309b2..503554e86ad 100644
--- a/apps/ember-admin/app/styles/app-dark.css
+++ b/apps/ember-admin/app/styles/app-dark.css
@@ -60,7 +60,6 @@
@import "layouts/post-history.css";
@import "layouts/post-preview.css";
@import "layouts/tiers.css";
-@import "layouts/explore.css";
:root {
/* Primary colours */
@@ -1058,24 +1057,6 @@ kbd {
fill: var(--midgrey-d2);
}
-/* Explore */
-
-.fullscreen-explore-container {
- background: var(--main-bg-color);
-}
-
-.explore-api {
- color: var(--middarkgrey);
-}
-
-.explore-permissions p {
- color: #fff;
-}
-
-.explore-permissions {
- background: var(--whitegrey-l1);
-}
-
/* Settings Links */
.kg-settings-link-url::before {
diff --git a/apps/ember-admin/app/styles/app.css b/apps/ember-admin/app/styles/app.css
index 4dab315b47f..597b9218e85 100644
--- a/apps/ember-admin/app/styles/app.css
+++ b/apps/ember-admin/app/styles/app.css
@@ -63,7 +63,6 @@
@import "layouts/post-history.css";
@import "layouts/post-preview.css";
@import "layouts/tiers.css";
-@import "layouts/explore.css";
@import "layouts/mentions.css";
@import "layouts/migrate.css";
diff --git a/apps/ember-admin/app/styles/layouts/explore.css b/apps/ember-admin/app/styles/layouts/explore.css
deleted file mode 100644
index f016b981a95..00000000000
--- a/apps/ember-admin/app/styles/layouts/explore.css
+++ /dev/null
@@ -1,242 +0,0 @@
-.gh-explore {
- position: absolute;
- top: 0;
- left: 0;
- height: 100%;
- width: 100%;
- z-index: 9999;
- background: var(--main-bg-color);
-}
-
-.gh-explore-container {
- position: relative;
- height: 100%;
- width: 100%;
-}
-
-.gh-explore.closed {
- display: none;
-}
-
-.gh-explore .close {
- position: absolute;
- top: 19px;
- right: 19px;
- z-index: 9999;
- margin: 0;
- padding: 0;
- width: 16px;
- height: 16px;
- border: none;
-}
-
-.gh-explore .explore-frame {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 100%;
- border: none;
- transform: translate3d(0, 0, 0);
-}
-
-.gh-explore-close {
- width: calc(50vw - 200px)
-}
-
-.gh-explore-close button {
- stroke: var(--midgrey);
- opacity: 0.6;
- transition: all 0.2s ease-in-out;
- top: 25px;
-}
-
-/* Connect */
-.explore {
- position: relative;
-}
-
-.iframe-explore-container {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 100%;
- border: none;
- transform: translate3d(0, 0, 0);
- height: 100vh;
- background: linear-gradient(180deg, var(--white) 0%, #E1E1E1 100%);
-}
-
-.fullscreen-explore-container {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 10000;
- height: 100vh;
- background: linear-gradient(180deg, var(--white) 0%, #E1E1E1 100%);
- overflow: hidden;
-}
-
-.explore-close {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding: 2rem;
-}
-
-.explore-close a {
- color: var(--middarkgrey);
-}
-
-.explore-close a svg {
- stroke: var(--middarkgrey);
- width: 18px;
- height: auto;
-}
-
-.explore-close a svg path {
- stroke-width: 1px;
-}
-
-.explore-close a:hover svg {
- stroke: var(--darkgrey);
-}
-
-.explore-content {
- display: flex;
- align-items: center;
- flex-direction: column;
- min-height: 100%;
- padding: 8vmin 2vmin 4vmin;
-}
-
-.explore-header {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
-}
-
-.explore-header h1 {
- margin: 1.6rem 0 0;
- font-size: 3.6rem;
- text-align: center;
- font-weight: 700;
- letter-spacing: -.03em;
- line-height: 40px;
-}
-
-.explore-api {
- margin-bottom: 50px;
- color: rgba(0, 0, 0, 0.52);
- font-size: 2.3rem;
- font-weight: 400;
- line-height: 30px;
- text-align: center;
- letter-spacing: -0.03em;
-}
-
-.explore-permissions {
- background: var(--white);
- padding: 3rem 3.5rem;
- max-width: 457px;
- width: 100%;
- border-radius: 6px;
-}
-
-.explore-permissions svg path {
- stroke: #86C600;
-}
-
-.explore-permissions > div {
- display: flex;
- flex-direction: row;
- align-items: baseline;
-}
-
-.explore-permissions p {
- color: rgba(0, 0, 0, 0.66);
- margin: 0;
- font-size: 1.9rem;
- font-weight: 400;
- letter-spacing: -0.03em;
- line-height: 1.32;
-}
-
-.explore-permissions div:not(:last-of-type) {
- margin-bottom: 3rem;
-}
-
-.explore-permissions > div span {
- padding-right: 18px;
-}
-
-.explore button {
- margin-top: 4vmin;
- max-width: 457px;
- width: 100%;
- height: 50px;
- border-radius: 6px;
-}
-
-.explore button span {
- font-size: 1.7rem;
- font-weight: 500;
- color: var(--white);
-}
-
-.explore button svg {
- fill: var(--white);
- margin-left: 0.1em;
- height: 14px;
-}
-
-@media (max-width: 800px) {
- .explore-content {
- padding: 2vmin;
- }
- .explore-header {
- margin-top: 10vmin;
- }
- .explore-header svg {
- width: 7rem;
- }
- .explore-header h1 {
- font-size: 2.8rem;
- }
- .explore-api {
- font-size: 1.8rem;
- }
- .explore-permissions {
- padding: 2rem 2.5rem;
- }
- .explore-permissions p {
- font-size: 1.6rem;
- }
-}
-
-@media (max-width: 500px) {
- .explore-header h1 {
- font-size: 2.4rem;
- }
- .explore-api {
- font-size: 1.6rem;
- margin-bottom: 20px;
- }
- .explore-permissions div:not(:last-of-type) {
- margin-bottom: 2rem;
- }
- .explore-permissions > div span {
- padding-right: 12px;
- }
- .explore-permissions > div span svg {
- width: 1.8rem;
- }
-}
diff --git a/apps/ember-admin/app/templates/application.hbs b/apps/ember-admin/app/templates/application.hbs
index aead6868001..0d474701667 100644
--- a/apps/ember-admin/app/templates/application.hbs
+++ b/apps/ember-admin/app/templates/application.hbs
@@ -33,8 +33,6 @@
{{#if this.showBilling}}
{{/if}}
-
-
diff --git a/apps/ember-admin/app/templates/explore/connect.hbs b/apps/ember-admin/app/templates/explore/connect.hbs
deleted file mode 100644
index b7574489522..00000000000
--- a/apps/ember-admin/app/templates/explore/connect.hbs
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}
-
Allow read-only access to your site data to create a directory listing.
-
-
-
{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}
-
You’ll be able to choose what data is shown publicly or hidden.
-
-
-
{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}
-
Your site will be promoted across the entire Ghost ecosystem.
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/ember-admin/tests/unit/routes/explore-test.js b/apps/ember-admin/tests/unit/routes/explore-test.js
deleted file mode 100644
index 9f1a37b9299..00000000000
--- a/apps/ember-admin/tests/unit/routes/explore-test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import {describe, it} from 'mocha';
-import {expect} from 'chai';
-import {setupTest} from 'ember-mocha';
-
-describe('Unit | Route | explore', function () {
- setupTest();
-
- it('exists', function () {
- let route = this.owner.lookup('route:explore.connect');
- expect(route).to.be.ok;
- });
-});
diff --git a/ghost/core/core/server/api/endpoints/explore.js b/ghost/core/core/server/api/endpoints/explore.js
deleted file mode 100644
index aa9c380c4d7..00000000000
--- a/ghost/core/core/server/api/endpoints/explore.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const exploreService = require('../../services/explore');
-
-/** @type {import('@tryghost/api-framework').Controller} */
-const controller = {
- docName: 'explore',
-
- read: {
- headers: {
- cacheInvalidate: false
- },
- permissions: true,
- query() {
- return exploreService.fetchData();
- }
- }
-};
-
-module.exports = controller;
diff --git a/ghost/core/core/server/api/endpoints/index.js b/ghost/core/core/server/api/endpoints/index.js
index 48d63adff79..7d4eacdc51a 100644
--- a/ghost/core/core/server/api/endpoints/index.js
+++ b/ghost/core/core/server/api/endpoints/index.js
@@ -172,10 +172,6 @@ module.exports = {
return apiFramework.pipeline(require('./config'), localUtils);
},
- get explore() {
- return apiFramework.pipeline(require('./explore'), localUtils);
- },
-
get themes() {
return apiFramework.pipeline(require('./themes'), localUtils);
},
diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/explore.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/explore.js
deleted file mode 100644
index edd52646673..00000000000
--- a/ghost/core/core/server/api/endpoints/utils/serializers/output/explore.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output:explore');
-
-module.exports = {
- all(data, apiConfig, frame) {
- debug('all');
-
- frame.response = {
- explore: data
- };
- }
-};
diff --git a/ghost/core/core/server/api/endpoints/utils/serializers/output/index.js b/ghost/core/core/server/api/endpoints/utils/serializers/output/index.js
index ea3ad909c91..919a7ff9740 100644
--- a/ghost/core/core/server/api/endpoints/utils/serializers/output/index.js
+++ b/ghost/core/core/server/api/endpoints/utils/serializers/output/index.js
@@ -28,10 +28,6 @@ module.exports = {
return require('./db');
},
- get explore() {
- return require('./explore');
- },
-
get pages() {
return require('./pages');
},
diff --git a/ghost/core/core/server/data/migrations/versions/6.58/2026-08-11-10-34-25-remove-legacy-explore-integration.js b/ghost/core/core/server/data/migrations/versions/6.58/2026-08-11-10-34-25-remove-legacy-explore-integration.js
new file mode 100644
index 00000000000..b71a35fdb5b
--- /dev/null
+++ b/ghost/core/core/server/data/migrations/versions/6.58/2026-08-11-10-34-25-remove-legacy-explore-integration.js
@@ -0,0 +1,131 @@
+const logging = require('@tryghost/logging');
+const ObjectID = require('bson-objectid').default;
+const {
+ createTransactionalMigration,
+ combineTransactionalMigrations,
+ createRemovePermissionMigration
+} = require('../../utils');
+
+const INTEGRATION = {
+ type: 'core',
+ name: 'Ghost Explore',
+ slug: 'ghost-explore',
+ description: 'Built-in Ghost Explore integration'
+};
+
+const ROLE = {
+ name: 'Ghost Explore Integration',
+ description: 'Internal Integration for the Ghost Explore directory'
+};
+
+const PERMISSION = {
+ name: 'Read explore data',
+ action: 'read',
+ object: 'explore'
+};
+
+// Mirrors the roles the permission was originally granted to in 5.3
+const PERMISSION_ROLES = ['Administrator', 'Admin Integration', ROLE.name];
+
+// `createRemovePermissionMigration` only unlinks the permission from roles, so any direct
+// user grant would be left pointing at a deleted permission. Ghost only ever grants this
+// via roles, so there is nothing to restore on the way back down.
+const removeDirectUserGrants = createTransactionalMigration(
+ async function up(knex) {
+ const permission = await knex('permissions').where({
+ name: PERMISSION.name,
+ action_type: PERMISSION.action,
+ object_type: PERMISSION.object
+ }).first();
+
+ if (!permission) {
+ logging.warn(`Skipping cleanup of direct "${PERMISSION.name}" user grants - the permission does not exist`);
+ return;
+ }
+
+ const removed = await knex('permissions_users').where('permission_id', permission.id).del();
+ logging.info(`Removed ${removed} direct "${PERMISSION.name}" user grants`);
+ },
+ async function down() {
+ logging.info(`Skipping restore of direct "${PERMISSION.name}" user grants - Ghost only grants it via roles`);
+ }
+);
+
+const removeIntegration = createTransactionalMigration(
+ async function up(knex) {
+ const integration = await knex('integrations').select('id').where('slug', INTEGRATION.slug).first();
+
+ if (!integration) {
+ logging.warn(`Skipping removal of ${INTEGRATION.slug} integration - it does not exist`);
+ return;
+ }
+
+ await knex('api_keys').where('integration_id', integration.id).del();
+ await knex('integrations').where('id', integration.id).del();
+ logging.info(`Removed ${INTEGRATION.slug} integration and API keys`);
+ },
+ async function down(knex) {
+ const existing = await knex('integrations').select('id').where('slug', INTEGRATION.slug).first();
+
+ if (existing) {
+ logging.warn(`Skipping restore of ${INTEGRATION.slug} integration - it already exists`);
+ return;
+ }
+
+ await knex('integrations').insert({
+ id: new ObjectID().toHexString(),
+ ...INTEGRATION,
+ created_at: knex.raw('CURRENT_TIMESTAMP'),
+ updated_at: knex.raw('CURRENT_TIMESTAMP')
+ });
+
+ // Deliberately no API key: the original secret is unrecoverable, and a rollback is
+ // about getting the schema back to what the previous version expects rather than
+ // reconnecting Explore - ghost.org stopped calling this endpoint long ago. Anyone
+ // who needs Admin API access should create a custom integration instead.
+ logging.info(`Restored ${INTEGRATION.slug} integration without an API key`);
+ }
+);
+
+const removeRole = createTransactionalMigration(
+ async function up(knex) {
+ const role = await knex('roles').select('id').where('name', ROLE.name).first();
+
+ if (!role) {
+ logging.warn(`Skipping removal of ${ROLE.name} role - it does not exist`);
+ return;
+ }
+
+ await knex('api_keys').where('role_id', role.id).del();
+ await knex('permissions_roles').where('role_id', role.id).del();
+ await knex('roles_users').where('role_id', role.id).del();
+ await knex('roles').where('id', role.id).del();
+ logging.info(`Removed ${ROLE.name} role`);
+ },
+ async function down(knex) {
+ const existing = await knex('roles').select('id').where('name', ROLE.name).first();
+
+ if (existing) {
+ logging.warn(`Skipping restore of ${ROLE.name} role - it already exists`);
+ return;
+ }
+
+ await knex('roles').insert({
+ id: new ObjectID().toHexString(),
+ ...ROLE,
+ created_at: knex.raw('CURRENT_TIMESTAMP'),
+ updated_at: knex.raw('CURRENT_TIMESTAMP')
+ });
+
+ logging.info(`Restored ${ROLE.name} role`);
+ }
+);
+
+// Down migrations run in reverse, so the role is restored before the permission that has
+// to be linked back to it
+module.exports = combineTransactionalMigrations(
+ removeDirectUserGrants,
+ createRemovePermissionMigration(PERMISSION, PERMISSION_ROLES),
+ removeIntegration,
+ removeRole
+);
diff --git a/ghost/core/core/server/data/schema/fixtures/fixtures.json b/ghost/core/core/server/data/schema/fixtures/fixtures.json
index c6613c1e35c..763be09d78a 100644
--- a/ghost/core/core/server/data/schema/fixtures/fixtures.json
+++ b/ghost/core/core/server/data/schema/fixtures/fixtures.json
@@ -99,10 +99,6 @@
"name": "Admin Integration",
"description": "External Apps"
},
- {
- "name": "Ghost Explore Integration",
- "description": "Internal Integration for the Ghost Explore directory"
- },
{
"name": "Self-Serve Migration Integration",
"description": "Internal Integration for the Self-Serve migration tool"
@@ -124,11 +120,6 @@
{
"name": "Permission",
"entries": [
- {
- "name": "Read explore data",
- "action_type": "read",
- "object_type": "explore"
- },
{
"name": "Export database",
"action_type": "exportContent",
@@ -900,13 +891,6 @@
"type": "builtin",
"api_keys": [{"type": "admin"}]
},
- {
- "slug": "ghost-explore",
- "name": "Ghost Explore",
- "description": "Built-in Ghost Explore integration",
- "type": "core",
- "api_keys": [{"type": "admin", "role": "Ghost Explore Integration"}]
- },
{
"slug": "self-serve-migration",
"name": "Self-Serve Migration Integration",
@@ -1002,7 +986,6 @@
"authentication": "reset",
"members_stripe_connect": "auth",
"newsletter": "all",
- "explore": "read",
"comment": "all",
"link": "all",
"mention": "browse",
@@ -1021,9 +1004,6 @@
"automation": "poll",
"gift": "flushReminders"
},
- "Ghost Explore Integration": {
- "explore": "read"
- },
"Self-Serve Migration Integration": {
"db": "importContent",
"content_import": "importContent",
@@ -1055,7 +1035,6 @@
"product": ["browse", "read", "add", "edit"],
"offer": ["browse", "read", "add", "edit"],
"newsletter": ["browse", "read", "add", "edit"],
- "explore": "read",
"comment": "all",
"link": "all",
"mention": "browse",
diff --git a/ghost/core/core/server/services/explore/explore-service.js b/ghost/core/core/server/services/explore/explore-service.js
deleted file mode 100644
index fc7c1d19277..00000000000
--- a/ghost/core/core/server/services/explore/explore-service.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const ghostVersion = require('@tryghost/version');
-
-module.exports = class ExploreService {
- /**
- * @param {Object} options
- * @param {Object} options.MembersService
- * @param {Object} options.PostsService
- * @param {Object} options.PublicConfigService
- * @param {Object} options.StatsService
- * @param {Object} options.StripeService
- * @param {Object} options.UserModel
- */
- constructor({MembersService, PostsService, PublicConfigService, StatsService, StripeService, UserModel}) {
- this.MembersService = MembersService;
- this.PostsService = PostsService;
- this.PublicConfigService = PublicConfigService;
- this.StatsService = StatsService;
- this.StripeService = StripeService;
- this.UserModel = UserModel;
- }
-
- /**
- * Build and return the response object containing the data for the Ghost Explore endpoint
- */
- async fetchData() {
- const totalMembers = await this.MembersService.stats.getTotalMembers();
- const mrrStats = await this.StatsService.api.getMRRHistory();
-
- const {description, icon, title, url, accent_color: accentColor, locale} = this.PublicConfigService.site;
-
- const exploreProperties = {
- version: ghostVersion.full,
- total_members: totalMembers,
- mrr_stats: mrrStats,
- site: {
- description,
- icon,
- title,
- url,
- accent_color: accentColor,
- locale
- },
- stripe: {
- configured: this.StripeService.api.configured,
- livemode: (this.StripeService.api.configured && this.StripeService.api.mode === 'live')
- }
- };
-
- const mostRecentlyPublishedPost = await this.PostsService.stats.getMostRecentlyPublishedPostDate();
- const totalPostsPublished = await this.PostsService.stats.getTotalPostsPublished();
- exploreProperties.most_recently_published_at = mostRecentlyPublishedPost ?? null;
- exploreProperties.total_posts_published = totalPostsPublished ?? null;
-
- const owner = await this.UserModel.findOne({role: 'Owner', status: 'all'});
- exploreProperties.owner_email = owner?.get('email') ?? null;
-
- return exploreProperties;
- }
-};
diff --git a/ghost/core/core/server/services/explore/index.js b/ghost/core/core/server/services/explore/index.js
deleted file mode 100644
index 99bb608c1c5..00000000000
--- a/ghost/core/core/server/services/explore/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const ExploreService = require('./explore-service');
-
-const MembersService = require('../members');
-const PostsService = require('../posts/posts-service-instance')();
-const PublicConfigService = require('../public-config');
-const StatsService = require('../stats');
-const StripeService = require('../stripe');
-
-const models = require('../../models');
-
-module.exports = new ExploreService({
- MembersService,
- PostsService,
- PublicConfigService,
- StatsService,
- StripeService,
- UserModel: models.User
-});
diff --git a/ghost/core/core/server/web/api/endpoints/admin/middleware.js b/ghost/core/core/server/web/api/endpoints/admin/middleware.js
index c6c45a15f7e..5479850743c 100644
--- a/ghost/core/core/server/web/api/endpoints/admin/middleware.js
+++ b/ghost/core/core/server/web/api/endpoints/admin/middleware.js
@@ -66,7 +66,6 @@ const tokenPermissionCheck = function tokenPermissionCheck(req, res, next) {
newsletters: ['GET', 'PUT', 'POST'],
automations: ['PUT'],
config: ['GET'],
- explore: ['GET'],
schedules: ['PUT'],
gifts: ['PUT'],
files: ['POST'],
diff --git a/ghost/core/core/server/web/api/endpoints/admin/routes.js b/ghost/core/core/server/web/api/endpoints/admin/routes.js
index f24d414cbdd..81866b875ed 100644
--- a/ghost/core/core/server/web/api/endpoints/admin/routes.js
+++ b/ghost/core/core/server/web/api/endpoints/admin/routes.js
@@ -23,9 +23,6 @@ module.exports = function apiRoutes() {
router.get('/config', mw.authAdminApi, http(api.config.read));
router.get('/config/featurebase', mw.authAdminApi, http(api.config.featurebase));
- // ## Ghost Explore
- router.get('/explore', mw.authAdminApi, http(api.explore.read));
-
// ## Posts
router.get('/posts', mw.authAdminApi, http(api.posts.browse));
router.get('/posts/export', mw.authAdminApi, http(api.posts.exportCSV));
diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/explore.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/explore.test.js.snap
deleted file mode 100644
index 38f040d023a..00000000000
--- a/ghost/core/test/e2e-api/admin/__snapshots__/explore.test.js.snap
+++ /dev/null
@@ -1,60 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`Explore API Read Can request Explore data 1: [body] 1`] = `
-Object {
- "explore": Object {
- "most_recently_published_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
- "mrr_stats": Object {
- "data": Array [
- Object {
- "currency": "usd",
- "date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
- "mrr": 0,
- },
- Object {
- "currency": "usd",
- "date": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/,
- "mrr": 1000,
- },
- ],
- "meta": Object {
- "totals": Array [
- Object {
- "currency": "usd",
- "mrr": 1000,
- },
- ],
- },
- },
- "owner_email": "jbloggs@example.com",
- "site": Object {
- "accent_color": "#FF1A75",
- "description": "Thoughts, stories and ideas",
- "icon": null,
- "locale": "en",
- "title": "Ghost",
- "url": "http://127.0.0.1:2369/",
- },
- "stripe": Object {
- "configured": true,
- "livemode": false,
- },
- "total_members": 8,
- "total_posts_published": Any,
- "version": StringMatching /\\\\d\\+\\\\\\.\\\\d\\+\\\\\\.\\\\d\\+/,
- },
-}
-`;
-
-exports[`Explore API Read Can request Explore data 2: [headers] 1`] = `
-Object {
- "access-control-allow-origin": "http://127.0.0.1:2369",
- "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
- "content-length": StringMatching /\\\\d\\+/,
- "content-type": "application/json; charset=utf-8",
- "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
- "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
- "vary": "Accept-Version, Origin, Accept-Encoding",
- "x-powered-by": "Express",
-}
-`;
diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/roles.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/roles.test.js.snap
index d4f0c516781..524a543f0f9 100644
--- a/ghost/core/test/e2e-api/admin/__snapshots__/roles.test.js.snap
+++ b/ghost/core/test/e2e-api/admin/__snapshots__/roles.test.js.snap
@@ -45,13 +45,6 @@ Object {
"name": "Admin Integration",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
},
- Object {
- "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
- "description": "Internal Integration for the Ghost Explore directory",
- "id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
- "name": "Ghost Explore Integration",
- "updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
- },
Object {
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": "Internal Integration for the Self-Serve migration tool",
@@ -88,7 +81,7 @@ exports[`Roles API Can request all roles 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
- "content-length": "1962",
+ "content-length": "1744",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
diff --git a/ghost/core/test/e2e-api/admin/explore.test.js b/ghost/core/test/e2e-api/admin/explore.test.js
deleted file mode 100644
index 7b1b22363f3..00000000000
--- a/ghost/core/test/e2e-api/admin/explore.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
-const {anyEtag, anyISODate, anyISODateTime, anyContentLength, anyContentVersion, stringMatching, anyNumber} = matchers;
-
-describe('Explore API', function () {
- let agent;
-
- beforeAll(async function () {
- agent = await agentProvider.getAdminAPIAgent();
- await fixtureManager.init('posts', 'members');
- await agent.loginAsOwner();
- });
-
- describe('Read', function () {
- it('Can request Explore data', async function () {
- await agent
- .get('explore/')
- .expectStatus(200)
- .matchBodySnapshot({
- explore: {
- most_recently_published_at: anyISODateTime,
- total_posts_published: anyNumber,
- mrr_stats: {
- data: [{
- date: anyISODate
- },
- {
- date: anyISODate
- }]
- },
- version: stringMatching(/\d+\.\d+\.\d+/)
- }
- })
- .matchHeaderSnapshot({
- etag: anyEtag,
- // Special rule for this test, as the labs setting changes a lot
- 'content-length': anyContentLength,
- 'content-version': anyContentVersion
- });
- });
- });
-});
diff --git a/ghost/core/test/e2e-api/admin/integrations.test.js b/ghost/core/test/e2e-api/admin/integrations.test.js
index abf92fb9790..28bbe8c73b0 100644
--- a/ghost/core/test/e2e-api/admin/integrations.test.js
+++ b/ghost/core/test/e2e-api/admin/integrations.test.js
@@ -26,7 +26,7 @@ describe('Integrations API', function () {
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
- assert.equal(res.body.integrations.length, 6);
+ assert.equal(res.body.integrations.length, 5);
// there is no enforced order for integrations which makes order different on SQLite and MySQL
const zapierIntegration = _.find(res.body.integrations, {name: 'Zapier'}); // from migrations
diff --git a/ghost/core/test/e2e-api/admin/key-authentication.test.js b/ghost/core/test/e2e-api/admin/key-authentication.test.js
index 4de97282b15..9e0a5f9e857 100644
--- a/ghost/core/test/e2e-api/admin/key-authentication.test.js
+++ b/ghost/core/test/e2e-api/admin/key-authentication.test.js
@@ -122,13 +122,13 @@ describe('Admin API key authentication', function () {
sinon.assert.calledOnce(loggingStub);
// CASE: Test with a different API key, related to a core integration
- const secondResponse = await request.get(localUtils.API.getApiQuery('explore/'))
+ const secondResponse = await request.get(localUtils.API.getApiQuery('tags/'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/admin/', 4)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
- assertExists(secondResponse.body.explore);
+ assertExists(secondResponse.body.tags);
});
});
});
diff --git a/ghost/core/test/e2e-api/admin/roles.test.js b/ghost/core/test/e2e-api/admin/roles.test.js
index c11a972abb6..36a235d598a 100644
--- a/ghost/core/test/e2e-api/admin/roles.test.js
+++ b/ghost/core/test/e2e-api/admin/roles.test.js
@@ -21,7 +21,7 @@ describe('Roles API', function () {
await agent.get('roles/')
.expectStatus(200)
.matchBodySnapshot({
- roles: Array(11).fill(rolesObjectMatcher)
+ roles: Array(10).fill(rolesObjectMatcher)
})
.matchHeaderSnapshot({
'content-version': anyContentVersion,
diff --git a/ghost/core/test/integration/migrations/migration.test.js b/ghost/core/test/integration/migrations/migration.test.js
index 391b0cc8c5c..e8d1298a095 100644
--- a/ghost/core/test/integration/migrations/migration.test.js
+++ b/ghost/core/test/integration/migrations/migration.test.js
@@ -91,7 +91,7 @@ describe('Migrations', function () {
// Custom assertion to wrap all permissions
function assertCompletePermissions(permissions) {
// If you have to change this number, please add the relevant `assertHavePermission` checks below
- assert.equal(permissions.length, 143);
+ assert.equal(permissions.length, 142);
assertHavePermission(permissions, 'Export database', ['Administrator', 'DB Backup Integration']);
assertHavePermission(permissions, 'Import database', ['Administrator', 'Self-Serve Migration Integration', 'DB Backup Integration']);
@@ -225,8 +225,6 @@ describe('Migrations', function () {
assertHavePermission(permissions, 'Edit newsletters', ['Administrator', 'Admin Integration']);
assertHavePermission(permissions, 'Add newsletters', ['Administrator', 'Admin Integration']);
- assertHavePermission(permissions, 'Read explore data', ['Administrator', 'Admin Integration', 'Ghost Explore Integration']);
-
assertHavePermission(permissions, 'Browse comments', ['Administrator', 'Admin Integration', 'Super Editor']);
assertHavePermission(permissions, 'Read comments', ['Administrator', 'Admin Integration', 'Super Editor']);
assertHavePermission(permissions, 'Edit comments', ['Administrator', 'Admin Integration', 'Super Editor']);
@@ -303,18 +301,17 @@ describe('Migrations', function () {
// Roles
assert(roles);
- assert.equal(roles.length, 11);
+ assert.equal(roles.length, 10);
assert.equal(roles.at(0).get('name'), 'Administrator');
assert.equal(roles.at(1).get('name'), 'Editor');
assert.equal(roles.at(2).get('name'), 'Author');
assert.equal(roles.at(3).get('name'), 'Contributor');
assert.equal(roles.at(4).get('name'), 'Owner');
assert.equal(roles.at(5).get('name'), 'Admin Integration');
- assert.equal(roles.at(6).get('name'), 'Ghost Explore Integration');
- assert.equal(roles.at(7).get('name'), 'Self-Serve Migration Integration');
- assert.equal(roles.at(8).get('name'), 'DB Backup Integration');
- assert.equal(roles.at(9).get('name'), 'Scheduler Integration');
- assert.equal(roles.at(10).get('name'), 'Super Editor');
+ assert.equal(roles.at(6).get('name'), 'Self-Serve Migration Integration');
+ assert.equal(roles.at(7).get('name'), 'DB Backup Integration');
+ assert.equal(roles.at(8).get('name'), 'Scheduler Integration');
+ assert.equal(roles.at(9).get('name'), 'Super Editor');
// Permissions
assertCompletePermissions(permissions.toJSON());
diff --git a/ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js b/ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
index 6bdc86bd262..48a31920fb1 100644
--- a/ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
+++ b/ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
@@ -398,7 +398,7 @@ describe('Migration Fixture Utils', function () {
const rolesAllStub = sinon.stub(models.Role, 'findAll').returns(Promise.resolve(dataMethodStub));
const result = await fixtureManager.addFixturesForRelation(fixtures.relations[0]);
- const FIXTURE_COUNT = 152;
+ const FIXTURE_COUNT = 149;
assertExists(result);
assert(_.isPlainObject(result));
assert.equal(result.expected, FIXTURE_COUNT);
@@ -408,7 +408,7 @@ describe('Migration Fixture Utils', function () {
sinon.assert.calledOnce(permsAllStub);
sinon.assert.calledOnce(rolesAllStub);
sinon.assert.callCount(dataMethodStub.filter, FIXTURE_COUNT);
- sinon.assert.callCount(dataMethodStub.find, 10);
+ sinon.assert.callCount(dataMethodStub.find, 9);
sinon.assert.callCount(baseUtilAttachStub, FIXTURE_COUNT);
sinon.assert.callCount(fromItem.related, FIXTURE_COUNT);
diff --git a/ghost/core/test/unit/server/data/schema/integrity.test.js b/ghost/core/test/unit/server/data/schema/integrity.test.js
index db3f0917efe..58274817998 100644
--- a/ghost/core/test/unit/server/data/schema/integrity.test.js
+++ b/ghost/core/test/unit/server/data/schema/integrity.test.js
@@ -36,7 +36,7 @@ const parseYaml = require('../../../../../core/server/services/route-settings/ya
describe('DB version integrity', function () {
// Only these variables should need updating
const currentSchemaHash = 'bb0b6f2ab291417ad87e0e7bc49c2310';
- const currentFixturesHash = '4e0c7b4fe3c1593e9d1fae1a891389ca';
+ const currentFixturesHash = 'd4c9e4fabcec3365c42d37642216021b';
const currentSettingsHash = '8650db85b9a61afe4797ad6333066c62';
const currentRoutesHash = 'd8c25fa01bf6d22a2bcb05ba0de70dc1';
diff --git a/ghost/core/test/utils/fixtures/fixtures.json b/ghost/core/test/utils/fixtures/fixtures.json
index f630c37c1d1..5515dda1618 100644
--- a/ghost/core/test/utils/fixtures/fixtures.json
+++ b/ghost/core/test/utils/fixtures/fixtures.json
@@ -100,10 +100,6 @@
"name": "Admin Integration",
"description": "External Apps"
},
- {
- "name": "Ghost Explore Integration",
- "description": "Internal Integration for the Ghost Explore directory"
- },
{
"name": "Self-Serve Migration Integration",
"description": "Internal Integration for the Self-Serve migration tool"
@@ -125,11 +121,6 @@
{
"name": "Permission",
"entries": [
- {
- "name": "Read explore data",
- "action_type": "read",
- "object_type": "explore"
- },
{
"name": "Export database",
"action_type": "exportContent",
@@ -1068,13 +1059,6 @@
"type": "builtin",
"api_keys": [{"type": "admin"}]
},
- {
- "slug": "ghost-explore",
- "name": "Ghost Explore",
- "description": "Built-in Ghost Explore integration",
- "type": "core",
- "api_keys": [{"type": "admin", "role": "Ghost Explore Integration"}]
- },
{
"slug": "self-serve-migration",
"name": "Self-Serve Migration Integration",
@@ -1156,7 +1140,6 @@
"authentication": "reset",
"members_stripe_connect": "auth",
"newsletter": "all",
- "explore": "read",
"comment": "all",
"link": "all",
"mention": "browse",
@@ -1175,9 +1158,6 @@
"automation": "poll",
"gift": "flushReminders"
},
- "Ghost Explore Integration": {
- "explore": "read"
- },
"Self-Serve Migration Integration": {
"db": "importContent",
"content_import": "importContent",
@@ -1210,7 +1190,6 @@
"product": ["browse", "read", "add", "edit"],
"offer": ["browse", "read", "add", "edit"],
"newsletter": ["browse", "read", "add", "edit"],
- "explore": "read",
"comment": "all",
"link": "all",
"mention": "browse",