Commit 812393c3 by Иван Кубота

API 0.1

parent c956b5bd
Pipeline #538 canceled with stage
var env = process.env;
const APP_HOST = env.APP_HOST || "127.0.0.1";
const APP_PORT = env.APP_PORT || 4001;
const DB_PATH = env.DB_PATH || "./db/users.json";
const data = require("./db.js");
const App = require('express');
const Router = require('node-async-router');
const app = App(),
router = Router();
const fs = require( 'fs' );
const tpls = {
index: fs.readFileSync('./index.html')+''
};
const print = function(data) {
return tpls.index.replace('$DATA$', data)
};
const api = [require('./src/api/generateRandom')].reduce((store, el)=>{
return Object.assign(store, el)
}, {});
const {api2html, api2routes} = require('./src/apiMappers.js');
/*
const api = {
'/api/generate/random': {
summary: 'Generate random quiz',
options:{
seed: {required: false, description: 'initialize random state. Make response determined', type: String},
photo: {required: false, description: 'question with photo', type: Boolean},
type: {required: false, description: '1 - products, 2 - standards', type: Number}
},
fn: async function() {
}
}
};*/
api2routes(api, router);
router.get('/api', async function(req, res) {
res.end(fs.readFileSync('./public/API.html', 'utf-8').replace('$API$', api2html(api)));
});
router.all('/', function(req, res) {
res.end('Open /api for details');
});
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Origin","*");
next();
});
app.use(router);
app.use(App.static('public'))
try{
app.listen( APP_PORT );
}catch(e){
console.error(e.message);
}
console.log(`LISTEN port :`+APP_PORT);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Question Generator API</title>
<link type="text/css" rel="stylesheet" href="/api.css">
</head>
<body>
$API$
</body>
</html>
\ No newline at end of file
* {
font-family: Verdana;
}
.api-option {
padding: 8px 16px;
}
.api-option-optional {
color: #888;
}
.api-options {
padding: 16px;
}
.api-description {
padding: 0 16px 8px;
}
.api-option-required {
color: #a00;
}
\ No newline at end of file
...@@ -67,7 +67,7 @@ const standardGenerator = function(random) { ...@@ -67,7 +67,7 @@ const standardGenerator = function(random) {
return { return {
type: q.multiple ? 'checkbox': 'radio', type: q.multiple ? 'checkbox': 'radio',
categoryId: q.category, categoryId: q.category,
productId: q.cardInfoID, productId: q.qID,
question: textFormat(q.title), question: textFormat(q.title),
answers: shuffle(q.answers.map(a => answers: shuffle(q.answers.map(a =>
a.correct ? a.correct ?
......
const sources = [
"js/releasable-observer.js",
"js/pcg-base.js",
"js/pcg-dom-util.js",
"js/helpers/rand.js",
"js/model/store.js",
"js/model/data.js",
"js/model/provider.js",
"js/model/slice/productTable.js",
"js/model/tag.js",
"js/model/product.js",
"js/helpers/textFilter.js",
"js/helpers/answer.js",
"js/controller/quizBits/main.js",
"js/controller/quizGenerator.js",
"js/controller/quizBits/checkbox.js",
"js/controller/quizBits/checkboxPhoto.js",
"js/controller/quizBits/radio.js",
"js/controller/quizBits/radioPhoto.js"
];
const fs = require('fs'),
path = require('path');
var js = sources.map(a=>path.join(__dirname,'../../public',a)).map(n=>fs.readFileSync(n)+'').join('\n\n');
const data = require("../../db.js");
const body = 'const window = {}, localStorage = {getItem:()=>"{}"};'+js+'; return {quizGenerator, standardGenerator, initDataProvider, seeded: Math.random.seeded, rand}';
var ctx = new Function('',body)();
const rand = ctx.rand,
seeded = ctx.seeded;
data.after = function(inComeData, skipInit){
if( inComeData ){
ctx.initDataProvider( data );
countStdQ = Object.values(data.standardQuestions).length;
}
};
let countStdQ;
module.exports = {
'/api/generate/random': {
method: 'GET',
summary: 'Generate random quiz',
options:{
seed: {required: false, description: 'initialize random state. Make response determined', type: String},
photo: {required: false, description: 'question with photo', type: Boolean},
type: {required: false, description: '1 - products, 2 - standards', type: Number},
nolog: {required: false, description: 'remove human readable log', type: Boolean}
},
fn: async function(args) {
let seed = args.seed || Math.random().toString(36).substr(2);
ctx.seeded.setStringSeed( seed );
let multiple = seeded() > 24 / ( 46 + 24 ),
photo = seeded() > 0.7,
result;
if((args.type !== void 0 ? args.type === 2: seeded()>0.5)){
result = ctx.standardGenerator(()=>rand(1,countStdQ))
}else{
result = ctx.quizGenerator( multiple ? 'checkbox' : 'radio', args.photo !== void 0 ? args.photo : photo );
}
console.log(`cat: ${result.categoryId}, prod: ${result.productId}, seed: ${seed}. ${result.question.substr(0,33)}`);
if(args.nolog)
delete result.log;
return result;
}
},
'/api/generate/random/sequence': {
method: 'GET',
summary: 'Generate list of random quizes',
options:{
count: {required: true, description: 'Count of generated questions', type: Number},
seed: {required: false, description: 'initialize random state. Make response determined', type: String},
photo: {required: false, description: 'question with photo', type: Boolean},
type: {required: false, description: '1 - products, 2 - standards', type: Number},
nolog: {required: false, description: 'remove human readable log', type: Boolean}
},
fn: async function(args) {
let seed = args.seed || Math.random().toString(36).substr(2);
ctx.seeded.setStringSeed( seed );
let used = {}, generated = 0;
let last = {c:-1},
list = [],
globalMaxTries = args.count*100,
globalTries = 0;
while(generated<args.count && globalTries < globalMaxTries){
globalTries++;
let cur = {
m: seeded() > 24 / ( 46 + 24 ),
p: seeded() > 0.7,
c: 1
};
if( last.c === 1 && seeded() > 0.5 ){
cur = {
c: 2
};
let id, tries = 0;
do{
id = rand(1, countStdQ);
tries++;
if(tries === 100){
break;
}
}while('2.'+id in used);
if(tries===100)
continue;
cur.id = id;
}
let tries = 0,
result;
do{
if(cur.c === 1){
result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p );
}else{
result = ctx.standardGenerator(()=>cur.id)
}
tries++;
if(tries === 100)
break;
}while(result.categoryId+'.'+result.productId in used);
if(tries !== 100){
used[result.categoryId+'.'+result.productId] = true;
list.push(result);
if(args.nolog)
delete result.log;
last = cur;
generated++;
result.number = generated;
}
}
return list;
}
}
};
\ No newline at end of file
const mappers = {
String: (t)=>{
if(typeof t !== 'string'){
throw new Error( '`' + t + '` is not a string' )
}
return t;
},
Boolean: (t)=>{
if('1,true,yes,yep,y'.split(',').indexOf(t)>-1)
return true;
if('0,false,n,no,nope,null'.split(',').indexOf(t)>-1)
return false;
if(typeof t !== 'boolean'){
throw new Error( '`' + t + '` is not boolean' )
}
return t;
},
Number: (t)=>{
if(typeof t !== 'number'){
let x= parseFloat(t)
if(x==t)
return x;
throw new Error( '`' + t + '` is not a number' )
}
return t;
}
};
const parseArgs = function(req, res, opts) {
const query = req.query;
const args = {};
const errs = [];
for(let k in opts) if(opts.hasOwnProperty(k)){
if( opts[ k ].required ){
if( !( k in query ) ){
errs.push( `Required argument ${k} (${opts[ k ].type.name}) is not specified` );
continue;
}
if( query[ k ] === null || query[ k ] === void 0 ){
if( opts[ k ].empty ){
args[ k ] = query[ k ];
continue
}else{
errs.push( `Argument ${k} (${opts[ k ].type.name}) can not be empty` );
continue
}
}
}
if( query[ k ] === null || query[ k ] === void 0 ){
args[ k ] = query[ k ];
continue
}
try{
args[ k ] = mappers[ opts[ k ].type.name ]( query[ k ] )
}catch( e ){
errs.push( `Argument \`${k}\` (${opts[ k ].type.name}) type mismatch: ${e.message}` );
}
}
if(errs.length)
throw errs;
return args;
};
module.exports = {
api2html: function(apis) {
var out = [];
for(let key in apis){
let api = apis[key];
out.push(`<div class="api"><H2>${api.method} ${key}</H2>
<div class="api-description">${api.summary}</div>
<div class="api-options"><span class="api-options-title">Options:</span>
${Object.keys(api.options).map(optName=>{
let opt = api.options[optName];
return `<div class="api-option">
<span class="api-option-name">${optName}</span>
<span class="api-option-type">${opt.type.name || opt.type}</span>
${opt.required?'<span class="api-option-required">Required</span>': '<span class="api-option-optional">Optional</span>'}
&nbsp;— <span class="api-option-description">${opt.description}</span>
</div>`;
}).join('')}
</div>
</div>`)
}
return out.join('')
},
api2routes: function(apis, router) {
for(let key in apis){
let api = apis[key], args;
router[api.method.toLowerCase()](key, async function(req, res) {
try{
args = parseArgs( req, res, api.options );
}catch(e){
return res.end('Errors:\n' +e.map(e=>'\t'+e).join('\n'))
}
res.header("Content-Type", "application/json; charset=utf-8");
try{
let result = await api.fn(args, req, res);
res.end(typeof result === 'string' ? result : JSON.stringify(result, null,1), 'utf-8');
}catch(e){
debugger
res.end(e.message+'\n'+e.stack)
}
})
}
}
};
\ No newline at end of file
openapi: 3.0.2
info:
title: 'Question Generator API'
version: 0.0.1
paths:
/api/database/components:
get:
operationId: downloadComponents
summary: Retrieves Product Components in csv format
responses:
200:
description: Product Components
content:
text/csv:
schema:
type: array
items:
$ref: '#/components/schemas/ProductComponent'
/api/get/components/{productID}:
get:
operationId: getComponents
summary: Retrieves Products Components
responses:
'200':
description: Get Products Components
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ProductComponent'
parameters:
- name: productID
in: path
required: true
schema:
type: integer
/api/generate/random:
x-swagger-router-controller: test
get:
operationId: generateRandom
summary: Generate random quiz
responses:
'200':
description: Generated quiz
content:
application/json:
schema:
type: object
items:
$ref: '#/components/schemas/Quiz'
parameters:
- name: seed
in: query
required: false
description: initialize random state. Make response determined
schema:
type: string
- name: photo
in: query
required: false
schema:
type: boolean
- name: type
in: query
required: false
description: 1 - products, 2 - standards
schema:
type: integer
components:
schemas:
ProductComponent:
type: object
description: Component of a product
properties:
productID:
type: integer
name:
type: string
Quiz:
type: object
description: quiz
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment