From f8bde4345f3c00836dc300cd51aa14f3701319ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Jaussoin?=
Date: Thu, 27 Jul 2023 15:23:53 +0000
Subject: [PATCH] First basic statistics graph generation using Chart.js
---
.../Admin/ContactsListController.php | 10 +-
.../Admin/StatisticsController.php | 224 +++++++++++++++---
flexiapi/app/Libraries/StatisticsCruncher.php | 196 ---------------
flexiapi/composer.lock | 68 +++---
.../factories/StatisticsMessageFactory.php | 18 ++
...raints_and_statistics_messages_indexes.php | 45 ++++
.../seeds/StatisticsMessagesSeeder.php} | 32 ++-
flexiapi/public/css/far.css | 8 +
.../account/documentation_markdown.blade.php | 2 +-
.../views/account/register/email.blade.php | 109 +++++----
.../views/account/register/phone.blade.php | 9 +-
.../views/admin/account/create_edit.blade.php | 28 +--
.../admin/contacts_list/create_edit.blade.php | 25 +-
.../views/admin/statistics/show.blade.php | 47 ++++
.../views/admin/statistics/show_day.blade.php | 37 ---
.../admin/statistics/show_month.blade.php | 37 ---
.../admin/statistics/show_week.blade.php | 37 ---
.../api/documentation_markdown.blade.php | 14 --
.../resources/views/layouts/main.blade.php | 3 +
.../resources/views/parts/graph.blade.php | 27 +++
.../resources/views/parts/sidebar.blade.php | 2 +-
flexiapi/resources/views/parts/tabs.blade.php | 2 +-
.../views/parts/tabs/register.blade.php | 4 +-
flexiapi/routes/api.php | 4 -
flexiapi/routes/web.php | 12 +-
25 files changed, 496 insertions(+), 504 deletions(-)
delete mode 100644 flexiapi/app/Libraries/StatisticsCruncher.php
create mode 100644 flexiapi/database/factories/StatisticsMessageFactory.php
create mode 100644 flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php
rename flexiapi/{app/Http/Controllers/Api/StatisticController.php => database/seeds/StatisticsMessagesSeeder.php} (58%)
create mode 100644 flexiapi/resources/views/admin/statistics/show.blade.php
delete mode 100644 flexiapi/resources/views/admin/statistics/show_day.blade.php
delete mode 100644 flexiapi/resources/views/admin/statistics/show_month.blade.php
delete mode 100644 flexiapi/resources/views/admin/statistics/show_week.blade.php
create mode 100644 flexiapi/resources/views/parts/graph.blade.php
diff --git a/flexiapi/app/Http/Controllers/Admin/ContactsListController.php b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php
index 667fc38..d995e6a 100644
--- a/flexiapi/app/Http/Controllers/Admin/ContactsListController.php
+++ b/flexiapi/app/Http/Controllers/Admin/ContactsListController.php
@@ -21,6 +21,7 @@ namespace App\Http\Controllers\Admin;
use App\ContactsList;
use App\Http\Controllers\Controller;
+use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
class ContactsListController extends Controller
@@ -49,8 +50,7 @@ class ContactsListController extends Controller
public function store(Request $request)
{
$request->validate([
- 'title' => 'required',
- 'description' => 'required'
+ 'title' => 'required|unique:contacts_lists'
]);
$contactsList = new ContactsList;
@@ -71,8 +71,10 @@ class ContactsListController extends Controller
public function update(Request $request, int $id)
{
$request->validate([
- 'title' => 'required',
- 'description' => 'required'
+ 'title' => [
+ 'required',
+ Rule::unique('contacts_lists')->ignore($id),
+ ],
]);
$contactsList = ContactsList::findOrFail($id);
diff --git a/flexiapi/app/Http/Controllers/Admin/StatisticsController.php b/flexiapi/app/Http/Controllers/Admin/StatisticsController.php
index d81d76d..925f711 100644
--- a/flexiapi/app/Http/Controllers/Admin/StatisticsController.php
+++ b/flexiapi/app/Http/Controllers/Admin/StatisticsController.php
@@ -2,52 +2,218 @@
namespace App\Http\Controllers\Admin;
+use App\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
-use App\Libraries\StatisticsCruncher;
+use App\StatisticsMessage;
+use Carbon\Carbon;
+use Carbon\CarbonInterval;
+use Carbon\CarbonPeriod;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
class StatisticsController extends Controller
{
- public function showDay(Request $request)
+ public function index(Request $request)
{
- $day = StatisticsCruncher::day();
- $maxDay = 0;
- foreach ($day as $hour) {
- if ($maxDay < $hour['all']) $maxDay = $hour['all'];
- }
-
- return view('admin.statistics.show_day', [
- 'day' => $day,
- 'max_day' => $maxDay,
+ return redirect()->route('admin.statistics.show', [
+ 'type' => 'messages'
]);
}
- public function showWeek(Request $request)
+ public function edit(Request $request)
{
- $week = StatisticsCruncher::week();
- $maxWeek = 0;
- foreach ($week as $day) {
- if ($maxWeek < $day['all']) $maxWeek = $day['all'];
- }
-
- return view('admin.statistics.show_week', [
- 'week' => $week,
- 'max_week' => $maxWeek,
+ return redirect()->route('admin.statistics.show', [
+ 'from' => $request->get('from'),
+ 'type' => $request->get('type'),
+ 'to' => $request->get('to'),
+ 'by' => $request->get('by'),
]);
}
- public function showMonth(Request $request)
+ public function show(Request $request, string $type = 'messages')
{
- $month = StatisticsCruncher::month();
- $maxMonth = 0;
- foreach ($month as $day) {
- if ($maxMonth < $day['all']) $maxMonth = $day['all'];
+ $request->validate([
+ 'from' => 'date_format:Y-m-d|before:to',
+ 'to' => 'date_format:Y-m-d|after:from',
+ 'by' => 'in:day,week,month,year',
+ ]);
+
+ $dateColumn = 'created_at';
+ $label = 'Label';
+
+ switch ($type) {
+ case 'messages':
+ $dateColumn = 'sent_at';
+ $label = 'Messages';
+ $data = StatisticsMessage::orderBy($dateColumn, 'asc');
+ break;
+
+ case 'accounts':
+ $label = 'Accounts';
+ $data = Account::orderBy($dateColumn, 'asc');
+ break;
}
- return view('admin.statistics.show_month', [
- 'month' => $month,
- 'max_month' => $maxMonth,
+ $data = $data->groupBy('moment')
+ ->orderBy('moment', 'desc')
+ ->setEagerLoads([]);
+
+ if ($request->get('to')) {
+ $data = $data->where($dateColumn, '<=', $request->get('to'));
+ }
+
+ $by = $request->get('by', 'day');
+
+ switch ($by) {
+ case 'day':
+ $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subDay()->format('Y-m-d H:i:s')))
+ ->get([
+ DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d %H') as moment"),
+ DB::raw('COUNT(*) as "count"')
+ ]);
+ break;
+ case 'week':
+ $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subWeek()->format('Y-m-d H:i:s')))
+ ->get([
+ DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"),
+ DB::raw('COUNT(*) as "count"')
+ ]);
+ break;
+ case 'month':
+ $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subMonth()->format('Y-m-d H:i:s')))
+ ->get([
+ DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"),
+ DB::raw('COUNT(*) as "count"')
+ ]);
+ break;
+ case 'year':
+ $data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subYear()->format('Y-m-d H:i:s')))
+ ->get([
+ DB::raw("date_format(" . $dateColumn . ",'%Y-%m') as moment"),
+ DB::raw('COUNT(*) as "count"')
+ ]);
+ break;
+ }
+
+ $data = $data->each->setAppends([])->pluck('count', 'moment');
+
+ $data = $this->compileStatistics(
+ $by,
+ $request->get('from'),
+ $request->get('to'),
+ $data
+ );
+
+ if ($request->get('export', false)) {
+ $file = fopen('php://output', 'w');
+
+ $callback = function () use ($data, $file) {
+ foreach ($data as $key => $value) {
+ fputcsv($file, [$key, $value]);
+ }
+
+ fclose($file);
+ };
+
+ return response()->stream($callback, 200, [
+ "Content-type" => "text/csv",
+ "Content-Disposition" => "attachment; filename=export.csv",
+ "Pragma" => "no-cache",
+ "Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
+ "Expires" => "0"
+ ]);
+ }
+
+ $config = [
+ 'type' => 'bar',
+ 'data' => [
+ 'labels' => $data->keys()->toArray(),
+ 'datasets' => [[
+ 'label' => $label,
+ 'borderColor' => 'rgba(108, 122, 135, 1)',
+ 'backgroundColor' => 'rgba(108, 122, 135, 1)',
+ 'data' => $data->values()->toArray(),
+ 'order' => 1
+ ]]
+ ],
+ 'options' => [
+ 'maintainAspectRatio' => false,
+ 'spanGaps' => true,
+ 'legend' => [
+ 'position' => 'right'
+ ],
+ 'scales' => [
+ 'y' => [
+ 'stacked' => true,
+ 'title' => [
+ 'display' => true,
+ 'text' => $label
+ ]
+ ],
+ 'x' => [
+ 'stacked' => true,
+ ]
+ ],
+ 'interaction' => [
+ 'mode' => 'nearest',
+ 'axis' => 'x',
+ 'intersect' => false
+ ],
+ ]
+ ];
+
+ return view('admin.statistics.show', [
+ 'jsonConfig' => json_encode($config),
+ 'by' => $by,
+ 'type' => $type,
+ 'request' => $request
]);
}
+
+ private static function compileStatistics(string $by, $from, $to, $data): Collection
+ {
+ $stats = [];
+
+ switch ($by) {
+ case 'day':
+ $period = collect(CarbonInterval::hour()->toPeriod(
+ $from ?? Carbon::now()->subDay()->format('Y-m-d H:i:s'),
+ $to ?? Carbon::now()->format('Y-m-d H:i:s')
+ ))->map->format('Y-m-d H');
+ break;
+
+ case 'week':
+ $period = collect(CarbonPeriod::create(
+ $from ?? Carbon::now()->subWeek(),
+ $to ?? Carbon::now()
+ ))->map->format('Y-m-d');
+ break;
+
+ case 'month':
+ $period = collect(
+ CarbonPeriod::create(
+ $from ?? Carbon::now()->subMonth(),
+ $to ?? Carbon::now()
+ )
+ )->map->format('Y-m-d');
+ break;
+
+ case 'year':
+ $period = collect(
+ CarbonPeriod::create(
+ $from ?? Carbon::now()->subYear(),
+ $to ?? Carbon::now()
+ )
+ )->map->format('Y-m');
+ break;
+ }
+
+ foreach ($period as $moment) {
+ $stats[$moment] = $data[$moment] ?? 0;
+ }
+
+ return collect($stats);
+ }
}
diff --git a/flexiapi/app/Libraries/StatisticsCruncher.php b/flexiapi/app/Libraries/StatisticsCruncher.php
deleted file mode 100644
index 3e7ac09..0000000
--- a/flexiapi/app/Libraries/StatisticsCruncher.php
+++ /dev/null
@@ -1,196 +0,0 @@
-.
-*/
-
-namespace App\Libraries;
-
-use Illuminate\Support\Facades\DB;
-
-use Carbon\Carbon;
-use Carbon\CarbonPeriod;
-use Carbon\CarbonInterval;
-
-use App\Account;
-
-class StatisticsCruncher
-{
- public static function month()
- {
- $data = self::getAccountFrom(Carbon::now()->subMonth())
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliases = self::getAccountFrom(Carbon::now()->subMonth())
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataActivated = self::getAccountFrom(Carbon::now()->subMonth())
- ->where('activated', true)
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subMonth())
- ->where('activated', true)
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- return self::compileStatistics(
- collect(CarbonPeriod::create(Carbon::now()->subMonth(), Carbon::now()))->map->format('Y-m-d'),
- $data,
- $dataAliases,
- $dataActivated,
- $dataAliasesActivated
- );
- }
-
- public static function week()
- {
- $data = self::getAccountFrom(Carbon::now()->subWeek())
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliases = self::getAccountFrom(Carbon::now()->subWeek())
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataActivated = self::getAccountFrom(Carbon::now()->subWeek())
- ->where('activated', true)
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subWeek())
- ->where('activated', true)
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- return self::compileStatistics(
- collect(CarbonPeriod::create(Carbon::now()->subWeek(), Carbon::now()))->map->format('Y-m-d'),
- $data,
- $dataAliases,
- $dataActivated,
- $dataAliasesActivated
- );
- }
-
- public static function day()
- {
- $data = self::getAccountFrom(Carbon::now()->subDay())
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliases = self::getAccountFrom(Carbon::now()->subDay())
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataActivated = self::getAccountFrom(Carbon::now()->subDay())
- ->where('activated', true)
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- $dataAliasesActivated = self::getAccountFrom(Carbon::now()->subDay())
- ->where('activated', true)
- ->whereIn('id', function ($query) {
- $query->select('account_id')
- ->from('aliases');
- })
- ->get(array(
- DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
- DB::raw('COUNT(*) as "count"')
- ))->each->setAppends([])->pluck('count', 'moment');
-
- return self::compileStatistics(
- collect(CarbonInterval::hour()->toPeriod(Carbon::now()->subDay(), Carbon::now()))->map->format('Y-m-d H'),
- $data,
- $dataAliases,
- $dataActivated,
- $dataAliasesActivated
- );
- }
-
- private static function getAccountFrom($date)
- {
- return Account::where('created_at', '>=', $date)
- ->groupBy('moment')
- ->orderBy('moment', 'DESC')
- ->setEagerLoads([]);
- }
-
- private static function compileStatistics($period, $data, $dataAliases, $dataActivated, $dataAliasesActivated)
- {
- $stats = [];
-
- foreach ($period as $moment) {
- $all = $data[$moment] ?? 0;
- $aliases = $dataAliases[$moment] ?? 0;
- $activated = $dataActivated[$moment] ?? 0;
- $activatedAliases = $dataAliasesActivated[$moment] ?? 0;
-
- $stats[$moment] = [
- 'all' => $all,
- 'phone' => $aliases,
- 'email' => $all - $aliases,
- 'activated_phone' => $activatedAliases,
- 'activated_email' => $activated - $activatedAliases
- ];
- }
-
- return $stats;
- }
-}
diff --git a/flexiapi/composer.lock b/flexiapi/composer.lock
index 9546ec0..9c0ee84 100644
--- a/flexiapi/composer.lock
+++ b/flexiapi/composer.lock
@@ -399,16 +399,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.6.4",
+ "version": "3.6.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f"
+ "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
- "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/96d5a70fd91efdcec81fc46316efc5bf3da17ddf",
+ "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf",
"shasum": ""
},
"require": {
@@ -423,10 +423,10 @@
"require-dev": {
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
- "jetbrains/phpstorm-stubs": "2022.3",
- "phpstan/phpstan": "1.10.14",
+ "jetbrains/phpstorm-stubs": "2023.1",
+ "phpstan/phpstan": "1.10.21",
"phpstan/phpstan-strict-rules": "^1.5",
- "phpunit/phpunit": "9.6.7",
+ "phpunit/phpunit": "9.6.9",
"psalm/plugin-phpunit": "0.18.4",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4|^6.0",
@@ -491,7 +491,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.6.4"
+ "source": "https://github.com/doctrine/dbal/tree/3.6.5"
},
"funding": [
{
@@ -507,7 +507,7 @@
"type": "tidelift"
}
],
- "time": "2023-06-15T07:40:12+00:00"
+ "time": "2023-07-17T09:15:50+00:00"
},
{
"name": "doctrine/deprecations",
@@ -1799,16 +1799,16 @@
},
{
"name": "laravel/framework",
- "version": "v9.52.10",
+ "version": "v9.52.12",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "858add225ce88a76c43aec0e7866288321ee0ee9"
+ "reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9",
- "reference": "858add225ce88a76c43aec0e7866288321ee0ee9",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/8bfd22be79f437fa335e70692e4e91ff40ce561d",
+ "reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d",
"shasum": ""
},
"require": {
@@ -1993,20 +1993,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2023-06-27T13:25:54+00:00"
+ "time": "2023-07-26T13:20:55+00:00"
},
{
"name": "laravel/serializable-closure",
- "version": "v1.3.0",
+ "version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37"
+ "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37",
- "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902",
+ "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902",
"shasum": ""
},
"require": {
@@ -2053,7 +2053,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2023-01-30T18:31:20+00:00"
+ "time": "2023-07-14T13:56:28+00:00"
},
{
"name": "laravelcollective/html",
@@ -3434,16 +3434,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.26",
+ "version": "9.2.27",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
+ "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
- "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1",
+ "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1",
"shasum": ""
},
"require": {
@@ -3499,7 +3499,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27"
},
"funding": [
{
@@ -3507,7 +3508,7 @@
"type": "github"
}
],
- "time": "2023-03-06T12:58:08+00:00"
+ "time": "2023-07-26T13:44:30+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -9009,16 +9010,16 @@
},
{
"name": "mockery/mockery",
- "version": "1.6.3",
+ "version": "1.6.4",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
- "reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d"
+ "reference": "d1413755e26fe56a63455f7753221c86cbb88f66"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mockery/mockery/zipball/b1be135c1ba7632f0248e07ee5e6e412576a309d",
- "reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/d1413755e26fe56a63455f7753221c86cbb88f66",
+ "reference": "d1413755e26fe56a63455f7753221c86cbb88f66",
"shasum": ""
},
"require": {
@@ -9032,16 +9033,17 @@
"require-dev": {
"phpunit/phpunit": "^8.5 || ^9.3",
"psalm/plugin-phpunit": "^0.18.4",
+ "symplify/easy-coding-standard": "^11.5.0",
"vimeo/psalm": "^5.13.1"
},
"type": "library",
"autoload": {
"files": [
- "src/helpers.php",
- "src/Mockery.php"
+ "library/helpers.php",
+ "library/Mockery.php"
],
"psr-4": {
- "Mockery\\": "src/Mockery"
+ "Mockery\\": "library/Mockery"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -9089,7 +9091,7 @@
"security": "https://github.com/mockery/mockery/security/advisories",
"source": "https://github.com/mockery/mockery"
},
- "time": "2023-07-18T17:47:29+00:00"
+ "time": "2023-07-19T15:51:02+00:00"
},
{
"name": "nunomaduro/collision",
diff --git a/flexiapi/database/factories/StatisticsMessageFactory.php b/flexiapi/database/factories/StatisticsMessageFactory.php
new file mode 100644
index 0000000..3252a96
--- /dev/null
+++ b/flexiapi/database/factories/StatisticsMessageFactory.php
@@ -0,0 +1,18 @@
+ $this->faker->uuid(),
+ 'from' => $this->faker->email(),
+ 'sent_at' => $this->faker->dateTimeBetween('-1 year'),
+ 'encrypted' => false
+ ];
+ }
+}
diff --git a/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php b/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php
new file mode 100644
index 0000000..42e59d5
--- /dev/null
+++ b/flexiapi/database/migrations/2023_07_24_144940_change_contacts_lists_contraints_and_statistics_messages_indexes.php
@@ -0,0 +1,45 @@
+unique('title');
+ $table->text('description')->nullable(true)->change();
+ });
+
+ Schema::table('statistics_messages', function (Blueprint $table) {
+ $table->index('sent_at');
+ });
+
+ Schema::table('accounts', function (Blueprint $table) {
+ $table->index('created_at');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('contacts_lists', function (Blueprint $table) {
+ $table->dropUnique('contacts_lists_title_unique');
+ $table->text('description')->nullable(false)->change();
+ });
+
+ Schema::table('statistics_messages', function (Blueprint $table) {
+ $table->dropIndex('statistics_messages_sent_at_index');
+ });
+
+ Schema::table('accounts', function (Blueprint $table) {
+ $table->dropIndex('accounts_created_at_index');
+ });
+ }
+};
diff --git a/flexiapi/app/Http/Controllers/Api/StatisticController.php b/flexiapi/database/seeds/StatisticsMessagesSeeder.php
similarity index 58%
rename from flexiapi/app/Http/Controllers/Api/StatisticController.php
rename to flexiapi/database/seeds/StatisticsMessagesSeeder.php
index 0398610..617ce34 100644
--- a/flexiapi/app/Http/Controllers/Api/StatisticController.php
+++ b/flexiapi/database/seeds/StatisticsMessagesSeeder.php
@@ -1,7 +1,7 @@
.
*/
-namespace App\Http\Controllers\Api;
+namespace Database\Seeders;
-use App\Http\Controllers\Controller;
-use Illuminate\Http\Request;
+use Illuminate\Database\Seeder;
-use App\Libraries\StatisticsCruncher;
+use App\StatisticsMessage;
+use App\StatisticsMessageDevice;
+use Illuminate\Support\Facades\Schema;
-class StatisticController extends Controller
+class StatisticsMessagesSeeder extends Seeder
{
- public function month(Request $request)
+ public function run()
{
- return StatisticsCruncher::month();
- }
+ Schema::disableForeignKeyConstraints();
+ StatisticsMessageDevice::truncate();
+ StatisticsMessage::truncate();
+ Schema::enableForeignKeyConstraints();
- public function week(Request $request)
- {
- return StatisticsCruncher::week();
- }
-
- public function day(Request $request)
- {
- return StatisticsCruncher::day();
+ StatisticsMessage::factory()
+ ->count(10000)
+ ->create();
}
}
diff --git a/flexiapi/public/css/far.css b/flexiapi/public/css/far.css
index ca61078..3de339d 100644
--- a/flexiapi/public/css/far.css
+++ b/flexiapi/public/css/far.css
@@ -637,6 +637,10 @@ table tr.empty td:before {
margin: 0;
}
+.chip.selected {
+ font-weight: bold;
+}
+
/** Pagination **/
ul.pagination {
@@ -703,4 +707,8 @@ select.list_toggle {
.disabled {
opacity: 0.5;
pointer-events: none;
+}
+
+#chart {
+ min-height: 80vh;
}
\ No newline at end of file
diff --git a/flexiapi/resources/views/account/documentation_markdown.blade.php b/flexiapi/resources/views/account/documentation_markdown.blade.php
index efe674b..9ef7291 100644
--- a/flexiapi/resources/views/account/documentation_markdown.blade.php
+++ b/flexiapi/resources/views/account/documentation_markdown.blade.php
@@ -114,4 +114,4 @@ An adminisator can create, edit and delete account types. Those can be used to c
## Statistics
-The statistics panel show registrations statistics based on their type (mobile and email based registration) and their activations states.
+The statistics panel show different statistics recorder by the Account Manager, they can be explored, filtered and exported.
\ No newline at end of file
diff --git a/flexiapi/resources/views/account/register/email.blade.php b/flexiapi/resources/views/account/register/email.blade.php
index 2bebb7b..f90f377 100644
--- a/flexiapi/resources/views/account/register/email.blade.php
+++ b/flexiapi/resources/views/account/register/email.blade.php
@@ -1,74 +1,71 @@
@extends('layouts.main', ['welcome' => true])
@section('content')
+
+
+ You already have an account?
+ Login
+
-
+ account_circle Register
-
- You already have an account?
- Login
-
+ @include('parts.tabs.register')
-account_circle Register
+ {!! Form::open(['route' => 'account.store']) !!}
-@include('parts.tabs.register')
+
+ {!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
+ {!! Form::label('username', 'Username') !!}
+ @include('parts.errors', ['name' => 'username'])
+
-{!! Form::open(['route' => 'account.store']) !!}
+
+
+
-
- {!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
- {!! Form::label('username', 'Username') !!}
- @include('parts.errors', ['name' => 'username'])
-
+
+ {!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!}
+ {!! Form::label('email', 'Email') !!}
+ @include('parts.errors', ['name' => 'email'])
+
+
+ {!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!}
+ {!! Form::label('email_confirmation', 'Confirm email') !!}
+ @include('parts.errors', ['name' => 'email_confirmation'])
+
-
-
-
+
+
+
+ @include('parts.errors', ['name' => 'password'])
+
+
+
+
+ @include('parts.errors', ['name' => 'password_confirmation'])
+
-
- {!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!}
- {!! Form::label('email', 'Email') !!}
- @include('parts.errors', ['name' => 'email'])
-
-
- {!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!}
- {!! Form::label('email_confirmation', 'Confirm email') !!}
- @include('parts.errors', ['name' => 'email_confirmation'])
-
+ @if (!empty(config('app.newsletter_registration_address')))
+
+ {!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!}
+
+
+ @endif
-
- {!! Form::password('password', ['required']) !!}
- {!! Form::label('password', 'Password') !!}
- @include('parts.errors', ['name' => 'password'])
-
-
- {!! Form::password('password_confirmation', ['required']) !!}
- {!! Form::label('password_confirmation', 'Confirm password') !!}
- @include('parts.errors', ['name' => 'password_confirmation'])
-
+ @include('parts.terms')
-@if (!empty(config('app.newsletter_registration_address')))
-
- {!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!}
-
-
-@endif
+
+ {!! Form::submit('Register', ['class' => 'btn oppose']) !!}
+
-@include('parts.terms')
-
-
- {!! Form::submit('Register', ['class' => 'btn oppose']) !!}
-
-
-{!! Form::close() !!}
-
-
-
-
-
+ {!! Form::close() !!}
+
+
+
+
@endsection
@section('footer')
-Hop
-@endsection
\ No newline at end of file
+ Hop
+@endsection
diff --git a/flexiapi/resources/views/account/register/phone.blade.php b/flexiapi/resources/views/account/register/phone.blade.php
index e3dfdfe..71b4203 100644
--- a/flexiapi/resources/views/account/register/phone.blade.php
+++ b/flexiapi/resources/views/account/register/phone.blade.php
@@ -31,13 +31,14 @@
- {!! Form::password('password', ['required']) !!}
- {!! Form::label('password', 'Password') !!}
+
+
@include('parts.errors', ['name' => 'password'])
- {!! Form::password('password_confirmation', ['required']) !!}
- {!! Form::label('password_confirmation', 'Confirm password') !!}
+
+
+ @include('parts.errors', ['name' => 'password_confirmation'])
@include('parts.terms')
diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php
index 26bf984..0cce434 100644
--- a/flexiapi/resources/views/admin/account/create_edit.blade.php
+++ b/flexiapi/resources/views/admin/account/create_edit.blade.php
@@ -1,18 +1,24 @@
@extends('layouts.main')
@section('content')
-
+ Cancel
+
+
+ @endif
-
@if ($account->id)
diff --git a/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php
index 51ee613..30d6f89 100644
--- a/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php
+++ b/flexiapi/resources/views/admin/contacts_list/create_edit.blade.php
@@ -4,13 +4,16 @@
@@ -18,20 +21,19 @@
Updated on {{ $contacts_list->updated_at->format('d/m/Y') }}
@endif
-
-
@@ -79,7 +81,8 @@
@foreach ($contacts_list->contacts as $contact)
|
-
+
|
{{ $contact->identifier }} |
diff --git a/flexiapi/resources/views/admin/statistics/show.blade.php b/flexiapi/resources/views/admin/statistics/show.blade.php
new file mode 100644
index 0000000..8f1bfe6
--- /dev/null
+++ b/flexiapi/resources/views/admin/statistics/show.blade.php
@@ -0,0 +1,47 @@
+@extends('layouts.main')
+
+@section('content')
+ @include('parts.tabs', [
+ 'items' => [
+ route('admin.statistics.show', ['type' => 'messages']) => 'Messages',
+ route('admin.statistics.show', ['type' => 'accounts']) => 'Accounts',
+ ],
+ ])
+
+
+
+ @include('parts.graph')
+@endsection
diff --git a/flexiapi/resources/views/admin/statistics/show_day.blade.php b/flexiapi/resources/views/admin/statistics/show_day.blade.php
deleted file mode 100644
index 164d33b..0000000
--- a/flexiapi/resources/views/admin/statistics/show_day.blade.php
+++ /dev/null
@@ -1,37 +0,0 @@
-@extends('layouts.main')
-
-@section('breadcrumb')
-
- Statistics
-
-@endsection
-
-@section('content')
-
-
- -
- Day
-
- -
- Week
-
- -
- Month
-
-
-
-Statistics
-
-@include('admin.statistics.parts.legend')
-
-Day
-
-
- @foreach ($day as $key => $hour)
-
- @include('admin.statistics.parts.columns', ['slice' => $hour, 'max' => $max_day])
-
- @endforeach
-
-
-@endsection
\ No newline at end of file
diff --git a/flexiapi/resources/views/admin/statistics/show_month.blade.php b/flexiapi/resources/views/admin/statistics/show_month.blade.php
deleted file mode 100644
index 7347442..0000000
--- a/flexiapi/resources/views/admin/statistics/show_month.blade.php
+++ /dev/null
@@ -1,37 +0,0 @@
-@extends('layouts.main')
-
-@section('breadcrumb')
-
- Statistics
-
-@endsection
-
-@section('content')
-
-
- -
- Day
-
- -
- Week
-
- -
- Month
-
-
-
-Statistics
-
-@include('admin.statistics.parts.legend')
-
-Month
-
-
-@foreach ($month as $key => $day)
-
- @include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_month])
-
-@endforeach
-
-
-@endsection
\ No newline at end of file
diff --git a/flexiapi/resources/views/admin/statistics/show_week.blade.php b/flexiapi/resources/views/admin/statistics/show_week.blade.php
deleted file mode 100644
index 8fc4a67..0000000
--- a/flexiapi/resources/views/admin/statistics/show_week.blade.php
+++ /dev/null
@@ -1,37 +0,0 @@
-@extends('layouts.main')
-
-@section('breadcrumb')
-
- Statistics
-
-@endsection
-
-@section('content')
-
-
- -
- Day
-
- -
- Week
-
- -
- Month
-
-
-
-Statistics
-
-@include('admin.statistics.parts.legend')
-
-Week
-
-
-@foreach ($week as $key => $day)
-
- @include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_week])
-
-@endforeach
-
-
-@endsection
\ No newline at end of file
diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php
index a2588d9..98497b2 100644
--- a/flexiapi/resources/views/api/documentation_markdown.blade.php
+++ b/flexiapi/resources/views/api/documentation_markdown.blade.php
@@ -525,20 +525,6 @@ JSON parameters:
* `to` required, SIP address of the receiver
* `body` required, content of the message
-## Statistics
-
-### `GET /statistics/day`
-Admin
-Retrieve registrations statistics for 24 hours.
-
-### `GET /statistics/week`
-Admin
-Retrieve registrations statistics for a week.
-
-### `GET /statistics/month`
-Admin
-Retrieve registrations statistics for a month.
-
# Non-API Endpoints
The following URLs are **not API endpoints** they are not returning `JSON` content and they are not located under `/api` but directly under the root path.
diff --git a/flexiapi/resources/views/layouts/main.blade.php b/flexiapi/resources/views/layouts/main.blade.php
index 35f9bda..4542dd6 100644
--- a/flexiapi/resources/views/layouts/main.blade.php
+++ b/flexiapi/resources/views/layouts/main.blade.php
@@ -12,6 +12,9 @@
+
+
+
@if (config('instance.custom_theme') & file_exists(public_path('css/' . config('app.env') . '.style.css')))
diff --git a/flexiapi/resources/views/parts/graph.blade.php b/flexiapi/resources/views/parts/graph.blade.php
new file mode 100644
index 0000000..84d23fd
--- /dev/null
+++ b/flexiapi/resources/views/parts/graph.blade.php
@@ -0,0 +1,27 @@
+
+
+
\ No newline at end of file
diff --git a/flexiapi/resources/views/parts/sidebar.blade.php b/flexiapi/resources/views/parts/sidebar.blade.php
index c6466c1..281e06f 100644
--- a/flexiapi/resources/views/parts/sidebar.blade.php
+++ b/flexiapi/resources/views/parts/sidebar.blade.php
@@ -7,7 +7,7 @@
if (auth()->user() && auth()->user()->admin) {
$items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'people'];
$items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'account_box'];
- $items['admin.statistics.show.day'] = ['title' => 'Statistics', 'icon' => 'analytics'];
+ $items['admin.statistics.show'] = ['title' => 'Statistics', 'icon' => 'analytics'];
}
@endphp
diff --git a/flexiapi/resources/views/parts/tabs.blade.php b/flexiapi/resources/views/parts/tabs.blade.php
index b502009..29f46f6 100644
--- a/flexiapi/resources/views/parts/tabs.blade.php
+++ b/flexiapi/resources/views/parts/tabs.blade.php
@@ -1,5 +1,5 @@
@foreach ($items as $route => $title)
- - current() == route($route))class="current"@endif>{{ $title }}
+ - current() == $route)class="current"@endif>{{ $title }}
@endforeach
diff --git a/flexiapi/resources/views/parts/tabs/register.blade.php b/flexiapi/resources/views/parts/tabs/register.blade.php
index be7fd45..21be028 100644
--- a/flexiapi/resources/views/parts/tabs/register.blade.php
+++ b/flexiapi/resources/views/parts/tabs/register.blade.php
@@ -1,6 +1,6 @@
@if(config('app.phone_authentication'))
@include('parts.tabs', ['items' => [
- 'account.register.email' => 'Email registration',
- 'account.register.phone' => 'Phone registration',
+ route('account.register.phone') => 'Phone registration',
+ route('account.register.email') => 'Email registration',
]])
@endif
diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php
index b303ddc..ebeabd5 100644
--- a/flexiapi/routes/api.php
+++ b/flexiapi/routes/api.php
@@ -54,10 +54,6 @@ Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store');
Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
Route::group(['middleware' => ['auth.digest_or_key']], function () {
- Route::get('statistic/month', 'Api\StatisticController@month');
- Route::get('statistic/week', 'Api\StatisticController@week');
- Route::get('statistic/day', 'Api\StatisticController@day');
-
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
Route::get('accounts/me/api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt');
diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php
index 8e7e7e0..eaca5dd 100644
--- a/flexiapi/routes/web.php
+++ b/flexiapi/routes/web.php
@@ -32,7 +32,7 @@ use App\Http\Controllers\Admin\AccountTypeController;
use App\Http\Controllers\Admin\AccountController as AdminAccountController;
use App\Http\Controllers\Admin\ContactsListController;
use App\Http\Controllers\Admin\ContactsListContactController;
-
+use App\Http\Controllers\Admin\StatisticsController;
use Illuminate\Support\Facades\Route;
Route::redirect('/', '/login')->name('account.home');
@@ -131,11 +131,11 @@ if (config('app.web_panel')) {
Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth');
Route::name('admin.')->prefix('admin')->middleware(['auth.admin'])->group(function () {
-
- // Statistics
- Route::get('statistics/day', 'Admin\StatisticsController@showDay')->name('statistics.show.day');
- Route::get('statistics/week', 'Admin\StatisticsController@showWeek')->name('statistics.show.week');
- Route::get('statistics/month', 'Admin\StatisticsController@showMonth')->name('statistics.show.month');
+ Route::name('statistics.')->controller(StatisticsController::class)->prefix('statistics')->group(function () {
+ Route::get('/', 'index')->name('index');
+ Route::get('/{type?}', 'show')->name('show');
+ Route::post('/', 'edit')->name('edit');
+ });
Route::name('account.')->prefix('accounts')->group(function () {
Route::controller(AdminAccountController::class)->group(function () {