Compare commits

...

No commits in common. 'main' and 'release/0.1.0' have entirely different histories.

33
.gitignore vendored

@ -55,9 +55,6 @@ web_modules/
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
@ -73,12 +70,10 @@ web_modules/
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
# dotenv environment variables file
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
@ -101,13 +96,6 @@ dist
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
@ -130,15 +118,8 @@ dist
.yarn/install-state.gz
.pnp.*
#
storage/*
!storage/.gitkeep
resources/actions/*
!resources/actions/.gitkeep
resources/config/enabled/*
!resources/config/enabled/.gitkeep
#
node_modules
resources/config/available/*
!resources/config/available/.gitkeep
storage/database/*
storage/database/.gitkeep

@ -1,26 +1,5 @@
# super-hog
Super Hog is a kind of process runner. It can be Custimize and build a frame to runs Tasks with
different Configurations. Tasks will be configured in a File by Yaml. Like this,
```
name: "Lorem Ipsum RSS"
url: "https://lorem-rss.herokuapp.com/feed"
cron: "0 1 * * *"
actions:
- class: "RssExample"
```
All Yaml-Files that have to be run, have to put in "resources/config/enabled".
## Action
Action Classes are find in "resources/actions". Each Action runs one after another,
and his result will be hand over to the next Action Class. For this you have access
in each Object to a Docket Object. By Default all data will be set to this.result
in a Action. If the class do nothing I/O will be the same.
## Docket
The Docket-Object is holds all Data, States and Configurations. It run to all
Actions.
yarn install
```

Binary file not shown.

@ -1,36 +0,0 @@
import Action from './../../packages/runner/actions/action.ts'
import State from './../../packages/runner/_state.ts'
import ical from 'node-ical'
/**
* getting ics from url and parse it to object
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class IcsExample extends Action {
async run() {
let response
try {
response = await fetch(this.config.url)
} catch(error) {
throw new Error(error)
}
// if code is 200
if (response && response.status === 200) {
const body = await response.text()
const events = ical.sync.parseICS(body)
if (events) {
this.result = events
}
}
}
}
export default IcsExample

@ -1,24 +0,0 @@
import Action from './../../packages/runner/actions/action.ts'
/**
* getting json from url, parse response and send to console
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class JsonExample extends Action {
async run() {
const response = await fetch(this.config.url)
if (response.status == 200) {
this.result = await response.json()
}
console.log(this.result)
}
}
export default JsonExample

@ -1,25 +0,0 @@
import { XMLParser } from 'fast-xml-parser'
import Action from './../../packages/runner/actions/action.ts'
/**
* getting rss feed from url, parse response and send to console
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class RssExample extends Action {
async run() {
const response = await fetch(this.config.url)
const body = await response.text()
const parser = new XMLParser()
this.result = parser.parse(body)
console.log(this.result)
}
}
export default RssExample

@ -1,5 +0,0 @@
name: "Lorem Ipsum RSS"
url: "https://lorem-rss.herokuapp.com/feed"
cron: "0 1 * * *"
actions:
- class: "RssExample"

@ -1,5 +1,5 @@
{
"/public/js/spritemap.js": "/public/js/spritemap.js?id=2dda73ecee3bb668b395026efda6524c",
"/public/js/app.js": "/public/js/app.js?id=6b0410c8053fe0fcd6adac7ea8217276",
"/public/css/styles.css": "/public/css/styles.css?id=4abe36859cc82c0d3eefc8bea720a6c5"
"/public/js/app.js": "/public/js/app.js?id=33b6f39f449c765b8c7674ad6a35b92e",
"/public/css/styles.css": "/public/css/styles.css?id=72a43d98017bdbcb83928c41f6289210"
}

23502
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,10 +1,11 @@
{
"private": true,
"name": "super-hog",
"workspaces": [
"packages/*"
],
"scripts": {
"start": "bun run packages/runner/index.ts"
}
"private": true,
"name": "super-hog",
"workspaces": [
"packages/*"
],
"scripts": {
"start": "yarn workspace server start",
"build": "yarn workspace frontend build"
}
}

@ -0,0 +1,25 @@
import * as riot from 'riot'
import AppSidebar from './components/sidebar.riot'
import AppTasks from './components/tasks.riot'
import AppTaskButton from './components/task-new.riot'
import AppTaskForm from './components/task-form.riot'
import AppNotification from './components/notification.riot'
// register components
riot.register('app-sidebar', AppSidebar)
riot.register('app-tasks', AppTasks)
riot.register('app-task-button', AppTaskButton)
riot.register('app-task-form', AppTaskForm)
riot.register('app-notification', AppNotification)
// mount components
riot.mount('app-sidebar')
riot.mount('app-tasks')
riot.mount('app-task-button')
riot.mount('app-task-form')
riot.mount('app-notification')

@ -0,0 +1 @@
riot = require('riot')

@ -0,0 +1,15 @@
<app-loading>
<div class="loading-wrapper" if={ props.loading }>
<div class="loading">
<span></span>
<span></span>
<span></span>
</div>
</div>
<script>
export default {
}
</script>
</app-loading>

@ -0,0 +1,115 @@
<app-notification>
<div class="toast-wrapper toast-wrapper--right" if={ state.items.length > 0 }>
<div
id={ item.id } class={ item.classes.join(' ') }
each={ item in state.items }
onclick={ (event) => { this.handleClick(event, item) } }>
<div class="toast__body">
{ item.message }
</div>
</div>
</div>
<script>
import { v4 as uuidv4 } from 'uuid'
import notificationStore from './../stores/notification'
/**
* notification
*
*
* @author Björn Hase
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.tentakelfabrik.de/herrhase/shiny-dashboard
*
*/
export default {
state: {
items: [],
timeout: 2500
},
/**
* on mounted
*
* @param {object} props
* @param {object} state
*
*/
onMounted(props, state)
{
// adding service for notifications and listen to "update"
notificationStore.on('update', (item) => {
// adding attributes
item.id = 'toast-' + uuidv4()
item.classes = [
'toast',
'toast--' + item.type
]
// create timeout to remove notification
item.timeout = setTimeout(() => {
this.removeItem(item)
}, this.state.timeout)
this.state.items.push(item)
this.update()
// add animation
requestAnimationFrame(() => {
this.$('#' + item.id).classList.add('toast--animation')
})
})
},
/**
* remove single item
*
*
* @param {object} item
*
*/
removeItem(item)
{
// adding event if animationend remove html element
this.$('#' + item.id).addEventListener('transitionend', () => {
// find item in state and remove it
for (let i = 0; i < this.state.items.length; i++) {
if (this.state.items[i].id === item.id) {
clearTimeout(this.state.items[i].timeout)
this.state.items.splice(i, 1)
break;
}
}
this.update()
})
// add animation
requestAnimationFrame(() => {
this.$('#' + item.id).classList.remove('toast--animation')
})
},
/**
* remove item by clicked on it
*
* @param {[type]} event
* @param {[type]} item
*
*/
handleClick(event, item)
{
this.removeItem(item)
}
}
</script>
</app-notification>

@ -0,0 +1,88 @@
<app-sidebar>
<div class={ getCssClasses() }>
<div class="sidebar__inner">
<!-- header -->
<div class="bar">
<div class="bar__main">
<slot name="title" />
</div>
<div class="bar__end">
<button class="button button--transparent" type="button" onclick={ (event) => { handleClose(event) } }>
<svg class="icon fill-danger fill-text-hover" aria-hidden="true">
<use xlink:href="/symbol-defs.svg#icon-close"></use>
</svg>
</button>
</div>
</div>
<!-- body -->
<div class="sidebar__body">
<slot name="form" />
<app-loading loading={ props.loading }></app-loading>
</div>
<!-- footer -->
<div class="sidebar__footer">
<button class="button m-bottom-0" type="submit" form="{ props.formId }" disabled={ props.loading }>
Save
<svg class="icon fill-success p-left-3" aria-hidden="true">
<use xlink:href="/symbol-defs.svg#icon-check"></use>
</svg>
</button>
<button class="button m-bottom-0" type="submit" form="{ props.formId }" disabled={ props.loading } close>
Save and Close
<svg class="icon fill-success p-left-3" aria-hidden="true">
<use xlink:href="/symbol-defs.svg#icon-arrow-right"></use>
</svg>
</button>
</div>
</div>
</div>
<script>
import * as riot from 'riot'
import AppLoading from './loading.riot'
riot.register('app-loading', AppLoading)
/**
*
*
*
*/
export default {
/**
*
*
*/
handleClose()
{
this.props.close()
},
/**
*
*
*/
getCssClasses()
{
const classes = [
'sidebar'
]
if (this.props.open === true) {
classes.push('sidebar--open')
}
return classes.join(' ')
}
}
</script>
</app-sidebar>

@ -0,0 +1,259 @@
<app-task-form>
<app-sidebar form-id="app-task-form" open={ state.isOpen } close={ () => { handleClose() } } loading={ state.isLoading }>
<!-- slot:title -->
<span slot="title">
<virtual if={ state.current._id }>
Edit Task { state.current.name }
</virtual>
<virtual if={ !state.current._id }>
New Task
</virtual>
</span>
<!-- slot:header -->
<form id="app-task-form" class="form" slot="form" onsubmit={ (event) => { state.isLoading = true; update(); state.validator.submit(event) } }>
<div class="field-group">
<label class="field-label">
name
<input class="field-text" name="name" type="text" value="{ state.current.name }" />
<field-error name="name"></field-error>
</label>
</div>
<div class="field-group">
<label class="field-label">
url
<input class="field-text" name="url" type="text" value="{ state.current.url }" />
<field-error name="url"></field-error>
</label>
</div>
<div class="field-group">
<label class="field-label">
requestHandlers
<select class="field-choice" name="requestHandler">
<option></option>
<option value="{ requestHandler.file }" each={ requestHandler in state.requestHandlers } selected={ state.current.requestHandler && requestHandler.file === state.current.requestHandler.file }>
{ requestHandler.name }
</option>
</select>
<field-error name="requestHandlers"></field-error>
</label>
</div>
<div class="field-group">
<label class="field-label">
actionHandlers
<select class="field-choice" name="action">
<option></option>
<option value="{ actionHandler.file }" each={ actionHandler in state.actionHandlers } selected={ state.current.actionHandler && actionHandler.file === state.current.actionHandler.file }>
{ actionHandler.name }
</option>
</select>
<field-error name="actionHandlers"></field-error>
</label>
</div>
<div class="field-group">
<label class="field-label">
options
<input type="text" name="options" class="field-text" value="{ JSON.stringify(state.current.options) }" />
<field-error name="options"></field-error>
</label>
</div>
<div class="field-group">
<label class="field-label">
timer
<input type="text" name="timer" class="field-text" value="{ state.current.timer }" />
<field-error name="timer"></field-error>
</label>
</div>
</form>
</app-sidebar>
<script>
import * as riot from 'riot'
import FormValidator from '@tiny-components/validator/src/formValidator.js'
// stores
import taskFormStore from './../stores/taskForm.js'
import notificationStore from './../stores/notification'
// mixins
import sidebar from './../mixins/sidebar.js'
import FieldError from '@tiny-components/validator/src/fieldError.riot'
riot.register('field-error', FieldError)
/**
*
*
*/
export default () => {
return {
...sidebar, // adding basic funtion for sidebar
state: {
requestHandlers: [],
validator: undefined
},
onBeforeMount()
{
this.resetCurrent()
},
/**
*
*
*/
onMounted()
{
// creating formValidator
this.state.validator = new FormValidator(this.$('.form'), {
'name': {
'presence': true
},
'url': {
'presence': true
},
'requestHandler': {
'presence': true
},
'action': {
'presence': true
},
'timer': {
}
})
// adding on success
this.state.validator.onSuccess((event, data) => {
this.handleSuccess(event, data)
})
// adding on success
this.state.validator.onError((event, data) => {
this.state.isLoading = false
// add notification
notificationStore.danger('Error! Check your input!')
this.update()
})
//
taskFormStore.on('open', (data) => {
this.resetCurrent()
this.state.isOpen = true
if (data) {
this.state.current = data
}
this.update()
})
this.getRequestHandlers()
this.getActionHandlers()
},
/**
*
*
*
*/
resetCurrent()
{
this.state.current = {
name: '',
url: '',
requestHandlers: '',
actionHandlers: '',
options: {
},
timer: ''
}
},
/**
* getting available RequestHandlers
*
*/
getRequestHandlers()
{
fetch('/api/v1/handler/request', {
'method': 'GET'
})
.then((response) => response.json())
.then((response) => {
this.state.requestHandlers = response.data
this.update()
})
},
/**
* getting available ActionHandlers
*
*
*/
getActionHandlers()
{
fetch('/api/v1/handler/action', {
'method': 'GET'
})
.then((response) => response.json())
.then((response) => {
this.state.actionHandlers = response.data
this.update()
})
},
/**
* send data to server
*
* @param {object} event
* @param {object} data
*
*/
handleSuccess(event, data)
{
event.preventDefault()
fetch('/api/v1/task', {
'method': 'POST',
'body': JSON.stringify(data),
'headers': {
'Content-Type': 'application/json'
}
})
.then((response) => response.json())
.then((response) => {
// stop loading
this.state.isLoading = false
// add id to current
this.state.current.id = response.data.id
this.update()
}).catch((error) => {
// stop loading
this.state.isLoading = false
// show error message
notificationStore.danger('Error! Server has a Error, Request can not proceed!')
this.update()
})
}
}
}
</script>
</app-task-form>

@ -0,0 +1,54 @@
<app-task-button>
<button class="button m-bottom-0 m-top-5" onclick={ (event) => { handleOpen(event) } }>
<svg class="icon fill-text fill-text-contrast-hover m-top-1 p-right-2" aria-hidden="true">
<use xlink:href="/symbol-defs.svg#icon-close"></use>
</svg>
{ state.text }
</button>
<script>
import taskFormStore from './../stores/taskForm.js'
/**
* handle open and close of form for tasks
*
*
* @author Björn Hase, <me@herr-hase.wtf>
*
*/
export default {
state: {
text: 'new'
},
/**
*
*
*/
onMounted()
{
// if props for text is set
if (this.props.text) {
this.state.text = this.props.text
}
},
/**
* trigger open in storage
*
*
* @param {object} event
*
*/
handleOpen(event, data = undefined)
{
taskFormStore.open(data)
}
}
</script>
</app-task-button>

@ -0,0 +1,153 @@
<app-tasks>
<div class="tasks">
<table class="table table--stripped">
<thead>
<tr>
<td class="table__th">
state
</td>
<th class="table__th">
name
</th>
<th class="table__th">
url
</th>
<th class="table__th">
requestHandler
</th>
<th class="table__th">
actions
</th>
<th class="table__th">
cron
</th>
<th class="table__th"></th>
<tr>
</thead>
<tbody>
<tr if={ state.tasks.length > 0 } each={ task in state.tasks }>
<td class="table__td">
</td>
<td class="table__td">
{ task.name }
</td>
<td class="table__td">
{ task.url }
</td>
<td class="table__td">
{ task.requestHandler }
</td>
<td class="table__td">
<virtual each={ action in task.actions }>
<span>
{ action.className }
</span>
<span if={ actions.options }>
{ JSON.stringify(action.options) }
</span>
</virtual>
</td>
<td class="table__td">
{ task.cron }
</td>
<td class="table__td">
<button class="button">
Pause
</button>
<button class="button" onclick={ (event) => { handleEdit(event, task) } }>
Edit
</button>
<button class="button" onclick={ (event) => { handleDelete(event, task) } }>
Delete
</button>
</td>
</tr>
<tr if={ state.tasks.length === 0 }>
<td class="table__td center" colspan="6">
Nothing found
</td>
</tr>
</tbody>
</table>
</div>
<script>
// stores
import taskFormStore from './../stores/taskForm.js'
import notificationStore from './../stores/notification'
/**
* show all tasks
*
*
*
*/
export default
{
state: {
tasks: []
},
/**
*
*
*/
onMounted()
{
this.getTasks()
},
/**
*
*
*/
getTasks()
{
fetch('/api/v1/task')
.then((response) => response.json())
.then((response) => {
this.state.tasks = response.data
this.update()
})
},
/**
* delete
*
* @param {object} event
* @param {object} task
*
*/
handleDelete(event, task)
{
fetch('/api/v1/task/' + task._id, {
'method': 'DELETE'
})
.then((response) => response.json())
.then((response) => {
notificationStore.success('Deleted ' + response.data.name + '!')
this.getTasks()
})
.catch((errors) => {
notificationStore.success('Error! ' + response.data.name + ' could not deleted!')
})
},
/**
* edit task, trigger form taskForm
*
* @param {object} event
* @param {object} task
*
*/
handleEdit(event, task)
{
taskFormStore.open(task)
}
}
</script>
</app-tasks>

