mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
✨ Added Ghost Explore endpoint
- this new endpoint returns a special set of data for use in Ghost Explore
This commit is contained in:
parent
4134a6ac4c
commit
1dd83e1a0f
11 changed files with 215 additions and 0 deletions
12
core/server/api/endpoints/explore.js
Normal file
12
core/server/api/endpoints/explore.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
const exploreService = require('../../services/explore');
|
||||
|
||||
module.exports = {
|
||||
docName: 'explore',
|
||||
|
||||
read: {
|
||||
permissions: true,
|
||||
query() {
|
||||
return exploreService.fetchData();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -137,6 +137,10 @@ module.exports = {
|
|||
return shared.pipeline(require('./config'), localUtils);
|
||||
},
|
||||
|
||||
get explore() {
|
||||
return shared.pipeline(require('./explore'), localUtils);
|
||||
},
|
||||
|
||||
get themes() {
|
||||
return shared.pipeline(require('./themes'), localUtils);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output:explore');
|
||||
|
||||
module.exports = {
|
||||
all(data, apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
frame.response = {
|
||||
explore: data
|
||||
};
|
||||
}
|
||||
};
|
|
@ -21,6 +21,10 @@ module.exports = {
|
|||
return require('./db');
|
||||
},
|
||||
|
||||
get explore() {
|
||||
return require('./explore');
|
||||
},
|
||||
|
||||
get pages() {
|
||||
return require('./pages');
|
||||
},
|
||||
|
|
18
core/server/services/explore/index.js
Normal file
18
core/server/services/explore/index.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
const ExploreService = require('./service');
|
||||
|
||||
const MembersService = require('../members');
|
||||
const PostsService = require('../posts/posts-service')();
|
||||
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
|
||||
});
|
55
core/server/services/explore/service.js
Normal file
55
core/server/services/explore/service.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
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.getMRRHistory();
|
||||
|
||||
const {description, icon, title, url} = this.PublicConfigService.site;
|
||||
|
||||
const exploreProperties = {
|
||||
version: ghostVersion.full,
|
||||
totalMembers,
|
||||
mrrStats,
|
||||
site: {
|
||||
description,
|
||||
icon,
|
||||
title,
|
||||
url
|
||||
},
|
||||
stripe: {
|
||||
configured: this.StripeService.api.configured,
|
||||
livemode: (this.StripeService.api.configured && this.StripeService.api.mode === 'live')
|
||||
}
|
||||
};
|
||||
|
||||
const mostRecentlyPublishedPost = await this.PostsService.getMostRecentlyPublishedPost();
|
||||
exploreProperties.mostRecentlyPublishedAt = mostRecentlyPublishedPost?.get('published_at') || null;
|
||||
|
||||
const owner = await this.UserModel.findOne({role: 'Owner', status: 'all'});
|
||||
exploreProperties.ownerEmail = owner?.get('email') || null;
|
||||
|
||||
return exploreProperties;
|
||||
}
|
||||
};
|
|
@ -76,6 +76,20 @@ class PostsService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recently `published_at` post that was published or sent
|
||||
* via email
|
||||
*/
|
||||
async getMostRecentlyPublishedPost() {
|
||||
const recentlyPublishedPost = await this.models.Post.findPage({
|
||||
status: ['published', 'sent'],
|
||||
order: 'published_at DESC',
|
||||
limit: 1
|
||||
});
|
||||
|
||||
return recentlyPublishedPost?.data[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates if the email should be tried to be sent out
|
||||
* @private
|
||||
|
|
|
@ -33,6 +33,7 @@ const notImplemented = function (req, res, next) {
|
|||
offers: ['GET', 'PUT', 'POST'],
|
||||
newsletters: ['GET', 'PUT', 'POST'],
|
||||
config: ['GET'],
|
||||
explore: ['GET'],
|
||||
schedules: ['PUT'],
|
||||
files: ['POST'],
|
||||
media: ['POST'],
|
||||
|
|
|
@ -20,6 +20,9 @@ module.exports = function apiRoutes() {
|
|||
// ## Configuration
|
||||
router.get('/config', mw.authAdminApi, http(api.config.read));
|
||||
|
||||
// ## Ghost Explore
|
||||
router.get('/explore', mw.authAdminApi, http(api.explore.read));
|
||||
|
||||
// ## Posts
|
||||
router.get('/posts', mw.authAdminApi, http(api.posts.browse));
|
||||
router.post('/posts', mw.authAdminApi, http(api.posts.add));
|
||||
|
|
56
test/e2e-api/admin/__snapshots__/explore.test.js.snap
Normal file
56
test/e2e-api/admin/__snapshots__/explore.test.js.snap
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Explore API Read Can request Explore data 1: [body] 1`] = `
|
||||
Object {
|
||||
"explore": Object {
|
||||
"mostRecentlyPublishedAt": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"mrrStats": 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"ownerEmail": "jbloggs@example.com",
|
||||
"site": Object {
|
||||
"description": "Thoughts, stories and ideas",
|
||||
"icon": null,
|
||||
"title": "Ghost",
|
||||
"url": "http://127.0.0.1:2369/",
|
||||
},
|
||||
"stripe": Object {
|
||||
"configured": true,
|
||||
"livemode": false,
|
||||
},
|
||||
"totalMembers": 8,
|
||||
"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": "463",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
"x-powered-by": "Express",
|
||||
}
|
||||
`;
|
37
test/e2e-api/admin/explore.test.js
Normal file
37
test/e2e-api/admin/explore.test.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
|
||||
const {anyEtag, anyISODate, anyISODateTime, stringMatching} = matchers;
|
||||
|
||||
describe('Explore API', function () {
|
||||
let agent;
|
||||
|
||||
before(async function () {
|
||||
agent = await agentProvider.getAdminAPIAgent();
|
||||
await fixtureManager.init('members');
|
||||
await agent.loginAsOwner();
|
||||
});
|
||||
|
||||
describe('Read', function () {
|
||||
it('Can request Explore data', async function () {
|
||||
await agent
|
||||
.get('explore/')
|
||||
.expectStatus(200)
|
||||
.matchBodySnapshot({
|
||||
explore: {
|
||||
mostRecentlyPublishedAt: anyISODateTime,
|
||||
mrrStats: {
|
||||
data: [{
|
||||
date: anyISODate
|
||||
},
|
||||
{
|
||||
date: anyISODate
|
||||
}]
|
||||
},
|
||||
version: stringMatching(/\d+\.\d+\.\d+/)
|
||||
}
|
||||
})
|
||||
.matchHeaderSnapshot({
|
||||
etag: anyEtag
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue