Skip to content

Commit

Permalink
chore: add has one relation for todo-list example
Browse files Browse the repository at this point in the history
  • Loading branch information
biniam authored and b-admike committed Dec 4, 2018
1 parent 7c2080a commit 1ad970f
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 8 deletions.
122 changes: 122 additions & 0 deletions examples/todo-list/src/controllers/author.controller.ts
@@ -0,0 +1,122 @@
import {
Count,
CountSchema,
Filter,
repository,
Where,
} from '@loopback/repository';
import {
post,
param,
get,
getFilterSchemaFor,
getWhereSchemaFor,
patch,
del,
requestBody,
} from '@loopback/rest';
import {Author} from '../models';
import {AuthorRepository} from '../repositories';

export class AuthorController {
constructor(
@repository(AuthorRepository)
public authorRepository : AuthorRepository,
) {}

@post('/authors', {
responses: {
'200': {
description: 'Author model instance',
content: {'application/json': {schema: {'x-ts-type': Author}}},
},
},
})
async create(@requestBody() author: Author): Promise<Author> {
return await this.authorRepository.create(author);
}

@get('/authors/count', {
responses: {
'200': {
description: 'Author model count',
content: {'application/json': {schema: CountSchema}},
},
},
})
async count(
@param.query.object('where', getWhereSchemaFor(Author)) where?: Where,
): Promise<Count> {
return await this.authorRepository.count(where);
}

@get('/authors', {
responses: {
'200': {
description: 'Array of Author model instances',
content: {
'application/json': {
schema: {type: 'array', items: {'x-ts-type': Author}},
},
},
},
},
})
async find(
@param.query.object('filter', getFilterSchemaFor(Author)) filter?: Filter,
): Promise<Author[]> {
return await this.authorRepository.find(filter);
}

@patch('/authors', {
responses: {
'200': {
description: 'Author PATCH success count',
content: {'application/json': {schema: CountSchema}},
},
},
})
async updateAll(
@requestBody() author: Author,
@param.query.object('where', getWhereSchemaFor(Author)) where?: Where,
): Promise<Count> {
return await this.authorRepository.updateAll(author, where);
}

@get('/authors/{id}', {
responses: {
'200': {
description: 'Author model instance',
content: {'application/json': {schema: {'x-ts-type': Author}}},
},
},
})
async findById(@param.path.string('id') id: string): Promise<Author> {
return await this.authorRepository.findById(id);
}

@patch('/authors/{id}', {
responses: {
'204': {
description: 'Author PATCH success',
},
},
})
async updateById(
@param.path.string('id') id: string,
@requestBody() author: Author,
): Promise<void> {
await this.authorRepository.updateById(id, author);
}

@del('/authors/{id}', {
responses: {
'204': {
description: 'Author DELETE success',
},
},
})
async deleteById(@param.path.string('id') id: string): Promise<void> {
await this.authorRepository.deleteById(id);
}
}
2 changes: 2 additions & 0 deletions examples/todo-list/src/controllers/index.ts
Expand Up @@ -6,3 +6,5 @@
export * from './todo.controller';
export * from './todo-list.controller';
export * from './todo-list-todo.controller';
export * from './todo-list-author.controller';
export * from './author.controller';
37 changes: 37 additions & 0 deletions examples/todo-list/src/controllers/todo-list-author.controller.ts
@@ -0,0 +1,37 @@
import {TodoListRepository} from '../repositories';
import {repository, Filter} from '@loopback/repository';
import {param, post, requestBody, get} from '@loopback/rest';
import {Author} from '../models';

export class TodoListAuthorController {
constructor(
@repository(TodoListRepository) protected todoListRepo: TodoListRepository,
) {}

@post('/todo-lists/{id}/author', {
responses: {
'200': {
description: 'create Author model instance',
content: {'application/json': {schema: {'x-ts-type': Author}}},
},
},
})
async create(
@param.path.number('id') id: number,
@requestBody() author: Author,
): Promise<Author> {
return await this.todoListRepo.author(id).create(author);
}

@get('/todo-lists/{id}/author', {
responses: {
'200': {
description: 'The author belonging to the TodoList',
content: {'application/json': {schema: {'x-ts-type': Author}}},
},
},
})
async find(@param.path.number('id') id: number): Promise<Author | undefined> {
return await this.todoListRepo.author(id).get();
}
}
26 changes: 26 additions & 0 deletions examples/todo-list/src/models/author.model.ts
@@ -0,0 +1,26 @@
import {Entity, model, property, belongsTo} from '@loopback/repository';
import {TodoList} from './todo-list.model';

@model()
export class Author extends Entity {
@belongsTo(
() => TodoList,
{},
{
id: true,
generated: false,
type: 'string',
},
)
todoListId: string;

@property({
type: 'string',
required: true,
})
name: string;

constructor(data?: Partial<Author>) {
super(data);
}
}
1 change: 1 addition & 0 deletions examples/todo-list/src/models/index.ts
Expand Up @@ -5,3 +5,4 @@