@ -0,0 +1,23 @@
/**
*
*
*
*/
export default {
state: {
isOpen: false,
isLoading: false
},
/**
*
*
*/
handleClose()
{
this.state.isOpen = false
this.update()
}
}

@ -0,0 +1,49 @@
import observable from '@riotjs/observable'
/**
* NotificationService
*
*
*/
export default observable({
SUCCESS: 'success',
DANGER: 'danger',
INFO: 'info',
/**
*
*
*/
success(message) {
this._add(message, this.SUCCESS)
},
/**
*
*
*/
danger(message) {
this._add(message, this.DANGER)
},
/**
*
*
*/
info(message) {
this._add(message, this.INFO)
},
/**
*
* @param {[type]} message [description]
* @param {[type]} type [description]
*/
_add(message, type) {
this.trigger('update', {
message: message,
type: type
})
}
})

@ -0,0 +1,23 @@
/**
*
*
*
* @author Björn Hase
*
*
*/
import observable from '@riotjs/observable'
export default observable(
{
/**
*
* @param {object} data
*
*/
open(data)
{
this.trigger('open', data)
}
})

File diff suppressed because it is too large Load Diff

@ -0,0 +1,24 @@
{
"private": true,
"name": "frontend",
"version": "0.1.0",
"scripts": {
"build": "npx mix"
},
"dependencies": {
"@riotjs/observable": "^4.1.1",
"@tiny-components/plain-ui": "^0.5.0",
"@tiny-components/validator": "^0.1.0",
"got": "^12.0.1",
"riot": "^6.1.2",
"uuid": "^8.3.2"
},
"devDependencies": {
"@riotjs/webpack-loader": "^6.0.0",
"laravel-mix": "^6.0.43",
"laravel-mix-purgecss": "^6.0.0",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"svg-spritemap-webpack-plugin": "^4.4.0"
}
}

@ -0,0 +1,7 @@
// format list of errors
.field-error {
ul {
margin: 0;
padding: 0 0 0 1em;
}
}

@ -0,0 +1,22 @@
.loading-wrapper {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
.loading {
align-self: center;
}
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #ffffff45;
}
}

@ -0,0 +1,6 @@
@import
'@tiny-components/plain-ui/src/scss/plain-ui',
'components/field-error',
'components/loading';

@ -0,0 +1,22 @@
{% layout 'layout.liquid' %}
{% block app_main %}
<div class="container-full">
<div class="grid">
<div class="col-12">
<app-task-button></app-task-button>
</div>
</div>
<div class="grid">
<div class="col-12">
<app-tasks></app-tasks>
</div>
</div>
<app-task-form></app-task-form>
</div>
{% endblock %}
{% block script %}
<script async src="/js/app.js"></script>
{% endblock %}

@ -0,0 +1,70 @@
<!doctype html>
<html lang="en_EN">
<head>
<meta charset="utf-8">
<title>
Super Hog
</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/styles.css" rel="stylesheet" type="text/css">
{% block head %}{% endblock %}
</head>
<body>
<header>
{% block app_header %}
<div class="bar">
<div class="bar__start">
<h1 class="m-top-4 m-bottom-4 h4">
Super Hog
</h1>
</div>
</div>
{% endblock %}
</header>
<main>
<noscript>
<div class="container-full">
<div class="grid justify-center">
<div class="col-12">
<div class="panel panel--border-highlight border-color-danger m-bottom-4">
Only works with Javascript!
</div>
</div>
</div>
</div>
</noscript>
{% block app_main %}{% endblock %}
<app-notification></app-notification>
</main>
<footer>
<div class="container-full">
<div class="grid justify-center">
<div class="col-12">
<hr / class="m-top-8">
<div class="group">
<div class="group__item">
Published under
<strong>
MIT License
</strong>
</div>
</div>
</div>
</div>
</div>
</footer>
<script>
const csrfToken = '{{ csrfToken }}'
</script>
{% block script %}{% endblock %}
</body>
</html>

@ -0,0 +1,15 @@
{% layout 'layout.liquid' %}
{% block app_main %}
<div class="container">
<div class="grid justify-center">
<div class="col-12 col-md-8">
<app-login></app-login>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script async src="/js/app.js"></script>
{% endblock %}

@ -0,0 +1,81 @@
const mix = require('laravel-mix')
const path = require('path')
require('laravel-mix-purgecss')
// plugins
const SvgSpritemapPlugin = require('svg-spritemap-webpack-plugin')
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel applications. By default, we are compiling the CSS
| file for the application as well as bundling up all the JS files.
|
*/
mix.webpackConfig({
module: {
rules: [{
test: /\.riot$/,
use: [{
loader: '@riotjs/webpack-loader',
options: {
hot: false
}
}]
}
]},
plugins: [
new SvgSpritemapPlugin('node_modules/@tiny-components/plain-ui/src/icons/mono-icons/svg/*.svg', {
output: {
filename: 'public/symbol-defs.svg',
chunk: {
keep: true
},
svgo: {
plugins: [{
name: 'convertStyleToAttrs',
active: true
},{
name: 'removeStyleElement',
active: true
}, {
name: 'removeAttrs',
params: {
attrs: 'fill'
}
}]
}
},
sprite: {
prefix: 'icon-'
}
})
]
})
mix
.setPublicPath('../../')
.js('js/app.js', 'public/js')
.sass('scss/styles.scss', 'public/css')
.purgeCss({
extend: {
content: [
path.join(__dirname, 'js/**/*.riot'),
path.join(__dirname, 'views/*.liquid')
]
}
})
.options({
terser: {
extractComments: false,
},
processCssUrls: false
})
.copyDirectory('node_modules/@tiny-components/plain-ui/src/fonts/**', '../../public/css')
.version()

@ -1,4 +0,0 @@
import dotenv from 'dotenv'
// getting .env
dotenv.config({ path: path.join(path.resolve(), '/.env') })

@ -1,48 +0,0 @@
import { JSONFilePreset } from 'lowdb/node'
import merge from 'deepmerge'
const db = await JSONFilePreset('./storage/db.json', { runs: [] })
/**
*
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class Db {
public constructor(slug) {
this.slug = slug
this.data = db.data.runs.find((runs) => runs.slug === this.slug)
}
public async get() {
if (this.data === undefined) {
this.data = {
'slug': this.slug,
'date_created_at': dayjs().toISOString(),
'date_run_started_at': dayjs().toISOString()
}
db.data.runs.push(run)
await db.write()
}
return this.data
}
public async update(data) {
db.data.runs.find((run, index) => {
if (run.slug === this.slug) {
db.data.runs[index] = merge(db.data.runs[index], data)
}
})
await db.write()
}
}
export default Db

@ -1,75 +0,0 @@
import State from './_state.ts'
/**
* Docket
*
* Is used to hold all data and configs that run through the actions
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class Docket {
// config for action
private config: object
// data
private data: object
private options: object
private db: object
private states: array
/**
*
*
* @param object config
*
*/
public constructor(config: object, db: object) {
this.config = config
this.db = db
this.states = []
}
public getData() {
return this.data
}
public setData(data: object) {
return this.data = data
}
public setOptions(options): object {
this.options = options
}
public getConfig(): object {
return this.config
}
public getDb(): object {
return this.db
}
public addState(value: State) {
this.states.push(value)
}
public hasState(value: State) {
let result = false
for (let index in this.states) {
if (this.states[index] == value) {
result = true
break
}
}
return result
}
}
export default Docket

