Merge pull request #133 from henrywhitaker3/dev-docker

Merge dev into latest
This commit is contained in:
Henry Whitaker
2020-06-27 01:45:46 +01:00
committed by GitHub
955 changed files with 80009 additions and 53103 deletions

View File

@@ -1,27 +1,8 @@
FROM linuxserver/nginx
MAINTAINER henrywhitaker3@outlook.com
# Install apt stuff
RUN apk add --no-cache --upgrade \
python3 \
py-pip \
supervisor
COPY conf/ /
# Install speedtest-cli
RUN pip3 install speedtest-cli
# Copy over static files
COPY conf/ /setup/
# Setup new init script
RUN cp /setup/entrypoint/init.sh /etc/cont-init.d/50-speedtest
# Update webroot
RUN cp /setup/default /defaults/default
RUN mkdir -p /etc/services.d/supervisord/ && \
cp /setup/supervisor-service.sh /etc/services.d/supervisord/run && \
mkdir -p /etc/supervisor.d/ && \
cp /setup/laravel-worker.conf /etc/supervisor.d/laravel-worker.ini
EXPOSE 80 443
VOLUME ["/config"]

View File

@@ -1,8 +1,10 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.5.0-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.7.1-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses [Ookla's Speedtest cli](https://www.speedtest.net/apps/cli) to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
Disclaimer: You will need to accept Ookla's [EULA](https://www.speedtest.net/about/eula) and privacy agreements in order to use this container.
![speedtest](https://user-images.githubusercontent.com/36062479/78822484-a82b8300-79ca-11ea-8525-fdeae496a0bd.gif)
@@ -10,8 +12,8 @@ This program runs a speedtest check every hour and graphs the results. The back-
- Automatically run a speedtest every hour
- Graph of previous speedtests going back x days
- Backup/restore data in JSON format
- Slack/Discord notifications
- Backup/restore data in JSON/CSV format
- Slack/Discord/Telegram notifications
- Organizr integration
## Usage
@@ -22,6 +24,9 @@ docker create \
-p 8765:80 \
-v /path/to/data:/config \
-e SLACK_WEBHOOK=webhook `#optional` \
-e PUID=uid `#optional` \
-e PGID=gid `#optional` \
-e OOKLA_EULA_GDPR=true \
--restart unless-stopped \
henrywhitaker3/speedtest-tracker
```
@@ -30,11 +35,16 @@ docker create \
Container images are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `<external>:<internal>` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container.
| Parameter | Function |
| :----: | --- |
| `-p 8765:80` | Exposes the webserver on port 8765 |
| `-e SLACK_WEBHOOK` | Put a slack webhook here to get slack notifications when a speedtest is run. To use discord webhooks, just append `/slack` to the end of your discord webhook URL |
| `-v /config` | All the config files reside here. |
| Parameter | Function |
| :----: | --- |
| `-p 8765:80` | Exposes the webserver on port 8765 |
| `-v /config` | All the config files reside here. |
| `-e OOKLA_EULA_GDPR` | Set to 'true' to accept the Ookla [EULA](https://www.speedtest.net/about/eula) and privacy agreement. If this is not set, the container will not start |
| `-e SLACK_WEBHOOK` | Optional. Put a slack webhook here to get slack notifications when a speedtest is run. To use discord webhooks, just append `/slack` to the end of your discord webhook URL |
| `-e TELEGRAM_BOT_TOKEN` | Optional. Telegram bot API token. |
| `-e TELEGRAM_CHAT_ID` | Optional. Telegram chat ID. |
| `-e PUID` | Optional. Supply a local user ID for volume permissions |
| `-e PGID` | Optional. Supply a local group ID for volume permissions |
## Getting the Image

9
conf/defaults/crontab Normal file
View File

@@ -0,0 +1,9 @@
# do daily/weekly/monthly maintenance
# min hour day month weekday command
*/15 * * * * run-parts /etc/periodic/15min
0 * * * * run-parts /etc/periodic/hourly
0 2 * * * run-parts /etc/periodic/daily
0 3 * * 6 run-parts /etc/periodic/weekly
0 5 1 * * run-parts /etc/periodic/monthly
# speedtest cron
* * * * * php /config/www/artisan schedule:run >> /config/log/speedtest/cron.log

View File

@@ -1,68 +0,0 @@
#!/usr/bin/with-contenv bash
# # This script sets up the speedtest app
# Copy site files to /config
echo "Copying latest site files to config"
cp -rfT /setup/site/ /config/www/
chown -R abc:abc /config/www
chmod -R 755 /config/www/storage
chmod -R 755 /config/www/bootstrap
# Check for DB
if [ ! -f /config/speed.db ]; then
echo "Database file not found! Creating empty database"
touch /config/speed.db
chown abc:abc /config/speed.db
else
echo "Database file exists"
chown abc:abc /config/speed.db
fi
# Check for .env
if [ ! -f /config/www/.env ]; then
echo "Env file not found! Creating .env file"
cp /setup/site/.env.example /config/www/.env
else
echo "Env file exists"
fi
sed "s,DB_DATABASE=.*,DB_DATABASE=/config/speed.db," -i.bak /config/www/.env
echo "Running database migrations"
php /config/www/artisan migrate
# Check app key exists
if cat /config/www/.env | grep -E "APP_KEY=[0-9A-Za-z:+\/=]{1,}" > /dev/null ; then
echo "App key exists"
else
echo "Generating app key"
php /config/www/artisan key:generate
fi
# Check JWT secret exists
if cat /config/www/.env | grep -E "JWT_SECRET=[0-9A-Za-z:+\/=]{1,}" > /dev/null ; then
echo "JWT secret exists"
else
echo "Generating JWT secret"
php /config/www/artisan jwt:secret
fi
if [ -z ${SLACK_WEBHOOK+x} ]; then
echo "Slack webhook is unset"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=," -i.bak /config/www/.env
else
echo "Slack webhook set, updating .env"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=$SLACK_WEBHOOK," -i.bak /config/www/.env
fi
if [ -z ${BASE_PATH+x} ]; then
echo "Base path is unset"
sed "s,BASE_PATH=.*,BASE_PATH=," -i.bak /config/www/.env
else
echo "Base path set, updating .env"
sed "s,BASE_PATH=.*,BASE_PATH=$BASE_PATH," -i.bak /config/www/.env
fi
mkdir -p /config/log/speedtest
echo "* * * * * php /config/www/artisan schedule:run >> /config/log/speedtest/cron.log" >> /etc/crontabs/root

View File

@@ -0,0 +1,103 @@
#!/usr/bin/with-contenv bash
# # This script sets up the speedtest app
function eulaError()
{
echo "##################################################################################################################################"
echo "##################################################################################################################################"
echo "You haven't accepted the Ookla EULA. Please re-create the container with the environment variable 'OOKLA_EULA_GDPR' set to 'true'."
echo "##################################################################################################################################"
echo "##################################################################################################################################"
exit 1
}
# Do Ookla stuff
if [ -z ${OOKLA_EULA_GDPR+x} ]; then
eulaError
else
if [ $OOKLA_EULA_GDPR != "true" ]; then
eulaError
fi
echo "Ookla GDPR and EULA accepted. Downloading Speedtest CLI."
cd /tmp
wget https://bintray.com/ookla/download/download_file?file_path=ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz > /dev/null
tar zxvf speedtest.tgz > /dev/null
cp speedtest /site/app/Bin/
HOME=/config && timeout 5s s6-setuidgid abc /site/app/Bin/speedtest --accept-license --accept-gdpr > /dev/null
HOME=/root
fi
# Copy site files to /config
echo "Copying latest site files to config"
cp -rfT /site/ /config/www/
# Check for DB
if [ ! -f /config/speed.db ]; then
echo "Database file not found! Creating empty database"
touch /config/speed.db
else
echo "Database file exists"
chown abc:abc /config/speed.db
fi
# Check for .env
if [ ! -f /config/www/.env ]; then
echo "Env file not found! Creating .env file"
cp /site/.env.example /config/www/.env
else
echo "Env file exists"
fi
sed "s,DB_DATABASE=.*,DB_DATABASE=/config/speed.db," -i.bak /config/www/.env
echo "Running database migrations"
php /config/www/artisan migrate
# Check app key exists
if grep -E "APP_KEY=[0-9A-Za-z:+\/=]{1,}" /config/www/.env > /dev/null; then
echo "App key exists"
else
echo "Generating app key"
php /config/www/artisan key:generate
fi
# Check JWT secret exists
if grep -E "JWT_SECRET=[0-9A-Za-z:+\/=]{1,}" /config/www/.env > /dev/null ; then
echo "JWT secret exists"
else
echo "Generating JWT secret"
php /config/www/artisan jwt:secret
fi
if [ -z ${SLACK_WEBHOOK+x} ]; then
echo "Slack webhook is unset"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=," -i.bak /config/www/.env
else
echo "Slack webhook set, updating .env"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=$SLACK_WEBHOOK," -i.bak /config/www/.env
fi
if [ -z ${TELEGRAM_BOT_TOKEN+x} ] && [ -z ${TELEGRAM_CHAT_ID+x} ]; then
echo "Telegram chat id and bot token unset"
sed "s,TELEGRAM_BOT_TOKEN=.*,TELEGRAM_BOT_TOKEN=," -i.bak /config/www/.env
sed "s,TELEGRAM_CHAT_ID=.*,TELEGRAM_CHAT_ID=," -i.bak /config/www/.env
else
echo "Telegram chat id and bot token set, updating .env"
sed "s,TELEGRAM_BOT_TOKEN=.*,TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN," -i.bak /config/www/.env
sed "s,TELEGRAM_CHAT_ID=.*,TELEGRAM_CHAT_ID=$TELEGRAM_CHAT_ID," -i.bak /config/www/.env
fi
if [ -z ${BASE_PATH+x} ]; then
echo "Base path is unset"
sed "s,BASE_PATH=.*,BASE_PATH=," -i.bak /config/www/.env
else
echo "Base path set, updating .env"
sed "s,BASE_PATH=.*,BASE_PATH=$BASE_PATH," -i.bak /config/www/.env
fi
mkdir -p /config/log/speedtest
cp /defaults/crontab /etc/crontabs/root
chown -R abc:abc /config

View File

@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bash
exec s6-setuidgid abc php /config/www/artisan queue:work --timeout=120 >> /config/log/speedtest/queue.log

View File

@@ -1,9 +0,0 @@
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /config/www/artisan queue:work
autostart=true
autorestart=true
user=abc
numprocs=1
redirect_stderr=true
logfile=/config/log/speedtest/laravel-worker.log

View File

@@ -24,4 +24,7 @@ REMEMBER_DAYS=30
SLACK_WEBHOOK=
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
BASE_PATH=

View File

@@ -8,5 +8,3 @@ Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.vscode/

View File

@@ -1,8 +1,10 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) ![version](https://img.shields.io/badge/version-v1.5.0-success) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.7.1-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://www.speedtest.net/apps/cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
Disclaimer: You will need to accept Ookla's EULA and privacy agreements in order to use this container.
![speedtest](https://user-images.githubusercontent.com/36062479/78822484-a82b8300-79ca-11ea-8525-fdeae496a0bd.gif)
@@ -10,8 +12,8 @@ This program runs a speedtest check every hour and graphs the results. The back-
- Automatically run a speedtest every hour
- Graph of previous speedtests going back x days
- Backup/restore data in JSON format
- Slack/Discord notifications
- Backup/restore data in JSON/CSV format
- Slack/Discord/Telegram notifications
- Organizr integration
## Installation & Setup
@@ -26,6 +28,9 @@ docker create \
-p 8765:80 \
-v /path/to/data:/config \
-e SLACK_WEBHOOK=webhook `#optional` \
-e PUID=uid `#optional` \
-e PGID=gid `#optional` \
-e OOKLA_EULA_GDPR=true \
--restart unless-stopped \
henrywhitaker3/speedtest-tracker
```
@@ -39,7 +44,7 @@ This program has some dependencies, to install them you need to run the followin
```bash
sudo apt update
sudo apt update
sudo apt install php-common php7.2 php7.2-cli php7.2-common php7.2-json php7.2-opcache php7.2-readline php-xml php-sqlite3 php-zip composer python3 python3-pip git
sudo apt install php-common php7.2 php7.2-cli php7.2-common php7.2-json php7.2-opcache php7.2-readline php-xml php-sqlite3 php-zip php-mbstring composer python3 python3-pip git
```
```bash
sudo apt install curl

1
conf/site/app/Bin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
speedtest

View File

@@ -32,9 +32,9 @@ class SpeedtestCommand extends Command
}
/**
* Execute the console command.
* Runs a speedtest synchroonously and displays the results..
*
* @return mixed
* @return null
*/
public function handle()
{
@@ -42,6 +42,16 @@ class SpeedtestCommand extends Command
$results = SpeedtestHelper::runSpeedtest();
if(!is_object($results)) {
$this->error('Something went wrong running the speedtest.');
exit();
}
if(property_exists($results, 'ping') && property_exists($results, 'download') && property_exists($results, 'upload')) {
$this->error('Something went wrong running the speedtest.');
exit();
}
$this->info('Ping: ' . $results->ping . ' ms');
$this->info('Download: ' . $results->download . ' Mbit/s');
$this->info('Upload: ' . $results->upload . ' Mbit/s');

View File

@@ -33,9 +33,9 @@ class SpeedtestLatestCommand extends Command
}
/**
* Execute the console command.
* Prints the latest speedtest values.
*
* @return mixed
* @return null
*/
public function handle()
{

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use App\Events\SpeedtestOverviewEvent;
use App\Helpers\SpeedtestHelper;
use App\Notifications\SpeedtestOverviewSlack;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
class SpeedtestOverviewCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:overview';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Trigger a speedtest overview event';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
event(new SpeedtestOverviewEvent());
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SpeedtestVersionCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:version';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Displays the version number of this instance';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('Speedtest Tracker v' . config('speedtest.version'));
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Console;
use App\Events\SpeedtestOverviewEvent;
use App\Helpers\SettingsHelper;
use App\Helpers\SpeedtestHelper;
use App\Jobs\SpeedtestJob;
@@ -28,6 +29,7 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule)
{
$schedule->job(new SpeedtestJob)->cron(SettingsHelper::get('schedule')['value']);
$schedule->command('speedtest:overview')->cron('0 ' . SettingsHelper::get('speedtest_overview_time')->value . ' * * *');
}
/**

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SpeedtestOverviewEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@@ -3,29 +3,103 @@
namespace App\Helpers;
use App\Speedtest;
use DateTime;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class BackupHelper {
public static function backup()
{
$data = Speedtest::get();
return $data;
/**
* Generates a backup of all speedtests.
*
* @param string $format json|csv
* @return string $name Returns the filename of the backup.
*/
public static function backup(String $format = 'json')
{
$timestamp = new DateTime();
$timestamp = $timestamp->format('Y-m-d_H:i:s');
$name = 'speedtest_backup_' . $timestamp;
switch($format) {
case 'csv':
$data = Speedtest::get();
$csv = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix() . $name . '.csv';
$name = $name . '.csv';
$handle = fopen($csv, 'w+');
fputcsv($handle, array('id', 'ping', 'download', 'upload', 'created_at', 'updated_at'));
foreach($data as $d) {
fputcsv($handle, array($d->id, $d->ping, $d->download, $d->upload, $d->created_at, $d->updated_at));
}
fclose($handle);
break;
case 'json':
default:
$data = Speedtest::get()->toJson();
$name = $name . '.json';
Storage::disk('local')->put($name, $data);
break;
}
return $name;
}
public static function restore($array)
/**
* Restore data from a backup in CSV or JSON format
*
* @param array|string $array Backup data
* @param string $format json|csv
* @return boolean
*/
public static function restore($array, $format)
{
foreach($array as $test) {
try {
$st = Speedtest::create([
'ping' => $test['ping'],
'download' => $test['download'],
'upload' => $test['upload'],
'created_at' => $test['created_at'],
]);
} catch(Exception $e) {
continue;
if($format == 'json') {
foreach($array as $test) {
try {
$st = Speedtest::create([
'ping' => $test['ping'],
'download' => $test['download'],
'upload' => $test['upload'],
'created_at' => $test['created_at'],
]);
} catch(Exception $e) {
continue;
}
}
return true;
} else if($format == 'csv') {
$csv = explode(PHP_EOL, $array);
$headers = 'id,ping,download,upload,created_at,updated_at';
if($csv[0] != $headers) {
Log::error('Incorrect CSV format');
return false;
}
unset($csv[0]);
$csv = array_values($csv);
for($i = 0; $i < sizeof($csv); $i++) {
$e = explode(',', $csv[$i]);
try {
$st = Speedtest::create([
'ping' => $e[1],
'download' => $e[2],
'upload' => $e[3],
'created_at' => substr($e[4], 1, -1),
]);
} catch(Exception $e) {
Log::error($e);
continue;
}
}
return true;
}
}
}

View File

@@ -5,6 +5,13 @@ namespace App\Helpers;
use App\Setting;
class SettingsHelper {
/**
* Get a Setting object by name
*
* @param String $name The name field in the setting table
* @return \App\Setting|boolean $name The Setting object. Returns false if no mathcing obj.
*/
public static function get(String $name)
{
$name = Setting::where('name', $name)->get();
@@ -19,6 +26,13 @@ class SettingsHelper {
}
}
/**
* Create / update value for Setting object.
*
* @param String $name Name of setting
* @param String $value Value of setting
* @return \App\Setting
*/
public static function set(String $name, String $value)
{
$setting = SettingsHelper::get($name);
@@ -36,6 +50,11 @@ class SettingsHelper {
return $setting;
}
/**
* Get the app's base path
*
* @return string
*/
public static function getBase()
{
$base = env('BASE_PATH', '/');

View File

@@ -3,11 +3,20 @@
namespace App\Helpers;
use App\Speedtest;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use JsonException;
class SpeedtestHelper {
/**
* Runs/processes speedtest output to created a Speedtest object
*
* @param boolean|string $output If false, new speedtest runs. If anything else, will try to parse as JSON for speedtest results.
* @return \App\Speedtest|boolean
*/
public static function runSpeedtest($output = false)
{
if($output === false) {
@@ -16,10 +25,19 @@ class SpeedtestHelper {
try {
$output = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
if(!SpeedtestHelper::checkOutputIsComplete($output)) {
return false;
}
$test = Speedtest::create([
'ping' => $output['ping'],
'download' => $output['download'] / 1000000,
'upload' => $output['upload'] / 1000000
'ping' => $output['ping']['latency'],
'download' => SpeedtestHelper::convert($output['download']['bandwidth']),
'upload' => SpeedtestHelper::convert($output['upload']['bandwidth']),
'server_id' => $output['server']['id'],
'server_name' => $output['server']['name'],
'server_host' => $output['server']['host'] . ':' . $output['server']['port'],
'url' => $output['result']['url'],
]);
} catch(JsonException $e) {
Log::error('Failed to parse speedtest JSON');
@@ -31,19 +49,60 @@ class SpeedtestHelper {
return (isset($test)) ? $test : false;
}
/**
* Gets the output of executing speedtest binary.
*
* @return boolean|string
*/
public static function output()
{
$server = SettingsHelper::get('server')['value'];
$binPath = app_path() . DIRECTORY_SEPARATOR . 'Bin' . DIRECTORY_SEPARATOR . 'speedtest';
if($server != '' && $server != false) {
$server = explode(',', $server);
$server = $server[array_rand($server)];
if($server == false) {
Log::error('Speedtest server undefined');
return false;
}
return shell_exec('speedtest-cli --json --server ' . $server);
return shell_exec('HOME=/config && ' . $binPath . ' -f json -s ' . $server);
}
return shell_exec('speedtest-cli --json');
return shell_exec('HOME=/config && ' . $binPath . ' -f json');
}
/**
* Get a 24 hour average of speedtest results
*
* @return array
*/
public static function last24Hours()
{
$t = Carbon::now()->subDay();
$s = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload'))
->where('created_at', '>=', $t)
->first()
->toArray();
return $s;
}
/**
* Converts bytes/s to Mbps
*
* @param int|float $bytes
* @return int|float
*/
public static function convert($bytes) {
return ( $bytes * 8 ) / 1000000;
}
/**
* Returns the latest speedtest object.
*
* @return boolean|\App\Speedtest
*/
public static function latest()
{
$data = Speedtest::latest()->get();
@@ -55,6 +114,12 @@ class SpeedtestHelper {
return $data->first();
}
/**
* Parses network speeds and return converted to Mbps
*
* @param array $input
* @return array
*/
public static function parseUnits($input)
{
$input = explode(' ', $input);
@@ -82,4 +147,46 @@ class SpeedtestHelper {
'unit' => 'Mbit/s'
];
}
/**
* Checks that the speedtest JSON output is complete/valid
*
* @param array $output
* @return boolean
*/
public static function checkOutputIsComplete($output)
{
/**
* Array of indexes that must exist in $output
*/
$checks = [
'type' => 'result',
'download' => [ 'bandwidth' => '*' ],
'upload' => [ 'bandwidth' => '*' ],
'ping' => [ 'latency' => '*' ],
'server' => [
'id' => '*',
'name' => '*',
'host' => '*',
'port' => '*'
],
'result' => [
'url' => '*'
],
];
/**
* Array of indexes that must not exist
*/
$checkMissing = [
'type' => 'error'
];
foreach($checks as $key => $value) {
if(!isset($output[$key])) {
return false;
}
}
return true;
}
}

View File

@@ -10,10 +10,39 @@ use RecursiveIteratorIterator;
use ZipArchive;
class UpdateHelper {
/**
* URL of updates
*
* @var string
*/
public $url;
/**
* Current app version number
*
* @var string
*/
public $currentVersion;
/**
* Username of GitHub repo
*
* @var string
*/
public $user;
/**
* Name of GitHub repo
*
* @var string
*/
public $repo;
/**
* Branch of GitHub repo
*
* @var string
*/
public $branch;
function __construct() {
@@ -25,6 +54,11 @@ class UpdateHelper {
$this->download = null;
}
/**
* Returns data on new version available
*
* @return boolean|array false|[ version, changelog ]
*/
public function check()
{
Log::info('Checking for new version');
@@ -50,6 +84,11 @@ class UpdateHelper {
}
}
/**
* Gets the latest version number from GitHub
*
* @return array [ repo, branch, version ]
*/
public function checkLatestVersion()
{
$url = 'https://raw.githubusercontent.com/'
@@ -78,6 +117,11 @@ class UpdateHelper {
];
}
/**
* Gets the latest changelog from GitHub
*
* @return array
*/
public function getChangelog()
{
$url = 'https://raw.githubusercontent.com/'
@@ -97,6 +141,11 @@ class UpdateHelper {
return $changelog;
}
/**
* Downloads the latest version from GitHub
*
* @return boolean|Exception
*/
public function downloadLatest()
{
Log::info('Downloading the latest version from GitHub');
@@ -121,6 +170,11 @@ class UpdateHelper {
}
}
/**
* Extracts zip archive from update
*
* @return boolean
*/
public function extractFiles()
{
Log::info('Extracting the update');
@@ -137,6 +191,11 @@ class UpdateHelper {
}
}
/**
* Replace existing files with newly downloaded files
*
* @return void
*/
public function updateFiles()
{
Log::info('Applying update');
@@ -151,6 +210,14 @@ class UpdateHelper {
Log::info('Successfully applied update');
}
/**
* Deletes default templates from updated files.
* This is for things like .env so that user specified files are not
* overwritten.
*
* @param string $path
* @return void
*/
private function deleteExcluded($path)
{
Log::info('Deleting excluded items from update directory');
@@ -172,6 +239,11 @@ class UpdateHelper {
Log::info('Excluded items deleted from update directory');
}
/**
* Creates a ZIP backup of current installation
*
* @return void
*/
private function backupCurrent()
{
Log::info('Backing up current installation');
@@ -209,6 +281,11 @@ class UpdateHelper {
Log::info('Backup created at: ' . $backupZip);
}
/**
* Move updated files into server dir.
*
* @return void
*/
private function moveFiles()
{
$new = array_filter(glob('/tmp/'.$this->repo.'-update/*'), 'is_dir');
@@ -242,6 +319,11 @@ class UpdateHelper {
}
/**
* Make a copy of excluded, user customised files
*
* @return void
*/
private function tempStoreExcludedFiles()
{
Log::info('Temporarily moving exluded files from root directory');
@@ -263,6 +345,11 @@ class UpdateHelper {
}
}
/**
* Restore user cusotmised files from the copy
*
* @return void
*/
private function restoreExcludedFiles()
{
Log::info('Restoring exluded files to root directory');
@@ -284,6 +371,11 @@ class UpdateHelper {
}
}
/**
* Delete update files from download dir.
*
* @return void
*/
private function clearup()
{
try {

View File

@@ -10,21 +10,39 @@ use Illuminate\Support\Facades\Validator;
class BackupController extends Controller
{
public function backup()
{
$data = BackupHelper::backup();
$timestamp = new DateTime();
$timestamp = $timestamp->format('Y-m-d_H:i:s');
$name = 'speedtest_backup_' . $timestamp . '.json';
Storage::disk('local')->put($name, $data);
return Storage::disk('local')->download($name);
/**
* Get backup of speedtests
*
* @param Request $request
* @return file
*/
public function backup(Request $request)
{
$validator = Validator::make($request->all(), [ 'format' => 'in:json,csv' ]);
if($validator->fails()) {
return response()->json([
'method' => 'backup data',
'error' => $validator->errors(),
], 422);
}
$filename = BackupHelper::backup($request->format);
return Storage::disk('local')->download($filename);
}
/**
* Retore from a backup
*
* @param Request $request
* @return Response
*/
public function restore(Request $request)
{
$rule = [
'data' => [ 'required', 'array' ],
'data' => [ 'required' ],
'format' => [ 'required', 'in:json,csv' ]
];
$validator = Validator::make($request->all(), $rule);
@@ -35,10 +53,14 @@ class BackupController extends Controller
], 422);
}
BackupHelper::restore($request->data);
return response()->json([
'method' => 'restore data from backup',
], 200);
if(BackupHelper::restore($request->data, $request->format) != false) {
return response()->json([
'method' => 'restore data from backup',
], 200);
} else {
return response()->json([
'method' => 'incorrect backup format',
], 422);
}
}
}

View File

@@ -10,16 +10,34 @@ use Illuminate\Support\Facades\Validator;
class SettingsController extends Controller
{
/**
* Return all settings
*
* @return array
*/
public function index()
{
return Setting::get()->keyBy('name');
}
/**
* Get setting by id
*
* @param Setting $setting
* @return Setting
*/
public function get(Setting $setting)
{
return $setting;
}
/**
* Store/update a setting
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$rule = [
@@ -49,6 +67,54 @@ class SettingsController extends Controller
], 200);
}
/**
* Bulk store/update a setting
*
* @param Request $request
* @return Response
*/
public function bulkStore(Request $request)
{
$rule = [
'data' => [ 'array', 'required' ],
'data.*.name' => [ 'string', 'required' ],
'data.*.value' => [ 'required' ],
];
$validator = Validator::make($request->all(), $rule);
if($validator->fails()) {
return response()->json([
'method' => 'Bulk store a setting',
'error' => $validator->errors()
], 422);
}
$settings = [];
foreach($request->data as $d) {
if($d['name'] == 'speedtest_overview_time') {
$ok = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23' ];
if(!in_array($d['value'], $ok)) {
return response()->json([
'method' => 'Bulk store a setting',
'error' => 'Invalid speedtest_overview_time value'
], 422);
}
}
$setting = SettingsHelper::set($d['name'], $d['value']);
array_push($settings, $setting);
}
return response()->json([
'method' => 'Bulk store a setting',
'data' => $settings,
], 200);
}
/**
* Returns instance config
*
* @return array
*/
public function config()
{

View File

@@ -13,6 +13,12 @@ use Illuminate\Support\Facades\Validator;
class SpeedtestController extends Controller
{
/**
* Returns paginated list of speedtests
*
* @return Response
*/
public function index()
{
$data = Speedtest::orderBy('created_at', 'desc')
@@ -24,6 +30,12 @@ class SpeedtestController extends Controller
], 200);
}
/**
* Returns speedtest going back 'x' days
*
* @param int $days
* @return void
*/
public function time($days)
{
$rule = [
@@ -50,6 +62,11 @@ class SpeedtestController extends Controller
], 200);
}
/**
* Return latest speedtest
*
* @return Response
*/
public function latest()
{
$data = SpeedtestHelper::latest();
@@ -73,6 +90,11 @@ class SpeedtestController extends Controller
}
}
/**
* Queue a new speedtest
*
* @return Response
*/
public function run()
{
try {

View File

@@ -8,6 +8,12 @@ use Illuminate\Http\Request;
class UpdateController extends Controller
{
/**
* Check for new update
*
* @return Response
*/
public function checkForUpdate()
{
return response()->json([
@@ -16,6 +22,11 @@ class UpdateController extends Controller
], 200);
}
/**
* Download new update
*
* @return Response
*/
public function downloadUpdate()
{
$dl = Updater::downloadLatest();
@@ -33,6 +44,11 @@ class UpdateController extends Controller
}
}
/**
* Trigger update extraction
*
* @return Response
*/
public function extractUpdate()
{
$ex = Updater::extractFiles();
@@ -50,6 +66,11 @@ class UpdateController extends Controller
}
}
/**
* Trigger update file move
*
* @return Response
*/
public function moveUpdate()
{
$cp = Updater::updateFiles();
@@ -60,6 +81,11 @@ class UpdateController extends Controller
], 200);
}
/**
* Get local changelog
*
* @return Response
*/
public function changelog()
{
$url = base_path() . '/changelog.json';

View File

@@ -25,9 +25,9 @@ class SpeedtestJob implements ShouldQueue
}
/**
* Execute the job.
* Runs a speedtest
*
* @return void
* @return \App\Speedtest
*/
public function handle()
{

View File

@@ -2,12 +2,15 @@
namespace App\Listeners;
use App\Notifications\SpeedtestComplete;
use App\Helpers\SettingsHelper;
use App\Notifications\SpeedtestCompleteSlack;
use App\Notifications\SpeedtestCompleteTelegram;
use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use NotificationChannels\Telegram\TelegramChannel;
class SpeedtestCompleteListener
{
@@ -22,21 +25,33 @@ class SpeedtestCompleteListener
}
/**
* Handle the event.
* Handle what to do after speedtest completes
*
* @param object $event
* @return void
*/
public function handle($event)
{
if(env('SLACK_WEBHOOK')) {
if(SettingsHelper::get('speedtest_notifications')->value == true) {
$data = $event->speedtest;
try {
Notification::route('slack', env('SLACK_WEBHOOK'))
->notify(new SpeedtestComplete($data));
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
Log::notice($e);
if(env('SLACK_WEBHOOK')) {
try {
Notification::route('slack', env('SLACK_WEBHOOK'))
->notify(new SpeedtestCompleteSlack($data));
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
Log::notice($e);
}
}
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
try {
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
->notify(new SpeedtestCompleteTelegram($data));
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');
Log::notice($e);
}
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Listeners;
use App\Helpers\SettingsHelper;
use App\Helpers\SpeedtestHelper;
use App\Notifications\SpeedtestOverviewSlack;
use App\Notifications\SpeedtestOverviewTelegram;
use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use NotificationChannels\Telegram\TelegramChannel;
class SpeedtestOverviewListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
if(SettingsHelper::get('speedtest_overview_notification')->value == true) {
$data = SpeedtestHelper::last24Hours();
if(env('SLACK_WEBHOOK')) {
try {
Notification::route('slack', env('SLACK_WEBHOOK'))
->notify(new SpeedtestOverviewSlack($data));
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
Log::notice($e);
}
}
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
try {
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
->notify(new SpeedtestOverviewTelegram($data));
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');
Log::notice($e);
}
}
}
}
}

View File

@@ -6,8 +6,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Support\Facades\Log;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
class SpeedtestComplete extends Notification
class SpeedtestCompleteSlack extends Notification
{
use Queueable;
@@ -20,6 +23,9 @@ class SpeedtestComplete extends Notification
*/
public function __construct($speedtest)
{
$speedtest->ping = number_format((float)$speedtest->ping, 1, '.', '');
$speedtest->download = number_format((float)$speedtest->download, 1, '.', '');
$speedtest->upload = number_format((float)$speedtest->upload, 1, '.', '');
$this->speedtest = $speedtest;
}
@@ -31,9 +37,17 @@ class SpeedtestComplete extends Notification
*/
public function via($notifiable)
{
return ['slack'];
return [
'slack',
];
}
/**
* Format slack notification
*
* @param mixed $notifiable
* @return SlackMessage
*/
public function toSlack($notifiable)
{
$speedtest = $this->speedtest;
@@ -42,9 +56,9 @@ class SpeedtestComplete extends Notification
->attachment(function ($attachment) use ($speedtest) {
$attachment->title('New speedtest')
->fields([
'Ping' => number_format((float)$speedtest->ping, 1, '.', '') . ' ms',
'Download' => number_format((float)$speedtest->download, 1, '.', '') . ' Mbit/s',
'Upload' => number_format((float)$speedtest->upload, 1, '.', '') . ' Mbit/s',
'Ping' => $speedtest->ping . ' ms',
'Download' => $speedtest->download . ' Mbit/s',
'Upload' => $speedtest->upload . ' Mbit/s',
]);
});
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
class SpeedtestCompleteTelegram extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($speedtest)
{
$speedtest->ping = number_format((float)$speedtest->ping, 1, '.', '');
$speedtest->download = number_format((float)$speedtest->download, 1, '.', '');
$speedtest->upload = number_format((float)$speedtest->upload, 1, '.', '');
$this->speedtest = $speedtest;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
TelegramChannel::class
];
}
/**
* Format tekegram notification
*
* @param mixed $notifiable
* @return TelegramMessage
*/
public function toTelegram($notifiable)
{
$speedtest = $this->speedtest;
$msg = "*New Speedtest*
Ping: *$speedtest->ping*
Download: *$speedtest->download*
Upload: *$speedtest->upload*";
return TelegramMessage::create()
->to(env('TELEGRAM_CHAT_ID'))
->content($msg)
->options(['parse_mode' => 'Markdown']);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class SpeedtestOverviewSlack extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($data)
{
$data['ping'] = number_format((float)$data['ping'], 1, '.', '');
$data['download'] = number_format((float)$data['download'], 1, '.', '');
$data['upload'] = number_format((float)$data['upload'], 1, '.', '');
$this->data = $data;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
'slack'
];
}
/**
* Format slack notification
*
* @param mixed $notifiable
* @return SlackMessage
*/
public function toSlack($notifiable)
{
$data = $this->data;
return (new SlackMessage)
->warning()
->attachment(function ($attachment) use ($data) {
$attachment->title('Speedtest Daily Overview')
->fields([
'Average ping' => $data['ping'] . ' ms',
'Average download' => $data['download'] . ' Mbit/s',
'Average upload' => $data['upload'] . ' Mbit/s',
]);
});
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
class SpeedtestOverviewTelegram extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($data)
{
$data['ping'] = number_format((float)$data['ping'], 1, '.', '');
$data['download'] = number_format((float)$data['download'], 1, '.', '');
$data['upload'] = number_format((float)$data['upload'], 1, '.', '');
$this->data = $data;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
TelegramChannel::class
];
}
/**
* Format tekegram notification
*
* @param mixed $notifiable
* @return TelegramMessage
*/
public function toTelegram($notifiable)
{
$data = $this->data;
$msg = "*Speedtest Daily Overview*
Average ping: *".$data["ping"]."*
Average download: *".$data["download"]."*
Average upload: *".$data["upload"]."*";
return TelegramMessage::create()
->to(env('TELEGRAM_CHAT_ID'))
->content($msg)
->options(['parse_mode' => 'Markdown']);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -3,7 +3,9 @@
namespace App\Providers;
use App\Events\SpeedtestCompleteEvent;
use App\Events\SpeedtestOverviewEvent;
use App\Listeners\SpeedtestCompleteListener;
use App\Listeners\SpeedtestOverviewListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -23,6 +25,9 @@ class EventServiceProvider extends ServiceProvider
SpeedtestCompleteEvent::class => [
SpeedtestCompleteListener::class,
],
SpeedtestOverviewEvent::class => [
SpeedtestOverviewListener::class
],
];
/**

View File

@@ -18,7 +18,7 @@ class Cron implements Rule
}
/**
* Determine if the validation rule passes.
* Determine if the value is a valid CRON expression
*
* @param string $attribute
* @param mixed $value

View File

@@ -12,7 +12,14 @@ class Speedtest extends Model
* @var array
*/
protected $fillable = [
'ping', 'download', 'upload', 'created_at'
'ping',
'download',
'upload',
'created_at',
'server_id',
'server_name',
'server_host',
'url',
];
protected $table = 'speedtests';

View File

@@ -75,6 +75,11 @@ class User extends Authenticatable implements JWTSubject
return $this->hasOne('\App\Auth\EmailVerification');
}
/**
* Returns a user's login sessions
*
* @return array
*/
public function sessions()
{
return $this->hasMany('\App\Auth\LoginSession');

View File

@@ -1,4 +1,66 @@
{
"1.7.1": [
{
"description": "Updated dependencies",
"link": ""
}
],
"1.7.0": [
{
"description": "Added notification toggles",
"link": ""
},
{
"description": "Added daily overview notification",
"link": ""
}
],
"1.6.1": [
{
"description": "Added cli option to get version number",
"link": ""
}
],
"1.6.0": [
{
"description": "Added telegram notifications",
"link": ""
}
],
"1.5.6": [
{
"description": "Auto-update all tests table",
"link": ""
}
],
"1.5.5": [
{
"description": "Store host/id of speedtest.net server",
"link": ""
}
],
"1.5.4": [
{
"description": "Updated dependencies",
"link": ""
}
],
"1.5.3": [
{
"description": "Changed speedtest client",
"link": ""
}
],
"1.5.2": [
{
"description": "Updated dependencies",
"link": ""
},
{
"description": "Added CSV backup/restore formats",
"link": ""
}
],
"1.5.1": [
{
"description": "Updated PHP dependencies",

View File

@@ -9,10 +9,12 @@
"license": "MIT",
"require": {
"php": "^7.2.5",
"doctrine/dbal": "^2.10",
"dragonmantank/cron-expression": "^2",
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3",
"laravel-notification-channels/telegram": "^0.4.0",
"laravel/framework": "^7.0",
"laravel/slack-notification-channel": "^2.0",
"laravel/tinker": "^2.0",

1353
conf/site/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -162,6 +162,8 @@ return [
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
NotificationChannels\Telegram\TelegramServiceProvider::class,
/*
* Package Service Providers...
*/

View File

@@ -30,4 +30,8 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'telegram-bot-api' => [
'token' => env('TELEGRAM_BOT_TOKEN', 'YOUR BOT TOKEN HERE')
],
];

View File

@@ -7,7 +7,7 @@ return [
|--------------------------------------------------------------------------
*/
'version' => '1.5.1',
'version' => '1.7.1',
/*
|--------------------------------------------------------------------------

View File

@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateSpeedtestsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('speedtests', function($table) {
$table->integer('server_id')->nullable();
$table->string('server_name')->nullable();
$table->string('server_host')->nullable();
$table->string('url')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('speedtests', function($table) {
$table->dropColumn('server_id');
});
Schema::table('speedtests', function($table) {
$table->dropColumn('server_name');
});
Schema::table('speedtests', function($table) {
$table->dropColumn('server_host');
});
Schema::table('speedtests', function($table) {
$table->dropColumn('url');
});
}
}

View File

@@ -0,0 +1,49 @@
<?php
use App\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNotificationsSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::create([
'name' => 'speedtest_notifications',
'value' => true,
'description' => 'Enable notifications for every speedtest that runs'
]);
Setting::create([
'name' => 'speedtest_overview_notification',
'value' => true,
'description' => 'Enable a daily notification with average values for the last 24 hours.'
]);
Setting::create([
'name' => 'speedtest_overview_time',
'value' => '12',
'description' => 'The hour (24-hour format) that the daily overview notification will be sent.'
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Setting::whereIn('name', [
'speedtest_notifications',
'speedtest_overview_notification',
'speedtest_overview_time',
])->delete();
}
}

Binary file not shown.

View File

@@ -0,0 +1,2 @@
1445738505eea9ecf1c6316bfa00d5e4f8e7b9bc {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"d0eb7143fab24c6a8eee\"}","integrity":"sha512-40raUk8AxZUpIpiQbdzRMGbr22HTLk5dvXGyq1tdXpYIVKmGqkTOHkp0805rvRV22VMPOzC523f+54iOm7uPFA==","time":1593035535440,"size":2530582}

View File

@@ -0,0 +1,2 @@
29e8e827f643e83dcbf4861045e8f6b33ec0ce43 {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"f37a73d0147609e4c181\"}","integrity":"sha512-7gt9YzGsmPp4KCLTRbXCAtGpbw8byW3G7dJlbOdGu8kKdGXagWWA4TZMeoyDDkL/r5lwEHT78NrvSLGKhS4/oQ==","time":1592674255184,"size":2518438}

View File

@@ -0,0 +1,2 @@
a70bcd8267a35b2bb95c0548579df554ef7a645d {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"63b9647686cac4d205cc\"}","integrity":"sha512-1IbzUCDiIryjYlsnM/Nymyc1ULFmxGdkj5Y4lAhsQlL7REc11CYoxIyiePZZoy+IbCyfrw8qVEQREMAU69UNXQ==","time":1592768500765,"size":2530607}

View File

@@ -0,0 +1,2 @@
4b1f5b6ad2f962e87fca86cc9013b53bc05faf55 {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"5d08c79e575268399f0e\"}","integrity":"sha512-NTa2Ah7hMH6QgAkijNHLzZkPF297n/wc9a82tnPsnOzSL4MZjjMtQVPoyNhsrHetNvf7HqBQyhqRXZ6SP+vd3w==","time":1592679656772,"size":2519155}

View File

@@ -0,0 +1,2 @@
8a39a57b3bb26f1d4342484d21be25efafbc291e {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"8e1727faad414ed63f5a\"}","integrity":"sha512-U4MQfP0Ychbi+DznMhKoxR1keBIoK8cnREMeuDxrzJbrTAoDzmS38xlBXfTuALGPEWQp61nXaCClikyg8usYMg==","time":1592414990817,"size":2511848}

17
conf/site/node_modules/csv-file-validator/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,17 @@
language: node_js
node_js:
- stable
branches:
only:
- master
install:
- npm install
script:
- npm run test
after_success:
- npm run report-coverage

View File

@@ -0,0 +1,26 @@
# Contributing
✨ Thank you for contributing ✨
This is just guideliness.
Please feel free to contribute by submitting PR's for improvements to code snippets, explanations, etc.
## Submitting an issue
Found a problem? Have an enhancement?
First of all see if your issue or idea has already been [reported](https://github.com/shystruk/csv-file-validator/issues).
If do not, open a [new one](https://github.com/shystruk/csv-file-validator/issues/new).
## Submitting a pull request
- Fork this repository
- Clone fork `git clone ...`
- Navigate to the cloned directory
- Crate a new branch for the feature `git checkout -b new-feature`
- Make changes
- Commit changes `git commit -am 'What is feature about? :)'`
- Push to the branch `git push origin new-feature`
- Submit a PR

21
conf/site/node_modules/csv-file-validator/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Vasyl Stokolosa <v.stokol@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

167
conf/site/node_modules/csv-file-validator/README.md generated vendored Normal file
View File

@@ -0,0 +1,167 @@
# CSV File Validator [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?hashtags=javascript&original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&text=Validation%20of%20CSV%20file%20against%20user%20defined%20schema%20(returns%20back%20object%20with%20data%20and%20invalid%20messages)&tw_p=tweetbutton&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fcsv-file-validator&via=shystrukk) #
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php)
[![codecov](https://codecov.io/gh/shystruk/csv-file-validator/branch/master/graph/badge.svg)](https://codecov.io/gh/shystruk/csv-file-validator)
[![Build Status](https://travis-ci.org/shystruk/csv-file-validator.svg?branch=master)](https://travis-ci.org/shystruk/csv-file-validator)
[![Known Vulnerabilities](https://snyk.io/test/github/shystruk/csv-file-validator/badge.svg?targetFile=package.json)](https://snyk.io/test/github/shystruk/csv-file-validator?targetFile=package.json)
[![npm version](https://badge.fury.io/js/csv-file-validator.svg)](https://badge.fury.io/js/csv-file-validator)
Validation of CSV file against user defined schema (returns back object with data and invalid messages)
## Getting csv-file-validator ##
#### npm
`npm install --save csv-file-validator`
#### yarn
`yarn add csv-file-validator --save`
## Example ##
```javascript
import CSVFileValidator from 'csv-file-validator'
CSVFileValidator(file, config)
.then(csvData => {
csvData.data // Array of objects from file
csvData.inValidMessages // Array of error messages
})
.catch(err => {})
```
Please see **Demo** for more details **/demo/index.html**
![](demo/demo.png)
## API ##
### CSVFileValidator(file, config) ###
returns the Promise
## file ##
Type: `File` <br>
.csv file
## config ##
Type: `Object` <br>
Config object should contain **headers** array, array of row header (title) objects
```javascript
const config = {
headers: []
}
```
### name
Type: `String` <br>
name of the row header (title)
### inputName
Type: `String` <br>
key name which will be return with value in a column
### optional
Type: `Boolean` <br>
Makes column optional. If true column value will be return
### headerError
Type: `Function` <br>
If a header name is omitted or is not the same as in config *name* headerError function will be called with arguments
**headerName**
### required
Type: `Boolean` <br>
If required is true than a column value will be checked if it is not empty
### requiredError
Type: `Function` <br>
If value is empty requiredError function will be called with arguments
**headerName, rowNumber, columnNumber**
### unique
Type: `Boolean` <br>
If it is true all header (title) column values will be checked for uniqueness
### uniqueError
Type: `Function` <br>
If one of the header value is not unique uniqueError function will be called with argument **headerName**
### validate
Type: `Function` <br>
Validate column value. As an argument column value will be passed
For e.g.
```javascript
function(email) {
return isEmailValid(email)
}
```
### validateError
Type: `Function` <br>
If validate returns false validateError function will be called with arguments **headerName, rowNumber, columnNumber**
### isArray
Type: `Boolean` <br>
If column contains list of values separated by comma in return object it will be as an array
#### Config example ####
```javascript
const config = {
headers: [
{
name: 'First Name',
inputName: 'firstName',
required: true,
requiredError: function (headerName, rowNumber, columnNumber) {
return `${headerName} is required in the ${rowNumber} row / ${columnNumber} column`
}
},
{
name: 'Last Name',
inputName: 'lastName',
required: false
},
{
name: 'Email',
inputName: 'email',
unique: true,
uniqueError: function (headerName) {
return `${headerName} is not unique`
},
validate: function(email) {
return isEmailValid(email)
},
validateError: function (headerName, rowNumber, columnNumber) {
return `${headerName} is not valid in the ${rowNumber} row / ${columnNumber} column`
}
},
{
name: 'Roles',
inputName: 'roles',
isArray: true
},
{
name: 'Country',
inputName: 'country',
optional: true
}
]
}
```
## Contributing
Any contributions you make **are greatly appreciated**.
Please read the [Contributions Guidelines](CONTRIBUTING.md) before submitting a PR.
## License
MIT © [Vasyl Stokolosa](https://about.me/shystruk)

View File

@@ -0,0 +1,4 @@
First Name;Last Name;Email;Password;Roles
Vasyl;Stokolosa;api01@test.com;123;admin, manager
Vasyl_2;Stokolosa_2;api01@test.com;123123123;admin, manager, user
;Stokolosa_2;api@test;123123123;admin, manager, user
1 First Name Last Name Email Password Roles
2 Vasyl Stokolosa api01@test.com 123 admin, manager
3 Vasyl_2 Stokolosa_2 api01@test.com 123123123 admin, manager, user
4 Stokolosa_2 api@test 123123123 admin, manager, user

BIN
conf/site/node_modules/csv-file-validator/demo/demo.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
<html>
<head>
<title>CSV File Validator Demo</title>
<style>
.red {
color: red;
font-size: 16px;
margin: 20px;
}
</style>
</head>
<body>
<input type="file" accept=".csv" id="file" />
<div id="invalidMessages"></div>
<script src="dist/bundle.js"></script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
import CSVFileValidator from '../src/csv-file-validator'
const requiredError = (headerName, rowNumber, columnNumber) => {
return `<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
}
const validateError = (headerName, rowNumber, columnNumber) => {
return `<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
}
const uniqueError = (headerName) => {
return `<div class="red">${headerName} is not unique</div>`
}
const isEmailValid = function (email) {
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
return reqExp.test(email)
}
const isPasswordValid = function (password) {
return password.length >= 4
}
const CSVConfig = {
headers: [
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true }
]
}
document.getElementById('file').onchange = function(event) {
CSVFileValidator(event.target.files[0], CSVConfig)
.then(csvData => {
csvData.inValidMessages.forEach(message => {
document.getElementById('invalidMessages').insertAdjacentHTML('beforeend', message)
})
console.log(csvData.inValidMessages)
console.log(csvData.data)
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": {
"name": "Vasyl Stokolosa",
"email": "v.stokol@gmail.com",
"url": "https://github.com/shystruk"
},
"license": "MIT"
}

View File

@@ -0,0 +1,9 @@
const path = require('path');
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

75
conf/site/node_modules/csv-file-validator/package.json generated vendored Normal file
View File

@@ -0,0 +1,75 @@
{
"_args": [
[
"csv-file-validator@1.8.0",
"/home/henry/Documents/git/Speedtest-tracker-docker/conf/site"
]
],
"_from": "csv-file-validator@1.8.0",
"_id": "csv-file-validator@1.8.0",
"_inBundle": false,
"_integrity": "sha512-+/wdJxbe9zk1KJv7GC5aCVOVrg10W7xWIypILuQsJ3ocegF/YueTarb8Dqg1snEfkPmh2aCjbhVXnu1gM3RRIA==",
"_location": "/csv-file-validator",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "csv-file-validator@1.8.0",
"name": "csv-file-validator",
"escapedName": "csv-file-validator",
"rawSpec": "1.8.0",
"saveSpec": null,
"fetchSpec": "1.8.0"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/csv-file-validator/-/csv-file-validator-1.8.0.tgz",
"_spec": "1.8.0",
"_where": "/home/henry/Documents/git/Speedtest-tracker-docker/conf/site",
"author": {
"name": "Vasyl Stokolosa",
"email": "v.stokol@gmail.com",
"url": "https://github.com/shystruk"
},
"bugs": {
"url": "https://github.com/shystruk/csv-file-validator/issues"
},
"dependencies": {
"famulus": "2.1.2",
"lodash": "4.17.15",
"papaparse": "^5.2.0"
},
"description": "Validation of CSV file against user defined schema (returns back object with data and invalid messages)",
"devDependencies": {
"ava": "^0.25.0",
"codecov.io": "^0.1.6",
"nyc": "^11.4.1"
},
"directories": {
"src": "src"
},
"homepage": "https://github.com/shystruk/csv-file-validator#readme",
"keywords": [
"csv parser",
"parser",
"validator",
"csv validator",
"csv file validator",
"reviewer",
"csv reviewer"
],
"license": "MIT",
"main": "./src/csv-file-validator.js",
"name": "csv-file-validator",
"repository": {
"type": "git",
"url": "git+https://github.com/shystruk/csv-file-validator.git"
},
"scripts": {
"coverage": "nyc report --reporter=lcov",
"report-coverage": "cat ./coverage/lcov.info | codecov",
"test": "nyc ava --browser && npm run coverage"
},
"version": "1.8.0"
}

View File

@@ -0,0 +1,140 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? module.exports = factory(require('papaparse'), require('lodash/uniqBy'), require('lodash/isFunction'), require('famulus/isValuesUnique'))
: typeof define === 'function' && define.amd
? define(['papaparse', 'lodash/uniqBy', 'lodash/isFunction', 'famulus/isValuesUnique'], factory)
: (global.myBundle = factory(global.Papa,global._uniqBy,global._isFunction, global.isValuesUnique));
}(this, (function (Papa, _uniqBy, _isFunction, isValuesUnique) {
'use strict';
Papa = Papa && Papa.hasOwnProperty('default') ? Papa['default'] : Papa;
isValuesUnique = isValuesUnique && isValuesUnique.hasOwnProperty('default') ? isValuesUnique['default'] : isValuesUnique;
_uniqBy = _uniqBy && _uniqBy.hasOwnProperty('default') ? _uniqBy['default'] : _uniqBy;
_isFunction = _isFunction && _isFunction.hasOwnProperty('default') ? _isFunction['default'] : _isFunction;
/**
* @param {File} csvFile
* @param {Object} config
*/
function CSVFileValidator(csvFile, config) {
return new Promise(function(resolve, reject) {
Papa.parse(csvFile, {
complete: function(results) {
resolve(_prepareDataAndValidateFile(results.data, config));
},
error: function(error, file) {
reject({ error: error, file: file });
}
});
})
}
/**
* @param {Array} csvData
* @param {Object} config
* @private
*/
function _prepareDataAndValidateFile(csvData, config) {
const file = {
inValidMessages: [],
data: []
};
csvData.forEach(function(row, rowIndex) {
const columnData = {};
const headers = [];
for (let i = 0; i < config.headers.length; i++) {
const data = config.headers[i];
if (!data.optional) {
headers.push(data);
}
}
if (row.length < headers.length) {
return;
}
row.forEach(function(columnValue, columnIndex) {
const valueConfig = config.headers[columnIndex];
if (!valueConfig) {
return;
}
// header validation
if (rowIndex === 0) {
if (valueConfig.name !== columnValue) {
file.inValidMessages.push(
_isFunction(valueConfig.headerError)
? valueConfig.headerError(columnValue)
: 'Header name ' + columnValue + ' is not correct or missing'
);
}
return;
}
if (valueConfig.required && !columnValue.length) {
file.inValidMessages.push(
_isFunction(valueConfig.requiredError)
? valueConfig.requiredError(valueConfig.name, rowIndex + 1, columnIndex + 1)
: String(valueConfig.name + ' is required in the ' + (rowIndex + 1) + ' row / ' + (columnIndex + 1) + ' column')
);
} else if (valueConfig.validate && !valueConfig.validate(columnValue)) {
file.inValidMessages.push(
_isFunction(valueConfig.validateError)
? valueConfig.validateError(valueConfig.name, rowIndex + 1, columnIndex + 1)
: String(valueConfig.name + ' is not valid in the ' + (rowIndex + 1) + ' row / ' + (columnIndex + 1) + ' column')
);
}
if (valueConfig.optional) {
columnData[valueConfig.inputName] = columnValue;
}
if (valueConfig.isArray) {
columnData[valueConfig.inputName] = columnValue.split(',').map(function(value) {
return value.trim();
});
} else {
columnData[valueConfig.inputName] = columnValue;
}
});
file.data.push(columnData);
});
_checkUniqueFields(file, config);
return file;
}
/**
* @param {Object} file
* @param {Object} config
* @private
*/
function _checkUniqueFields(file, config) {
if (!file.data.length) {
return;
}
config.headers
.filter(function(header) {
return header.unique
})
.forEach(function(header) {
if (!isValuesUnique(file.data, header.inputName)) {
file.inValidMessages.push(
_isFunction(header.uniqueError)
? header.uniqueError(header.name)
: String(header.name + ' is not unique')
);
}
});
};
return CSVFileValidator;
})));

71
conf/site/node_modules/csv-file-validator/test.js generated vendored Normal file
View File

@@ -0,0 +1,71 @@
import test from 'ava';
import CSVFileValidator from './src/csv-file-validator';
const CSVInvalidFile = `
Vasyl;Stokolosa;v.stokol@gmail.com;123;admin,manager\n
Vasyl_2;"";v.stokol@gmail.com;123123123;user
`;
const CSVValidFile = `
Vasyl;Stokolosa;v.stokol@gmail.com;123123;admin,manager\n
Vasyl;Stokolosa;fake@test.com;123123123;user;Ukraine
`;
const requiredError = (headerName, rowNumber, columnNumber) => (
`<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
)
const validateError = (headerName, rowNumber, columnNumber) => (
`<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
)
const isEmailValid = (email) => {
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
return reqExp.test(email)
}
const isPasswordValid = (password) => (password.length >= 4)
const uniqueError = (headerName) => (`<div class="red">${headerName} is not unique</div>`)
const CSVConfig = {
headers: [
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true },
{ name: 'Country', inputName: 'country', optional: true }
]
}
test('module should be a function', t => {
t.is(typeof CSVFileValidator, 'function');
});
test('should return an object with empty inValidMessages/data keys', async t => {
const csvData = await CSVFileValidator('', {});
t.is(typeof csvData, 'object');
t.deepEqual(csvData.inValidMessages, []);
t.deepEqual(csvData.data, []);
});
test('should validate .csv file and return invalid messages with data', async t => {
const csvData = await CSVFileValidator(CSVInvalidFile, CSVConfig);
t.is(csvData.inValidMessages.length, 3);
t.is(csvData.data.length, 2);
});
test('should validate .csv file and return data, file is valid', async t => {
const csvData = await CSVFileValidator(CSVValidFile, CSVConfig);
t.is(csvData.inValidMessages.length, 0);
t.is(csvData.data.length, 2);
});
test('should return optional column', async t => {
const csvData = await CSVFileValidator(CSVValidFile, CSVConfig);
t.is(csvData.data[1].country, 'Ukraine');
});

77
conf/site/node_modules/famulus/.internal/dates.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
var MILLISECONDS_IN_DAY = 86400000;
var MILLISECONDS_IN_HOUR = 3600000;
var MILLISECONDS_IN_MINUTE = 60000;
var HOURS_IN_DAY = 24;
var MINUTES_IN_DAY = 1440;
var MINUTES_IN_HOUR = 60;
/**
* @param {Number} milliseconds
* @return {Number}
* @private
*/
function _getDaysDiff(milliseconds) {
return Math.abs(Math.floor(milliseconds / MILLISECONDS_IN_DAY));
}
/**
* @param {Number} milliseconds
* @return {Number}
* @private
*/
function _getHoursDiff(milliseconds) {
return Math.abs(Math.floor((milliseconds % MILLISECONDS_IN_DAY) / MILLISECONDS_IN_HOUR));
}
/**
* @param {Number} milliseconds
* @return {Number}
* @private
*/
function _getMinutesDiff(milliseconds) {
return Math.abs(Math.round(((milliseconds % MILLISECONDS_IN_DAY) % MILLISECONDS_IN_HOUR) / MILLISECONDS_IN_MINUTE));
}
/**
* @param {Number} milliseconds
* @return {Number}
* @private
*/
function _differenceInHours(milliseconds) {
var days = _getDaysDiff(milliseconds);
if (days !== 0) {
return _getHoursDiff(milliseconds) + (days * HOURS_IN_DAY);
}
return _getHoursDiff(milliseconds);
}
/**
* @param {Number} milliseconds
* @return {Number}
* @private
*/
function _differenceInMinutes(milliseconds) {
var days = _getDaysDiff(milliseconds),
hours = _getHoursDiff(milliseconds);
if (days !== 0) {
days = days * MINUTES_IN_DAY;
}
if (hours !== 0) {
hours = hours * MINUTES_IN_HOUR;
}
return _getMinutesDiff(milliseconds) + days + hours;
}
module.exports = {
_getDaysDiff: _getDaysDiff,
_getHoursDiff: _getHoursDiff,
_getMinutesDiff: _getMinutesDiff,
_differenceInHours: _differenceInHours,
_differenceInMinutes: _differenceInMinutes
};

17
conf/site/node_modules/famulus/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,17 @@
language: node_js
node_js:
- stable
branches:
only:
- master
install:
- npm install
script:
- npm run test
after_success:
- npm run report-coverage

9
conf/site/node_modules/famulus/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
## 2.1.0 (July 9, 2018)
* Added [objectInterface](docs/objectInterface.md) (in [#2](https://github.com/shystruk/famulus/pull/2))
## 2.0.0 (June 26, 2018)
* Added [dateDifferenceFromNow](docs/dateDifferenceFromNow.md) (in [#1](https://github.com/shystruk/famulus/pull/1))
* [dateDifference](docs/dateDifference.md) compares two dates (in [#1](https://github.com/shystruk/famulus/pull/1))

39
conf/site/node_modules/famulus/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,39 @@
# Contributing
✨ Thank you for contributing ✨
Please feel free to contribute by submitting PR's with your own helper, improvement to code snippets, explanations, etc.
## Submitting an issue
Found a problem? Have an enhancement?
First of all see if your issue or idea has already been [reported](https://github.com/shystruk/famulus/issues).
If do not, open a [new one](https://github.com/shystruk/famulus/issues/new).
## Add your own helper
- Please take for example helper **isValuesUnique.js**
- Create *[helper_name].js* file in the root folder
- In docs folder create *[helper_name].md* file and fill in data keeping the structure
- In tests folder create *[helper_name].test.js* file and cover the helper by unit tests and much as possible
- In famulus.js file import and export already created helper
- In documentation section at README.md file add the helper to a specific category. If the category does not exist add the new one
- Before submitting a PR please check *Submitting a pull request* section below
## Submitting a pull request
- Fork this repository
- Clone fork `git clone ...`
- Navigate to the cloned directory
- Install all dependencies `npm install`
- Crate a new branch for the feature `git checkout -b new-feature`
- Make changes
- Run tests `npm run test`
- Commit changes `git commit -am 'What is feature about? :)'`
- Push to the branch `git push origin new-feature`
- Submit a PR

21
conf/site/node_modules/famulus/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Vasyl Stokolosa <v.stokol@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

56
conf/site/node_modules/famulus/README.md generated vendored Normal file
View File

@@ -0,0 +1,56 @@
# famulus [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?hashtags=javascript%20%23helpers%20%23utilities&original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&text=JavaScript%20library%20that%20provides%20a%20useful%20functional%20programming%20helpers.%20Do%20not%20wait%2C%20add%20your%20own%20%F0%9F%94%A7%F0%9F%92%AA&tw_p=tweetbutton&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Ffamulus&via=shystrukk) #
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) [![codecov](https://codecov.io/gh/shystruk/famulus/branch/master/graph/badge.svg)](https://codecov.io/gh/shystruk/famulus) [![Build Status](https://travis-ci.org/shystruk/famulus.svg?branch=master)](https://travis-ci.org/shystruk/famulus)
[![Known Vulnerabilities](https://snyk.io/test/github/shystruk/famulus/badge.svg?targetFile=package.json)](https://snyk.io/test/github/shystruk/famulus?targetFile=package.json)
[![npm version](https://badge.fury.io/js/famulus.svg)](https://badge.fury.io/js/famulus)
JavaScript library that provides a useful functional programming helpers. Do not wait, fork and add your own 🔧💪
## Installation
#### npm
`npm install --save famulus`
#### yarn
`yarn add famulus --save`
## Usage
#### ES6 module
```javascript
import famulus from 'famulus'
```
```javascript
import helper from 'famulus/helper'
```
#### Node.js
```javascript
const famulus = require('famulus')
```
```javascript
const helper = require('famulus/helper')
```
## Documentation
### String
- [substr](docs/substr.md)
### Object
- [objectInterface](docs/objectInterface.md)
### Array
- [sortAndAddFirstElement](docs/sortAndAddFirstElement.md)
- [isValuesUnique](docs/isValuesUnique.md)
### Date
- [dateDifferenceFromNow](docs/dateDifferenceFromNow.md)
- [dateDifference](docs/dateDifference.md)
## Contributing
Please read the [Contributions Guidelines](CONTRIBUTING.md) before adding your own helper or improvement to code snippets, explanations, etc.
## License
MIT © [Vasyl Stokolosa](https://about.me/shystruk)

57
conf/site/node_modules/famulus/dateDifference.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
var dates = require('./.internal/dates');
/**
* Difference between dates which are passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
*
* @customNeeds -
*
* @since 2.0.0
* @category Date
*
* @param {Date} date1 - The Date for compare
* @param {Date} date2 - The Date for compare
* @param {String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
*
* @returns {Number|Object} Returns the numeric value or object depends on passed differenceType param
*
* @example
*
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'days')
* // => 6
*
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'hours')
* // => 144
*
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'minutes')
* // => 8640
*
* famulus.dateDifference(new Date('06-26-2018'), new Date('06-20-2018'), 'milliseconds')
* // => 518400000
*
* famulus.dateDifference(new Date('06-26-2018 10:10'), new Date('06-20-2018 08:00'), 'all')
* // => {days: 6, hours: 2, minutes: 10, milliseconds: 526200000}
*/
function dateDifference(date1, date2, differenceType) {
var diffMilliseconds = Math.abs(date1 - date2);
switch(differenceType) {
case 'days':
return dates._getDaysDiff(diffMilliseconds);
case 'hours':
return dates._differenceInHours(diffMilliseconds);
case 'minutes':
return dates._differenceInMinutes(diffMilliseconds);
case 'milliseconds':
return diffMilliseconds;
default:
return {
days: dates._getDaysDiff(diffMilliseconds),
hours: dates._getHoursDiff(diffMilliseconds),
minutes: dates._getMinutesDiff(diffMilliseconds),
milliseconds: diffMilliseconds
}
}
}
module.exports = dateDifference;

View File

@@ -0,0 +1,52 @@
var dates = require('./.internal/dates');
/**
* Difference between now and date which is passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
*
* @customNeeds -
*
* @since 2.0.0
* @category Date
*
* @param {Date} date - The Date to inspect
* @param {String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
*
* @returns {Number|Object} Returns the numeric value or object depends on passed differenceType param
*
* @example
* example result for now is new Date('12-26-2017')
*
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'milliseconds')
* // => 555261242
*
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'days')
* // => 6
*
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'hours')
* // => 156
*/
function dateDifferenceFromNow(date, differenceType) {
var now = new Date(),
diffMilliseconds = Math.abs(date - now);
switch(differenceType) {
case 'days':
return dates._getDaysDiff(diffMilliseconds);
case 'hours':
return dates._differenceInHours(diffMilliseconds);
case 'minutes':
return dates._differenceInMinutes(diffMilliseconds);
case 'milliseconds':
return diffMilliseconds;
default:
return {
days: dates._getDaysDiff(diffMilliseconds),
hours: dates._getHoursDiff(diffMilliseconds),
minutes: dates._getMinutesDiff(diffMilliseconds),
milliseconds: diffMilliseconds
}
}
}
module.exports = dateDifferenceFromNow;

47
conf/site/node_modules/famulus/docs/dateDifference.md generated vendored Normal file
View File

@@ -0,0 +1,47 @@
## dateDifference(date1, date2, differenceType)
[Source](../dateDifference.js)
Difference between dates which are passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
#### Custom Needs
#### Since
2.0.0
#### Category
Date
#### Arguments
{Date} date1 - The Date for compare<br>
{Date} date2 - The Date for compare<br>
{String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
#### Returns
{Number|Object} Returns the numeric value or object depends on passed differenceType param
#### Example
```javascript
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'days')
// => 6
```
```javascript
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'hours')
// => 144
```
```javascript
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'minutes')
// => 8640
```
```javascript
dateDifference(new Date('06-26-2018'), new Date('06-20-2018'), 'milliseconds')
// => 518400000
```
```javascript
dateDifference(new Date('06-26-2018 10:10'), new Date('06-20-2018 08:00'), 'all')
// => {days: 6, hours: 2, minutes: 10, milliseconds: 526200000}
```

View File

@@ -0,0 +1,47 @@
## dateDifferenceFromNow(date, differenceType)
[Source](../dateDifferenceFromNow.js)
Difference between now and date which is passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
#### Custom Needs
#### Since
2.0.0
#### Category
Date
#### Arguments
{Date} date - The Date to inspect<br>
{String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
#### Returns
{Number|Object} Returns the numeric value or object depends on passed differenceType param
#### Example
Example result for now is Date('12-26-2017')
```javascript
dateDifferenceFromNow(new Date('12-20-2017'), 'days')
// => 6
```
```javascript
dateDifferenceFromNow(new Date('12-20-2017'), 'hours')
// => 156
```
```javascript
dateDifferenceFromNow(new Date('12-20-2017'), 'minutes')
// => 9381
```
```javascript
dateDifferenceFromNow(new Date('12-20-2017'), 'milliseconds')
// => 555261242
```
```javascript
dateDifferenceFromNow(new Date('12-20-2017'), 'all')
// => {days: 6, hours: 12, minutes: 30, milliseconds: 563406381}
```

33
conf/site/node_modules/famulus/docs/isValuesUnique.md generated vendored Normal file
View File

@@ -0,0 +1,33 @@
## isValuesUnique(array, keyName)
[Source](../isValuesUnique.js)
Checking if values are unique
#### Custom Needs
Having an array of objects and checking if values are unqiue by object key
#### Since
1.3.0
#### Category
Array
#### Arguments
{Array} array - The array of objects<br>
{String} keyName - Name of the object property from an array in which unique will be checking<br>
#### Returns
{Boolean} Returns true if values are unique and false if not
#### Example
Unique emails
```javascript
isValuesUnique([{email: 'api@test.com'}, {email: 'api@test.com'}], 'email');
// => false
```
Emails are not unique
```javascript
isValuesUnique([{email: 'api@test.com'}, {email: 'api_1@test.com'}], 'email');
// => true
```

35
conf/site/node_modules/famulus/docs/objectInterface.md generated vendored Normal file
View File

@@ -0,0 +1,35 @@
## objectInterface(config)(object)
[Source](../objectInterface.js)
Interface for building an object by configuration
#### Custom Needs
Have an interface for building an object based on configuration
#### Since
2.1.0
#### Category
Object
#### Arguments
{Array} config - Keys with configuration<br>
```
[
'key/value' - "OR" if no value, set value after "/",
'key|this.firstName + " " + this.lastName' - set value from the expression after "|" which is bind to the passed object,
'key:new Date()' - set value from the expression after ":"
]
```
#### Returns
{Object}
#### Example
```javascript
var email = objectInterface(['body', 'count/1', 'sender|this.firstName + " " + this.lastName', 'isRead: false', 'created: new Date()'])
// => function
email({body: 'Hello world!', count: '', firstName: 'Vasyl', lastName: 'Stokolosa', another: ''})
// => {body: "Hello world!", count: 1, created: Mon Jul 09 2018 10:31:08, isRead: false, sender: "Vasyl Stokolosa"}
```

View File

@@ -0,0 +1,28 @@
## sortAndAddFirstElement(array, sortBy, element)
[Source](../sortAndAddFirstElement.js)
Sort an array by the name of existing property and add a first element into array
#### Custom Needs
Sort array by name and add first element<br>
For e.g. user names - [ {name: 'All'}, {name: 'Aron'}, {name: 'Bob'} ]
#### Since
1.1.0
#### Category
Array
#### Arguments
{Array} array - The array to sort and add<br>
{String} sortBy - Name of the property from an array by which array will be sorted<br>
{*Any} element - Element which is added into an array
#### Returns
{Array} Returns the new array
#### Example
```javascript
sortAndAddFirstElement([{name:'Bob'}, {name:'Aron'}], 'name', {name:'All'});
// => [ {name:'All'}, {name:'Aron'}, {name:'Bob'} ]
```

38
conf/site/node_modules/famulus/docs/substr.md generated vendored Normal file
View File

@@ -0,0 +1,38 @@
## substr(string, start, length)
[Source](../substr.js)
Extracts parts of a string, beginning at the character at the specified position, and returns the specified number of characters.
The substr() does not change the original string.
#### Custom Needs
Validate string type for preventing SyntaxError
#### Since
1.0.0
#### Category
String
#### Arguments
{String} string - The string to extract<br>
{Number} start - The position where to start the extraction. First character is at index 0<br>
{Number?} length - Optional. The number of characters to extract. If omitted, it extracts the rest of the string
#### Returns
{String} Returns extract part of a string
#### Example
```javascript
substr('Hello World!', 0, 5)
// => 'Hello'
```
```javascript
substr({}, 0, 5)
// => {}
```
```javascript
substr('Hello World!', 6)
// => 'World!'
```

13
conf/site/node_modules/famulus/famulus.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
var substr = require('./substr');
var sortAndAddFirstElement = require('./sortAndAddFirstElement');
var dateDifferenceFromNow = require('./dateDifferenceFromNow');
var isValuesUnique = require('./isValuesUnique');
var objectInterface = require('./objectInterface');
module.exports = {
substr: substr,
sortAndAddFirstElement: sortAndAddFirstElement,
dateDifferenceFromNow: dateDifferenceFromNow,
isValuesUnique: isValuesUnique,
objectInterface: objectInterface
};

26
conf/site/node_modules/famulus/isValuesUnique.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
var _uniqBy = require('lodash/uniqBy');
/**
* Checking if values are unique
*
* @customNeeds
* For e.g. [{email:'api@test.com'}, {email:'api@test.com'}] - email is not valid
*
* @since 1.3.0
* @category Array
*
* @param {Array} array - The array of objects
* @param {String} keyName - Name of the object property from an array in which unique will be checking
*
* @returns {Boolean} Returns true if values are unique and false if not
*
* @example
*
* famulus.isValuesUnique([{email:'api@test.com'}, {email:'api@test.com'}], 'email')
* // => false
*/
function isValuesUnique(array, keyName) {
return _uniqBy(array, keyName).length === array.length;
}
module.exports = isValuesUnique;

51
conf/site/node_modules/famulus/objectInterface.js generated vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* Interface for building an object by configuration
*
* @customNeeds - have an interface for building an object based on configuration
*
* @since 2.1.0
* @category Object
*
* @param {Array} config - Keys with configuration
* [
* 'key/value' - "OR" if no value, set value after "/",
* 'key|this.firstName + " " + this.lastName' - set value from the expression after "|" which is bind to the passed object,
* 'key:new Date()' - set value from the expression after ":"
* ]
*
* @returns {Function}
*
* @example
*
* var email = objectInterface(['body', 'count/1', 'sender|this.firstName + " " + this.lastName', 'isRead: false', 'created: new Date()'])
* // => function
*
* email({body: 'Hello world!', count: '', firstName: 'Vasyl', lastName: 'Stokolosa', another: ''})
* // => {body: "Hello world!", count: 1, created: Mon Jul 09 2018 10:31:08, isRead: false, sender: "Vasyl Stokolosa"}
*/
function objectInterface(config) {
return function(obj) {
var result = {};
for (var i = 0; i < config.length; i++) {
var OR, NEXT, REAL;
if ((OR = config[i].split('/')) && OR[1]) {
result[OR[0]] = obj[OR[0]] || Function('return ' + OR[1])();
}
else if ((NEXT = config[i].split('|')) && NEXT[1]) {
result[NEXT[0]] = Function('return ' + NEXT[1]).call(obj);
}
else if ((REAL = config[i].split(':')) && REAL[1]) {
result[REAL[0]] = Function('return ' + REAL[1])();
}
else {
result[config[i]] = obj[config[i]];
}
}
return result;
}
}
module.exports = objectInterface;

66
conf/site/node_modules/famulus/package.json generated vendored Normal file
View File

@@ -0,0 +1,66 @@
{
"_args": [
[
"famulus@2.1.2",
"/home/henry/Documents/git/Speedtest-tracker-docker/conf/site"
]
],
"_from": "famulus@2.1.2",
"_id": "famulus@2.1.2",
"_inBundle": false,
"_integrity": "sha512-UjfF9lOEP6IFLC/DTwUe5KbCYINbuYYJS+mivlnWyK8yqt/9WYHrJ4RihZ0pa9HVxQObu8IWroJOyyt8dXCVkw==",
"_location": "/famulus",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "famulus@2.1.2",
"name": "famulus",
"escapedName": "famulus",
"rawSpec": "2.1.2",
"saveSpec": null,
"fetchSpec": "2.1.2"
},
"_requiredBy": [
"/csv-file-validator"
],
"_resolved": "https://registry.npmjs.org/famulus/-/famulus-2.1.2.tgz",
"_spec": "2.1.2",
"_where": "/home/henry/Documents/git/Speedtest-tracker-docker/conf/site",
"author": {
"name": "Vasyl Stokolosa",
"email": "v.stokol@gmail.com",
"url": "https://github.com/shystruk"
},
"bugs": {
"url": "https://github.com/shystruk/famulus/issues"
},
"dependencies": {
"lodash": "^4.17.15"
},
"description": "JavaScript library that provides a useful functional programming helpers. Add your own.",
"devDependencies": {
"ava": "^0.24.0",
"codecov.io": "^0.1.6",
"nyc": "^11.4.1"
},
"homepage": "https://github.com/shystruk/famulus#readme",
"keywords": [
"utilities",
"helpers",
"helper"
],
"license": "MIT",
"main": "famulus.js",
"name": "famulus",
"repository": {
"type": "git",
"url": "git+https://github.com/shystruk/famulus.git"
},
"scripts": {
"coverage": "nyc report --reporter=lcov",
"report-coverage": "cat ./coverage/lcov.info | codecov",
"test": "nyc ava tests && npm run coverage"
},
"version": "2.1.2"
}

View File

@@ -0,0 +1,30 @@
var _ = require('lodash');
/**
* Sort an array by the name of existing property and add a first element into array
*
* @customNeeds Sort array by name and add first element.
* For e.g. user names - [ {name: 'All'}, {name: 'Aron'}, {name: 'Bob'} ]
*
* @since 1.1.0
* @category Array
*
* @param {Array} array - The array to sort and add
* @param {String} sortBy - Name of the property from an array by which array will be sorted
* @param {*} element - Element which is added into an array
*
* @returns {Array} Returns the new array
*
* @example
*
* famulus.sortAndAddFirstElement([{name:'Bob'}, {name:'Aron'}], 'name', {name:'All'})
* // => [ {name:'All'}, {name:'Aron'}, {name:'Bob'} ]
*/
function sortAndAddFirstElement(array, sortBy, element) {
return _(array)
.sortBy(sortBy)
.unshift(element)
.value();
}
module.exports = sortAndAddFirstElement;

Some files were not shown because too many files have changed in this diff Show More