From ae1631239dfeadf43e3938539246300faf67e584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 22 Jan 2018 13:31:18 +0100 Subject: [PATCH] Add a deprecation message pointing to our monorepo --- .gitignore | 66 --- .npmrc | 1 - .travis.yml | 4 - README.md | 411 +----------------- config/datasources.json | 5 - data/db.json | 17 - index.d.ts | 1 - index.js | 14 - index.ts | 1 - package.json | 39 -- src/application.ts | 49 --- src/controllers/index.ts | 1 - src/controllers/todo.controller.ts | 54 --- src/datasources/db.datasource.ts | 13 - src/index.ts | 16 - src/models/index.ts | 1 - src/models/todo.model.ts | 57 --- src/repositories/index.ts | 1 - src/repositories/todo.repository.ts | 12 - test/acceptance/application.test.ts | 127 ------ test/helpers.ts | 36 -- test/unit/controllers/todo.controller.test.ts | 151 ------- tsconfig.json | 14 - 23 files changed, 2 insertions(+), 1089 deletions(-) delete mode 100644 .gitignore delete mode 100644 .npmrc delete mode 100644 .travis.yml delete mode 100644 config/datasources.json delete mode 100644 data/db.json delete mode 100644 index.d.ts delete mode 100644 index.js delete mode 100644 index.ts delete mode 100644 package.json delete mode 100644 src/application.ts delete mode 100644 src/controllers/index.ts delete mode 100644 src/controllers/todo.controller.ts delete mode 100644 src/datasources/db.datasource.ts delete mode 100644 src/index.ts delete mode 100644 src/models/index.ts delete mode 100644 src/models/todo.model.ts delete mode 100644 src/repositories/index.ts delete mode 100644 src/repositories/todo.repository.ts delete mode 100644 test/acceptance/application.test.ts delete mode 100644 test/helpers.ts delete mode 100644 test/unit/controllers/todo.controller.test.ts delete mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 496a8d7..0000000 --- a/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Typescript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# Generated apidocs -api-docs/ - -# Transpiled JavaScript files from Typescript -dist/ -dist6/ - diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e09e401..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -sudo: false -language: node_js -node_js: - - "8" diff --git a/README.md b/README.md index 637fcb2..3e27908 100644 --- a/README.md +++ b/README.md @@ -1,413 +1,6 @@ # loopback4-example-getting-started -This is the basic tutorial for getting started with Loopback 4! +This example has been moved to our monorepo, see -**NOTICE**: This tutorial is currently under construction! This notice will be -removed when it is ready for use! +https://github.com/strongloop/loopback-next/tree/master/packages/example-getting-started -## Prerequisites - -Before we can begin, you'll need to make sure you have some things installed: -- [Node.js](https://nodejs.org/en/) at v6.x or greater -- [TypeScript](http://www.typescriptlang.org/) -- [Git](https://git-scm.com/) - -Additionally, this tutorial assumes that you are comfortable with -certain technologies, languages and concepts. -- JavaScript (ES6) -- [npm](https://www.npmjs.com/) -- [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) -- [Using Git](https://githowto.com/) - -## Setup -1. Clone this repository if you haven't already: -``` -git clone https://github.com/strongloop/loopback4-example-getting-started -``` -1. Switch to the directory and install the app's dependencies: -``` -cd loopback4-example-getting-started && npm install -``` -1. Run it! -``` -npm start -``` - -## Tutorial - -Here's a step-by-step guide of how to build this repository! - -### Create your app scaffolding -Install the `@loopback/cli` package. This will give you the command-line -toolkit that can generate a basic REST app for you. -`npm i -g @loopback/cli` - -Next, navigate to whichever directory you'd like to create your new project -and run `lb4`. Follow the prompts to generate your application. For this -tutorial, when prompted with the options for selecting things like whether or -not to enable certain project features (loopback's build, tslint, mocha, etc.), -leave them all enabled. - - - -### Adding Legacy Juggler Capabilities -Jump into the directory for your new application. You'll see a folder structure -similar to this: -``` -dist\ -node_modules\ -src\ - controllers\ - ping.controller.ts - README.md - repositories\ - README.md - application.ts - index.ts -test\ - mocha.opts - ping.controller.test.ts - README.md -index.js -index.d.ts -index.ts -``` - -The application template comes with a controller, and some default wireup in -`src/application.ts` that handles the basic configuration for your application. -For this tutorial, we won't need `ping.controller.ts` or its corresponding test, -but you can leave them in for now. - -Now that you have your setup, it's time to modify it to add in -`@loopback/repository`. Install this dependency by running -`npm i --save @loopback/repository`. - -Next, modify `src/application.ts` to change the base class of your app to use -the `RepositoryMixin`: - -#### src/application.ts -```ts -import {Application, ApplicationConfig} from '@loopback/core'; -import {RestComponent} from '@loopback/rest'; -import {PingController} from './controllers/ping-controller'; -import {Class, Repository, RepositoryMixin} from '@loopback/repository'; - -export class TodoApplication extends RepositoryMixin(Application) { - constructor(options?: ApplicationConfig) { - // Allow options to replace the defined components array, if desired. - options = Object.assign( - {}, - { - components: [RestComponent], - }, - options, - ); - super(options); - this.setupControllers(); - } - - setupControllers() { - this.controller(PingController); - } -} -``` - -### Building the Todo model -The Todo model will be the object we use both as a Data Transfer Object (DTO) on -the controller, and as a LoopBack model for the Legacy Juggler implementation. - -Create another folder in `src` called `repositories` and inside of that folder, -create two files: -- `index.ts` -- `todo.repository.ts` - ->**NOTE:** -The `index.ts` file is an export helper file; this pattern is a huge time-saver -as the number of models in your project grows, because it allows you to point -to the _directory_ when attempting to import types from a file within the target -folder. We will use this concept throughout the tutorial! -```ts -// in src/models/index.ts -export * from './foo.model'; -export * from './bar.model'; -export * from './baz.model'; - -// elsewhere... - -// with index.ts -import {Foo, Bar, Baz} from './models'; -// ...and without index.ts -import {Foo} from './models/foo.model'; -import {Bar} from './models/bar.model'; -import {Baz} from './models/baz.model'; -``` - -In our Todo model, we'll create a basic representation of what would go in -a Todo list. Our model will include: -- a unique id -- a title -- a description that details what the todo is all about -- a boolean flag for whether or not we've completed the task. - -For the Legacy Juggler to understand how to work with our model class, it -will need to extend the `Entity` type, as well as provide an override for -the `getId` function, so that it can retrieve a Todo model's ID as needed. - -Additionally, we'll define a `SchemaObject` that represents our Todo model -as an [OpenAPI Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema-object). -This will give the OpenAPI spec builder the information it needs to describe the -Todo model on your app's OpenAPI endpoints. - -#### src/models/todo.model.ts -```ts -import {Entity, property, model} from '@loopback/repository'; -import {SchemaObject} from '@loopback/openapi-spec'; - -@model() -export class Todo extends Entity { - @property({ - type: 'number', - id: true - }) - id?: number; - - @property({ - type: 'string', - required: true - }) - title: string; - - @property({ - type: 'string' - }) - desc?: string; - - @property({ - type: 'boolean' - }) - isComplete: boolean; - - getId() { - return this.id; - } -} - -export const TodoSchema: SchemaObject = { - title: 'todoItem', - properties: { - id: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - title: { - type: 'string', - description: 'Title of the Todo entry.' - }, - desc: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - isComplete: { - type: 'boolean', - description: 'Whether or not the Todo entry is complete.' - } - }, - required: ['title'], -}; -``` - -### Building a Datasource -Before we can begin constructing controllers and repositories for our -application, we need to define our datasource. - -Create a new folder in the root directory of the project called `config`, -and then inside that folder, create a `datasources.json` file. For now, we'll -be using the memory connector provided with the Juggler. - -#### config/datasources.json -```json -{ - "name": "ds", - "connector": "memory" -} -``` - -Create another folder called `datasources` in the `src` directory, and inside -that folder, create a new file called `db.datasource.ts`. - -#### src/datasources/db.datasource.ts - -```ts -import * as path from 'path'; -import * as fs from 'fs'; -import { DataSourceConstructor, juggler } from '@loopback/repository'; - -const dsConfigPath = path.resolve('config', 'datasources.json'); -const config = require(dsConfigPath); -export const db = new DataSourceConstructor(config); -``` - -This will give us a strongly-typed datasource export that we can work with to -construct our TodoRepository definition. - -### Create your repository -Create another folder in `src` called `repositories` and inside of that folder, -create two files: -- `index.ts` (our export helper) -- `todo.repository.ts` - -Our TodoRepository will contain a small base class that uses the -`DefaultCrudRepository` class from `@loopback/repository` and will define the -model type we're working with, as well as its ID type. We'll also inject our -datasource so that this repository can connect to it when executing data -operations. - -#### src/repositories/todo.repository.ts -```ts -import { DefaultCrudRepository, DataSourceType } from '@loopback/repository'; -import { Todo } from '../models'; -import { inject } from '@loopback/core'; - -export class TodoRepository extends DefaultCrudRepository< - Todo, - typeof Todo.prototype.id -> { - constructor(@inject('datasource') protected datasource: DataSourceType) { - super(Todo, datasource); - } -} -``` - - -### Create your controller -Now, we'll create a controller to handle our Todo routes. Create the -`src/controllers` directory and two files inside: -- `index.ts` (export helper) -- `todo.controller.ts` - -In addition to creating the CRUD methods themselves, we'll also be adding -decorators that setup the routing as well as the expected parameters of -incoming requests. - -#### src/controllers/todo.controller.ts -```ts -import {post, param, get, put, patch, del, HttpErrors} from '@loopback/rest'; -import {TodoSchema, Todo} from '../models'; -import {repository} from '@loopback/repository'; -import {TodoRepository} from '../repositories/index'; - -export class TodoController { - constructor( - @repository(TodoRepository.name) protected todoRepo: TodoRepository, - ) {} - @post('/todo') - @param.body('todo', TodoSchema) - async createTodo(todo: Todo) { - if (!todo.title) { - return Promise.reject(new HttpErrors.BadRequest('title is required')); - } - return await this.todoRepo.create(todo); - } - - @get('/todo/{id}') - @param.path.number('id') - @param.query.boolean('items') - async findTodoById(id: number, items?: boolean): Promise { - return await this.todoRepo.findById(id); - } - - @get('/todo') - async findTodos(): Promise { - return await this.todoRepo.find(); - } - - @put('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async replaceTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.replaceById(id, todo); - } - - @patch('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async updateTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.updateById(id, todo); - } - - @del('/todo/{id}') - @param.path.number('id') - async deleteTodo(id: number): Promise { - return await this.todoRepo.deleteById(id); - } -} -``` - -### Putting it all together - -Now that we've got all of our artifacts made, let's set them up in our -application! - -We'll define a new helper function for setting up the repositories, as well -as adding in our new controller binding. - -#### src/application.ts -```ts -import {Application, ApplicationConfig} from '@loopback/core'; -import {RestComponent} from '@loopback/rest'; -import {TodoController, PingController} from './controllers'; -import { - Class, - Repository, - RepositoryMixin, - DataSourceConstructor, -} from '@loopback/repository'; -import {db} from './datasources/db.datasource'; -import {TodoRepository} from './repositories'; - -export class TodoApplication extends RepositoryMixin(Application) { - constructor(options?: ApplicationConfig) { - // Allow options to replace the defined components array, if desired. - options = Object.assign( - {}, - { - components: [RestComponent], - }, - options, - ); - super(options); - this.setupControllers(); - this.setupRepositories(); - } - - setupControllers() { - this.controller(TodoController); - this.controller(PingController); - } - - setupRepositories() { - // This will allow you to test your application without needing to - // use the "real" datasource! - const datasource = - this.options && this.options.datasource - ? new DataSourceConstructor(this.options.datasource) - : db; - this.bind('datasource').to(datasource); - this.repository(TodoRepository); - } -} -``` - -### Try it out -Now that your app is ready to go, try it out with your favourite REST client! -Start the app (`npm start`) and then make some REST requests: -- `POST /todo` with a body of `{ "title": "get the milk" }` -- `GET /todo/1` and see if you get your Todo object back. -- `PATCH /todo/1` with a body of `{ "desc": "need milk for cereal" }` - -### Stuck? -Check out our [Gitter channel](https://gitter.im/strongloop/loopback) and ask -for help with this tutorial! - -### Bugs/Feedback -Open an issue in this repository **OR** on [loopback-next](https://github.com/strongloop/loopback-next) and we'll take a look! diff --git a/config/datasources.json b/config/datasources.json deleted file mode 100644 index 2737944..0000000 --- a/config/datasources.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "ds", - "connector": "memory", - "file": "./data/db.json" -} diff --git a/data/db.json b/data/db.json deleted file mode 100644 index 2cfe444..0000000 --- a/data/db.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ids": { - "Todo": 2, - "TodoItem": 5 - }, - "models": { - "Todo": { - "1": "{\"title\":\"Take over the galaxy\",\"desc\":\"MWAHAHAHAHAHAHAHAHAHAHAHAHAMWAHAHAHAHAHAHAHAHAHAHAHAHA\",\"id\":1}" - }, - "TodoItem": { - "1": "{\"title\":\"build death star\",\"todoId\":1,\"id\":1,\"checklist\":[{\"title\":\"create death star\"},{\"title\":\"destroy alderaan\"},{\"title\":\"terrorize senate\"}]}", - "2": "{\"title\":\"destroy alderaan\",\"todoId\":1,\"id\":2,\"checklist\":[{\"title\":\"create death star\"},{\"title\":\"destroy alderaan\"},{\"title\":\"terrorize senate\"}]}", - "3": "{\"title\":\"terrorize senate\",\"todoId\":1,\"id\":3,\"checklist\":[{\"title\":\"create death star\"},{\"title\":\"destroy alderaan\"},{\"title\":\"terrorize senate\"}]}", - "4": "{\"title\":\"crush rebel scum\",\"todoId\":1,\"id\":4}" - } - } -} \ No newline at end of file diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 5703fb5..0000000 --- a/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dist'; diff --git a/index.js b/index.js deleted file mode 100644 index c648f78..0000000 --- a/index.js +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright IBM Corp. 2017. All Rights Reserved. -// Node module: loopback4-example-getting-started -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT - -const nodeMajorVersion = +process.versions.node.split('.')[0]; -const dist = nodeMajorVersion >= 7 ? './dist' : './dist6'; - -const application = (module.exports = require(dist)); - -if (require.main === module) { - // Run the application - application.main(); -} diff --git a/index.ts b/index.ts deleted file mode 100644 index 8420b10..0000000 --- a/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src'; diff --git a/package.json b/package.json deleted file mode 100644 index 1869dae..0000000 --- a/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "loopback4-example-getting-started", - "version": "1.0.0", - "description": "An application and tutorial on how to build with LoopBack 4.", - "main": "index.js", - "engines": { - "node": ">=8" - }, - "scripts": { - "start": "npm run build && node .", - "build": "lb-tsc && lb-tslint", - "test": "npm run build && mocha \"./dist/test/**/*.test.js\"" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/strongloop/loopback4-example-getting-started.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/strongloop/loopback4-example-getting-started/issues" - }, - "homepage": "https://github.com/strongloop/loopback4-example-getting-started#readme", - "dependencies": { - "@loopback/core": "^4.0.0-alpha.27", - "@loopback/openapi-v2": "^4.0.0-alpha.3", - "@loopback/repository": "^4.0.0-alpha.23", - "@loopback/rest": "^4.0.0-alpha.18", - "@types/sinon": "^2.3.6", - "sinon": "^4.1.5" - }, - "devDependencies": { - "@loopback/build": "^4.0.0-alpha.8", - "@loopback/testlab": "^4.0.0-alpha.18", - "@types/mocha": "^2.2.46", - "@types/node": "^8.5.8", - "mocha": "^4.1.0", - "typescript": "^2.5.2" - } -} diff --git a/src/application.ts b/src/application.ts deleted file mode 100644 index 64128c8..0000000 --- a/src/application.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Application, ApplicationConfig } from '@loopback/core'; -import { RestComponent } from '@loopback/rest'; -import { TodoController } from './controllers'; -import { TodoRepository } from './repositories'; -import { db } from './datasources/db.datasource'; -import { DataSourceConstructor, RepositoryMixin } from '@loopback/repository'; - -export class TodoApplication extends RepositoryMixin(Application) { - constructor(options?: ApplicationConfig) { - // TODO(bajtos) The comment below does not make sense to me. - // Consumers of TodoApplication object should not be changing the shape - // of the app (what components are mounted, etc.) The config object should - // be used only to configure what ports the app is listening on, - // which database to connect to, etc. - // See https://github.com/strongloop/loopback-next/issues/742 - - // Allow options to replace the defined components array, if desired. - options = Object.assign( - {}, - { - components: [RestComponent] - }, - options - ); - super(options); - this.setupRepositories(); - this.setupControllers(); - } - - // Helper functions (just to keep things organized) - setupRepositories() { - // TODO(bajtos) Automate datasource and repo registration via @loopback/boot - // See https://github.com/strongloop/loopback-next/issues/441 - const datasource = - this.options && this.options.datasource - ? new DataSourceConstructor(this.options.datasource) - : db; - // TODO(bajtos) use app.dataSource() from @loopback/repository mixin - // (app.dataSource() is not implemented there yet) - // See https://github.com/strongloop/loopback-next/issues/743 - this.bind('datasource').to(datasource); - this.repository(TodoRepository); - } - - setupControllers() { - // TODO(bajtos) Automate controller registration via @loopback/boot - this.controller(TodoController); - } -} diff --git a/src/controllers/index.ts b/src/controllers/index.ts deleted file mode 100644 index 30ed124..0000000 --- a/src/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './todo.controller'; diff --git a/src/controllers/todo.controller.ts b/src/controllers/todo.controller.ts deleted file mode 100644 index eb4c1cf..0000000 --- a/src/controllers/todo.controller.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {post, param, get, put, patch, del} from '@loopback/openapi-v2'; -import {HttpErrors} from '@loopback/rest'; -import {TodoSchema, Todo} from '../models'; -import {repository} from '@loopback/repository'; -import {TodoRepository} from '../repositories/index'; - -export class TodoController { - // TODO(bajtos) Fix documentation (and argument names?) of @repository() - // to allow the usage below. - // See https://github.com/strongloop/loopback-next/issues/744 - constructor(@repository(TodoRepository.name) protected todoRepo: TodoRepository) {} - @post('/todo') - @param.body('todo', TodoSchema) - async createTodo(todo: Todo) { - // TODO(bajtos) This should be handled by the framework - // See https://github.com/strongloop/loopback-next/issues/118 - if (!todo.title) { - return Promise.reject(new HttpErrors.BadRequest('title is required')); - } - return await this.todoRepo.create(todo); - } - - @get('/todo/{id}') - @param.path.number('id') - @param.query.boolean('items') - async findTodoById(id: number, items?: boolean): Promise { - return await this.todoRepo.findById(id); - } - - @get('/todo') - async findTodos(): Promise { - return await this.todoRepo.find(); - } - - @put('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async replaceTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.replaceById(id, todo); - } - - @patch('/todo/{id}') - @param.path.number('id') - @param.body('todo', TodoSchema) - async updateTodo(id: number, todo: Todo): Promise { - return await this.todoRepo.updateById(id, todo); - } - - @del('/todo/{id}') - @param.path.number('id') - async deleteTodo(id: number): Promise { - return await this.todoRepo.deleteById(id); - } -} diff --git a/src/datasources/db.datasource.ts b/src/datasources/db.datasource.ts deleted file mode 100644 index 759b794..0000000 --- a/src/datasources/db.datasource.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as path from 'path'; -import { DataSourceConstructor } from '@loopback/repository'; - -const dsConfigPath = path.resolve('config', 'datasources.json'); -const config = require(dsConfigPath); - -// TODO(bajtos) Ideally, datasources should be created by @loopback/boot -// and registered with the app for dependency injection. -// However, we need to investigate how to access these datasources from -// integration tests where we don't have access to the full app object. -// For example, @loopback/boot can provide a helper function for -// performing a partial boot that creates datasources only. -export const db = new DataSourceConstructor(config); diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index c35311d..0000000 --- a/src/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TodoApplication } from './application'; -import { RestServer } from '@loopback/rest'; - - -export async function main() { - const app = new TodoApplication(); - try { - await app.start(); - } catch (err) { - console.error(`Unable to start application: ${err}`); - } - const server = await app.getServer(RestServer); - console.log(`Server is running on port ${await server.get('rest.port')}`); - return app; -} - diff --git a/src/models/index.ts b/src/models/index.ts deleted file mode 100644 index 01d54cc..0000000 --- a/src/models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './todo.model'; diff --git a/src/models/todo.model.ts b/src/models/todo.model.ts deleted file mode 100644 index c6b722f..0000000 --- a/src/models/todo.model.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {Entity, property, model} from '@loopback/repository'; -import {SchemaObject} from '@loopback/openapi-spec'; - -@model() -export class Todo extends Entity { - @property({ - type: 'number', - id: true - }) - id?: number; - - @property({ - type: 'string', - required: true - }) - title: string; - - @property({ - type: 'string' - }) - desc?: string; - - @property({ - type: 'boolean' - }) - isComplete: boolean; - - getId() { - return this.id; - } -} - -// TODO(bajtos) The schema should be generated from model definition -// See https://github.com/strongloop/loopback-next/issues/700 -// export const TodoSchema = createSchemaFromModel(Todo); -export const TodoSchema: SchemaObject = { - title: 'todoItem', - properties: { - id: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - title: { - type: 'string', - description: 'Title of the Todo entry.' - }, - desc: { - type: 'number', - description: 'ID number of the Todo entry.' - }, - isComplete: { - type: 'boolean', - description: 'Whether or not the Todo entry is complete.' - } - }, - required: ['title'], -}; diff --git a/src/repositories/index.ts b/src/repositories/index.ts deleted file mode 100644 index 12bb630..0000000 --- a/src/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './todo.repository'; diff --git a/src/repositories/todo.repository.ts b/src/repositories/todo.repository.ts deleted file mode 100644 index 9677b56..0000000 --- a/src/repositories/todo.repository.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DefaultCrudRepository, DataSourceType } from '@loopback/repository'; -import { Todo } from '../models'; -import { inject } from '@loopback/core'; - -export class TodoRepository extends DefaultCrudRepository< - Todo, - typeof Todo.prototype.id -> { - constructor(@inject('datasource') protected datasource: DataSourceType) { - super(Todo, datasource); - } -} diff --git a/test/acceptance/application.test.ts b/test/acceptance/application.test.ts deleted file mode 100644 index 64b81d4..0000000 --- a/test/acceptance/application.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import {createClientForHandler, expect, supertest} from '@loopback/testlab'; -import {RestServer} from '@loopback/rest'; -import {TodoApplication} from '../../src/application'; -import {TodoRepository} from '../../src/repositories/index'; -import {givenTodo} from '../helpers'; -import {Todo} from '../../src/models/index'; - -describe('Application', () => { - let app: TodoApplication; - let server: RestServer; - let client: supertest.SuperTest; - let todoRepo: TodoRepository; - - before(givenAnApplication); - before(givenARestServer); - before(givenTodoRepository); - before(async () => { - await app.start(); - }); - before(() => { - client = createClientForHandler(server.handleHttp); - }); - after(async () => { - await app.stop(); - }); - - it('creates a todo', async () => { - const todo = givenTodo(); - const response = await client - .post('/todo') - .send(todo) - .expect(200); - expect(response.body).to.containEql(todo); - const result = await todoRepo.findById(response.body.id); - expect(result).to.containEql(todo); - }); - - it('gets a todo by ID', async () => { - const todo = await givenTodoInstance(); - await client - .get(`/todo/${todo.id}`) - .send() - .expect(200, todo); - }); - - it('replaces the todo by ID', async () => { - const todo = await givenTodoInstance(); - const updatedTodo = givenTodo({ - title: 'DO SOMETHING AWESOME', - desc: 'It has to be something ridiculous', - isComplete: true - }); - await client - .put(`/todo/${todo.id}`) - .send(updatedTodo) - .expect(200); - const result = await todoRepo.findById(todo.id); - expect(result).to.containEql(updatedTodo); - }); - - it('updates the todo by ID ', async () => { - const todo = await givenTodoInstance(); - const updatedTodo = givenTodo({ - title: 'DO SOMETHING AWESOME', - isComplete: true - }); - await client - .patch(`/todo/${todo.id}`) - .send(updatedTodo) - .expect(200); - const result = await todoRepo.findById(todo.id); - expect(result).to.containEql(updatedTodo); - }); - - it('deletes the todo', async () => { - const todo = await givenTodoInstance(); - await client - .del(`/todo/${todo.id}`) - .send() - .expect(200); - try { - await todoRepo.findById(todo.id); - } catch (err) { - expect(err).to.match(/No Todo found with id/); - return; - } - throw new Error('No error was thrown!'); - }); - - /* - ============================================================================ - TEST HELPERS - These functions help simplify setup of your test fixtures so that your tests - can: - - operate on a "clean" environment each time (a fresh in-memory database) - - avoid polluting the test with large quantities of setup logic to keep - them clear and easy to read - - keep them DRY (who wants to write the same stuff over and over?) - ============================================================================ - */ - function givenAnApplication() { - app = new TodoApplication({ - rest: { - port: 0 - }, - datasource: { - connector: 'memory' - } - }); - } - - async function givenARestServer() { - server = await app.getServer(RestServer); - } - - async function givenTodoRepository() { - // TODO(bajtos) enhance RepositoryMixin to provide repository getter - // Example usage: - // todoRepo = await app.getRepository(TodoRepository.name) - // See https://github.com/strongloop/loopback-next/issues/745 - todoRepo = (await app.get('repositories.TodoRepository')) as TodoRepository; - } - - async function givenTodoInstance(todo?: Partial) { - return await todoRepo.create(givenTodo(todo)); - } -}); diff --git a/test/helpers.ts b/test/helpers.ts deleted file mode 100644 index c330ffd..0000000 --- a/test/helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Todo} from '../src/models/index'; - -/* - ============================================================================== - HELPER FUNCTIONS - If you find yourself creating the same helper functions across different - test files, then extracting those functions into helper modules is an easy - way to reduce duplication. - - Other tips: - - - Using the super awesome Partial type in conjunction with Object.assign - means you can: - * customize the object you get back based only on what's important - to you during a particular test - * avoid writing test logic that is brittle with respect to the properties - of your object - - Making the input itself optional means you don't need to do anything special - for tests where the particular details of the input don't matter. - ============================================================================== - * - -/** - * Generate a complete Todo object for use with tests. - * @param todo A partial (or complete) Todo object. - */ -export function givenTodo(todo?: Partial) { - return Object.assign( - new Todo({ - title: 'do a thing', - desc: 'There are some things that need doing', - isComplete: false - }), - todo - ); -} diff --git a/test/unit/controllers/todo.controller.test.ts b/test/unit/controllers/todo.controller.test.ts deleted file mode 100644 index 910ee56..0000000 --- a/test/unit/controllers/todo.controller.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import {expect} from '@loopback/testlab'; -import {TodoController} from '../../../src/controllers'; -import {TodoRepository} from '../../../src/repositories'; -import * as sinon from 'sinon'; -import {Todo} from '../../../src/models/index'; -import {givenTodo} from '../../helpers'; - -describe('TodoController', () => { - let todoRepo: TodoRepository; - - /* - ============================================================================= - METHOD STUBS - These handles give us a quick way to fake the response of our repository - without needing to wrangle fake repository objects or manage real ones - in our tests themselves. - ============================================================================= - */ - let create: sinon.SinonStub; - let findById: sinon.SinonStub; - let find: sinon.SinonStub; - let replaceById: sinon.SinonStub; - let updateById: sinon.SinonStub; - let deleteById: sinon.SinonStub; - - /* - ============================================================================= - TEST VARIABLES - Combining top-level objects with our resetRepositories method means we don't - need to duplicate several variable assignments (and generation statements) - in all of our test logic. - - NOTE: If you wanted to parallelize your test runs, you should avoid this - pattern since each of these tests is sharing references. - ============================================================================= - */ - let controller: TodoController; - let aTodo: Todo; - let aTodoWithId: Todo; - let aChangedTodo: Todo; - let aTodoList: Todo[]; - - const noError = 'No error was thrown!'; - - beforeEach(resetRepositories); - describe('createTodo', () => { - it('creates a Todo', async () => { - create.resolves(aTodoWithId); - const result = await controller.createTodo(aTodo); - expect(result).to.eql(aTodoWithId); - sinon.assert.calledWith(create, aTodo); - }); - - it('throws if the payload is missing a title', async () => { - const todo = givenTodo(); - delete todo.title; - try { - await controller.createTodo(todo); - } catch (err) { - expect(err).to.match(/title is required/); - sinon.assert.notCalled(create); - return; - } - // Repository stub should not have been called! - throw new Error(noError); - }); - }); - - describe('findTodoById', () => { - it('returns a todo if it exists', async () => { - findById.resolves(aTodoWithId); - expect(await controller.findTodoById(aTodoWithId.id as number)).to.eql( - aTodoWithId - ); - sinon.assert.calledWith(findById, aTodoWithId.id); - }); - }); - - describe('findTodos', () => { - it('returns multiple todos if they exist', async () => { - find.resolves(aTodoList); - expect(await controller.findTodos()).to.eql(aTodoList); - sinon.assert.called(find); - }); - - it('returns empty list if no todos exist', async () => { - const expected: Todo[] = []; - find.resolves(expected); - expect(await controller.findTodos()).to.eql(expected); - sinon.assert.called(find); - }); - }); - - describe('replaceTodo', () => { - it('successfully replaces existing items', async () => { - replaceById.resolves(true); - expect( - await controller.replaceTodo(aTodoWithId.id as number, aChangedTodo) - ).to.eql(true); - sinon.assert.calledWith(replaceById, aTodoWithId.id, aChangedTodo); - }); - }); - - describe('updateTodo', () => { - it('successfully updates existing items', async () => { - updateById.resolves(true); - expect( - await controller.updateTodo(aTodoWithId.id as number, aChangedTodo) - ).to.eql(true); - sinon.assert.calledWith(updateById, aTodoWithId.id, aChangedTodo); - }); - }); - - describe('deleteTodo', () => { - it('successfully deletes existing items', async () => { - deleteById.resolves(true); - expect(await controller.deleteTodo(aTodoWithId.id as number)).to.eql( - true - ); - sinon.assert.calledWith(deleteById, aTodoWithId.id); - }); - }); - - function resetRepositories() { - todoRepo = sinon.createStubInstance(TodoRepository); - aTodo = givenTodo(); - aTodoWithId = givenTodo({ - id: 1 - }); - aTodoList = [ - aTodoWithId, - givenTodo({ - id: 2, - title: 'so many things to do' - }) - ] as Todo[]; - aChangedTodo = givenTodo({ - id: aTodoWithId.id, - title: 'Do some important things' - }); - - // Setup CRUD fakes - create = todoRepo.create as sinon.SinonStub; - findById = todoRepo.findById as sinon.SinonStub; - find = todoRepo.find as sinon.SinonStub; - updateById = todoRepo.updateById as sinon.SinonStub; - replaceById = todoRepo.replaceById as sinon.SinonStub; - deleteById = todoRepo.deleteById as sinon.SinonStub; - controller = new TodoController(todoRepo); - } -}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index ac4ca0d..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noUnusedLocals": true, - "module": "commonjs", - "moduleResolution": "node", - "target": "es6", - "outDir": "dist", - "sourceMap": true - } -}