0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added createdAt prop to Offers API request. (#19012)

refs https://github.com/TryGhost/Product/issues/4153

- We need use the `created_at` timestamp in the new AdminX offers. The
API doesn't return that value.
- With this change the API returns the created_at property so that we
can consume it.
---

<!-- Leave the line below if you'd like GitHub Copilot to generate a
summary from your commit -->
<!--
copilot:summary
-->
### <samp>🤖[[deprecated]](https://githubnext.com/copilot-for-prs-sunset)
Generated by Copilot at dc282af</samp>

This pull request adds a `createdAt` property to the offer domain model,
data transfer object, and repository. This allows tracking and auditing
the creation and modification of offers and offer codes in
`ghost/offers`.
This commit is contained in:
Ronald Langeveld 2023-11-16 17:33:01 +07:00 committed by GitHub
parent e5f644c27f
commit 2bd597fe0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 25 deletions

View file

@ -97,7 +97,6 @@ class OfferBookshelfRepository {
*/
async mapToOffer(model, options) {
const json = model.toJSON();
const count = await this.OfferRedemptionModel.where({offer_id: json.id}).count('id', {
transacting: options.transacting
});
@ -119,7 +118,8 @@ class OfferBookshelfRepository {
tier: {
id: json.product.id,
name: json.product.name
}
},
created_at: json.created_at
}, null);
} catch (err) {
logger.error(err);

View file

@ -171,6 +171,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -195,7 +196,7 @@ exports[`Offers API Can archive an offer 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": "428",
"content-length": "468",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -212,6 +213,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "black-friday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan",
@ -232,6 +234,7 @@ Object {
"amount": 50,
"cadence": "month",
"code": "easter",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -252,6 +255,7 @@ Object {
"amount": 20,
"cadence": "year",
"code": "summer-sale",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -272,6 +276,7 @@ Object {
"amount": 100,
"cadence": "year",
"code": "4th",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": "USD",
"currency_restriction": true,
"display_description": "",
@ -292,6 +297,7 @@ Object {
"amount": 20,
"cadence": "year",
"code": "4th-trial",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -316,7 +322,7 @@ exports[`Offers API Can browse 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": "1863",
"content-length": "2063",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -332,6 +338,7 @@ Object {
"amount": 50,
"cadence": "month",
"code": "easter",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -352,6 +359,7 @@ Object {
"amount": 20,
"cadence": "year",
"code": "summer-sale",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -372,6 +380,7 @@ Object {
"amount": 100,
"cadence": "year",
"code": "4th",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": "USD",
"currency_restriction": true,
"display_description": "",
@ -392,6 +401,7 @@ Object {
"amount": 20,
"cadence": "year",
"code": "4th-trial",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -416,7 +426,7 @@ exports[`Offers API Can browse active 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": "1461",
"content-length": "1621",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -432,6 +442,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -456,7 +467,7 @@ exports[`Offers API Can browse archived 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": "428",
"content-length": "468",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -472,6 +483,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -496,7 +508,7 @@ exports[`Offers API Can edit an offer 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": "426",
"content-length": "466",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -513,6 +525,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "black-friday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan",
@ -537,7 +550,7 @@ exports[`Offers API Can get a single offer 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": "414",
"content-length": "454",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -553,6 +566,7 @@ Object {
"amount": 20,
"cadence": "year",
"code": "4th-trial",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "",
@ -577,7 +591,7 @@ exports[`Offers API Can get a trial offer 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": "384",
"content-length": "424",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -686,6 +700,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -710,7 +725,7 @@ exports[`Offers API Cannot update offer amount 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": "428",
"content-length": "468",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -727,6 +742,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -751,7 +767,7 @@ exports[`Offers API Cannot update offer cadence 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": "428",
"content-length": "468",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
@ -861,6 +877,7 @@ Object {
"amount": 12,
"cadence": "year",
"code": "cyber-monday",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"currency": null,
"currency_restriction": false,
"display_description": "10% off on yearly plan, only today",
@ -885,7 +902,7 @@ exports[`Offers API Cannot update offer tier 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": "428",
"content-length": "468",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,

View file

@ -1,5 +1,5 @@
const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyContentVersion, anyEtag, anyObjectId, anyLocationFor, anyErrorId} = matchers;
const {anyContentVersion, anyEtag, anyObjectId, anyLocationFor, anyErrorId, anyISODateTime} = matchers;
const should = require('should');
const models = require('../../../core/server/models');
const sinon = require('sinon');
@ -322,7 +322,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
});
});
@ -340,7 +341,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
});
});
@ -360,7 +362,8 @@ describe('Offers API', function () {
duration: 'trial',
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
});
});
@ -387,7 +390,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
@ -484,7 +488,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
@ -506,7 +511,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
});
});
@ -525,7 +531,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
});
});
@ -549,7 +556,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
@ -577,7 +585,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
@ -608,7 +617,8 @@ describe('Offers API', function () {
id: anyObjectId,
tier: {
id: anyObjectId
}
},
created_at: anyISODateTime
})
})
.expect(({body}) => {

View file

@ -28,6 +28,7 @@
* @prop {object} tier
* @prop {string} tier.id
* @prop {string} tier.name
* @prop {Date} created_at
*/
class OfferMapper {
@ -54,7 +55,8 @@ class OfferMapper {
tier: {
id: offer.tier.id,
name: offer.tier.name
}
},
created_at: offer.createdAt
};
}
}