@ -1,65 +0,0 @@
import dayjs from 'dayjs'
import Db from './_db.ts'
import Docket from './_docket.ts'
import { resolveActionClass } from './helpers/resolver.ts'
import logger from './helpers/logger.ts'
import State from './_state.ts'
/**
* run through a single config, getting each action with config
* and run them, the docket will be used to hold data and configs
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
async function run(config: object) {
let db = new Db(config.slug)
let docket = new Docket(config, db)
// update db
await db.update({
'date_started_at': dayjs().toISOString()
})
logger(config.slug).info('has started')
for (const actionConfig of config.actions) {
// resolve action class
const ActionClass = await import(resolveActionClass(actionConfig.class))
// options are exists, add to docket
if (actionConfig.hasOwnProperty('options')) {
docket.setOptions(action.options)
}
try {
const action = new ActionClass.default(docket)
logger(config.slug).info('action / ' + actionConfig.class)
await action.run()
docket = action.getDocket()
} catch(error) {
logger(config.slug).error('action / ' + actionConfig.class + ' / ' + error)
}
}
logger(config.slug).info('has finished')
// update db only state FINISHED is set
if (docket.hasState(State.FINISHED)) {
await db.update({
'date_finished_at': dayjs().toISOString()
})
}
return docket
}
export default run

@ -1,14 +0,0 @@
/**
*
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
enum State {
FINISHED
}
export default State

@ -1,27 +0,0 @@
import logger from './helpers/logger.ts'
/**
* Webhook
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class Webhook {
static send(message: string) {
const query = new URLSearchParams({
'message': message
})
try {
fetch(process.env.WEBHOOK_URL + '?' + query.toString())
} catch(error) {
logger().error(error)
}
}
}
export default Webhook

@ -1,46 +0,0 @@
import ActionInterface from './actionInterface.ts'
import Docket from './../docket.ts'
/**
* Action
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
class Action implements ActionInterface {
private docket: Docket
private data: object
private result: object
private options: object
private config: object
/**
*
*
*
*/
public constructor(docket: Docket, options: object = {}) {
this.docket = docket
this.data = docket.getData()
// current data will be set as data
this.result = this.data
this.options = options
this.config = docket.getConfig()
}
/**
* set result as data
*
*/
public getDocket() {
this.docket.setData(this.result)
return this.docket
}
}
export default Action

@ -1,14 +0,0 @@
/**
* Interface for Action
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
interface ActionInterface {
async run(): any
}
export default ActionInterface

@ -0,0 +1,14 @@
import Interface from 'es6-interface'
import Action from './../../actions/action.js'
class MoveFile extends Action
{
run()
{
for (const item of this.data.rss.channel.item) {
console.log(item)
}
}
}
export default MoveFile

@ -0,0 +1,30 @@
import Interface from 'es6-interface'
import https from 'https'
import ActionInterface from './actionInterface.js'
/**
*
*
*
*
*/
class Action extends Interface(ActionInterface)
{
/**
*
* @param {[type]} data
* @param {[type]} source
*/
constructor(source, data, options)
{
super()
this.source = source
this.data = data
this.options = options
}
}
export default Action

@ -0,0 +1,15 @@
/**
*
*
*
*
*/
const ActionInterface = {
run: function()
{
}
}
export default ActionInterface

@ -0,0 +1,58 @@
import got from 'got'
import url from 'url'
import path from 'path'
import { createWriteStream } from 'fs'
import Action from './action.js'
/**
*
*
* @extends Action
*
*/
class DownloadPodcast extends Action
{
async run()
{
// count of files
let files = 0
// errors
let errors = []
for (let item of this.data.rss.channel.item)
{
// gettin
const pubDate = new Date(item.pubDate)
// check for new entries
if (pubDate >= new Date(this.source.last_run_at))
{
const parsedUrl = url.parse(item.link)
const filename = decodeURIComponent(path.basename(parsedUrl.pathname))
const downloadStream = got.stream(item.link)
const fileWriterStream = createWriteStream(this.options.destination + '/' + filename)
downloadStream
.on('error', (error) => {
console.error(`Download failed: ${error.message}`)
})
fileWriterStream
.on('error', (error) => {
console.error(`Could not write file to system: ${error.message}`)
})
.on('finish', () => {
files++
})
await downloadStream.pipe(fileWriterStream)
}
}
}
}
export default DownloadPodcast

@ -0,0 +1,34 @@
import got from 'got'
import { XMLParser } from 'fast-xml-parser'
import RequestHandler from './requestHandler.js'
/**
*
*
*
*
*/
class JsonHandler extends RequestHandler
{
/**
* getting rss feed from url
*
*
*/
async send()
{
const buffer = await got(this.source.url, {
responseType: 'buffer',
resolveBodyOnly: true
})
const parser = new XMLParser()
const feed = parser.parse(buffer.toString())
this.processActions(feed)
}
}
export default RssHandler

@ -0,0 +1,64 @@
import Interface from 'es6-interface'
import RequestHandlerInterface from './requestHandlerInterface.js'
import ResolverClass from './../helpers/resolverClass.js'
/**
*
*
*
*
*/
class RequestHandler extends Interface(RequestHandlerInterface)
{
/**
*
* @param {[type]} source
*
*/
constructor(source)
{
super()
this.source = source
}
/**
* process actions that are saved in a source
*
*
* @param {object} data
*
*/
async processActions(data)
{
let errors = false
this.source.actions.forEach(async (actions) =>
{
const resolverClass = new ResolverClass('actions')
const classPath = resolverClass.find(actions.className)
// import class from action
const Action = await import(classPath)
// create action an call run
const action = new Action.default(this.source, data, actions.options)
//
if (!await action.run())
{
errors = true
}
})
// if errors
if (errors && this.source.errors < 5) {
this.source.errors++
} else {
this.source.last_run_at = new Date()
}
}
}
export default RequestHandler

@ -0,0 +1,19 @@
/**
*
*
*
*/
const RequestHandlerInterface = {
/**
*
*
*/
send: function() {
}
}
export default RequestHandlerInterface

@ -0,0 +1,34 @@
import got from 'got'
import { XMLParser } from 'fast-xml-parser'
import RequestHandler from './requestHandler.js'
/**
*
*
*
*
*/
class RssHandler extends RequestHandler
{
/**
* getting rss feed from url
*
*
*/
async send()
{
const buffer = await got(this.source.url, {
responseType: 'buffer',
resolveBodyOnly: true
})
const parser = new XMLParser()
const feed = parser.parse(buffer.toString())
this.processActions(feed)
}
}
export default RssHandler

@ -1,36 +0,0 @@
import pino from 'pino'
import pretty from 'pino-pretty'
import dayjs from 'dayjs'
import { mkdirSync } from "node:fs"
import slug from 'slug'
/**
* Wrapper for logger
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
function logger(slug?: string) {
let destination = './storage/logs/'
// if name is set, adding name as directory
if (slug) {
destination += slug + '/'
mkdirSync(destination, { recursive: true })
}
destination += dayjs().format('DD-MM-YYYY') + '.log'
return pino({
timestamp: () => {
return `, "time":"${new Date(Date.now()).toISOString()}"`
},
},
pino.destination(destination)
)
}
export default logger

@ -1,71 +0,0 @@
import fs from 'fs'
import path from 'path'
import yaml from 'js-yaml'
import slug from 'slug'
/**
* resolve action class
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
function resolveActionClass(className) {
let classPath = path.join(path.resolve(), 'resources/actions/' + className + '.ts')
let result = undefined
if (fs.existsSync(classPath)) {
result = classPath
}
if (!result) {
throw new Error('Action Class ' + className + ' / ' + classPath + ' not found!')
}
return result
}
/**
* loading all configs from enabled directory
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
function resolveEnabledConfig(name?: string) {
const configs = []
const directoryPath = path.join(path.resolve(), 'resources/config')
// load files from enabled
const files = fs.readdirSync(directoryPath + '/enabled')
for (let index in files) {
let file = files[index]
if (!file.endsWith('.yml')) {
continue
}
if (name !== undefined && file !== (name + '.yml')) {
continue
}
const result = yaml.load(fs.readFileSync(directoryPath + '/enabled/' + file))
if (result) {
result.slug = slug(result.name)
configs.push(result)
}
}
return configs
}
export {
resolveActionClass,
resolveEnabledConfig
}

@ -0,0 +1,72 @@
import path from 'path'
import fs from 'fs'
/**
*
*
*
*/
class ResolverClass
{
/**
*
* @param {String} prefix
*
*/
constructor(prefix)
{
this.prefix = prefix
}
/**
*
*
* @param {String} className
* @param {Boolean} [isCustom=false]
* @return {String}
*
*/
lookupPath(className, isCustom = false)
{
let custom = ''
if (isCustom) {
custom = '/custom/'
}
return path.join(path.resolve(), custom + this.prefix + '/' + className + '.js')
}
/**
*
* @param {[type]} className
* @return {[type]} [description]
*/
find(className)
{
// getting
let classPath = this.lookupPath(className, true)
// results
let result = false
if (fs.existsSync(classPath)) {
result = classPath
} else {
classPath = this.lookupPath(className)
if (fs.existsSync(classPath)) {
result = classPath
}
}
if (!result) {
throw new Error('Class ' + className + ' not found!')
}
return result
}
}
export default ResolverClass

