mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 03:14:03 +03:00
Added use of a slug generator to offload calculation of slugs
When we end up wiring this to the database, this generator will also ensure uniqueness by appending/incrementing a number on the end of the slug. Long term it would be good to offload this to a shared slug service, this could also ensure that slugs are unique globally or between multiple tables, if desired
This commit is contained in:
parent
0a3e36cd62
commit
cf83d169db
@ -20,9 +20,14 @@ const messages = {
|
||||
}
|
||||
};
|
||||
|
||||
interface SlugService {
|
||||
generate(desired: string): Promise<string>;
|
||||
}
|
||||
|
||||
type CollectionsServiceDeps = {
|
||||
collectionsRepository: CollectionRepository;
|
||||
postsRepository: PostsRepository;
|
||||
slugService: SlugService;
|
||||
DomainEvents: {
|
||||
subscribe: (event: any, handler: (e: any) => void) => void;
|
||||
};
|
||||
@ -96,12 +101,14 @@ export class CollectionsService {
|
||||
subscribe: (event: any, handler: (e: any) => void) => void;
|
||||
};
|
||||
private uniqueChecker: RepositoryUniqueChecker;
|
||||
private slugService: SlugService;
|
||||
|
||||
constructor(deps: CollectionsServiceDeps) {
|
||||
this.collectionsRepository = deps.collectionsRepository;
|
||||
this.postsRepository = deps.postsRepository;
|
||||
this.DomainEvents = deps.DomainEvents;
|
||||
this.uniqueChecker = new RepositoryUniqueChecker(this.collectionsRepository);
|
||||
this.slugService = deps.slugService;
|
||||
}
|
||||
|
||||
private toDTO(collection: Collection): CollectionDTO {
|
||||
@ -165,9 +172,10 @@ export class CollectionsService {
|
||||
}
|
||||
|
||||
async createCollection(data: CollectionInputDTO): Promise<CollectionDTO> {
|
||||
const slug = await this.slugService.generate(data.slug || data.title);
|
||||
const collection = await Collection.create({
|
||||
title: data.title,
|
||||
slug: data.slug,
|
||||
slug: slug,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
filter: data.filter,
|
||||
|
@ -41,7 +41,12 @@ describe('CollectionsService', function () {
|
||||
collectionsService = new CollectionsService({
|
||||
collectionsRepository,
|
||||
postsRepository,
|
||||
DomainEvents
|
||||
DomainEvents,
|
||||
slugService: {
|
||||
async generate(input) {
|
||||
return input.replace(/\s+/g, '-').toLowerCase();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -199,10 +204,12 @@ describe('CollectionsService', function () {
|
||||
id: savedCollection.id,
|
||||
title: 'Edited title',
|
||||
description: 'Edited description',
|
||||
feature_image: '/assets/images/edited.jpg'
|
||||
feature_image: '/assets/images/edited.jpg',
|
||||
slug: 'changed'
|
||||
});
|
||||
|
||||
assert.equal(editedCollection?.title, 'Edited title', 'Collection title should be edited');
|
||||
assert.equal(editedCollection?.slug, 'changed', 'Collection slug should be edited');
|
||||
assert.equal(editedCollection?.description, 'Edited description', 'Collection description should be edited');
|
||||
assert.equal(editedCollection?.feature_image, '/assets/images/edited.jpg', 'Collection feature_image should be edited');
|
||||
assert.equal(editedCollection?.type, 'manual', 'Collection type should not be edited');
|
||||
|
@ -16,7 +16,12 @@ class CollectionsServiceWrapper {
|
||||
const collectionsService = new CollectionsService({
|
||||
collectionsRepository: collectionsRepositoryInMemory,
|
||||
postsRepository: postsRepository,
|
||||
DomainEvents: DomainEvents
|
||||
DomainEvents: DomainEvents,
|
||||
slugService: {
|
||||
async generate(input) {
|
||||
return input.toLowerCase().trim().replace(/^[^a-z0-9A-Z_]+/, '').replace(/[^a-z0-9A-Z_]+$/, '').replace(/[^a-z0-9A-Z_]+/g, '-');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.api = collectionsService;
|
||||
|
@ -19,7 +19,8 @@ Object {
|
||||
"sort_order": 1,
|
||||
},
|
||||
],
|
||||
"title": "Test Collection",
|
||||
"slug": "test-featured-collection",
|
||||
"title": "Test Featured Collection",
|
||||
"type": "automatic",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
},
|
||||
@ -31,7 +32,7 @@ exports[`Collections API Automatic Collection Filtering Creates an automatic Col
|
||||
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": "374",
|
||||
"content-length": "417",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -81,6 +82,7 @@ Object {
|
||||
"sort_order": 6,
|
||||
},
|
||||
],
|
||||
"slug": "test-collection-with-published_at-filter",
|
||||
"title": "Test Collection with published_at filter",
|
||||
"type": "automatic",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -93,7 +95,7 @@ exports[`Collections API Automatic Collection Filtering Creates an automatic Col
|
||||
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": "681",
|
||||
"content-length": "731",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -192,6 +194,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection",
|
||||
"title": "Test Collection",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -203,6 +206,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection-to-read",
|
||||
"title": "Test Collection to Read",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -225,7 +229,7 @@ exports[`Collections API Browse Can browse Collections 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": "1759",
|
||||
"content-length": "1817",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -833,6 +837,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection",
|
||||
"title": "Test Collection",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -845,7 +850,7 @@ exports[`Collections API Can add a Collection 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": "263",
|
||||
"content-length": "288",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -866,6 +871,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection-to-delete",
|
||||
"title": "Test Collection to Delete",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -878,7 +884,7 @@ exports[`Collections API Can delete a Collection 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": "248",
|
||||
"content-length": "283",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -944,6 +950,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection-to-read",
|
||||
"title": "Test Collection to Read",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -956,7 +963,7 @@ exports[`Collections API Can read a Collection 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": "246",
|
||||
"content-length": "279",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -977,6 +984,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection-to-read",
|
||||
"title": "Test Collection to Read",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -989,7 +997,7 @@ exports[`Collections API Can read a Collection 4: [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": "246",
|
||||
"content-length": "279",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -1039,6 +1047,7 @@ Object {
|
||||
"filter": null,
|
||||
"id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"posts": Array [],
|
||||
"slug": "test-collection-to-edit",
|
||||
"title": "Test Collection Edited",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
@ -1051,7 +1060,7 @@ exports[`Collections API Edit Can edit a Collection 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": "245",
|
||||
"content-length": "278",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
@ -1326,6 +1326,7 @@ Object {
|
||||
"sort_order": 0,
|
||||
},
|
||||
],
|
||||
"slug": "collection-to-remove",
|
||||
"title": "Collection to remove.",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/,
|
||||
@ -1420,7 +1421,7 @@ exports[`Posts API Update Can add and remove collections 4: [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": "3992",
|
||||
"content-length": "4022",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
@ -1451,6 +1452,7 @@ Object {
|
||||
"sort_order": 0,
|
||||
},
|
||||
],
|
||||
"slug": "collection-to-add",
|
||||
"title": "Collection to add.",
|
||||
"type": "manual",
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.\\\\d\\{3\\}Z/,
|
||||
@ -1545,7 +1547,7 @@ exports[`Posts API Update Can add and remove collections 6: [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": "3989",
|
||||
"content-length": "4016",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
@ -307,7 +307,7 @@ describe('Collections API', function () {
|
||||
describe('Automatic Collection Filtering', function () {
|
||||
it('Creates an automatic Collection with a featured filter', async function () {
|
||||
const collection = {
|
||||
title: 'Test Collection',
|
||||
title: 'Test Featured Collection',
|
||||
description: 'Test Collection Description',
|
||||
type: 'automatic',
|
||||
filter: 'featured:true'
|
||||
|
Loading…
Reference in New Issue
Block a user