Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(generate): basic zine structure #5

Merged
merged 62 commits into from
Nov 2, 2021
Merged
Changes from 1 commit
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8485c21
Template stuff
superflyxxi Oct 29, 2021
e178fbb
Copied template
superflyxxi Oct 29, 2021
b411676
init
superflyxxi Oct 29, 2021
c615483
Copy
superflyxxi Oct 30, 2021
33e5459
Remove validate
superflyxxi Oct 30, 2021
8504de4
Provide rankRules
superflyxxi Oct 30, 2021
31cc80e
Actual function
superflyxxi Oct 30, 2021
5cc224f
Remove cache dependency
superflyxxi Oct 30, 2021
ff2b736
Use versions
superflyxxi Oct 30, 2021
2536a76
Basic controller
superflyxxi Oct 30, 2021
aa1705c
Basic rank-score test
superflyxxi Oct 30, 2021
d0016a5
Add tests
superflyxxi Oct 30, 2021
7dfba43
Test empty items
superflyxxi Oct 30, 2021
6ace394
Update package json
superflyxxi Oct 30, 2021
833ca72
Args for testing
superflyxxi Oct 30, 2021
441afcb
Ignore test results
superflyxxi Oct 30, 2021
2a8af3f
Fixed formatting
superflyxxi Oct 30, 2021
aa33b79
Test no rankRules
superflyxxi Oct 30, 2021
536991a
Common libraries
superflyxxi Oct 30, 2021
5f2b6ff
Error handler
superflyxxi Oct 30, 2021
c224fbb
common files
superflyxxi Oct 30, 2021
8dc957a
Include assert
superflyxxi Oct 30, 2021
e487664
Merged origin/main
superflyxxi Oct 31, 2021
b44606d
Fixed XO
superflyxxi Oct 31, 2021
28d9e61
Fixed etst-results name
superflyxxi Oct 31, 2021
1f83c67
Moved validation error to common
superflyxxi Oct 31, 2021
28b5683
Validate function
superflyxxi Oct 31, 2021
b470094
Use common
superflyxxi Oct 31, 2021
101dcbb
Fixed xo
superflyxxi Oct 31, 2021
04a670a
Moved validate.js to common
superflyxxi Oct 31, 2021
3a2b354
Tested some basic things
superflyxxi Oct 31, 2021
3bf05ce
Test boolean
superflyxxi Oct 31, 2021
8e26295
Test all Booleans
superflyxxi Oct 31, 2021
43ab56a
Added integers
superflyxxi Oct 31, 2021
940b798
Decimal
superflyxxi Oct 31, 2021
e629eb2
Boolean type
superflyxxi Oct 31, 2021
2fefdbe
Validated arrays
superflyxxi Oct 31, 2021
5e5c8d3
Version Object
superflyxxi Oct 31, 2021
544e210
TEst version
superflyxxi Oct 31, 2021
f8df1bc
Replaced with common
superflyxxi Oct 31, 2021
8523281
Make it incomplete
superflyxxi Oct 31, 2021
a3f208d
Fixed XO
superflyxxi Oct 31, 2021
aa941e9
Validate int-test Dockerfile
superflyxxi Oct 31, 2021
c90f7c1
test empty rank
superflyxxi Oct 31, 2021
4f41543
Test single object
superflyxxi Oct 31, 2021
17eb872
Place identifier in output
superflyxxi Oct 31, 2021
c7473b1
Support providing an identifier field
superflyxxi Nov 1, 2021
91bc893
Test numbers prefer lower
superflyxxi Nov 1, 2021
380b7b2
Test for version
superflyxxi Nov 1, 2021
c5e391d
Finish testing
superflyxxi Nov 1, 2021
6d80f92
sonar scan
superflyxxi Nov 2, 2021
7c0ced8
Renamed to match service
superflyxxi Nov 2, 2021
fbf2d6e
Fixed imports
superflyxxi Nov 2, 2021
94a58e8
Dummy code for now
superflyxxi Nov 2, 2021
77d1e85
Renamed file
superflyxxi Nov 2, 2021
49d6fd3
Basic routing
superflyxxi Nov 2, 2021
08283b9
make it simpler
superflyxxi Nov 2, 2021
f3a6df2
Fixed path
superflyxxi Nov 2, 2021
00e56fd
Fixed correct order of error handling
superflyxxi Nov 2, 2021
80386a2
Renamed to apiDocs
superflyxxi Nov 2, 2021
19bbc8e
Renamed to match file
superflyxxi Nov 2, 2021
b8de3ae
Renamed
superflyxxi Nov 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Basic controller
superflyxxi committed Oct 30, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 2536a768a530fe57d11bb570e5e9255ddc457ee9
206 changes: 9 additions & 197 deletions zine-generator/src/controllers/generator.js
Original file line number Diff line number Diff line change
@@ -1,207 +1,19 @@
import process from 'node:process';
import axios from 'axios';
import lodash from 'lodash';
import {server} from '../config/index.js';
import {server, rankRules} from '../config/index.js';
import validation from '../helpers/validation.js';
import rank from '../services/rank-score.js';