@ -0,0 +1,29 @@
import cron from 'node-cron'
const tasks = []
// uuid, schedule, source, requestHandler, actions, state
// start main cron to check for updates while running
//const main = cron.schedule('* * * * *', () => {
//})
//main.start()
import RssHandler from './requestHandlers/rssHandler.js'
const source = {
'name': 'Insert Moin',
'url': 'https://steadyhq.com/rss/insertmoin?auth=d37bffc9-9a84-4eed-95f6-3b6cb77c2406',
'actions': [{
'className': 'downloadPodcast',
'options': {
'destination': '/home/herrhase/Downloads'
}
}],
'last_run_at': '2022-01-16T23:01:00.000Z'
}
const rssHandler = new RssHandler(source)
rssHandler.send()

@ -1,44 +0,0 @@
import cron from 'node-cron'
import { resolveEnabledConfig } from './helpers/resolver.ts'
import logger from './helpers/logger.ts'
import run from './_run.ts'
/**
* Run all Configs that are in Directory /resources/configs/enabled
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
// get all configs from resolveEnabledConfig
const configs = resolveEnabledConfig()
// check if there is any valid config
if (configs.length === 0) {
throw new Error('No valid config found!')
}
// running through configs
for (const config in configs) {
// check for cron
if (!config.hasOwnProperty('cron')) {
return
}
/**
* adding task to schedule, using cron
*
*/
const task = cron.schedule(config.cron, async() => {
try {
const docket = await run(config, docket)
} catch(error) {
logger(config.slug).error(error)
}
})
}

@ -0,0 +1,13 @@
/**
*
*
*
*/
class Notiication
{
webhook(url)
{
}
}

File diff suppressed because it is too large Load Diff

@ -1,20 +1,21 @@
{
"private": true,
"name": "runner",
"version": "0.1.0",
"type": "module",
"dependencies": {
"chalk": "^5.1.2",
"dayjs": "^1.11.7",
"deepmerge": "^4.3.1",
"dotenv": "^16.0.3",
"fast-xml-parser": "^4.1.3",
"js-yaml": "^4.1.0",
"lowdb": "^7.0.1",
"node-cron": "^3.0.2",
"pino": "^8.7.0",
"pino-pretty": "^10.0.0",
"slug": "^9.0.0",
"yargs": "^17.6.0"
}
"private": true,
"name": "runner",
"version": "0.1.0",
"scripts": {
"start": "node index.js"
},
"type": "module",
"dependencies": {
"date-format-parse": "^0.2.7",
"dotenv": "^10.0.0",
"es6-interface": "^3.2.1",
"fast-xml-parser": "^4.0.1",
"got": "^12.0.1",
"isomorphic-dompurify": "^0.16.0",
"node-cron": "^3.0.0",
"pouchdb": "^7.2.2",
"pouchdb-find": "^7.2.2",
"slugify": "^1.6.5"
}
}

@ -1,40 +0,0 @@
import chalk from 'chalk'
import { resolveEnabledConfig } from './helpers/resolver.ts'
import Docket from './docket.ts'
import logger from './helpers/logger.ts'
import run from './_run.ts'
/**
* Run single Config that is in Directory /resources/configs/enabled
*
* @author Björn Hase <me@herr-hase.wtf>
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://git.node001.net/HerrHase/super-hog.git
*
*/
const args = process.argv.slice(2)
if (args[0] === undefined) {
console.log(chalk.red('Required name for config'))
process.exit(0)
}
// get all configs from resolveEnabledConfig
const configs = resolveEnabledConfig(args[0])
// check if there is any valid config
if (configs.length === 0) {
console.log(chalk.red('Config ' + args[0] + ' not found!'))
process.exit(0)
}
const config = configs[0]
try {
const docket = await run(config)
} catch(error) {
logger(config.slug).error(error)
}

@ -1,15 +0,0 @@
import { getDB } from './../db.js'
/**
*
*
*
*/
class Store {
constructor() {
this._db = getDB()
}
}
export default Store

@ -1,43 +0,0 @@
import Store from './_store.js'
/**
*
*
*
*/
class State extends Store {
/**
*
* @param {string} name
*
*/
add(data) {
const result = this.findOneByName(data.name)
if (!result) {
this._db.prepare('INSERT INTO states (name, date_lastrun) VALUES (:name, :date_lastrun)')
.run(data)
} else {
this._db.prepare('UPDATE states SET name = @name, date_lastrun = @date_lastrun WHERE id = ?')
.run(data, result.id)
}
return this.findOneByName(data.name)
}
/**
*
* @param {string} name
*
*/
findOneByName(name) {
const result = this._db.prepare('SELECT id, name, date_lastrun FROM states WHERE name = ?')
.get(name)
return result
}
}
export default State

@ -0,0 +1,48 @@
import fastify from 'fastify'
import dotenv from 'dotenv'
import path from 'path'
// getting .env
dotenv.config({ path: path.join(path.resolve(), '/../../.env') })
// create server
const server = fastify()
/**
* add plugins
*
*/
import { Liquid } from 'liquidjs'
import pov from 'point-of-view'
const engine = new Liquid({
root: path.join(path.resolve(), '../frontend/views'),
extname: '.liquid',
})
server.register(pov, {
engine: {
liquid: engine
}
})
/**
* add routes
*
*/
import taskHttp from './http/api/task.js'
import handlerHttp from './http/api/handler.js'
import indexHttp from './http/index.js'
import staticHttp from './http/static.js'
server
.register(taskHttp, {
'prefix': '/api/v1'
})
.register(handlerHttp, {
'prefix': '/api/v1'
})
.register(indexHttp)
.register(staticHttp)
export default server

@ -0,0 +1,65 @@
import DOMPurify from 'isomorphic-dompurify'
import bcrypt from 'bcrypt'
import UserRepository from './../../repositories/userRepository.js'
import loginSchema from './../../schemas/auth/login.js'
/**
* handle auth
*
* @author Björn Hase, Tentakelfabrik
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://github.com/tentakelfabrik/fastify-lowdb-riotjs-lessons-learned
*
*/
export default async function(fastify, opts)
{
/**
* auth
*
* @param {object} request
* @param {object} response
*
*/
fastify.post('/auth', loginSchema, async function (request, reply)
{
let { username, password } = request.body
// strip crap from strings
username = DOMPurify.sanitize(username)
password = DOMPurify.sanitize(password)
const userRepository = new UserRepository()
const user = await userRepository.findOneByUsername(username)
// add header for json
reply.header('Content-Type', 'application/json; charset=utf-8')
// user not found
if (!user) {
return reply
.code(404)
.send()
}
// password wrong
if (!bcrypt.compareSync(password, user.password)) {
return reply
.code(401)
.send()
}
// setting session to store and set cookie
request.sessionStore.set(request.session.sessionId, request.session, async function() {
user.sessionId = request.session.sessionId
await userRepository.update(user)
// send 200 and send set-token
reply
.code(200)
.send()
})
})
}

@ -0,0 +1,56 @@
import * as fs from 'fs'
import * as path from 'path'
/**
* getting handlers
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
export default async function(fastify, opts)
{
fastify.get('/handler/:namespace(request|action)', async function (request, reply)
{
// handlers that found
const handlers = []
// directories to find
const directories = [
'custom',
'handlers'
]
// getting
directories.forEach((directory) => {
// create path
const directoryPath = './../runner/' + directory + '/' + request.params.namespace
// adding each from directory
fs.readdirSync(directoryPath).forEach(file => {
let name = file.replace('.js', '')
// if custom add to name
if (request.params.namespace === 'custom') {
name += ' (custom)'
}
handlers.push({
'name': file.replace('.js', ''),
'className': file,
'file': directoryPath + file
})
})
})
reply
.code(200)
.send({
'data': handlers
})
})
}

@ -0,0 +1,107 @@
import DOMPurify from 'isomorphic-dompurify'
import ActionRepository from './../../repositories/actionRepository.js'
/**
*
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
export default async function(fastify, opts)
{
const actionRepository = new ActionRepository()
/**
*
*
* @param {object} request
* @param {object} response
*
*/
fastify.get('/task', async function (request, reply)
{
// getting actions
const actions = await actionRepository.find({ 'selector': {
'type': 'action'
}})
// send 200 and send set-token
reply
.code(200)
.send({
'data': actions
})
})
/**
*
*
* @param {object} request
* @param {object} response
*
*/
fastify.post('/task', async function (request, reply)
{
// getting actions
const action = await actionRepository.create(request.body)
// send 200 and send set-token
reply
.code(200)
.send({
'data': action
})
})
/**
*
*
* @param {object} request
* @param {object} response
*
*/
fastify.put('/task/:id', async function (request, reply)
{
// getting actions
const action = await actionRepository.update({
})
// send 200 and send set-token
reply
.code(200)
.send({
'data': action
})
})
/**
* delete single action
*
*
* @param {object} request
* @param {object} response
*
*/
fastify.delete('/task/:id', async function (request, reply)
{
// getting actions
const action = await actionRepository.remove(request.params.id)
if (!action) {
return reply
.code(404)
.send()
}
// send 200 and send set-token
reply
.code(200)
.send({
'success': true
})
})
}

@ -0,0 +1,44 @@
import token from '@fastify/csrf'
/**
* index
*
* - home view
* - logout user
*
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
export default async function(fastify, opts)
{
/**
* home
*
* @param {object} request
* @param {object} response
*
*/
fastify.get('/', (request, response) =>
{
response.view('../frontend/views/index')
})
/**
* logout
*
* @param {object} request
* @param {object} response
*
*/
fastify.get('/logout', async function(request, response)
{
request.destroySession(() => {
response.redirect('/')
})
})
}

@ -0,0 +1,24 @@
import fastifyStatic from 'fastify-static'
import path from 'path'
/**
* handle static
*
* @author Björn Hase, Tentakelfabrik
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://github.com/tentakelfabrik/fastify-lowdb-riotjs-lessons-learned
*
*/
export default async function(fastify, opts)
{
/**
*
*
*/
fastify.register(fastifyStatic, {
root: path.join(path.resolve(), '/../../public'),
prefix: '/',
preCompressed: true
})
}

@ -0,0 +1,14 @@
import server from './bootstrap.js'
// let it rain
const start = async () => {
try {
await server.listen(process.env.APP_PORT)
console.log('Server is running on port ' + process.env.APP_PORT)
} catch (error) {
console.log(error)
process.exit(1)
}
}
start()

@ -0,0 +1,722 @@
{
"name": "server",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@fastify/ajv-compiler": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz",
"integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==",
"requires": {
"ajv": "^6.12.6"
}
},
"@fastify/csrf": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@fastify/csrf/-/csrf-4.0.1.tgz",
"integrity": "sha512-LkMtGoj0PhnkcbB8W/oWkUKciDY8dnOYEhDUh3h8TmbzhuYd+PtVTMZjM1DcAaSNxLYTjZqe2aX6Rn7tXJO9XA==",
"requires": {
"rndm": "^1.2.0",
"tsscmp": "^1.0.6",
"uid-safe": "^2.1.5"
}
},
"abstract-logging": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"archy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
"integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
},
"atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
},
"avvio": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz",
"integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==",
"requires": {
"archy": "^1.0.0",
"debug": "^4.0.0",
"fastq": "^1.6.1",
"queue-microtask": "^1.1.2"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"requires": {
"safe-buffer": "5.2.1"
}
},
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"requires": {
"ms": "2.1.2"
}
},
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"encoding-negotiator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz",
"integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ=="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"fast-decode-uri-component": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
"integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"fast-json-stringify": {
"version": "2.7.13",
"resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz",
"integrity": "sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==",
"requires": {
"ajv": "^6.11.0",
"deepmerge": "^4.2.2",
"rfdc": "^1.2.0",
"string-similarity": "^4.0.1"
}
},
"fast-redact": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.0.tgz",
"integrity": "sha512-dir8LOnvialLxiXDPESMDHGp82CHi6ZEYTVkcvdn5d7psdv9ZkkButXrOeXST4aqreIRR+N7CYlsrwFuorurVg=="
},
"fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"fastify": {
"version": "3.27.0",
"resolved": "https://registry.npmjs.org/fastify/-/fastify-3.27.0.tgz",
"integrity": "sha512-p99Fd7xt4DFew39U5Wnp/Soy7jkpxpaqToekwQ3XWv+ECUPXd6bSF9l79EiwkutWALtEU/JiRlzS9qjP2gLHFg==",
"requires": {
"@fastify/ajv-compiler": "^1.0.0",
"abstract-logging": "^2.0.0",
"avvio": "^7.1.2",
"fast-json-stringify": "^2.5.2",
"fastify-error": "^0.3.0",
"find-my-way": "^4.5.0",
"flatstr": "^1.0.12",
"light-my-request": "^4.2.0",
"pino": "^6.13.0",
"process-warning": "^1.0.0",
"proxy-addr": "^2.0.7",
"rfdc": "^1.1.4",
"secure-json-parse": "^2.0.0",
"semver": "^7.3.2",
"tiny-lru": "^7.0.0"
}
},
"fastify-auth": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fastify-auth/-/fastify-auth-1.1.0.tgz",
"integrity": "sha512-8IajmAZB3QJ3wTP0q8Z3TG9DkxrIcAlS85TdPCBEfJi3mMKQd/sCYxtZ0dYv11v5hZaJ9z8XmNzhK3AH6/JpNw==",
"requires": {
"fastify-plugin": "^3.0.0",
"reusify": "^1.0.4"
}
},
"fastify-basic-auth": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/fastify-basic-auth/-/fastify-basic-auth-2.2.0.tgz",
"integrity": "sha512-xXeNhyhONlQNoWIzy9rhms0td6PFL1KCRqu0lkEpd54Ju4dHLbB/woPwtK0Vutuqy0sDav77If6UtdfDajx44Q==",
"requires": {
"basic-auth": "^2.0.1",
"fastify-plugin": "^3.0.0",
"http-errors": "^1.7.3"
}
},
"fastify-error": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz",
"integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ=="
},
"fastify-formbody": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/fastify-formbody/-/fastify-formbody-5.2.0.tgz",
"integrity": "sha512-d8Y5hCL82akPyoFiXh2wYOm3es0pV9jqoPo3pO9OV2cNF0cQx39J5WAVXzCh4MSt9Z2qF4Fy5gHlvlyESwjtvg==",
"requires": {
"fastify-plugin": "^3.0.0"
}
},
"fastify-plugin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz",
"integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w=="
},
"fastify-static": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.5.0.tgz",
"integrity": "sha512-Q7Tgl55AjsmBwiO4hKYib2BUCt+XTWLJ6Xp8YPPHU3EsrKNpevJ4cz8pjf1Ey1QhHw9O8Y2FDKdu+IC74oHvqw==",
"requires": {
"content-disposition": "^0.5.3",
"encoding-negotiator": "^2.0.1",
"fastify-plugin": "^3.0.0",
"glob": "^7.1.4",
"p-limit": "^3.1.0",
"readable-stream": "^3.4.0",
"send": "^0.17.1"
}
},
"fastify-warning": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
"integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw=="
},
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"requires": {
"reusify": "^1.0.4"
}
},
"find-my-way": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.5.1.tgz",
"integrity": "sha512-kE0u7sGoUFbMXcOG/xpkmz4sRLCklERnBcg7Ftuu1iAxsfEt2S46RLJ3Sq7vshsEy2wJT2hZxE58XZK27qa8kg==",
"requires": {
"fast-decode-uri-component": "^1.0.1",
"fast-deep-equal": "^3.1.3",
"safe-regex2": "^2.0.0",
"semver-store": "^0.3.0"
}
},
"flatstr": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz",
"integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw=="
},
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"hashlru": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz",
"integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A=="
},
"http-errors": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.1"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"light-my-request": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.7.0.tgz",
"integrity": "sha512-LTa8YZp3K2AUpqUnwwKajoIHcsKOBnzwJNQSrk7unziPwo6CjOYjyO0F9wfkxFvP+nBsCGe3eMPnedVgIIgdAw==",
"requires": {
"ajv": "^8.1.0",
"cookie": "^0.4.0",
"fastify-warning": "^0.2.0",
"set-cookie-parser": "^2.4.1"
},
"dependencies": {
"ajv": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}
}
},
"liquidjs": {
"version": "9.33.1",
"resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-9.33.1.tgz",
"integrity": "sha512-qIMSXkUMFG5VZmBp1qxcOEFeGzmA2A8Fy818rMTAWKyy2ftUwOEoVtvXFdqm3iSeQEoj8pzTOmC6KQOP0SipUA=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"requires": {
"yocto-queue": "^0.1.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"pino": {
"version": "6.13.4",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.4.tgz",
"integrity": "sha512-g4tHSISmQJYUEKEMVdaZ+ZokWwFnTwZL5JPn+lnBVZ1BuBbrSchrXwQINknkM5+Q4fF6U9NjiI8PWwwMDHt9zA==",
"requires": {
"fast-redact": "^3.0.0",
"fast-safe-stringify": "^2.0.8",
"flatstr": "^1.0.12",
"pino-std-serializers": "^3.1.0",
"process-warning": "^1.0.0",
"quick-format-unescaped": "^4.0.3",
"sonic-boom": "^1.0.2"
}
},
"pino-std-serializers": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz",
"integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg=="
},
"point-of-view": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/point-of-view/-/point-of-view-5.0.0.tgz",
"integrity": "sha512-JY12G3+jVFYccKTexe2OHG9WHrEUb3eLKPtLM/YEnaJf2i2OuzIun/v6SBzjHFm5sOuNXyKKo47HIe/YkGtEMA==",
"requires": {
"fastify-plugin": "^3.0.0",
"hashlru": "^2.3.0"
}
},
"process-warning": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz",
"integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"requires": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"ret": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",
"integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ=="
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
"rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
},
"rndm": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safe-regex2": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz",
"integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==",
"requires": {
"ret": "~0.2.0"
}
},
"secure-json-parse": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz",
"integrity": "sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg=="
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"semver-store": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz",
"integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg=="
},
"send": {
"version": "0.17.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
"integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "1.8.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"set-cookie-parser": {
"version": "2.4.8",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz",
"integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg=="
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"sonic-boom": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz",
"integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==",
"requires": {
"atomic-sleep": "^1.0.0",
"flatstr": "^1.0.12"
}
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"string-similarity": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz",
"integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
},
"tiny-lru": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz",
"integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow=="
},
"toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
"tsscmp": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
}
}
}

