mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Structured the common folder slightly
- gave everything a type in it's file name - grouped things into folders - subject to much change!
This commit is contained in:
parent
38e5935d40
commit
c559c59dd2
19 changed files with 277 additions and 14 deletions
|
@ -1,6 +1,6 @@
|
|||
import {Actor} from './actor';
|
||||
import {Actor} from '../types/actor.type';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import {now} from './date';
|
||||
import {now} from '../helpers/date.helper';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function equals(a: any, b: any) {
|
|
@ -1,5 +1,5 @@
|
|||
import ObjectID from 'bson-objectid';
|
||||
import {Entity} from './entity';
|
||||
import {Entity} from '../base/entity.base';
|
||||
|
||||
export type Page = {
|
||||
page: number;
|
|
@ -1,4 +1,4 @@
|
|||
import {Entity} from '../../common/entity';
|
||||
import {Entity} from '../../common/base/entity.base';
|
||||
|
||||
type SnippetData = {
|
||||
name: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import ObjectID from 'bson-objectid';
|
||||
import {Snippet} from './snippet.entity';
|
||||
import {SnippetsRepository} from './snippets.repository.interface';
|
||||
import {OrderOf, Page} from '../../common/repository';
|
||||
import {OrderOf, Page} from '../../common/interfaces/repository.interface';
|
||||
import nql from '@tryghost/nql';
|
||||
|
||||
export class SnippetsRepositoryInMemory implements SnippetsRepository {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Snippet} from './snippet.entity';
|
||||
import {Repository} from '../../common/repository';
|
||||
import {Repository} from '../../common/interfaces/repository.interface';
|
||||
|
||||
export interface SnippetsRepository extends Repository<Snippet, string, []> {
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {Inject} from '@nestjs/common';
|
||||
import {Knex} from 'knex';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import {OrderOf, Page, Repository} from '../../common/repository';
|
||||
import {OrderOf, Page, Repository} from '../../common/interfaces/repository.interface';
|
||||
import assert from 'assert';
|
||||
import nql from '@tryghost/nql';
|
||||
import {Entity} from '../../common/entity';
|
||||
import {Entity} from '../../common/base/entity.base';
|
||||
|
||||
export abstract class BaseKnexRepository<T extends Entity<unknown>, F, O extends OrderOf<Fields>, Fields extends string[], Row> implements Repository<T, F, Fields> {
|
||||
constructor(@Inject('knex') private readonly knex: Knex) {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {SnippetsRepository} from '../../core/snippets/snippets.repository.interface';
|
||||
import {Snippet} from '../../core/snippets/snippet.entity';
|
||||
import {OrderOf} from '../../common/repository';
|
||||
import {OrderOf} from '../../common/interfaces/repository.interface';
|
||||
import {BaseKnexRepository} from './knex.repository';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import {Knex} from 'knex';
|
||||
|
@ -93,4 +93,3 @@ export class KnexSnippetsRepository
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
ghost/ghost/src/http/admin/controllers/snippet.dto.ts
Normal file
24
ghost/ghost/src/http/admin/controllers/snippet.dto.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {Snippet} from '../../../core/snippets/snippet.entity';
|
||||
|
||||
export class SnippetDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
lexical?: string|null;
|
||||
mobiledoc?: string|null;
|
||||
created_at: Date;
|
||||
updated_at: Date|null;
|
||||
|
||||
constructor(data: Snippet, options: {formats?: 'mobiledoc'|'lexical'}) {
|
||||
this.id = data.id.toString();
|
||||
this.name = data.name;
|
||||
|
||||
if (options.formats === 'mobiledoc') {
|
||||
this.mobiledoc = data.mobiledoc || null;
|
||||
} else {
|
||||
this.lexical = data.lexical || null;
|
||||
}
|
||||
|
||||
this.created_at = data.createdAt;
|
||||
this.updated_at = data.updatedAt || null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import ObjectId from 'bson-objectid';
|
||||
import {Test} from '@nestjs/testing';
|
||||
import {Snippet} from '../../../core/snippets/snippet.entity';
|
||||
import {SnippetsController} from './snippets.controller';
|
||||
import {SnippetsService} from '../../../core/snippets/snippets.service';
|
||||
import {SnippetsRepositoryInMemory} from '../../../core/snippets/snippets.repository.inmemory';
|
||||
import assert from 'assert/strict';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('SnippetsController', () => {
|
||||
let snippetsController: SnippetsController;
|
||||
let snippetsService: SnippetsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [SnippetsController],
|
||||
providers: [
|
||||
{
|
||||
provide: 'SnippetsRepository',
|
||||
useClass: SnippetsRepositoryInMemory
|
||||
},
|
||||
SnippetsService
|
||||
]
|
||||
}).compile();
|
||||
|
||||
snippetsService = moduleRef.get<SnippetsService>(SnippetsService);
|
||||
snippetsController = moduleRef.get<SnippetsController>(SnippetsController);
|
||||
});
|
||||
|
||||
describe('browse', () => {
|
||||
it('should return an array of snippets', async () => {
|
||||
const serviceSnippetResult = {
|
||||
snippets: [
|
||||
new Snippet({
|
||||
id: ObjectId(),
|
||||
deleted: false,
|
||||
name: 'Test',
|
||||
mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Test"]]]]}',
|
||||
lexical: undefined,
|
||||
createdAt: new Date(),
|
||||
updatedAt: null,
|
||||
createdBy: ObjectId(),
|
||||
updatedBy: null
|
||||
})
|
||||
],
|
||||
pagination: {
|
||||
page: 1,
|
||||
limit: 15,
|
||||
pages: 1,
|
||||
total: 1
|
||||
}
|
||||
};
|
||||
|
||||
snippetsService.getAll = sinon.stub().returns(serviceSnippetResult);
|
||||
|
||||
const response = await snippetsController.browse();
|
||||
assert.equal(response.snippets.length, 1);
|
||||
assert.equal(Object.keys(response.snippets[0]).length, 6);
|
||||
assert.equal(response.snippets[0].id, serviceSnippetResult.snippets[0].id.toString());
|
||||
assert.equal(response.snippets[0].name, serviceSnippetResult.snippets[0].name);
|
||||
assert.equal(response.snippets[0].mobiledoc, serviceSnippetResult.snippets[0].mobiledoc);
|
||||
assert.equal(response.snippets[0].lexical, undefined);
|
||||
assert.equal(response.snippets[0].created_at, serviceSnippetResult.snippets[0].createdAt);
|
||||
assert.equal(response.snippets[0].updated_at, serviceSnippetResult.snippets[0].updatedAt);
|
||||
});
|
||||
});
|
||||
});
|
123
ghost/ghost/src/http/admin/controllers/snippets.controller.ts
Normal file
123
ghost/ghost/src/http/admin/controllers/snippets.controller.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import {Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Query, UseFilters, UseInterceptors} from '@nestjs/common';
|
||||
import {SnippetsService} from '../../../core/snippets/snippets.service';
|
||||
import {SnippetDTO} from './snippet.dto';
|
||||
import {Pagination} from '../../../common/types/pagination.type';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import {now} from '../../../common/helpers/date.helper';
|
||||
import {LocationHeaderInterceptor} from '../../interceptors/location-header.interceptor';
|
||||
import {GlobalExceptionFilter} from '../../filters/global-exception.filter';
|
||||
import {NotFoundError} from '@tryghost/errors';
|
||||
|
||||
@Controller('snippets')
|
||||
@UseInterceptors(LocationHeaderInterceptor)
|
||||
@UseFilters(GlobalExceptionFilter)
|
||||
export class SnippetsController {
|
||||
constructor(private readonly service: SnippetsService) {}
|
||||
|
||||
@Get(':id')
|
||||
async read(
|
||||
@Param('id') id: 'string',
|
||||
@Query('formats') formats?: 'mobiledoc' | 'lexical'
|
||||
): Promise<{snippets: [SnippetDTO]}> {
|
||||
const snippet = await this.service.getOne(ObjectID.createFromHexString(id));
|
||||
if (snippet === null) {
|
||||
throw new NotFoundError({
|
||||
context: 'Snippet not found.',
|
||||
message: 'Resource not found error, cannot read snippet.'
|
||||
});
|
||||
}
|
||||
return {
|
||||
snippets: [new SnippetDTO(snippet, {formats})]
|
||||
};
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(204)
|
||||
async destroy(
|
||||
@Param('id') id: 'string'
|
||||
) {
|
||||
const snippet = await this.service.delete(ObjectID.createFromHexString(id));
|
||||
if (snippet === null) {
|
||||
throw new NotFoundError({
|
||||
context: 'Resource could not be found.',
|
||||
message: 'Resource not found error, cannot delete snippet.'
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async edit(
|
||||
@Param('id') id: 'string',
|
||||
@Body() body: any,
|
||||
@Query('formats') formats?: 'mobiledoc' | 'lexical'
|
||||
): Promise<{snippets: [SnippetDTO]}> {
|
||||
const snippet = await this.service.update(ObjectID.createFromHexString(id), body.snippets[0]);
|
||||
if (snippet === null) {
|
||||
throw new NotFoundError({
|
||||
context: 'Snippet not found.',
|
||||
message: 'Resource not found error, cannot read snippet.'
|
||||
});
|
||||
}
|
||||
return {
|
||||
snippets: [new SnippetDTO(snippet, {formats})]
|
||||
};
|
||||
}
|
||||
|
||||
@Post('')
|
||||
async add(
|
||||
@Body() body: any,
|
||||
@Query('formats') formats?: 'mobiledoc' | 'lexical'
|
||||
): Promise<{snippets: [SnippetDTO]}> {
|
||||
const snippet = await this.service.create({
|
||||
...body.snippets[0],
|
||||
updatedAt: now()
|
||||
});
|
||||
|
||||
return {
|
||||
snippets: [new SnippetDTO(snippet, {formats})]
|
||||
};
|
||||
}
|
||||
|
||||
@Get('')
|
||||
async browse(
|
||||
@Query('formats') formats?: 'mobiledoc' | 'lexical',
|
||||
@Query('filter') filter?: string,
|
||||
@Query('page') page: number = 1,
|
||||
@Query('limit') limit: number | 'all' = 15
|
||||
): Promise<{snippets: SnippetDTO[], meta: {pagination: Pagination;};}> {
|
||||
let snippets;
|
||||
let total;
|
||||
if (limit === 'all') {
|
||||
snippets = await this.service.getAll({
|
||||
filter
|
||||
});
|
||||
total = snippets.length;
|
||||
} else {
|
||||
const result = await this.service.getPage({
|
||||
filter,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
total = result.count;
|
||||
snippets = result.data;
|
||||
}
|
||||
const pages = limit === 'all' ? 0 : Math.ceil(total / limit);
|
||||
|
||||
const snippetDTOs = snippets.map(snippet => new SnippetDTO(snippet, {formats}));
|
||||
|
||||
return {
|
||||
snippets: snippetDTOs,
|
||||
meta: {
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages,
|
||||
prev: page > 1 ? page - 1 : null,
|
||||
next: page < pages ? page + 1 : null
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
50
ghost/ghost/src/http/admin/controllers/snippets.dto.test.ts
Normal file
50
ghost/ghost/src/http/admin/controllers/snippets.dto.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import {Snippet} from '../../../core/snippets/snippet.entity';
|
||||
import ObjectId from 'bson-objectid';
|
||||
import assert from 'assert/strict';
|
||||
import {SnippetDTO} from './snippet.dto';
|
||||
|
||||
describe('BrowseSnippetDTO', () => {
|
||||
it('constructs a BrowseSnippetDTO object from a Snippet object with mobiledoc field', async () => {
|
||||
const snippet = new Snippet({
|
||||
id: ObjectId(),
|
||||
deleted: false,
|
||||
name: 'Test',
|
||||
mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Test"]]]]}',
|
||||
lexical: undefined,
|
||||
createdAt: new Date(),
|
||||
updatedAt: null,
|
||||
createdBy: ObjectId(),
|
||||
updatedBy: null
|
||||
});
|
||||
|
||||
const browseSnippetDTO = new SnippetDTO(snippet, {formats: 'mobiledoc'});
|
||||
|
||||
assert(browseSnippetDTO, 'BrowseSnippetDTO object is not null');
|
||||
assert.equal(browseSnippetDTO.id, snippet.id.toString());
|
||||
assert.equal(browseSnippetDTO.name, snippet.name);
|
||||
assert.equal(browseSnippetDTO.mobiledoc, snippet.mobiledoc);
|
||||
assert.equal(browseSnippetDTO.lexical, undefined);
|
||||
});
|
||||
|
||||
it('constructs a BrowseSnippetDTO object from a Snippet object with lexical field', async () => {
|
||||
const snippet = new Snippet({
|
||||
deleted: false,
|
||||
id: ObjectId(),
|
||||
name: 'Test',
|
||||
mobiledoc: undefined,
|
||||
lexical: `{"root":{"children":[{"type":"html","version":1,"html":"<p>hey!</p>"}],"direction":null,"format":"","indent":0,"type":"root","version":1}}`,
|
||||
createdAt: new Date(),
|
||||
updatedAt: null,
|
||||
createdBy: ObjectId(),
|
||||
updatedBy: null
|
||||
});
|
||||
|
||||
const browseSnippetDTO = new SnippetDTO(snippet, {formats: 'lexical'});
|
||||
|
||||
assert(browseSnippetDTO, 'BrowseSnippetDTO object is not null');
|
||||
assert.equal(browseSnippetDTO.id, snippet.id.toString());
|
||||
assert.equal(browseSnippetDTO.name, snippet.name);
|
||||
assert.equal(browseSnippetDTO.mobiledoc, undefined);
|
||||
assert.equal(browseSnippetDTO.lexical, snippet.lexical);
|
||||
});
|
||||
});
|
|
@ -1,9 +1,9 @@
|
|||
import {Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Query, UseFilters, UseInterceptors} from '@nestjs/common';
|
||||
import {SnippetsService} from '../../core/snippets/snippets.service';
|
||||
import {SnippetDTO} from './snippet.dto';
|
||||
import {Pagination} from '../../common/pagination.type';
|
||||
import {Pagination} from '../../common/types/pagination.type';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import {now} from '../../common/date';
|
||||
import {now} from '../../common/helpers/date.helper';
|
||||
import {LocationHeaderInterceptor} from '../interceptors/location-header.interceptor';
|
||||
import {GlobalExceptionFilter} from '../filters/global-exception.filter';
|
||||
import {NotFoundError} from '@tryghost/errors';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'reflect-metadata';
|
||||
import {AppModule} from './nestjs/AppModule';
|
||||
import {NestApplication, NestFactory} from '@nestjs/core';
|
||||
import {registerEvents} from './common/handle-event.decorator';
|
||||
import {registerEvents} from './common/decorators/handle-event.decorator';
|
||||
|
||||
export async function create() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {MilestoneCreatedEvent} from '@tryghost/milestones';
|
||||
import {SlackNotifications} from '@tryghost/slack-notifications';
|
||||
import {OnEvent} from '../common/handle-event.decorator';
|
||||
import {OnEvent} from '../common/decorators/handle-event.decorator';
|
||||
import {Inject} from '@nestjs/common';
|
||||
|
||||
interface IConfig {
|
||||
|
|
Loading…
Add table
Reference in a new issue