Commit 67af0688 authored by Avris's avatar Avris

init

parents
/node_modules
/public
# Avris Generator
Generate and validate numbers and identifiers like IBAN, ISBN, EAN, PESEL, BSN, passwords...
## GUI & API
yarn
yarn build
yarn start
## Library
yarn add avris-generator
const gm = require('avris-generator');
const pesel = gm.generate('PL', 'pesel', {gender: 'm'});
try {
const data = gm.validate('PL', 'pesel', '12345678900');
// valid
} catch (e) {
// invalid
alert(e.message);
}
## Copyright
* **Author:** Andrzej Prusinowski [(Avris.it)](https://avris.it)
* **Licence:** [MIT](https://mit.avris.it)
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const fs = require('fs');
const translator = require('./assets/helpers/translator');
const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(function (req, res, next) {
req.locale = 'en';
req.t = translator;
res.locals.t = translator;
next();
});
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.locals.asset = require('./assets/helpers/asset');
app.locals.flag = require('./assets/helpers/flag');
app.locals.includedynamic = require('./assets/helpers/includedynamic');
app.use('/', require('./routes/index'));
app.use('/api', require('./routes/api'));
app.use(function(req, res, next) {
next(createError(404));
});
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
$web-font-path: "https://fonts.googleapis.com/css?family=Ubuntu|Indie+Flower|Cabin+Sketch" !default;
$font-family-sans-serif: 'Ubuntu', -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
@import "~bootswatch/dist/sketchy/variables";
@import "~bootstrap/scss/bootstrap";
@import "~bootswatch/dist/sketchy/bootswatch";
@import "~@fortawesome/fontawesome-pro/scss/fontawesome";
@import "~@fortawesome/fontawesome-pro/scss/regular";
@import "~@fortawesome/fontawesome-pro/scss/brands";
@import "~select2/src/scss/core";
@import "select2-bootstrap/shim";
@import "~select2-bootstrap-theme/src/select2-bootstrap";
@import "modules/layout";
@import "modules/emoji";
@import "modules/list";
@import "modules/shadows";
@import "modules/form";
@import "modules/grid";
@import "modules/card";
@import "modules/code";
.card-footer {
background-color: $gray-300;
}
pre, code {
background-color: $gray-300;
padding: 0.4rem;
}
img.emoji {
height: 1em;
}
img.emoji + img.emoji {
margin-left: .1em;
}
.valid-feedback, .invalid-feedback {
display: block;
}
.feedback {
font-size: 1rem;
font-weight: normal;
}
.grid-item-hidden {
visibility: hidden;
overflow: hidden;
height: 0;
width: -1px;
margin: 0;
padding: 0 !important;
}
body {
background: fixed url('../../images/background.jpg') center;
background-size: cover;
min-height: 100vh;
}
header {
h1 {
font-size: 4rem;
}
}
footer {
background-color: rgba(0, 0, 0, .6);
color: $white;
a {
color: $white;
text-decoration: none;
&:hover,&:active,&:visited {
color: $white;
}
}
}
img {
max-width: 100%;
}
.link-simple {
text-decoration: none;
}
@mixin listHorizontal {
&.list-horizontal {
display: inline-block;
list-style-type: none;
padding: 0;
margin: 0;
> li {
position: relative;
display: inline;
vertical-align: middle;
&:after {
content: " ";
margin: 0 8px;
display: inline-block;
}
&:last-child:after {
content: " ";
margin: 0;
display: inline-block;
}
}
&.list-horizontal-wide {
> li {
&:after {
margin: 0 16px;
}
&:last-child:after {
margin: 0;
}
}
}
&.list-horizontal-separated {
> li {
&:after {
content: "|";
}
&:last-child:after {
content: " ";
}
}
}
}
}
ul, ol {
&.list {
display: block;
list-style-type: none;
padding: 0;
> li {
margin: 8px 0;
}
}
&.list-horizontal-xs {
@include listHorizontal;
}
@include media-breakpoint-up(xs) {
@include listHorizontal;
}
}
.list-spread {
> a, > span {
margin: 0 8px;
display: inline-block;
}
}
.links-spread {
> a, > span {
margin: 0 0.5em;
}
}
a.list-group-item-link {
margin: #{-$list-group-item-padding-y} #{-$list-group-item-padding-x};
padding: $list-group-item-padding-y $list-group-item-padding-x;
display: inline-block;
width: 100%;
transition: margin-left .25s ease;
&:hover {
margin-left: 0;
}
}
.shadow-hoverable:hover {
@extend .shadow-lg
}
// http://avladov.com/blog/494/select2-with-bootstrap4
//
// Overrides of Select2 SASS variables to make it work with Bootstrap 4 and match its look.
// Import this BEFORE https://github.com/select2/select2-bootstrap-theme SASS files or SASS compilation will fail
// Since there is no official Bootstrap 4 theme for Select 2 this is a good fallback.
/**
* We need a clone of bootstrap color-yiq mixin so we can get the same value for color
*/
@function bs4-color-yiq($color) {
$r: red($color);
$g: green($color);
$b: blue($color);
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
@if ($yiq >= 150) {
@return "#111";
} @else {
@return "#fff";
}
}
$s2bs-input-border: $input-border-color !default;
$s2bs-border-radius-base: $border-radius !default;
$s2bs-border-radius-large: $border-radius-lg !default;
$s2bs-border-radius-small: $border-radius-sm !default;
$s2bs-btn-default-bg: lighten(theme-color("secondary"), $theme-color-interval * 2) !default;
$s2bs-btn-default-border: theme-color("secondary") !default;
$s2bs-btn-default-color: bs4-color-yiq($s2bs-btn-default-bg) !default;
$s2bs-clear-selection-hover-color: $s2bs-btn-default-color !default;
$s2bs-remove-choice-hover-color: $s2bs-btn-default-color !default;
$s2bs-caret-width-base: .25rem !default; // 4px
$s2bs-caret-width-large: .3125rem !default; // 5px
$s2bs-font-size-base: $font-size-base !default;
$s2bs-font-size-large: $font-size-lg !default;
$s2bs-font-size-small: $font-size-sm !default;
$s2bs-padding-base-horizontal: $input-btn-padding-x !default;
$s2bs-padding-large-horizontal: $input-btn-padding-x-lg !default;
$s2bs-padding-small-horizontal: $input-btn-padding-x-sm !default;
$s2bs-padding-base-vertical: $input-btn-padding-y !default;
$s2bs-padding-large-vertical: $input-btn-padding-y-lg !default;
$s2bs-padding-small-vertical: $input-btn-padding-y-sm !default;
$s2bs-line-height-base: $input-btn-line-height !default;
$s2bs-line-height-large: $input-btn-line-height !default;
$s2bs-line-height-small: $input-btn-line-height !default;
$s2bs-input-height-base: calc(#{$input-btn-padding-y * 2 + $input-btn-line-height} + #{$border-width * 2}) !default;
$s2bs-input-height-large: calc(#{$input-btn-padding-y-lg * 2 + $input-btn-line-height} + #{$border-width * 2}) !default;
$s2bs-input-height-small: calc(#{$input-btn-padding-y-sm * 2 + $input-btn-line-height} + #{$border-width * 2}) !default;
$s2bs-input-bg-disabled: $input-disabled-bg !default;
$s2bs-input-color-placeholder: $input-placeholder-color !default;
$s2bs-input-border-focus: $input-focus-border-color !default;
$s2bs-selection-choice-border-radius: $border-radius !default;
$s2bs-cursor-disabled: not-allowed !default; // Missing in bootstrap 4
// Required Bootstrap 3 vars used in mixins
$state-warning-text: theme-color("warning") !default;
$state-danger-text: theme-color("danger") !default;
$state-success-text: theme-color("secondary") !default;
$screen-sm-min: breakpoint-min("md") !default;
const manifest = require('../../public/manifest.json');
module.exports = (name) => manifest[name];
const countryMarkers = {
'A': '🇦', 'B': '🇧', 'C': '🇨', 'D': '🇩', 'E': '🇪', 'F': '🇫', 'G': '🇬', 'H': '🇭', 'I': '🇮', 'J': '🇯',
'K': '🇰', 'L': '🇱', 'M': '🇲', 'N': '🇳', 'O': '🇴', 'P': '🇵', 'Q': '🇶', 'R': '🇷', 'S': '🇸', 'T': '🇹',
'U': '🇺', 'V': '🇻', 'W': '🇼', 'X': '🇽', 'Y': '🇾', 'Z': '🇿',
};
module.exports = (code) => {
if (code.startsWith('@')) {
return '';
}
if (code === '_') {
return '🌍';
}
return code.split('').map((c) => countryMarkers[c]).join('');
};
const pug = require('pug');
const path = require('path');
const translator = require('./translator');
module.exports = (filename) => {
try {
return pug.renderFile(path.join(__dirname, '..', '..', 'views', filename), {
t: translator,
});
} catch (e) {
if (e.message.startsWith('ENOENT: no such file or directory')) {
return '';
}
throw e;
}
};
const ExpressTranslate = require('express-translate');
const expressTranslate = new ExpressTranslate();
expressTranslate.addLanguage('en', require('../../translations/en.json'));
module.exports = expressTranslate.translator('en');
This diff is collapsed.
require('bootstrap');
require('./images');
require('./modules/clipboard');
require('./modules/emoji');
require('./modules/generator');
require('./modules/grid');
require('../images/favicon.png');
require('../images/logo.svg');
require('../images/banner.png');
const $ = require('jquery');
const t = require('../../helpers/translator');
$('.btn-clipboard').click(function() {
const $el = $(this);
this.parentNode.previousSibling.select();
const result = document.execCommand('copy');
$el.attr('title', result ? t('copy.success') : t('copy.failure'));
$el.tooltip('show');
setTimeout(function () {
$el.tooltip('dispose');
}, 3000);
return false;
});
const twemoji = require('twemoji').default;
twemoji.parse(document.getElementsByTagName('body')[0]);
const $ = require('jquery');
require('select2');
const twemoji = require('twemoji').default;
const generators = require('../../../src/generators');
const t = require('../../helpers/translator');
const flag = require('../../helpers/flag');
const dataDisplay = function(key, value) {
switch (key) {
case 'country':
return twemoji.parse(flag(value)) + ' ' + t('country.' + value);
case 'countries':
return value.map((country) => twemoji.parse(flag(country)) + ' ' + t('country.' + country)).join(', ');
case 'gender':
return t('gender.' + (value === 'm' ? 'male' : 'female'));
case 'birthdate':
const year = value.getUTCFullYear();
const month = (value.getUTCMonth() + 1).toString().padStart(2, '0');
const day = value.getUTCDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
default:
return value;
}
};
$('.generator').each(function() {
const $t = $(this);
const generator = generators[$t.data('country')][$t.data('generator')];
const $field = $t.find('.generator-output');
const $generateBtn = $t.find('.btn-generate');
const $feedback = $t.find('.feedback');
$t.find('[data-param]').on('change keyup', function(e) {
const $p = $(this);
$p.removeClass('is-valid is-invalid');
if (!this.checkValidity()) {
$p.addClass('is-invalid');
return;
} else {
$p.addClass('is-valid');
}
if (e.keyCode === 13) {
return $generateBtn.click();
}
}).trigger('change');
$generateBtn.click(e => {
const params = {};
$t.find('[data-param]').each(function(i, el) {
const $el = $(el);
const param = $el.data('param');
if (!(param in params)) {
params[param] = '';
}
if ($el.is('[type=text]') || $el.is('[type=number]') || $el.is(':checked') || $el.is('select')) {
params[param] += $el.val();
}
});
const value = generator.generate(params);
$field.val(value).trigger('change');
if (e.originalEvent !== undefined) {
return $field.focus().select();
}
});
$field.on('change keyup', e => {
$field.removeClass('is-valid is-invalid');
$feedback.removeClass('valid-feedback invalid-feedback');
$feedback.html('');
try {
const data = generator.validate($field.val());
$field.addClass('is-valid');
$feedback.addClass('valid-feedback');
if (data === null) {
return;
}
$feedback.html('<strong>' + t('valid') + '</strong>');
if (typeof data === 'object') {
for (let key in data) {
if (data.hasOwnProperty(key)) {
$feedback.append(`<br/>${t('valid.data.' + key)}: ${dataDisplay(key, data[key])}`);
}
}
}
} catch (ex) {
$field.addClass('is-invalid');
$feedback.addClass('invalid-feedback');
$feedback.html('<strong>' + t('invalid.' + ex.message) + '</strong>');
}
if (e.keyCode === 13) {
return $generateBtn.click();
}
});
$t.find('select[data-countries]').each(function () {
const $select = $(this);
$select.append(`<option value=""></option>`);
for (let countryCode in generator.constructor.countries) {
if (generator.constructor.countries.hasOwnProperty(countryCode)) {
$select.append(`<option value="${countryCode}">${flag(countryCode)} ${t('country.' + countryCode)}</option>`);
}
}
const entryTemplate = function(entry) {
return $(twemoji.parse('<span>' + entry.text + '</span>'));
};
$select.select2({
width: '100%',
theme: 'bootstrap',
templateResult: entryTemplate,
templateSelection: entryTemplate,
allowClear: true,
placeholder: { id: "", text: "" },
});
})
});
$('.btn-generate').click();
$('.generator-output').blur();
const Masonry = require('masonry-layout');
const $ = require('jquery');
const grid = document.querySelector('.grid');
let msnr = null;
if (grid) {
msnr = new Masonry(grid, {
itemSelector: '.grid-item'
});
setInterval(function() { msnr.layout(); }, 2000);
}
const $filterBtns = $('[data-filter-target]');
$filterBtns.click(function() {
const $btn = $(this);
const $target = $($btn.data('filter-target'));
const on = $btn.hasClass('active');
const expected = on ? '' : $btn.data('filter-value');
$filterBtns.removeClass('active');
$filterBtns.filter('[data-filter-value="'+$btn.data('filter-value')+'"]').toggleClass('active', !on);
//window.history.pushState(null, null, on ? '/' : $btn.attr('href'));
$target.find('[data-filter-value]').each(function () {
const $el = $(this);
const stays = expected === '' || $el.data('filter-value') === expected;
$el.toggleClass('grid-item-hidden', !stays)
});
if (msnr) {
msnr.layout();
}
return false;
});
#!/usr/bin/env node
const app = require('../app');
const debug = require('debug')('x:server');
const http = require('http');
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
function onListening() {
const addr = server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
{
"name": "avris-generator",
"version": "0.0.1",
"description": "Generate and validate numbers and identifiers like IBAN, ISBN, EAN, PESEL, BSN, passwords...",
"keywords": [
"generate",
"validate",
"generator",
"validator",
"number",
"identifier",
"control sum",
"password"
],
"homepage": "https://generator.avris.it",
"author": "Andre Prusinowski <andre@avris.it>",
"repository": {
"type": "git",
"url": "git+https://gitlab.com/Avris/Generator.git"
},
"license": "MIT",
"main": "./src/generator.js",
"scripts": {
"start": "node ./bin/www",
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production"
},
"devDependencies": {
"@fortawesome/fontawesome-pro": "^5.2.0",
"@symfony/webpack-encore": "^0.19.0",
"autoprefixer": "^9.1.3",
"bootstrap": "^4.1.3",
"bootswatch": "^4.1.3",
"cookie-parser": "^1.4.3",
"debug": "~2.6.9",
"express": "^4.16.3",
"express-translate": "^0.4.0",
"fs": "^0.0.1-security",
"http": "^0.0.0",
"http-errors": "~1.6.2",