@ -0,0 +1,22 @@
{
"private": true,
"name": "server",
"version": "0.1.0",
"scripts": {
"start": "node index.js"
},
"type": "module",
"dependencies": {
"@fastify/csrf": "^4.0.1",
"dotenv": "^10.0.0",
"fastify": "^3.27.0",
"fastify-auth": "^1.1.0",
"fastify-basic-auth": "^2.2.0",
"fastify-formbody": "^5.2.0",
"fastify-static": "^4.5.0",
"liquidjs": "^9.33.1",
"point-of-view": "^5.0.0",
"pouchdb": "^7.2.2",
"pouchdb-find": "^7.2.2"
}
}

@ -0,0 +1,28 @@
import { Liquid } from 'liquidjs'
import pov from 'point-of-view'
import path from 'path'
/**
* plugin: usindg render engine liquidjs
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
async function plugin(fastify, options)
{
const engine = new Liquid({
root: path.join(path.resolve(), '/../../frontend/views'),
extname: '.liquid',
})
fastify.register(pov, {
engine: {
liquid: engine
}
})
}
export default plugin

@ -0,0 +1,212 @@
import fastifySession from '@fastify/session'
import fastifyCookie from 'fastify-cookie'
import PouchDB from 'pouchdb'
import PouchDBfind from 'pouchdb-find'
/**
* pouchdb storage
*
*
* @extends Store
*
*/
class PouchdbStore extends Store
{
/**
*
*
* @param {Object}
*
*/
constructor()
{
super()
PouchDB.plugin(PouchDBfind)
// create db if not already exists
this.db = new PouchDB('./../../storage/session', {
revs_limit: 0
})
}
/**
*
* @param {[type]} sessionId [description]
* @param {Function} callback [description]
*/
async set(sessionId, session, callback)
{
const result = await this.db.find({
'selector': {
'_id': sessionId
}
})
if (result.docs.length === 0) {
session._id = sessionId
await this.db.post(session)
}
callback()
}
/**
*
* @param {[type]} sessionId [description]
* @param {Function} callback [description]
* @return {[type]} [description]
*/
async get(sessionId, callback)
{
const result = await this.db.find({
'selector': {
'_id': sessionId
}
})
let session = null
if (result.docs.length > 0) {
session = result.docs[0]
}
callback(null, session)
}
/**
*
* @param {[type]} sessionId [description]
* @param {Function} callback [description]
* @return {[type]} [description]
*/
async destroy(sessionId, callback)
{
const result = await this.db.find({
'selector': {
'_id': sessionId
}
})
if (result.docs.length > 0) {
await this.db.remove(result.docs[0])
}
callback()
}
}
/**
*
*
*
*/
const loginSchema = {
schema: {
body: {
username: { type: 'string' },
password: { type: 'string' }
},
response: {
200: {
type: 'object',
properties: {
token: { type: 'string' }
}
}
}
}
}
/**
* create Plugin
*
*
*/
function plugin(fastify, options, next) {
{
// create cookie
fastify
.register(fastifyCookie)
.register(fastifySession, {
secret: options.secret,
cookieName: 'session',
cookie: {
path: '/',
maxAge: options.secret,
secure: true,
httpOnly: true,
sameSite: 'Strict'
},
store: new PouchdbStore()
})
/**
* route: logout user and destroy session
*
*
*/
fastify.get('/logout', async function(request, response)
{
request.destroySession(() => {
response.redirect('/')
})
})
/**
* route: auth
*
* @param {object} request
* @param {object} response
*
*/
fastify.post('/auth', loginSchema, async function (request, reply)
{
let { username, password } = request.body
// strip crap from strings
username = DOMPurify.sanitize(username)
password = DOMPurify.sanitize(password)
const userRepository = new UserRepository()
const user = await userRepository.findOneByUsername(username)
// add header for json
reply.header('Content-Type', 'application/json; charset=utf-8')
// user not found
if (!user) {
return reply
.code(404)
.send()
}
// password wrong
if (!bcrypt.compareSync(password, user.password)) {
return reply
.code(401)
.send()
}
// setting session to store and set cookie
request.sessionStore.set(request.session.sessionId, request.session, async function() {
user.sessionId = request.session.sessionId
await userRepository.update(user)
// send 200 and send set-token
reply
.code(200)
.send()
})
})
done()
}
module.exports = plugin

@ -0,0 +1,23 @@
import Repository from './repository.js'
/**
* Action
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
class ActionRepository extends Repository
{
constructor()
{
super()
this.type = 'action'
this.index.push('name')
}
}
export default ActionRepository

@ -0,0 +1,174 @@
import PouchDB from 'pouchdb'
import PouchDBfind from 'pouchdb-find'
/**
* Repository
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
class Repository
{
constructor()
{
PouchDB.plugin(PouchDBfind)
this.db = new PouchDB('./../../storage/database', {
revs_limit: 0
})
// adding index
this.index = [
'type'
]
if (this.index && this.index.lenth > 0) {
this.addIndex(this.index)
}
}
/**
* add index
*
* @param {array} fields
*
*/
async addIndex(fields)
{
try {
await this.db.createIndex({
index: {
fields: fields
}
})
} catch (error) {
console.log(error);
}
}
/**
*
*
* @param {array} data
* @param {function} success
*
*/
create(data)
{
//data._id = uuidv4()
data.type = this.type
// if beforeCreate exists
if (typeof this['beforeCreate'] === 'function') {
data = this.beforeCreate(data)
}
return this.db.post(data)
.then((response) => {
console.log(response)
// if afterCreate exists
if (typeof this['afterCreate'] === 'function') {
this.afterCreate(response)
}
return response
})
}
/**
*
*
* @param {array} data
* @param {function} success
*
*/
update(data)
{
// if beforeUpdate exists
if (typeof this['beforeUpdate'] === 'function') {
data = this.beforeUpdate(data)
}
return this.db.put(data)
.then((response) => {
// if beforeUpdate exists
if (typeof this['afterUpdate'] === 'function') {
this.afterUpdate(response)
}
return response
})
}
/**
*
*
* @param {string} id
* @param {function} success
*
*/
async remove(id)
{
this.findOne({
'selector': {
'_id': id
}
}).then((document) => {
if (document) {
// if beforeUpdate exists
if (typeof this['beforeRemove'] === 'function') {
this.beforeRemove(document)
}
return this.db.remove(document._id, document._rev, () => {
return document
})
// if beforeUpdate exists
if (typeof this['afterRemove'] === 'function') {
this.afterRemove(document)
}
}
})
}
/**
* find documents
*
* @param {object} fields
* @param {object} query
* @param {function} success
*/
find(query)
{
return this.db.find(query).then((documents) => {
return documents.docs
})
}
/**
* find documents
*
* @param {object} fields
* @param {object} query
* @param {function} success
*/
findOne(query)
{
return this.db.find(query).then((documents) => {
if (documents.docs.length === 0) {
return null
} else {
return documents.docs[0]
}
})
}
}
export default Repository

