@miso.ai/doggoganger 0.9.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +33 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/bin/version.js +46 -0
- package/cli/nodemon.js +21 -0
- package/cli/server.js +4 -0
- package/package.json +40 -0
- package/src/api/answers.js +117 -0
- package/src/api/handlers.js +10 -0
- package/src/api/index.js +18 -0
- package/src/api/interactions.js +9 -0
- package/src/api/recommendation.js +9 -0
- package/src/api/search.js +8 -0
- package/src/data/articles.js +47 -0
- package/src/data/index.js +4 -0
- package/src/data/lorem.js +112 -0
- package/src/data/products.js +51 -0
- package/src/data/utils.js +28 -0
- package/src/data/words.yaml +178 -0
- package/src/index.js +19 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: NPM publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish-npm:
|
|
9
|
+
environment: production
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Get version
|
|
13
|
+
id: version
|
|
14
|
+
run: |
|
|
15
|
+
VERSION=${{ github.event.release.tag_name }}
|
|
16
|
+
echo "::set-output name=version::$(echo $VERSION | sed -e 's/v//gI')"
|
|
17
|
+
- uses: actions/checkout@v2
|
|
18
|
+
- uses: actions/setup-node@v2
|
|
19
|
+
with:
|
|
20
|
+
node-version: 16
|
|
21
|
+
registry-url: https://registry.npmjs.org/
|
|
22
|
+
- run: npm install
|
|
23
|
+
- run: git config --global user.name "${{ github.actor }}"
|
|
24
|
+
- run: git config --global user.email "github-action-${{ github.actor }}@users.noreply.github.com"
|
|
25
|
+
- run: npm run version ${{ steps.version.outputs.version }}
|
|
26
|
+
- run: npm publish
|
|
27
|
+
if: "!github.event.release.prerelease"
|
|
28
|
+
env:
|
|
29
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
30
|
+
- run: npm publish --tag beta
|
|
31
|
+
if: "github.event.release.prerelease"
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Miso Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/bin/version.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
const VERSION_REGEXP = /^\d+\.\d+\.\d+(?:-beta\.\d+)?$/;
|
|
8
|
+
const version = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!version) {
|
|
11
|
+
console.log(`Usage: npm run version [version]`);
|
|
12
|
+
process.exit();
|
|
13
|
+
}
|
|
14
|
+
if (!VERSION_REGEXP.test(version)) {
|
|
15
|
+
console.error(`Illegal version: ${version}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rootDir = join(__dirname, '..');
|
|
20
|
+
//const VERSION_FILE_NAME = 'src/version.js';
|
|
21
|
+
const PACKAGE_FILE_NAME = 'package.json';
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
function writeVersionFile(path, version) {
|
|
25
|
+
const filePath = join(rootDir, path, VERSION_FILE_NAME);
|
|
26
|
+
if (existsSync(filePath)) {
|
|
27
|
+
writeFileSync(filePath, `export default '${version}';`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
function readPackageFileSync(path) {
|
|
33
|
+
const file = join(path, PACKAGE_FILE_NAME);
|
|
34
|
+
return existsSync(file) ? JSON.parse(readFileSync(file)) : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writePackageFileSync(path, data) {
|
|
38
|
+
const file = join(path, PACKAGE_FILE_NAME);
|
|
39
|
+
if (!existsSync(file)) {
|
|
40
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
writeFileSync(join(path, PACKAGE_FILE_NAME), JSON.stringify(data, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { name, version: _version, ...project } = readPackageFileSync(rootDir);
|
|
46
|
+
writePackageFileSync(rootDir, { name, version, ...project });
|
package/cli/nodemon.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, resolve, join } from 'path';
|
|
4
|
+
import nodemon from 'nodemon';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
nodemon({
|
|
9
|
+
script: resolve(__dirname, 'server.js'),
|
|
10
|
+
watch: join(__dirname, '../src'),
|
|
11
|
+
ext: 'js json yaml',
|
|
12
|
+
verbose: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
nodemon.on('log', ({ colour }) => {
|
|
16
|
+
console.log(colour);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
nodemon.on('quit', () => {
|
|
20
|
+
process.exit();
|
|
21
|
+
});
|
package/cli/server.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@miso.ai/doggoganger",
|
|
3
|
+
"version": "0.9.0-beta.2",
|
|
4
|
+
"description": "A dummy miso endpoint for demo and testing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"doggomon": "cli/nodemon.js",
|
|
8
|
+
"doggoganger": "cli/server.js"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "nodemon src/index.js",
|
|
15
|
+
"version": "node ./bin/version.js"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/MisoAI/miso-client-js-sdk.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://misoai.github.io/miso-client-js-sdk/",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"contributors": [
|
|
24
|
+
"simonpai <simon.pai@askmiso.com>"
|
|
25
|
+
],
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@koa/cors": "^4.0.0",
|
|
31
|
+
"@koa/router": "^12.0.0",
|
|
32
|
+
"js-yaml": "^4.1.0",
|
|
33
|
+
"koa": "^2.14.1",
|
|
34
|
+
"koa-body": "^6.0.1",
|
|
35
|
+
"uuid": "^8.3.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"nodemon": "^2.0.15"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Router from '@koa/router';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
import { lorem, articles, utils } from '../data/index.js';
|
|
4
|
+
|
|
5
|
+
const router = new Router();
|
|
6
|
+
|
|
7
|
+
const WPS = 20;
|
|
8
|
+
const ITEMS_LOADING_TIME = 3;
|
|
9
|
+
const STAGES = [
|
|
10
|
+
{
|
|
11
|
+
duration: 1.5,
|
|
12
|
+
text: `Fetching results...`,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
duration: 1.5,
|
|
16
|
+
text: `Generating answer...`,
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function formatDatetime(timestamp) {
|
|
21
|
+
const str = new Date(timestamp).toISOString();
|
|
22
|
+
return str.endsWith('Z') ? str.slice(0, -1) : str;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const answers = new Map();
|
|
26
|
+
|
|
27
|
+
class Answer {
|
|
28
|
+
|
|
29
|
+
constructor(question, previous_question_id) {
|
|
30
|
+
this.question_id = uuid();
|
|
31
|
+
this.question = question;
|
|
32
|
+
this.previous_answer_id = previous_question_id;
|
|
33
|
+
this.timestamp = Date.now();
|
|
34
|
+
this.datetime = formatDatetime(this.timestamp);
|
|
35
|
+
|
|
36
|
+
this.answer = lorem.lorem({
|
|
37
|
+
min: 50,
|
|
38
|
+
max: 100,
|
|
39
|
+
decorates: ['description'],
|
|
40
|
+
output: 'array',
|
|
41
|
+
});
|
|
42
|
+
this.relatedResources = [...articles({ rows: utils.randomInt(6, 8) })];
|
|
43
|
+
this.sources = [...articles({ rows: utils.randomInt(4, 6) })];
|
|
44
|
+
|
|
45
|
+
this.previous_question_id = previous_question_id && answers.get(previous_question_id) || undefined;
|
|
46
|
+
answers.set(this.question_id, this);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get() {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const elapsed = (now - this.timestamp) / 1000;
|
|
52
|
+
const [answer, finished] = this._answer(elapsed);
|
|
53
|
+
const sources = this._sources(elapsed);
|
|
54
|
+
const related_resources = this._relatedResources(elapsed);
|
|
55
|
+
const { question_id, question, datetime, previous_question_id } = this;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
affiliation: undefined,
|
|
59
|
+
answer,
|
|
60
|
+
datetime,
|
|
61
|
+
finished,
|
|
62
|
+
previous_question_id,
|
|
63
|
+
question,
|
|
64
|
+
question_id,
|
|
65
|
+
related_resources,
|
|
66
|
+
sources,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_answer(elapsed) {
|
|
71
|
+
for (const stage of STAGES) {
|
|
72
|
+
elapsed -= stage.duration;
|
|
73
|
+
if (elapsed < 0) {
|
|
74
|
+
return [stage.text, false];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const wordCount = Math.floor(elapsed * WPS);
|
|
78
|
+
const finished = wordCount >= this.answer.length;
|
|
79
|
+
const text = (finished ? this.answer : this.answer.slice(0, wordCount)).join(' ');
|
|
80
|
+
return [text, finished];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_sources(elapsed) {
|
|
84
|
+
const { sources } = this;
|
|
85
|
+
const { length } = sources;
|
|
86
|
+
const loaded = Math.floor(length * elapsed / ITEMS_LOADING_TIME);
|
|
87
|
+
return sources.slice(0, loaded);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_relatedResources(elapsed) {
|
|
91
|
+
const { relatedResources } = this;
|
|
92
|
+
const { length } = relatedResources;
|
|
93
|
+
const loaded = Math.floor(length * elapsed / ITEMS_LOADING_TIME);
|
|
94
|
+
return relatedResources.slice(0, loaded);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
router.post('/questions', (ctx) => {
|
|
100
|
+
const { q: question, previous_answer_id } = JSON.parse(ctx.request.body);
|
|
101
|
+
const answer = new Answer(question, previous_answer_id);
|
|
102
|
+
const data = answer.get();
|
|
103
|
+
ctx.body = JSON.stringify({ data });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
router.get('/questions/:id/answer', (ctx) => {
|
|
107
|
+
const { id } = ctx.params;
|
|
108
|
+
const answer = answers.get(id);
|
|
109
|
+
if (!answer) {
|
|
110
|
+
ctx.status = 404;
|
|
111
|
+
} else {
|
|
112
|
+
const data = answer.get();
|
|
113
|
+
ctx.body = JSON.stringify({ data });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export default router;
|
package/src/api/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Router from '@koa/router';
|
|
2
|
+
import answers from './answers.js';
|
|
3
|
+
import recommendation from './recommendation.js';
|
|
4
|
+
import search from './search.js';
|
|
5
|
+
import interactions from './interactions.js';
|
|
6
|
+
|
|
7
|
+
function use(router, path, middleware) {
|
|
8
|
+
router.use(path, middleware.routes(), middleware.allowedMethods());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const router = new Router();
|
|
12
|
+
|
|
13
|
+
use(router, '/answers', answers);
|
|
14
|
+
use(router, '/recommendation', recommendation);
|
|
15
|
+
use(router, '/search', search);
|
|
16
|
+
use(router, '/interactions', interactions);
|
|
17
|
+
|
|
18
|
+
export default router;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as u from './utils.js';
|
|
2
|
+
import * as lorem from './lorem.js';
|
|
3
|
+
|
|
4
|
+
export function *articles({ rows, ...options } = {}) {
|
|
5
|
+
for (let i = 0; i < rows; i++) {
|
|
6
|
+
yield article({ ...options, index: i });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function article({} = {}) {
|
|
11
|
+
const id = u.id();
|
|
12
|
+
const prices = u.repeat(u.price, 1, 2);
|
|
13
|
+
prices.sort();
|
|
14
|
+
const seed = Math.floor(Math.random() * 1000);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
product_id: id,
|
|
18
|
+
authors: lorem.lorem({
|
|
19
|
+
min: 1,
|
|
20
|
+
max: 3,
|
|
21
|
+
fixedStarts: 0,
|
|
22
|
+
decorates: ['title'],
|
|
23
|
+
output: 'array',
|
|
24
|
+
}),
|
|
25
|
+
categories: [],
|
|
26
|
+
tags: lorem.lorem({
|
|
27
|
+
min: 1,
|
|
28
|
+
max: 4,
|
|
29
|
+
fixedStarts: 0,
|
|
30
|
+
output: 'array',
|
|
31
|
+
}),
|
|
32
|
+
title: lorem.lorem({
|
|
33
|
+
min: 4,
|
|
34
|
+
max: 10,
|
|
35
|
+
fixedStarts: 0,
|
|
36
|
+
decorates: ['title'],
|
|
37
|
+
}),
|
|
38
|
+
description: lorem.lorem({
|
|
39
|
+
min: 20,
|
|
40
|
+
max: 40,
|
|
41
|
+
decorates: ['description'],
|
|
42
|
+
}),
|
|
43
|
+
//html,
|
|
44
|
+
cover_image: `https://picsum.photos/seed/${seed}/300`,
|
|
45
|
+
url: `/products/${id}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, resolve } from 'path';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
5
|
+
import { randomInt } from './utils.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const DEFAULT_WORDS = yaml.load(readFileSync(resolve(__dirname, './words.yaml'), 'utf8'));
|
|
9
|
+
|
|
10
|
+
export function lorem({ decorates = [], min, max, n, output = 'string', ...options } = {}) {
|
|
11
|
+
let iterator = limit({ min, max, n })(base(options));
|
|
12
|
+
for (const decorate of decorates) {
|
|
13
|
+
iterator = lookup(decorate)(iterator);
|
|
14
|
+
}
|
|
15
|
+
return lookup(output)(iterator);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FNS = {
|
|
19
|
+
string,
|
|
20
|
+
array,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function lookup(fn) {
|
|
26
|
+
return typeof fn === 'string' ? FNS[fn]() : fn();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// base //
|
|
30
|
+
export function *base({ words = DEFAULT_WORDS, fixedStarts = 2 } = {}) {
|
|
31
|
+
const wordsLength = words.length;
|
|
32
|
+
for (let i = 0; ; i++) {
|
|
33
|
+
yield words[i < fixedStarts ? i : Math.floor(Math.random() * wordsLength)];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// output //
|
|
38
|
+
export function string({ separator = ' ' } = {}) {
|
|
39
|
+
return iterator => [...iterator].join(separator);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function array() {
|
|
43
|
+
return iterator => [...iterator];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// decorators //
|
|
47
|
+
export function limit({ n, min = 5, max = 10 }) {
|
|
48
|
+
n = n || randomInt(min, max);
|
|
49
|
+
return function *(iterator) {
|
|
50
|
+
let i = 0;
|
|
51
|
+
for (let word of iterator) {
|
|
52
|
+
if (i++ >= n) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
yield word;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function title({} = {}) {
|
|
61
|
+
return function *(iterator) {
|
|
62
|
+
for (let word of iterator) {
|
|
63
|
+
yield capitalize(word);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function description({
|
|
69
|
+
wordsPerSentenceAvg = 24,
|
|
70
|
+
wordsPerSentenceStd = 5,
|
|
71
|
+
} = {}) {
|
|
72
|
+
return function *(iterator) {
|
|
73
|
+
let word;
|
|
74
|
+
let slen = 0;
|
|
75
|
+
for (let _word of iterator) {
|
|
76
|
+
if (word) {
|
|
77
|
+
yield word;
|
|
78
|
+
}
|
|
79
|
+
word = _word;
|
|
80
|
+
if (slen === 0) {
|
|
81
|
+
word = capitalize(word);
|
|
82
|
+
slen = Math.max(1, gaussMS(wordsPerSentenceAvg, wordsPerSentenceStd));
|
|
83
|
+
}
|
|
84
|
+
if (--slen === 0) {
|
|
85
|
+
word += '.';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (word) {
|
|
89
|
+
if (!word.endsWith('.')) {
|
|
90
|
+
word += '.';
|
|
91
|
+
}
|
|
92
|
+
yield word;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// helpers //
|
|
98
|
+
function capitalize(word) {
|
|
99
|
+
return word[0].toUpperCase() + word.substring(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function gaussMS(mean, std) {
|
|
103
|
+
return Math.round(gaussRandom() * std + mean);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function gaussRandom() {
|
|
107
|
+
return uniformRandom() + uniformRandom() + uniformRandom();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function uniformRandom() {
|
|
111
|
+
return Math.random() * 2 - 1;
|
|
112
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as u from './utils.js';
|
|
2
|
+
import * as lorem from './lorem.js';
|
|
3
|
+
|
|
4
|
+
export function *products({ rows, ...options } = {}) {
|
|
5
|
+
for (let i = 0; i < rows; i++) {
|
|
6
|
+
yield product({ ...options, index: i });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function product({} = {}) {
|
|
11
|
+
const id = u.id();
|
|
12
|
+
const prices = u.repeat(u.price, 1, 2);
|
|
13
|
+
prices.sort();
|
|
14
|
+
const seed = Math.floor(Math.random() * 1000);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
product_id: id,
|
|
18
|
+
authors: lorem.lorem({
|
|
19
|
+
min: 1,
|
|
20
|
+
max: 3,
|
|
21
|
+
fixedStarts: 0,
|
|
22
|
+
decorates: ['title'],
|
|
23
|
+
output: 'array',
|
|
24
|
+
}),
|
|
25
|
+
categories: [],
|
|
26
|
+
tags: lorem.lorem({
|
|
27
|
+
min: 1,
|
|
28
|
+
max: 4,
|
|
29
|
+
fixedStarts: 0,
|
|
30
|
+
output: 'array',
|
|
31
|
+
}),
|
|
32
|
+
title: lorem.lorem({
|
|
33
|
+
min: 2,
|
|
34
|
+
max: 6,
|
|
35
|
+
fixedStarts: 0,
|
|
36
|
+
decorates: ['title'],
|
|
37
|
+
}),
|
|
38
|
+
description: lorem.lorem({
|
|
39
|
+
min: 10,
|
|
40
|
+
max: 20,
|
|
41
|
+
decorates: ['description'],
|
|
42
|
+
}),
|
|
43
|
+
//html,
|
|
44
|
+
cover_image: `https://picsum.photos/seed/${seed}/300`,
|
|
45
|
+
url: `/products/${id}`,
|
|
46
|
+
sale_price: prices[0],
|
|
47
|
+
original_price: prices[prices.length - 1],
|
|
48
|
+
rating: u.rating(),
|
|
49
|
+
availability: u.availability(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function randomInt(min, max) {
|
|
2
|
+
return max > min ? min + Math.floor(Math.random() * (max - min)) : min;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function repeat(fn, min = 1, max = 2) {
|
|
6
|
+
const n = randomInt(min, max);
|
|
7
|
+
const result = [];
|
|
8
|
+
for (let i = 0; i < n; i++) {
|
|
9
|
+
result.push(fn());
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function id() {
|
|
15
|
+
return Math.random().toString(36).substring(2, 10);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function availability() {
|
|
19
|
+
return Math.random() > 0.3 ? 'IN_STOCK' : 'OUT_OF_STOCK';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function price() {
|
|
23
|
+
return Math.floor(Math.random() * 10000) / 100;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function rating() {
|
|
27
|
+
return Math.floor(Math.random() * 500) / 100 + 1;
|
|
28
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
- lorem
|
|
2
|
+
- ipsum
|
|
3
|
+
- dolor
|
|
4
|
+
- sit
|
|
5
|
+
- amet
|
|
6
|
+
- consectetur
|
|
7
|
+
- adipiscing
|
|
8
|
+
- elit
|
|
9
|
+
- curabitur
|
|
10
|
+
- vel
|
|
11
|
+
- hendrerit
|
|
12
|
+
- libero
|
|
13
|
+
- eleifend
|
|
14
|
+
- blandit
|
|
15
|
+
- nunc
|
|
16
|
+
- ornare
|
|
17
|
+
- odio
|
|
18
|
+
- ut
|
|
19
|
+
- orci
|
|
20
|
+
- gravida
|
|
21
|
+
- imperdiet
|
|
22
|
+
- nullam
|
|
23
|
+
- purus
|
|
24
|
+
- lacinia
|
|
25
|
+
- a
|
|
26
|
+
- pretium
|
|
27
|
+
- quis
|
|
28
|
+
- congue
|
|
29
|
+
- praesent
|
|
30
|
+
- sagittis
|
|
31
|
+
- laoreet
|
|
32
|
+
- auctor
|
|
33
|
+
- mauris
|
|
34
|
+
- non
|
|
35
|
+
- velit
|
|
36
|
+
- eros
|
|
37
|
+
- dictum
|
|
38
|
+
- proin
|
|
39
|
+
- accumsan
|
|
40
|
+
- sapien
|
|
41
|
+
- nec
|
|
42
|
+
- massa
|
|
43
|
+
- volutpat
|
|
44
|
+
- venenatis
|
|
45
|
+
- sed
|
|
46
|
+
- eu
|
|
47
|
+
- molestie
|
|
48
|
+
- lacus
|
|
49
|
+
- quisque
|
|
50
|
+
- porttitor
|
|
51
|
+
- ligula
|
|
52
|
+
- dui
|
|
53
|
+
- mollis
|
|
54
|
+
- tempus
|
|
55
|
+
- at
|
|
56
|
+
- magna
|
|
57
|
+
- vestibulum
|
|
58
|
+
- turpis
|
|
59
|
+
- ac
|
|
60
|
+
- diam
|
|
61
|
+
- tincidunt
|
|
62
|
+
- id
|
|
63
|
+
- condimentum
|
|
64
|
+
- enim
|
|
65
|
+
- sodales
|
|
66
|
+
- in
|
|
67
|
+
- hac
|
|
68
|
+
- habitasse
|
|
69
|
+
- platea
|
|
70
|
+
- dictumst
|
|
71
|
+
- aenean
|
|
72
|
+
- neque
|
|
73
|
+
- fusce
|
|
74
|
+
- augue
|
|
75
|
+
- leo
|
|
76
|
+
- eget
|
|
77
|
+
- semper
|
|
78
|
+
- mattis
|
|
79
|
+
- tortor
|
|
80
|
+
- scelerisque
|
|
81
|
+
- nulla
|
|
82
|
+
- interdum
|
|
83
|
+
- tellus
|
|
84
|
+
- malesuada
|
|
85
|
+
- rhoncus
|
|
86
|
+
- porta
|
|
87
|
+
- sem
|
|
88
|
+
- aliquet
|
|
89
|
+
- et
|
|
90
|
+
- nam
|
|
91
|
+
- suspendisse
|
|
92
|
+
- potenti
|
|
93
|
+
- vivamus
|
|
94
|
+
- luctus
|
|
95
|
+
- fringilla
|
|
96
|
+
- erat
|
|
97
|
+
- donec
|
|
98
|
+
- justo
|
|
99
|
+
- vehicula
|
|
100
|
+
- ultricies
|
|
101
|
+
- varius
|
|
102
|
+
- ante
|
|
103
|
+
- primis
|
|
104
|
+
- faucibus
|
|
105
|
+
- ultrices
|
|
106
|
+
- posuere
|
|
107
|
+
- cubilia
|
|
108
|
+
- curae
|
|
109
|
+
- etiam
|
|
110
|
+
- cursus
|
|
111
|
+
- aliquam
|
|
112
|
+
- quam
|
|
113
|
+
- dapibus
|
|
114
|
+
- nisl
|
|
115
|
+
- feugiat
|
|
116
|
+
- egestas
|
|
117
|
+
- class
|
|
118
|
+
- aptent
|
|
119
|
+
- taciti
|
|
120
|
+
- sociosqu
|
|
121
|
+
- ad
|
|
122
|
+
- litora
|
|
123
|
+
- torquent
|
|
124
|
+
- per
|
|
125
|
+
- conubia
|
|
126
|
+
- nostra
|
|
127
|
+
- inceptos
|
|
128
|
+
- himenaeos
|
|
129
|
+
- phasellus
|
|
130
|
+
- nibh
|
|
131
|
+
- pulvinar
|
|
132
|
+
- vitae
|
|
133
|
+
- urna
|
|
134
|
+
- iaculis
|
|
135
|
+
- lobortis
|
|
136
|
+
- nisi
|
|
137
|
+
- viverra
|
|
138
|
+
- arcu
|
|
139
|
+
- morbi
|
|
140
|
+
- pellentesque
|
|
141
|
+
- metus
|
|
142
|
+
- commodo
|
|
143
|
+
- ut
|
|
144
|
+
- facilisis
|
|
145
|
+
- felis
|
|
146
|
+
- tristique
|
|
147
|
+
- ullamcorper
|
|
148
|
+
- placerat
|
|
149
|
+
- aenean
|
|
150
|
+
- convallis
|
|
151
|
+
- sollicitudin
|
|
152
|
+
- integer
|
|
153
|
+
- rutrum
|
|
154
|
+
- duis
|
|
155
|
+
- est
|
|
156
|
+
- etiam
|
|
157
|
+
- bibendum
|
|
158
|
+
- donec
|
|
159
|
+
- pharetra
|
|
160
|
+
- vulputate
|
|
161
|
+
- maecenas
|
|
162
|
+
- mi
|
|
163
|
+
- fermentum
|
|
164
|
+
- consequat
|
|
165
|
+
- suscipit
|
|
166
|
+
- aliquam
|
|
167
|
+
- habitant
|
|
168
|
+
- senectus
|
|
169
|
+
- netus
|
|
170
|
+
- fames
|
|
171
|
+
- quisque
|
|
172
|
+
- euismod
|
|
173
|
+
- curabitur
|
|
174
|
+
- lectus
|
|
175
|
+
- elementum
|
|
176
|
+
- tempor
|
|
177
|
+
- risus
|
|
178
|
+
- cras
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Koa from 'koa';
|
|
2
|
+
import Router from '@koa/router';
|
|
3
|
+
import cors from '@koa/cors';
|
|
4
|
+
import { koaBody } from 'koa-body';
|
|
5
|
+
import api from './api/index.js';
|
|
6
|
+
|
|
7
|
+
export default function doggoganger() {
|
|
8
|
+
const app = new Koa();
|
|
9
|
+
const router = new Router();
|
|
10
|
+
|
|
11
|
+
router.use('/api', api.routes(), api.allowedMethods());
|
|
12
|
+
|
|
13
|
+
app
|
|
14
|
+
.use(cors())
|
|
15
|
+
.use(koaBody())
|
|
16
|
+
.use(router.routes())
|
|
17
|
+
.use(router.allowedMethods())
|
|
18
|
+
.listen(9901);
|
|
19
|
+
}
|