export * from './todo.model';
export * from './todo-list.model';
export * from './author.model';
6 changes: 5 additions & 1 deletion examples/todo-list/src/models/todo-list.model.ts
Expand Up @@ -3,8 +3,9 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Entity, model, property, hasMany} from '@loopback/repository';
import {Entity, model, property, hasMany, hasOne} from '@loopback/repository';
import {Todo} from './todo.model';
import {Author} from './author.model';

@model()
export class TodoList extends Entity {
Expand All @@ -28,6 +29,9 @@ export class TodoList extends Entity {
@hasMany(() => Todo)
todos: Todo[];

@hasOne(() => Author)
author: Author;

constructor(data?: Partial<TodoList>) {
super(data);
}
Expand Down
31 changes: 31 additions & 0 deletions examples/todo-list/src/repositories/author.repository.ts
@@ -0,0 +1,31 @@
import {
DefaultCrudRepository,
juggler,
repository,
BelongsToAccessor,
} from '@loopback/repository';
import {Author, TodoList} from '../models';
import {DbDataSource} from '../datasources';
import {inject, Getter} from '@loopback/core';
import {TodoListRepository} from './todo-list.repository';

export class AuthorRepository extends DefaultCrudRepository<
Author,
typeof Author.prototype.todoListId
> {
public readonly todoList: BelongsToAccessor<
TodoList,
typeof Author.prototype.todoListId
>;
constructor(
@inject('datasources.db') dataSource: DbDataSource,
@repository.getter('TodoListRepository')
protected todoListRepositoryGetter: Getter<TodoListRepository>,
) {
super(Author, dataSource);
this.todoList = this._createBelongsToAccessorFor(
'todoList',
todoListRepositoryGetter,
);
}
}
1 change: 1 addition & 0 deletions examples/todo-list/src/repositories/index.ts
Expand Up @@ -5,3 +5,4 @@

export * from './todo.repository';
export * from './todo-list.repository';
export * from './author.repository';
16 changes: 14 additions & 2 deletions examples/todo-list/src/repositories/todo-list.repository.ts
Expand Up @@ -9,9 +9,11 @@ import {
HasManyRepositoryFactory,
juggler,
repository,
HasOneRepositoryFactory,
} from '@loopback/repository';
import {Todo, TodoList} from '../models';
import {Todo, TodoList, Author} from '../models';
import {TodoRepository} from './todo.repository';
import {AuthorRepository} from './author.repository';

export class TodoListRepository extends DefaultCrudRepository<
TodoList,
Expand All @@ -21,17 +23,27 @@ export class TodoListRepository extends DefaultCrudRepository<
Todo,
typeof TodoList.prototype.id
>;
public readonly author: HasOneRepositoryFactory<
Author,
typeof TodoList.prototype.id
>;

constructor(
@inject('datasources.db') dataSource: juggler.DataSource,
@repository.getter(TodoRepository)
@repository.getter('TodoRepository')
protected todoRepositoryGetter: Getter<TodoRepository>,
@repository.getter('AuthorRepository')
protected todoListImageRepositoryGetter: Getter<AuthorRepository>,
) {
super(TodoList, dataSource);
this.todos = this._createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);
this.author = this._createHasOneRepositoryFactoryFor(
'author',
todoListImageRepositoryGetter,
);
}

public findByTitle(title: string) {
Expand Down
Expand Up @@ -12,6 +12,7 @@ import {
constrainFilter,
} from '../../repositories/constraint-utils';
import {EntityCrudRepository} from '../../repositories/repository';
import {EntityNotFoundError} from '../../errors';

/**
* CRUD operations for a target repository of a HasMany relation
Expand Down Expand Up @@ -70,13 +71,17 @@ export class DefaultHasOneRepository<
async get(
filter?: Exclude<Filter<TargetEntity>, Where<TargetEntity>>,
options?: Options,
): Promise<TargetEntity | undefined> {
): Promise<TargetEntity> {
const targetRepository = await this.getTargetRepository();
const found = await targetRepository.find(
Object.assign({limit: 1}, constrainFilter(filter, this.constraint)),
options,
);
// TODO: throw EntityNotFound error when target model instance not found
if (found.length < 1) {
// We don't have a direct access to the foreign key value here :(
const id = 'constraint ' + JSON.stringify(this.constraint);
throw new EntityNotFoundError(targetRepository.entityClass, id);
}
return found[0];
}
}
Expand Up @@ -58,17 +58,15 @@ describe('hasOne relation', () => {
expect(
controller.createCustomerAddress(existingCustomerId, {
street: '456 test street',
customerId: 44012,
}),
).to.be.rejectedWith(/Property "customerId" cannot be changed!/);
).to.be.rejectedWith(/Duplicate entry for Address.customerId/);
expect(address.toObject()).to.containDeep({
customerId: existingCustomerId,
street: '123 test avenue',
});

const persisted = await addressRepo.findById(address.customerId);
expect(persisted.toObject()).to.deepEqual(address.toObject());
expect(addressRepo.findById(44012)).to.be.rejectedWith(/Entity not found/);
});

it('can find instance of the related model', async () => {
Expand Down

0 comments on commit 1ad970f

Please sign in to comment.