@ -0,0 +1,70 @@
import Repository from './repository.js'
import bcrypt from 'bcrypt'
/**
*
*
* @author Björn Hase, me@herr-hase.wtf
* @license http://opensource.org/licenses/MIT The MIT License
* @link https://gitea.node001.net/HerrHase/super-hog
*
*/
class UserRepository extends Repository
{
constructor()
{
super()
this.type = 'user'
this.index.push('username')
this.index.push('sessionId')
}
/**
*
* @param {[type]} data
* @return {[type]}
*/
beforeCreate(data)
{
if (data.password) {
const salt = bcrypt.genSaltSync(15)
data.password = bcrypt.hashSync(data.password, salt)
}
return data
}
/**
* getting user by username
*
* @param {string} username
* @return {promise}
*/
async findOneByUsername(username)
{
return this.findOne({
'selector': {
'username': username
}
})
}
/**
* getting user by sessionToken
*
* @param {string} sessionToken
* @return {promise}
*/
findOneBySessionId(sessionId)
{
return this.findOne({
'selector': {
'sessionId': sessionId
}
})
}
}
export default UserRepository

@ -0,0 +1,19 @@
/**
*
*
*
*/
export default {
schema: {
body: {
username: { type: 'string' },
password: { type: 'string' }
},
response: {
200: {
type: 'object'
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,93 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, noarchive">
<meta name="format-detection" content="telephone=no">
<title>Transfonter demo</title>
<link href="stylesheet.css" rel="stylesheet">
<style>
/*
http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* demo styles */
body {
background: #f0f0f0;
color: #000;
}
.page {
background: #fff;
width: 920px;
margin: 0 auto;
padding: 20px 20px 0 20px;
overflow: hidden;
}
.font-container {
overflow-x: auto;
overflow-y: hidden;
margin-bottom: 40px;
line-height: 1.3;
white-space: nowrap;
padding-bottom: 5px;
}
h1 {
position: relative;
background: #444;
font-size: 32px;
color: #fff;
padding: 10px 20px;
margin: 0 -20px 12px -20px;
}
.letters {
font-size: 25px;
margin-bottom: 20px;
}
.s10:before {
content: '10px';
}
.s11:before {
content: '11px';
}
.s12:before {
content: '12px';
}
.s14:before {
content: '14px';
}
.s18:before {
content: '18px';
}
.s24:before {
content: '24px';
}
.s30:before {
content: '30px';
}
.s36:before {
content: '36px';
}
.s48:before {
content: '48px';
}
.s60:before {
content: '60px';
}
.s72:before {
content: '72px';
}
.s10:before, .s11:before, .s12:before, .s14:before,
.s18:before, .s24:before, .s30:before, .s36:before,
.s48:before, .s60:before, .s72:before {
font-family: Arial, sans-serif;
font-size: 10px;
font-weight: normal;
font-style: normal;
color: #999;
padding-right: 6px;
}
pre {
display: block;
position: relative;
padding: 9px;
margin: 0 0 10px;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
font-size: 13px;
line-height: 1.428571429;
color: #333;
font-weight: normal;
font-style: normal;
background-color: #f5f5f5;
border: 1px solid #ccc;
overflow-x: auto;
border-radius: 4px;
}
pre:after {
display: block;
position: absolute;
right: 0;
top: 0;
content: 'Usage';
line-height: 1;
padding: 5px 8px;
font-size: 12px;
color: #767676;
background-color: #fff;
border: 1px solid #ccc;
border-right: none;
border-top: none;
border-radius: 0 4px 0 4px;
z-index: 10;
}
/* responsive */
@media (max-width: 959px) {
.page {
width: auto;
margin: 0;
}
}
</style>
</head>
<body>
<div class="page">
<div class="demo">
<h1 style="font-family: 'IBM Plex Mono'; font-weight: normal; font-style: normal;">IBM Plex Mono</h1>
<pre>.your-style {
font-family: 'IBM Plex Mono';
font-weight: normal;
font-style: normal;
}</pre>
<div class="font-container" style="font-family: 'IBM Plex Mono'; font-weight: normal; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#<>$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
<div class="demo">
<h1 style="font-family: 'IBM Plex Mono'; font-weight: bold; font-style: normal;">IBM Plex Mono Bold</h1>
<pre>.your-style {
font-family: 'IBM Plex Mono';
font-weight: bold;
font-style: normal;
}</pre>
<div class="font-container" style="font-family: 'IBM Plex Mono'; font-weight: bold; font-style: normal;">
<p class="letters">
abcdefghijklmnopqrstuvwxyz<br>
ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>
0123456789.:,;()*!?'@#<>$%&^+-=~
</p>
<p class="s10" style="font-size: 10px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s11" style="font-size: 11px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s12" style="font-size: 12px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s14" style="font-size: 14px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s18" style="font-size: 18px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s24" style="font-size: 24px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s30" style="font-size: 30px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s36" style="font-size: 36px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s48" style="font-size: 48px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s60" style="font-size: 60px;">The quick brown fox jumps over the lazy dog.</p>
<p class="s72" style="font-size: 72px;">The quick brown fox jumps over the lazy dog.</p>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,24 @@
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono.eot');
src: url('IBMPlexMono.eot?#iefix') format('embedded-opentype'),
url('IBMPlexMono.woff2') format('woff2'),
url('IBMPlexMono.woff') format('woff'),
url('IBMPlexMono.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'IBM Plex Mono';
src: url('IBMPlexMono-Bold.eot');
src: url('IBMPlexMono-Bold.eot?#iefix') format('embedded-opentype'),
url('IBMPlexMono-Bold.woff2') format('woff2'),
url('IBMPlexMono-Bold.woff') format('woff'),
url('IBMPlexMono-Bold.ttf') format('truetype');
font-weight: bold;
font-style: normal;
font-display: swap;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
(self["webpackChunkfrontend"] = self["webpackChunkfrontend"] || []).push([["spritemap"],{
/***/ "?c20d":
/*!******************************!*\
!*** spritemap-dummy-module ***!
\******************************/
/***/ (() => {
/***/ })
}]);

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save