export default async function generate(req, res) {
validation(req.body, {
startDate: {presence: true, type: 'date'},
endDate: {presence: true, type: 'date'},
});

const ranking = req.body.ranking;
const phoneScoreList = await getItemScoreList(req.body.phones);
const rankScale = await generateScoreScale(ranking, phoneScoreList);
await scoreAndSortPhones(phoneScoreList, rankScale);
res.set('cache-control', 'public, max-age=2419200').send({
best: phoneScoreList[0],
results: phoneScoreList,
const items = [];
const ranking = ['comments', 'likes', 'date'];
const rankedList = await rank(rankRules, ranking, items);
//res.set('cache-control', 'public, max-age=2419200').send({
res.send({
best: rankedList[0],
results: rankedList,
});
}

async function scoreAndSortPhones(phoneScoreList, rankScale) {
const promises = [];
for (const phoneScore of phoneScoreList) {
promises.push(getFinalScore(rankScale, phoneScore));
}

await Promise.all(promises);

phoneScoreList.sort((alpha, beta) => beta.score - alpha.score);
}

async function getItemScoreList(itemList) {
const promises = [];
for (const item of itemList) {
promises.push(getPhoneScore(item));
}

return Promise.all(promises);
}

/**
* Generates an object that describes how each ranked property should be scored on a scale.
* For example, if the min height in the phone list is 130 and the max height is 150, then for
* each mm, it would be equal to X points. If the min height is 140 and the max is 145, then
* each mm is worth Y points, where Y > X.
*/
async function generateScoreScale(rankList, phoneScoreList) {
const scales = {};

for (const rank of rankList) {
scales[rank] = initScoreScaleForRank(rank, rankRules[rank], phoneScoreList);
}

let i = rankList.length;
for (const rank of rankList) {
populateScoreScaleForRank(scales[rank], 2 ** i--, rankRules[rank]);
}

return scales;
}

async function getPhoneScore(phone) {
const url =
'/v1/phones/' +
(phone.href ?? 'manufacturers/' + phone.manufacturer.toLowerCase() + '/models/' + phone.model.toLowerCase());
let data = cache.get(url);
if (!data) {
const res = await axios.get(PHONE_BASE_URL + url);
data = res.data;
const ttl = res.headers['cache-control'] ? res.headers['cache-control'].match(/max-age=(\d+)/i)[1] : 600;
cache.set(url, data, ttl);
}

return {href: url, phone: data};
}

function scoreNumber(value, rankRule, rankScale) {
if (rankRule.scoreMethod === 'PREFER_LOW') {
return (rankScale.values.max - value) * rankScale.multiplier;
}

if (rankRule.scoreMethod === 'PREFER_HIGH') {
return (value - rankScale.values.min) * rankScale.multiplier;
}

return 0;
}

function scoreBoolean(value, rankRule, rankScale) {
if (value && rankRule.scoreMethod === 'PREFER_TRUE') {
return rankScale.multiplier;
}

return 0;
}

function scoreVersion(value, rankRule, rankScale) {
const version = versions.getVersionObject(value);
const semantic = rankScale.semantic;
if (version[semantic] && rankRule.scoreMethod === 'PREFER_HIGH') {
return (version[semantic] - rankScale[semantic].min) * rankScale.multiplier;
}

return 0;
}

async function getFinalScore(rankScale, phoneScore) {
phoneScore.scoreBreakdown = {};
phoneScore.score = 0;
for (const rank in rankScale) {
const value = lodash.get(phoneScore.phone, rank);
let score = 0;
switch (rankRules[rank].type) {
case 'number':
score = scoreNumber(value, rankRules[rank], rankScale[rank]);
break;

case 'boolean':
score = scoreBoolean(value, rankRules[rank], rankScale[rank]);
break;

case 'version':
score = scoreVersion(value, rankRules[rank], rankScale[rank]);
break;

default:
console.error('Invalid type configured: rank=', rank, 'type=', rankRules[rank].type);
}

phoneScore.scoreBreakdown[rank] = score;
phoneScore.score += score;
}

delete phoneScore.phone;
}

function initScoreScaleForRank(rank, rankRule, phoneScoreList) {
const result = {};
const mapValues = {};
switch (rankRule.type) {
case 'number':
mapValues.values = [];
break;
case 'version':
mapValues.major = [];
mapValues.minor = [];
mapValues.patch = [];
break;
default:
// Skip any types that don't need counting
return result;
}

for (const phoneScore of phoneScoreList) {
const value = lodash.get(phoneScore.phone, rank);

if (value) {
let version;
switch (rankRule.type) {
case 'number':
mapValues.values.push(value);
break;
case 'version':
version = versions.getVersionObject(value);
if (version?.major) mapValues.major.push(version.major);
if (version?.minor) mapValues.minor.push(version.minor);
if (version?.patch) mapValues.patch.push(version.patch);
break;
default:
// Should never get here
}
}
}

for (const item of Object.keys(mapValues)) {
result[item] = {};
result[item].max = Math.max(...mapValues[item]);
result[item].min = Math.min(...mapValues[item]);
}

return result;
}

function populateScoreScaleForRank(scoreScale, maxPoints, rankRule) {
let temporarySemantic;
let semantic = 'major';
switch (rankRule.type) {
case 'number':
scoreScale.multiplier = maxPoints / (scoreScale.values.max - scoreScale.values.min);
break;
case 'version':
for (temporarySemantic of ['major', 'minor', 'patch']) {
if (scoreScale[temporarySemantic]?.max !== scoreScale[temporarySemantic]?.min) {
semantic = temporarySemantic;
break;
}
}

scoreScale.semantic = semantic;
scoreScale.multiplier = maxPoints / (scoreScale[semantic].max - scoreScale[semantic].min);
break;
default:
scoreScale.multiplier = maxPoints;
}
}