mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-04 02:01:58 -05:00
Refactored collections entity logic
refs https://github.com/TryGhost/Team/issues/3294 - The factory method for the Collection and validations should live close together based on our latest architectural direction
This commit is contained in:
parent
8fd2468995
commit
fdd73d01b7
7 changed files with 149 additions and 75 deletions
|
@ -10,7 +10,7 @@
|
||||||
"dev": "tsc --watch --preserveWatchOutput --sourceMap",
|
"dev": "tsc --watch --preserveWatchOutput --sourceMap",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"prepare": "tsc",
|
"prepare": "tsc",
|
||||||
"test:unit": "NODE_ENV=testing c8 --src src --all --exclude 'src/Collection.ts' --exclude 'test/' --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
|
"test:unit": "NODE_ENV=testing c8 --src src --all --exclude 'test/' --check-coverage --100 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
|
||||||
"test": "yarn test:types && yarn test:unit",
|
"test": "yarn test:types && yarn test:unit",
|
||||||
"test:types": "tsc --noEmit",
|
"test:types": "tsc --noEmit",
|
||||||
"lint:code": "eslint src/ --ext .ts --cache",
|
"lint:code": "eslint src/ --ext .ts --cache",
|
||||||
|
|
|
@ -1,15 +1,79 @@
|
||||||
// @NOTE: file names having only type declarations should also
|
// have to use requires until there are type definitions for these modules
|
||||||
// be uppercased
|
|
||||||
/* eslint-disable ghost/filenames/match-regex */
|
|
||||||
|
|
||||||
export type Collection = {
|
const {ValidationError} = require('@tryghost/errors');
|
||||||
|
const tpl = require('@tryghost/tpl');
|
||||||
|
|
||||||
|
import ObjectID from 'bson-objectid';
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
invalidIDProvided: 'Invalid ID provided for Collection',
|
||||||
|
invalidDateProvided: 'Invalid date provided for {fieldName}'
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Collection {
|
||||||
id: string;
|
id: string;
|
||||||
// @NOTE: this field feels out of place here and needs clarification
|
|
||||||
// it's here for now to implement the InMemoryRepository pattern
|
|
||||||
deleted: boolean;
|
|
||||||
title: string;
|
title: string;
|
||||||
description: string,
|
slug: string;
|
||||||
|
description: string;
|
||||||
type: 'manual' | 'automatic';
|
type: 'manual' | 'automatic';
|
||||||
filter: string | null;
|
filter: string | null;
|
||||||
feature_image: string | null;
|
featureImage: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
deleted: boolean;
|
||||||
|
|
||||||
|
private constructor(data: any) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.title = data.title;
|
||||||
|
this.slug = data.slug;
|
||||||
|
this.description = data.description;
|
||||||
|
this.type = data.type;
|
||||||
|
this.filter = data.filter;
|
||||||
|
this.featureImage = data.featureImage;
|
||||||
|
this.createdAt = data.createdAt;
|
||||||
|
this.updatedAt = data.updatedAt;
|
||||||
|
this.deleted = data.deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateDateField(date: any, fieldName: string): Date {
|
||||||
|
if (!date) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date instanceof Date) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ValidationError({
|
||||||
|
message: tpl(messages.invalidDateProvided, {fieldName})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(data: any): Promise<Collection> {
|
||||||
|
let id;
|
||||||
|
|
||||||
|
if (!data.id) {
|
||||||
|
id = new ObjectID();
|
||||||
|
} else if (typeof data.id === 'string') {
|
||||||
|
id = ObjectID.createFromHexString(data.id);
|
||||||
|
} else if (data.id instanceof ObjectID) {
|
||||||
|
id = data.id;
|
||||||
|
} else {
|
||||||
|
throw new ValidationError({
|
||||||
|
message: tpl(messages.invalidIDProvided)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Collection({
|
||||||
|
id: id.toHexString(),
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
type: data.type,
|
||||||
|
filter: data.filter,
|
||||||
|
featureImage: data.feature_image,
|
||||||
|
createdAt: Collection.validateDateField(data.created_at, 'created_at'),
|
||||||
|
updatedAt: Collection.validateDateField(data.updated_at, 'updated_at'),
|
||||||
|
deleted: data.deleted || false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,16 @@
|
||||||
// have to use requires until there are type definitions for these modules
|
|
||||||
const {ValidationError} = require('@tryghost/errors');
|
|
||||||
const tpl = require('@tryghost/tpl');
|
|
||||||
|
|
||||||
import ObjectID from 'bson-objectid';
|
|
||||||
import {InMemoryRepository} from '@tryghost/in-memory-repository';
|
import {InMemoryRepository} from '@tryghost/in-memory-repository';
|
||||||
import {Collection} from './Collection';
|
import {Collection} from './Collection';
|
||||||
|
|
||||||
const messages = {
|
|
||||||
invalidIDProvided: 'Invalid ID provided for Collection'
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CollectionsRepositoryInMemory extends InMemoryRepository<string, Collection> {
|
export class CollectionsRepositoryInMemory extends InMemoryRepository<string, Collection> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: any): Promise<Collection> {
|
|
||||||
let id;
|
|
||||||
|
|
||||||
if (!data.id) {
|
|
||||||
id = new ObjectID();
|
|
||||||
} else if (typeof data.id === 'string') {
|
|
||||||
id = ObjectID.createFromHexString(data.id);
|
|
||||||
} else if (data.id instanceof ObjectID) {
|
|
||||||
id = data.id;
|
|
||||||
} else {
|
|
||||||
throw new ValidationError({
|
|
||||||
message: tpl(messages.invalidIDProvided)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: id.toHexString(),
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
type: data.type,
|
|
||||||
filter: data.filter,
|
|
||||||
feature_image: data.feature_image,
|
|
||||||
deleted: data.deleted || false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected toPrimitive(entity: Collection): object {
|
protected toPrimitive(entity: Collection): object {
|
||||||
return {
|
return {
|
||||||
title: entity.title,
|
title: entity.title,
|
||||||
description: entity.description,
|
description: entity.description,
|
||||||
feature_image: entity.feature_image
|
feature_image: entity.featureImage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export class CollectionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(data: any): Promise<Collection> {
|
async save(data: any): Promise<Collection> {
|
||||||
const collection = await this.repository.create(data);
|
const collection = await Collection.create(data);
|
||||||
await this.repository.save(collection);
|
await this.repository.save(collection);
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './CollectionsService';
|
export * from './CollectionsService';
|
||||||
export * from './CollectionsRepositoryInMemory';
|
export * from './CollectionsRepositoryInMemory';
|
||||||
|
export * from './Collection';
|
||||||
|
|
72
ghost/collections/test/Collection.test.ts
Normal file
72
ghost/collections/test/Collection.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import assert from 'assert';
|
||||||
|
import ObjectID from 'bson-objectid';
|
||||||
|
import {Collection} from '../src/index';
|
||||||
|
|
||||||
|
describe('Collection', function () {
|
||||||
|
it('Create Collection entity', async function () {
|
||||||
|
const collection = await Collection.create({
|
||||||
|
title: 'Test Collection'
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(collection instanceof Collection);
|
||||||
|
assert.ok(collection.id, 'generated id should be set');
|
||||||
|
assert.ok(ObjectID.isValid(collection.id), 'generated id should be valid ObjectID');
|
||||||
|
|
||||||
|
assert.equal(collection.title, 'Test Collection');
|
||||||
|
assert.ok(collection.createdAt instanceof Date);
|
||||||
|
assert.ok(collection.updatedAt instanceof Date);
|
||||||
|
assert.ok((collection.deleted === false), 'deleted should be false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can create a Collection with predefined ID', async function () {
|
||||||
|
const id = new ObjectID();
|
||||||
|
const savedCollection = await Collection.create({
|
||||||
|
id: id.toHexString()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(savedCollection.id, id.toHexString(), 'Collection should have same id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can create a Collection with predefined ObjectID instance', async function () {
|
||||||
|
const id = new ObjectID();
|
||||||
|
const savedCollection = await Collection.create({
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(savedCollection.id, id.toHexString(), 'Collection should have same id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can create a Collection with predefined created_at and updated_at values', async function () {
|
||||||
|
const createdAt = new Date();
|
||||||
|
const updatedAt = new Date();
|
||||||
|
const savedCollection = await Collection.create({
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: updatedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(savedCollection.createdAt, createdAt, 'Collection should have same created_at');
|
||||||
|
assert.equal(savedCollection.updatedAt, updatedAt, 'Collection should have same updated_at');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Throws an error when trying to create a Collection with an invalid ID', async function () {
|
||||||
|
assert.rejects(async () => {
|
||||||
|
await Collection.create({
|
||||||
|
id: 12345
|
||||||
|
});
|
||||||
|
}, (err: any) => {
|
||||||
|
assert.equal(err.message, 'Invalid ID provided for Collection', 'Error message should match');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Throws an error when trying to create a Collection with invalid created_at date', async function () {
|
||||||
|
assert.rejects(async () => {
|
||||||
|
await Collection.create({
|
||||||
|
created_at: 'invalid date'
|
||||||
|
});
|
||||||
|
}, (err: any) => {
|
||||||
|
assert.equal(err.message, 'Invalid date provided for created_at', 'Error message should match');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -40,34 +40,6 @@ describe('collections', function () {
|
||||||
assert.equal(deletedCollection, null, 'Collection should be deleted');
|
assert.equal(deletedCollection, null, 'Collection should be deleted');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can create a collection with predefined ID', async function () {
|
|
||||||
const id = new ObjectID();
|
|
||||||
const savedCollection = await collectionsService.save({
|
|
||||||
id: id.toHexString()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(savedCollection.id, id.toHexString(), 'Collection should have same id');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Can create a collection with predefined ObjectID instance', async function () {
|
|
||||||
const id = new ObjectID();
|
|
||||||
const savedCollection = await collectionsService.save({
|
|
||||||
id: id
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(savedCollection.id, id.toHexString(), 'Collection should have same id');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Throws an error when trying to save a collection with an invalid ID', async function () {
|
|
||||||
try {
|
|
||||||
await collectionsService.save({
|
|
||||||
id: 12345
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
assert.equal(error.message, 'Invalid ID provided for Collection', 'Error message should match');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('edit', function () {
|
describe('edit', function () {
|
||||||
it('Can edit existing collection', async function () {
|
it('Can edit existing collection', async function () {
|
||||||
const savedCollection = await collectionsService.save({
|
const savedCollection = await collectionsService.save({
|
||||||
|
|
Loading…
Add table
Reference in a new issue