diff --git a/ghost/core/core/boot.js b/ghost/core/core/boot.js index 16d4a57400b..c4db7c8cc46 100644 --- a/ghost/core/core/boot.js +++ b/ghost/core/core/boot.js @@ -127,11 +127,13 @@ async function initCore({ghostServer, config, frontend}) { }); debug('End: Url Service'); - // Gift links service: wires the (knex-backed) repository once the DB is ready. - debug('Begin: Gift Links Service'); - const giftLinksService = require('./server/services/gift-links'); - giftLinksService.init(); - debug('End: Gift Links Service'); + // Gift links: construct the service against the ready DB and inject it into the API controller. + debug('Begin: Gift Links'); + const {GiftLinksService} = require('./server/services/gift-links'); + const {knex: giftLinksKnex} = require('./server/data/db'); + const giftLinksEndpoint = require('./server/api/endpoints/gift-links'); + giftLinksEndpoint.controller = giftLinksEndpoint.createController(new GiftLinksService({knex: giftLinksKnex})); + debug('End: Gift Links'); if (ghostServer) { // Job Service allows parts of Ghost to run in the background diff --git a/ghost/core/core/server/api/endpoints/gift-links.ts b/ghost/core/core/server/api/endpoints/gift-links.ts index 3aa0fdee1a2..7633c0b503a 100644 --- a/ghost/core/core/server/api/endpoints/gift-links.ts +++ b/ghost/core/core/server/api/endpoints/gift-links.ts @@ -1,4 +1,4 @@ -import {service} from '../../services/gift-links'; +import type {GiftLinksService} from '../../services/gift-links'; // permissions is untyped JS; require, don't import. // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -21,59 +21,65 @@ async function assertCanManageGiftLink(frame: Frame): Promise { const noCacheInvalidation = {cacheInvalidate: false}; -const controller = { - docName: 'gift_links', +function createController(service: GiftLinksService) { + return { + docName: 'gift_links', - read: { - headers: noCacheInvalidation, - options: ['id'], - validation: {options: {id: {required: true}}}, - permissions(frame: Frame) { - return assertCanManageGiftLink(frame); + read: { + headers: noCacheInvalidation, + options: ['id'], + validation: {options: {id: {required: true}}}, + permissions(frame: Frame) { + return assertCanManageGiftLink(frame); + }, + query(frame: Frame) { + return service.getPost(frame.options.id); + } }, - query(frame: Frame) { - return service!.getPost(frame.options.id); - } - }, - issue: { - headers: noCacheInvalidation, - statusCode: 200, - options: ['id'], - validation: {options: {id: {required: true}}}, - permissions(frame: Frame) { - return assertCanManageGiftLink(frame); + issue: { + headers: noCacheInvalidation, + statusCode: 200, + options: ['id'], + validation: {options: {id: {required: true}}}, + permissions(frame: Frame) { + return assertCanManageGiftLink(frame); + }, + query(frame: Frame) { + return service.issue(frame.options.id); + } }, - query(frame: Frame) { - return service!.issue(frame.options.id); - } - }, - reissue: { - headers: noCacheInvalidation, - statusCode: 200, - options: ['id'], - validation: {options: {id: {required: true}}}, - permissions(frame: Frame) { - return assertCanManageGiftLink(frame); + reissue: { + headers: noCacheInvalidation, + statusCode: 200, + options: ['id'], + validation: {options: {id: {required: true}}}, + permissions(frame: Frame) { + return assertCanManageGiftLink(frame); + }, + query(frame: Frame) { + return service.reissue(frame.options.id); + } }, - query(frame: Frame) { - return service!.reissue(frame.options.id); - } - }, - revokeAll: { - headers: noCacheInvalidation, - statusCode: 200, - permissions(frame: Frame) { - return permissionsService.canThis(frame.options.context).revokeAll.gift_link(); - }, - async query() { - const count = await service!.revokeAll(); - return {count}; + revokeAll: { + headers: noCacheInvalidation, + statusCode: 200, + permissions(frame: Frame) { + return permissionsService.canThis(frame.options.context).revokeAll.gift_link(); + }, + async query() { + const count = await service.revokeAll(); + return {count}; + } } - } -}; + }; +} -// module.exports (not export): the API framework loads controllers via require(). -module.exports = controller; +// module.exports (not export): boot (the composition root) calls createController with the +// constructed service and assigns the result to `controller`; the API endpoints index reads it. +module.exports = { + createController, + controller: undefined as ReturnType | undefined +}; diff --git a/ghost/core/core/server/api/endpoints/index.js b/ghost/core/core/server/api/endpoints/index.js index 005acd61f31..8a8b8dec099 100644 --- a/ghost/core/core/server/api/endpoints/index.js +++ b/ghost/core/core/server/api/endpoints/index.js @@ -302,7 +302,7 @@ module.exports = { }, get giftLinks() { - return apiFramework.pipeline(require('./gift-links'), localUtils); + return apiFramework.pipeline(require('./gift-links').controller, localUtils); }, get giftReminders() { diff --git a/ghost/core/core/server/services/gift-links/index.ts b/ghost/core/core/server/services/gift-links/index.ts index fe6fbe31ea3..94a9a7e802c 100644 --- a/ghost/core/core/server/services/gift-links/index.ts +++ b/ghost/core/core/server/services/gift-links/index.ts @@ -1,15 +1 @@ -import {GiftLinksService} from './service'; - -// Set by init() at boot, not at import: knex only exists once the DB has connected. -export let service: GiftLinksService | undefined; - -export function init(): void { - if (service) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-require-imports - const {knex} = require('../../data/db'); - - service = new GiftLinksService({knex}); -} +export {GiftLinksService} from './service';