View file

@ -13,6 +13,7 @@ const OfferCurrency = require('./OfferCurrency');
const OfferStatus = require('./OfferStatus');
const OfferCreatedEvent = require('../events/OfferCreatedEvent');
const OfferCodeChangeEvent = require('../events/OfferCodeChangeEvent');
const OfferCreatedAt = require('./OfferCreatedAt');
/**
* @typedef {object} OfferProps
@ -29,6 +30,7 @@ const OfferCodeChangeEvent = require('../events/OfferCodeChangeEvent');
* @prop {OfferStatus} status
* @prop {OfferTier} tier
* @prop {number} redemptionCount
* @prop {Date} createdAt
*/
/**
@ -47,6 +49,7 @@ const OfferCodeChangeEvent = require('../events/OfferCodeChangeEvent');
* @prop {string} status
* @prop {number} redemptionCount
* @prop {TierProps|OfferTier} tier
* @prop {Date} createdAt
*/
/**
@ -179,6 +182,10 @@ class Offer {
return !!this.options.isNew;
}
get createdAt() {
return this.props.createdAt;
}
/**
* @param {OfferCode} code
* @param {UniqueChecker} uniqueChecker
@ -275,6 +282,7 @@ class Offer {
const cadence = OfferCadence.create(data.cadence);
const duration = OfferDuration.create(data.duration, data.duration_in_months);
const status = OfferStatus.create(data.status || 'active');
const createdAt = isNew ? new Date().toISOString : OfferCreatedAt.create(data.created_at);
if (isNew && data.redemptionCount !== undefined) {
// TODO correct error
@ -340,7 +348,8 @@ class Offer {
currency,
tier,
redemptionCount,
status
status,
createdAt
}, {isNew});
}
}

View file

@ -0,0 +1,28 @@
const ValueObject = require('./shared/ValueObject');
const InvalidOfferCreatedAt = require('../errors').InvalidOfferCreatedAt;
/** @extends ValueObject<string> */
class OfferCreatedAt extends ValueObject {
/** @param {Date} createdAt */
constructor(createdAt) {
super(createdAt.toISOString()); // Convert Date to ISO string
}
static create(createdAt) {
if (createdAt === null || createdAt === undefined) {
return new Date().toISOString();
}
if (!(createdAt instanceof Date)) {
throw new InvalidOfferCreatedAt({
message: 'Offer `created_at` must be a Date.'
});
}
return createdAt.toISOString();
}
static InvalidOfferCreatedAt = InvalidOfferCreatedAt;
}
module.exports = OfferCreatedAt;