diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3483434 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +## Emojis + +- New Features -> :zap: +- Enhancements -> :star2: +- Breaking Changes -> :boom: +- Bugs -> :beetle: +- Pull Requests -> :book: +- Documents -> :mortar_board: +- Tests -> :eyeglasses: + +--- + +## [v1.1.0](https://github.com/foxifyjs/foxify-restify-odin/releases/tag/v1.1.0) - *(2018-11-15)* + +- :zap: Added ability to set `defaults` for decoded params + +## [v1.0.0](https://github.com/foxifyjs/foxify-restify-odin/releases/tag/v1.0.0) - *(2018-11-15)* + +- :tada: First Release diff --git a/README.md b/README.md index 785178a..8ebcd11 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,32 @@ app.start(); ## Documentation +```typescript +type Operator = "lt" | "lte" | "eq" | "ne" | "gte" | "gt" | + "ex" | "in" | "nin" | "bet" | "nbe"; + +interface FilterObject { + field: string; + operator: Operator; + value: string | number | boolean | any[] | object | Date; +} + +interface Filter { + and?: Array; + or?: Array; +} + +interface Query { + filter?: Filter | FilterObject; + include?: string[]; + sort?: string[]; + skip?: number; + limit?: number; +} + +restify(Model: typeof Odin, defaults?: Query): Foxify.Handler; +``` + This middleware parses url query string and executes a query on the given model accordingly and passes the `query` to you (since you might need to do some modifications on the query, too) It also passes a `counter` which is exactly like `query` but without applying `skip`, `limit`, `sort` just because you might want to send a total count in your response as well diff --git a/package-lock.json b/package-lock.json index 94b6526..a8b1d66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "foxify-restify-odin", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d4bd28f..cca2cf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foxify-restify-odin", - "version": "1.0.0", + "version": "1.1.0", "description": "Easily restify odin databases", "author": "Ardalan Amini [https://github.com/ardalanamini]", "license": "MIT", diff --git a/src/index.ts b/src/index.ts index 8437f15..dafe33b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,15 +20,15 @@ namespace restify { } export interface Query { - filter: Filter | FilterObject; - include: string[]; - sort: string[]; - skip: number; - limit: number; + filter?: Filter | FilterObject; + include?: string[]; + sort?: string[]; + skip?: number; + limit?: number; } } -const restify = (model: typeof Odin) => { +const restify = (model: typeof Odin, defaults: restify.Query = {}) => { if (!(typeof model === typeof Odin)) { throw new TypeError("Expected model to be a Odin database model"); } @@ -37,7 +37,7 @@ const restify = (model: typeof Odin) => { const foxify_restify_odin: Foxify.Handler = (req, res, next) => { const parsed = parse((req.url as string).replace(/^.*\?/, "")); - const decoded = decoder(parsed) as restify.Query; + const decoded = Object.assign({}, defaults, decoder(parsed)); req.fro = { decoded, diff --git a/test/defaults.ts b/test/defaults.ts new file mode 100644 index 0000000..e9586be --- /dev/null +++ b/test/defaults.ts @@ -0,0 +1,144 @@ +import * as Odin from "@foxify/odin"; +import * as Foxify from "foxify"; +import "prototyped.js"; +import { stringify } from "qs"; +import * as restify from "../src"; + +declare global { + namespace NodeJS { + interface Global { + __MONGO_DB_NAME__: string; + __MONGO_CONNECTION__: any; + } + } +} + +const TABLE = "users"; +const ITEMS = [ + { + email: "ardalanamini22@gmail.com", + username: "ardalanamini", + age: 22, + name: { + first: "Ardalan", + last: "Amini", + }, + }, + { + email: "johndue@example.com", + username: "john", + age: 45, + name: { + first: "John", + last: "Due", + }, + }, +]; + +Odin.connections({ + default: { + driver: "MongoDB", + database: global.__MONGO_DB_NAME__, + connection: global.__MONGO_CONNECTION__, + }, +}); + +beforeAll((done) => { + Odin.DB.table(TABLE).insert(ITEMS, (err) => { + if (err) throw err; + + Odin.DB.table(TABLE).get((err, items) => { + if (err) throw err; + + ITEMS.length = 0; + + ITEMS.push(...items); + + done(); + }); + }); +}); + +afterEach((done) => { + Odin.DB.table(TABLE).delete((err) => { + if (err) throw err; + + Odin.DB.table(TABLE).insert(ITEMS, (err) => { + if (err) throw err; + + done(); + }); + }); +}); + +afterAll((done) => { + Odin.DB.table(TABLE).delete((err) => { + if (err) throw err; + + done(); + }); +}); + +interface User extends Odin { } + +class User extends Odin { + public static schema = { + email: User.Types.String.email.required, + username: User.Types.String.alphanum.min(3).required, + age: User.Types.Number.min(18).required, + name: { + first: User.Types.String.min(3).required, + last: User.Types.String.min(3), + }, + }; +} + +it("Should limit 1 item by default", async () => { + expect.assertions(2); + + const app = new Foxify(); + + app.get("/users", restify(User, { limit: 1 }), async (req, res) => { + expect(req.fro).toBeDefined(); + + res.json({ + users: await req.fro.query.get(), + total: await req.fro.counter.count(), + }); + }); + + const result = await app.inject(`/users`); + + const users = ITEMS.initial(); + + expect(JSON.parse(result.body)) + .toEqual({ users, total: ITEMS.length }); +}); + +it("Should skip 1 item by default and sort by -age", async () => { + expect.assertions(2); + + const app = new Foxify(); + + app.get("/users", restify(User, { skip: 1 }), async (req, res) => { + expect(req.fro).toBeDefined(); + + res.json({ + users: await req.fro.query.get(), + total: await req.fro.counter.count(), + }); + }); + + const result = await app.inject(`/users?${stringify( + { + sort: [ + "-age", + ], + }, + )}`); + + const users = ITEMS.orderBy("age", "desc").tail(); + + expect(JSON.parse(result.body)) + .toEqual({ users, total: ITEMS.length }); +}); diff --git a/test/limit.ts b/test/limit.ts index e3acf2f..a6eaff9 100644 --- a/test/limit.ts +++ b/test/limit.ts @@ -93,7 +93,7 @@ class User extends Odin { }; } -it("Should limit 1", async () => { +it("Should limit 1 item", async () => { expect.assertions(2); const app = new Foxify(); diff --git a/test/skip.ts b/test/skip.ts index efb472f..e581eba 100644 --- a/test/skip.ts +++ b/test/skip.ts @@ -93,7 +93,7 @@ class User extends Odin { }; } -it("Should skip 1", async () => { +it("Should skip 1 item", async () => { expect.assertions(2); const app = new Foxify(); @@ -118,3 +118,29 @@ it("Should skip 1", async () => { expect(JSON.parse(result.body)) .toEqual({ users, total: ITEMS.length }); }); + +it("Should skip 0 item", async () => { + expect.assertions(2); + + const app = new Foxify(); + + app.get("/users", restify(User), async (req, res) => { + expect(req.fro).toBeDefined(); + + res.json({ + users: await req.fro.query.get(), + total: await req.fro.counter.count(), + }); + }); + + const result = await app.inject(`/users?${stringify( + { + skip: 0, + }, + )}`); + + const users = ITEMS; + + expect(JSON.parse(result.body)) + .toEqual({ users, total: ITEMS.length }); +});