Added bio to members api (#15168)

refs https://github.com/TryGhost/Team/issues/1716

- Adds the bio field to the API output
- Allow setting bio when updating the member
- Includes new E2E tests for the members API that were missing
This commit is contained in:
Simon Backx 2022-08-04 15:51:23 +02:00 committed by GitHub
parent f4a31aae7d
commit bac8f4b8db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 480 additions and 2 deletions

View File

@ -116,7 +116,7 @@ const updateMemberNewsletters = async function (req, res) {
const updateMemberData = async function (req, res) {
try {
const data = _.pick(req.body, 'name', 'subscribed', 'newsletters', 'enable_comment_notifications');
const data = _.pick(req.body, 'name', 'bio', 'subscribed', 'newsletters', 'enable_comment_notifications');
const member = await membersService.ssr.getMemberDataFromSession(req, res);
if (member) {
const options = {

View File

@ -13,6 +13,7 @@ module.exports.formattedMemberResponse = function formattedMemberResponse(member
email: member.email,
name: member.name,
firstname: member.name && member.name.split(' ')[0],
bio: member.bio,
avatar_image: member.avatar_image,
subscribed: !!member.subscribed,
subscriptions: member.subscriptions || [],

View File

@ -0,0 +1,291 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Comments API when authenticated can get member data 1: [body] 1`] = `
Object {
"avatar_image": null,
"bio": null,
"email": "member@example.com",
"enable_comment_notifications": true,
"firstname": null,
"name": null,
"newsletters": Array [
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sort_order": 0,
},
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"sort_order": 2,
},
],
"paid": false,
"subscribed": false,
"subscriptions": Array [],
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when authenticated can get member data 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "430",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when authenticated can update comment notifications 1: [body] 1`] = `
Object {
"avatar_image": null,
"bio": "Head of Testing",
"email": "member@example.com",
"enable_comment_notifications": false,
"firstname": "Test",
"name": "Test User",
"newsletters": Array [
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sort_order": 0,
},
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"sort_order": 2,
},
],
"paid": false,
"subscribed": false,
"subscriptions": Array [],
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when authenticated can update comment notifications 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "453",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when authenticated can update comment notifications 3: [body] 1`] = `
Object {
"email": "member@example.com",
"enable_comment_notifications": true,
"name": "Test User",
"newsletters": Array [
Object {
"body_font_category": "sans_serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sender_email": null,
"sender_name": null,
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "default-newsletter",
"sort_order": 0,
"status": "active",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "sans_serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
Object {
"body_font_category": "serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": "http://127.0.0.1:2369/content/images/2022/05/test.jpg",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"sender_email": "jamie@example.com",
"sender_name": "Jamie",
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "weekly-newsletter",
"sort_order": 2,
"status": "active",
"subscribe_on_signup": true,
"title_alignment": "center",
"title_font_category": "serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
"status": "free",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when authenticated can update comment notifications 4: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "1506",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when authenticated can update member bio 1: [body] 1`] = `
Object {
"avatar_image": null,
"bio": "Head of Testing",
"email": "member@example.com",
"enable_comment_notifications": true,
"firstname": null,
"name": null,
"newsletters": Array [
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sort_order": 0,
},
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"sort_order": 2,
},
],
"paid": false,
"subscribed": false,
"subscriptions": Array [],
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when authenticated can update member bio 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "443",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when authenticated can update name 1: [body] 1`] = `
Object {
"avatar_image": null,
"bio": "Head of Testing",
"email": "member@example.com",
"enable_comment_notifications": true,
"firstname": "Test",
"name": "Test User",
"newsletters": Array [
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Default Newsletter",
"sort_order": 0,
},
Object {
"description": null,
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Weekly newsletter",
"sort_order": 2,
},
],
"paid": false,
"subscribed": false,
"subscriptions": Array [],
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when authenticated can update name 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "452",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`Comments API when not authenticated but enabled can update comment notifications 1: [body] 1`] = `
Object {
"email": "member1@test.com",
"enable_comment_notifications": false,
"name": "Mr Egg",
"newsletters": Array [
Object {
"body_font_category": "serif",
"created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"description": null,
"footer_content": null,
"header_image": "http://127.0.0.1:2369/content/images/2022/05/test.jpg",
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
"name": "Daily newsletter",
"sender_email": "jamie@example.com",
"sender_name": "Jamie",
"sender_reply_to": "newsletter",
"show_badge": true,
"show_feature_image": true,
"show_header_icon": true,
"show_header_name": true,
"show_header_title": true,
"slug": "daily-newsletter",
"sort_order": 1,
"status": "active",
"subscribe_on_signup": false,
"title_alignment": "center",
"title_font_category": "serif",
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"visibility": "members",
},
],
"status": "free",
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
}
`;
exports[`Comments API when not authenticated but enabled can update comment notifications 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "*",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "858",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Encoding",
"x-powered-by": "Express",
}
`;

View File

@ -0,0 +1,181 @@
const {agentProvider, mockManager, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyEtag, anyObjectId, anyUuid, anyISODateTime} = matchers;
const models = require('../../../core/server/models');
require('should');
let membersAgent;
const memberMatcher = (newslettersCount) => {
return {
uuid: anyUuid,
newsletters: new Array(newslettersCount).fill(
{
id: anyObjectId
}
)
};
};
// @todo: we currently don't serialise the output of /api/member/newsletters/, we should fix this
const memberMatcherUnserialised = (newslettersCount) => {
return {
uuid: anyUuid,
newsletters: new Array(newslettersCount).fill(
{
id: anyObjectId,
uuid: anyUuid,
created_at: anyISODateTime,
updated_at: anyISODateTime
}
)
};
};
describe('Comments API', function () {
before(async function () {
membersAgent = await agentProvider.getMembersAPIAgent();
await fixtureManager.init('newsletters', 'members:newsletters');
});
beforeEach(function () {
mockManager.mockMail();
});
afterEach(function () {
mockManager.restore();
});
describe('when not authenticated but enabled', function () {
it('can not get member data', async function () {
await membersAgent
.get(`/api/member/`)
.expectStatus(204)
.expectEmptyBody();
});
it('can update comment notifications', async function () {
// Only via updateMemberNewsletters
let member = await models.Member.findOne({id: fixtureManager.get('members', 0).id}, {require: true});
member.get('enable_comment_notifications').should.eql(true, 'This test requires the initial value to be true');
await membersAgent
.put(`/api/member/newsletters/?uuid=${member.get('uuid')}`)
.body({
enable_comment_notifications: false
})
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcherUnserialised(1))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
body.enable_comment_notifications.should.eql(false);
});
member = await models.Member.findOne({id: member.id}, {require: true});
member.get('enable_comment_notifications').should.eql(false);
});
});
describe('when authenticated', function () {
let member;
before(async function () {
await membersAgent.loginAs('member@example.com');
member = await models.Member.findOne({email: 'member@example.com'}, {require: true});
});
it('can get member data', async function () {
await membersAgent
.get(`/api/member/`)
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcher(2))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
});
});
it('can update member bio', async function () {
await membersAgent
.put(`/api/member/`)
.body({
bio: 'Head of Testing'
})
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcher(2))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
body.bio.should.eql('Head of Testing');
});
member = await models.Member.findOne({id: member.id}, {require: true});
member.get('bio').should.eql('Head of Testing');
});
it('can update name', async function () {
await membersAgent
.put(`/api/member/`)
.body({
name: 'Test User'
})
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcher(2))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
body.name.should.eql('Test User');
body.firstname.should.eql('Test');
});
member = await models.Member.findOne({id: member.id}, {require: true});
member.get('name').should.eql('Test User');
});
it('can update comment notifications', async function () {
member.get('enable_comment_notifications').should.eql(true, 'This test requires the initial value to be true');
// Via general way
await membersAgent
.put(`/api/member/`)
.body({
enable_comment_notifications: false
})
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcher(2))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
body.enable_comment_notifications.should.eql(false);
});
member = await models.Member.findOne({id: member.id}, {require: true});
member.get('enable_comment_notifications').should.eql(false);
// Via updateMemberNewsletters
await membersAgent
.put(`/api/member/newsletters/?uuid=${member.get('uuid')}`)
.body({
enable_comment_notifications: true
})
.expectStatus(200)
.matchHeaderSnapshot({
etag: anyEtag
})
.matchBodySnapshot(memberMatcherUnserialised(2))
.expect(({body}) => {
body.email.should.eql(member.get('email'));
body.enable_comment_notifications.should.eql(true);
});
member = await models.Member.findOne({id: member.id}, {require: true});
member.get('enable_comment_notifications').should.eql(true);
});
});
});

View File

@ -19,6 +19,7 @@ describe('Members Service - utils', function () {
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
bio: null,
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,
status: 'free',
@ -29,6 +30,7 @@ describe('Members Service - utils', function () {
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
bio: null,
firstname: 'Jamie',
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,
@ -43,6 +45,7 @@ describe('Members Service - utils', function () {
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
bio: 'Hello world',
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,
status: 'comped',
@ -61,6 +64,7 @@ describe('Members Service - utils', function () {
uuid: 'uuid-1',
email: 'jamie+1@example.com',
name: 'Jamie Larson',
bio: 'Hello world',
firstname: 'Jamie',
avatar_image: 'https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank',
subscribed: true,

View File

@ -319,7 +319,8 @@ module.exports = class MemberRepository {
'newsletters',
'enable_comment_notifications',
'last_seen_at',
'last_commented_at'
'last_commented_at',
'bio'
]);
// Determine if we need to fetch the initial member with relations