2020-04-30 22:26:12 +03:00
const errors = require ( '@tryghost/errors' ) ;
2021-07-01 14:37:32 +03:00
const tpl = require ( '@tryghost/tpl' ) ;
2021-06-15 17:36:27 +03:00
const logging = require ( '@tryghost/logging' ) ;
2021-06-16 14:54:14 +03:00
const request = require ( '@tryghost/request' ) ;
2020-05-26 21:11:23 +03:00
const { blogIcon } = require ( '../lib/image' ) ;
2020-05-28 13:57:02 +03:00
const urlUtils = require ( '../../shared/url-utils' ) ;
2021-10-18 17:27:57 +03:00
const urlService = require ( './url' ) ;
2021-06-30 16:56:57 +03:00
const settingsCache = require ( '../../shared/settings-cache' ) ;
2020-04-29 18:44:27 +03:00
const schema = require ( '../data/schema' ) . checks ;
const moment = require ( 'moment' ) ;
2018-11-12 15:04:50 +03:00
2021-07-07 17:49:45 +03:00
// Used to receive post.published model event, but also the slack.test event from the API which iirc this was done to avoid circular deps a long time ago
const events = require ( '../lib/common/events' ) ;
2021-07-01 14:37:32 +03:00
const messages = {
requestFailedError : 'The {service} service was unable to send a ping request, your site will continue to function.' ,
requestFailedHelp : 'If you get this error repeatedly, please seek help on {url}.'
} ;
2020-04-29 18:44:27 +03:00
const defaultPostSlugs = [
'welcome' ,
'the-editor' ,
'using-tags' ,
'managing-users' ,
'private-sites' ,
'advanced-markdown' ,
'themes'
] ;
2017-10-25 17:27:56 +03:00
function getSlackSettings ( ) {
2020-07-14 11:10:59 +03:00
const username = settingsCache . get ( 'slack_username' ) ;
const url = settingsCache . get ( 'slack_url' ) ;
return {
username ,
url
} ;
2017-10-25 17:27:56 +03:00
}
function ping ( post ) {
2020-04-29 18:44:27 +03:00
let message ;
let title ;
let author ;
let description ;
let slackData = { } ;
let slackSettings = getSlackSettings ( ) ;
let blogTitle = settingsCache . get ( 'title' ) ;
2017-10-25 17:27:56 +03:00
// If this is a post, we want to send the link of the post
if ( schema . isPost ( post ) ) {
✨Dynamic Routing Beta (#9596)
refs #9601
### Dynamic Routing
This is the beta version of dynamic routing.
- we had a initial implementation of "channels" available in the codebase
- we have removed and moved this implementation
- there is now a centralised place for dynamic routing - server/services/routing
- each routing component is represented by a router type e.g. collections, routes, static pages, taxonomies, rss, preview of posts
- keep as much as possible logic of routing helpers, middlewares and controllers
- ensure test coverage
- connect all the things together
- yaml file + validation
- routing + routers
- url service
- sitemaps
- url access
- deeper implementation of yaml validations
- e.g. hard require slashes
- ensure routing hierarchy/order
- e.g. you enable the subscriber app
- you have a custom static page, which lives under the same slug /subscribe
- static pages are stronger than apps
- e.g. the first collection owns the post it has filtered
- a post cannot live in two collections
- ensure apps are still working and hook into the routers layer (or better said: and register in the routing service)
- put as much as possible comments to the code base for better understanding
- ensure a clean debug log
- ensure we can unmount routes
- e.g. you have a collection permalink of /:slug/ represented by {globals.permalink}
- and you change the permalink in the admin to dated permalink
- the express route get's refreshed from /:slug/ to /:year/:month/:day/:slug/
- unmount without server restart, yey
- ensure we are backwards compatible
- e.g. render home.hbs for collection index if collection route is /
- ensure you can access your configured permalink from the settings table with {globals.permalink}
### Render 503 if url service did not finish
- return 503 if the url service has not finished generating the resource urls
### Rewrite sitemaps
- we have rewritten the sitemaps "service", because the url generator does no longer happen on runtime
- we generate all urls on bootstrap
- the sitemaps service will consume created resource and router urls
- these urls will be shown on the xml pages
- we listen on url events
- we listen on router events
- we no longer have to fetch the resources, which is nice
- the urlservice pre-fetches resources and emits their urls
- the urlservice is the only component who knows which urls are valid
- i made some ES6 adaptions
- we keep the caching logic -> only regenerate xml if there is a change
- updated tests
- checked test coverage (100%)
### Re-work usage of Url utility
- replace all usages of `urlService.utils.urlFor` by `urlService.getByResourceId`
- only for resources e.g. post, author, tag
- this is important, because with dynamic routing we no longer create static urls based on the settings permalink on runtime
- adapt url utility
- adapt tests
2018-06-05 20:02:20 +03:00
message = urlService . getUrlByResourceId ( post . id , { absolute : true } ) ;
2018-11-12 15:04:50 +03:00
title = post . title ? post . title : null ;
author = post . authors ? post . authors [ 0 ] : null ;
2020-03-26 19:38:30 +03:00
if ( post . custom _excerpt ) {
description = post . custom _excerpt ;
} else if ( post . html ) {
2020-03-26 19:45:33 +03:00
description = ` ${ post . html . replace ( /<[^>]+>/g , '' ) . split ( '.' ) . slice ( 0 , 3 ) . join ( '.' ) } . ` ;
2020-03-26 19:38:30 +03:00
} else {
description = null ;
}
2017-10-25 17:27:56 +03:00
} else {
message = post . message ;
}
2018-06-11 12:08:08 +03:00
2017-10-25 17:27:56 +03:00
// Quit here if slack integration is not activated
2018-06-11 12:08:08 +03:00
if ( slackSettings && slackSettings . url && slackSettings . url !== '' ) {
2018-12-14 14:57:32 +03:00
slackSettings . username = slackSettings . username ? slackSettings . username : 'Ghost' ;
2017-10-25 17:27:56 +03:00
// Only ping when not a page
2019-09-16 13:51:54 +03:00
if ( post . type === 'page' ) {
2017-10-25 17:27:56 +03:00
return ;
}
// Don't ping for the default posts.
// This also handles the case where during ghost's first run
// models.init() inserts this post but permissions.init() hasn't
// (can't) run yet.
if ( defaultPostSlugs . indexOf ( post . slug ) > - 1 ) {
return ;
}
2018-11-12 15:04:50 +03:00
if ( schema . isPost ( post ) ) {
slackData = {
// We are handling the case of test notification here by checking
// if it is a post or a test message to check webhook working.
text : ` Notification from * ${ blogTitle } * :ghost: ` ,
unfurl _links : true ,
2020-05-26 21:11:23 +03:00
icon _url : blogIcon . getIconUrl ( true ) ,
2018-12-14 14:57:32 +03:00
username : slackSettings . username ,
2018-11-12 15:04:50 +03:00
// We don't want to send attachment if it is a test notification.
attachments : [
{
fallback : 'Sorry, content cannot be shown.' ,
title : title ,
title _link : message ,
author _name : blogTitle ,
2019-06-18 16:13:55 +03:00
image _url : post ? urlUtils . urlFor ( 'image' , { image : post . feature _image } , true ) : null ,
2018-11-12 15:04:50 +03:00
color : '#008952' ,
fields : [
{
title : 'Description' ,
2020-03-26 19:38:30 +03:00
value : description ,
2018-11-12 15:04:50 +03:00
short : false
}
]
} ,
{
fallback : 'Sorry, content cannot be shown.' ,
color : '#008952' ,
2019-06-18 16:13:55 +03:00
thumb _url : author ? urlUtils . urlFor ( 'image' , { image : author . profile _image } , true ) : null ,
2018-11-12 15:04:50 +03:00
fields : [
{
title : 'Author' ,
value : author ? ` < ${ urlService . getUrlByResourceId ( author . id , { absolute : true } )} | ${ author . name } > ` : null ,
short : true
}
] ,
footer : blogTitle ,
2020-05-26 21:11:23 +03:00
footer _icon : blogIcon . getIconUrl ( true ) ,
2018-11-12 15:04:50 +03:00
ts : moment ( ) . unix ( )
}
]
} ;
} else {
slackData = {
text : message ,
unfurl _links : true ,
2020-05-26 21:11:23 +03:00
icon _url : blogIcon . getIconUrl ( true ) ,
2018-12-14 14:57:32 +03:00
username : slackSettings . username
2018-11-12 15:04:50 +03:00
} ;
}
2017-10-25 17:27:56 +03:00
2017-12-14 18:08:48 +03:00
return request ( slackSettings . url , {
body : JSON . stringify ( slackData ) ,
headers : {
'Content-type' : 'application/json'
}
} ) . catch ( function ( err ) {
2021-12-01 13:22:01 +03:00
logging . error ( new errors . InternalServerError ( {
2017-12-14 18:08:48 +03:00
err : err ,
2021-07-01 14:37:32 +03:00
context : tpl ( messages . requestFailedError , { service : 'slack' } ) ,
help : tpl ( messages . requestFailedHelp , { url : 'https://ghost.org/docs/' } )
2017-12-14 18:08:48 +03:00
} ) ) ;
} ) ;
2017-10-25 17:27:56 +03:00
}
}
function listener ( model , options ) {
// CASE: do not ping slack if we import a database
// TODO: refactor post.published events to never fire on importing
if ( options && options . importing ) {
return ;
}
ping ( model . toJSON ( ) ) ;
}
function testPing ( ) {
ping ( {
2017-12-15 07:19:37 +03:00
message : 'Heya! This is a test notification from your Ghost blog :smile:. Seems to work fine!'
2017-10-25 17:27:56 +03:00
} ) ;
}
function listen ( ) {
2020-04-30 22:26:12 +03:00
events . on ( 'post.published' , listener ) ;
events . on ( 'slack.test' , testPing ) ;
2017-10-25 17:27:56 +03:00
}
// Public API
module . exports = {
listen : listen
} ;