Skip to content

Commit

Permalink
feature: get multiple random quotes (#174)
Browse files Browse the repository at this point in the history
* feature: get multiple random quotes

Add a new version of the random endpoint.   This endpoint is equivalent
to the current /random endpoint.  However, it supports a limit param
that allows you to specify the number of random quotes to retrieve
with each request.
  • Loading branch information
lukePeavey authored Mar 10, 2023
1 parent fe102db commit c5a1bab
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 17,7 @@ https://api.quotable.io
## API Reference <!-- omit in toc -->

- [Get random quote](#get-random-quote)
- [Get Random Quotes (beta)](#get-random-quotes-beta)
- [List Quotes](#list-quotes)
- [Get Quote By ID](#get-quote-by-id)
- [List Authors](#list-authors)
Expand Down Expand Up @@ -103,6 104,90 @@ GET /random?minLength=100&maxLength=140

<br>

## Get Random Quotes (beta)

```HTTP
GET /quotes/random
```

Get one or more random quotes from the database. This method supports several filters that can be used to get random quotes with specific properties (ie tags, quote length, etc.)

By default, this methods returns a single random quote. You can specify the number of random quotes to return via the `limit` parameter.

> ⚠️ This method is similar to the `/random` endpoint. The only difference is the response format:
>
> Instead of retuning a single `Quote` object, this method returns an `Array` of `Quote` objects.

<br>

| param | type | Description |
| :-------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| limit | `Int` | `default: 1` &nbsp; `min: 1` &nbsp; `max: 50` <br> The number of random quotes to retrieve. |
| maxLength | `Int` | The maximum Length in characters ( can be combined with `minLength` ) |
| minLength | `Int` | The minimum Length in characters ( can be combined with `maxLength` ) |
| tags | `String` | Get a random quote with specific tag(s). This takes a list of one or more tag names, separated by a comma (meaning `AND`) or a pipe (meaning `OR`). A comma separated list will match quotes that have **_all_** of the given tags. While a pipe (`\|`) separated list will match quotes that have **any one** of the provided tags. |
| author | `String` | Get a random quote by one or more authors. The value can be an author `name` or `slug`. To include quotes by multiple authors, provide a pipe-separated list of author names/slugs. |
| authorId | `String` | `deprecated` <br><br> Same as `author` param, except it uses author `_id` instead of `slug` |

**Response**

```ts
// An array containing one or more Quotes
Array<{
_id: string
// The quotation text
content: string
// The full name of the author
author: string
// The `slug` of the quote author
authorSlug: string
// The length of quote (number of characters)
length: number
// An array of tag names for this quote
tags: string[]
}>
```


**Examples**

Get random quote [try in browser](https://api.quotable.io/quotes/random)

```HTTP
GET /quotes/random
```

Get 5 random quotes [try in browser](https://api.quotable.io/quotes/random?limit=3)

```HTTP
GET /quotes/random?limit=3
```


Random Quote with tags "technology" **`AND`** "famous-quotes" [try in browser](https://api.quotable.io/quotes/random?tags=technology,famous-quotes)

```HTTP
GET /quotes/random?tags=technology,famous-quotes
```

Random Quote with tags "History" **`OR`** "Civil Rights" [try in browser](https://api.quotable.io/quotes/random?tags=history|civil-rights)

```HTTP
GET /quotes/random?tags=history|civil-rights
```

Random Quote with a maximum length of 50 characters [try in browser](https://api.quotable.io/quotes/random?maxLength=50)

```HTTP
GET /quotes/random?maxLength=50
```

Random Quote with a length between 100 and 140 characters [try in browser](https://api.quotable.io/quotes/random?minLength=100&maxLength=140)

```HTTP
GET /quotes/random?minLength=100&maxLength=140
```
## List Quotes

```HTTP
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,2 1,3 @@
export const MAX_LIMIT = 150
export const DEFAULT_LIMIT = 20
export const MAX_RANDOM_COUNT = 2000
91 changes: 91 additions & 0 deletions src/controllers/quotes/randomQuote.v2.js
Original file line number Diff line number Diff line change
@@ -0,0 1,91 @@
import createError from 'http-errors'
import { clamp } from 'lodash-es'
import Quotes from '../../models/Quotes.js'
import getTagsFilter from '../utils/getTagsFilter.js'
import getLengthFilter from '../utils/getLengthFilter.js'
import slug from '../utils/slug.js'
import { MAX_RANDOM_COUNT } from '../../constants.js'

/**
* Get one or more random quotes.
*
* This is a non-paginated endpoint that can return multiple objects.
* Unlike paginated endpoints, the response does not include pagination
* properties such as `count`, `totalCount`, `page`, etc. The response
* is simply an Array containing one or more quote objects.
*
* The `limit` parameter specifies the maximum number of random quotes
* to return. If filter parameters are also used, the number of quotes
* returned may be less than the limit parameter, or even zero if no
* quotes match the parameters.
*
* @param {Object} params
* @param {string} [params.tags] List of tags separated by comma or pipe
* @param {string} [params.authorSlug] One or more author slugs (pipe separated)
* @param {string} [params.minLength] minimum quote length in characters
* @param {string} [params.maxLength] maximum quote length in characters
*/
export default async function getRandomQuote(req, res, next) {
try {
const {
minLength,
maxLength,
tags,
author,
authorId,
authorSlug,
limit = 1,
} = req.query

// Let `size` be the number of random quotes to retrieve
// Note: if
const size = clamp(parseInt(limit), 1, MAX_RANDOM_COUNT)

const filter = {}

if (minLength || maxLength) {
filter.length = getLengthFilter(minLength, maxLength)
}

if (tags) {
filter.tags = getTagsFilter(tags)
}

if (authorId) {
// @deprecated
// Use the `author` param to filter by author `name` or `slug` instead
filter.authorId = { $in: authorId.split('|') }
}

if (author) {
if (/,/.test(author)) {
// If `author` is a comma-separated list, respond with an error
const message = 'Multiple authors should be separated by a pipe.'
return next(createError(400, message))
}
// Filter quotes by author slug.
filter.authorSlug = { $in: author.split('|').map(slug) }
}

if (authorSlug) {
// @deprecated
// use `author` param instead
filter.authorSlug = { $in: author.split('|').map(slug) }
}

const results = await Quotes.aggregate([
// Apply filters (if any)
{ $match: filter },
// Select n random quotes from the database, where n = limit
{ $sample: { size: limit } },
{ $project: { __v: 0, authorId: 0 } },
])
const count = results.length
// Return the array of random quotes
// If there are no quotes that match the given query parameters (filters),
// the method will response with an empty array
res.status(200).json(results)
} catch (error) {
return next(error)
}
}
2 changes: 2 additions & 0 deletions src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 4,7 @@ import getQuoteById from './controllers/quotes/getQuoteById.js'
import searchQuotes from './controllers/search/searchQuotes.js'
import searchAuthors from './controllers/search/searchAuthors.js'
import randomQuote from './controllers/quotes/randomQuote.js'
import randomQuotes from './controllers/quotes/randomQuote.v2.js'
import listAuthors from './controllers/authors/listAuthors.js'
import getAuthorById from './controllers/authors/getAuthorById.js'
import getAuthorBySlug from './controllers/authors/getAuthorBySlug.js'
Expand All @@ -16,6 17,7 @@ router.get('/internal/uip', (request, response) => response.send(request.ip))
** Quotes
**-----------------------------------------------*/
router.get('/quotes', listQuotes)
router.get('/quotes/random', randomQuotes)
router.get('/quotes/:id', getQuoteById)
router.get('/random', randomQuote)

Expand Down

0 comments on commit c5a1bab

Please sign in to comment.