Merge pull request #466 from allerta-vvf/master
Pushing to prod without testing
This commit is contained in:
commit
0d0949acf2
|
@ -30,7 +30,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
steps:
|
||||
# Checkout your code repository to scan
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
name: Testing ${{ matrix.php-versions }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
coverage: xdebug, pcov
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
|
@ -60,7 +60,7 @@ jobs:
|
|||
name: Deploy to staging
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
@ -71,7 +71,7 @@ jobs:
|
|||
coverage: xdebug, pcov
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
|
@ -86,13 +86,13 @@ jobs:
|
|||
php -r 'require("deployment_remotes.php");'
|
||||
cat deployment.log | grep "After-jobs:" ; exit $?
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: deploy_php_log_${{ github.run_id }}
|
||||
path: /tmp/php.log
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: deploy_log_${{ github.run_id }}
|
||||
|
@ -106,7 +106,7 @@ jobs:
|
|||
name: Deploy to production
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
@ -117,7 +117,7 @@ jobs:
|
|||
coverage: xdebug, pcov
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
|
@ -132,13 +132,13 @@ jobs:
|
|||
php -r 'require("deployment_remotes.php");'
|
||||
cat deployment.log | grep "After-jobs:" ; exit $?
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: deploy_php_log_${{ github.run_id }}
|
||||
path: /tmp/php.log
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: deploy_log_${{ github.run_id }}
|
||||
|
|
|
@ -0,0 +1,415 @@
|
|||
<?php
|
||||
require_once 'utils.php';
|
||||
|
||||
final class NoChiefAvailableException extends Exception {}
|
||||
final class NoDriverAvailableException extends Exception {}
|
||||
final class NotEnoughAvailableUsersException extends Exception {}
|
||||
|
||||
function callsList($type) {
|
||||
global $db;
|
||||
$crew = [];
|
||||
|
||||
if($db->selectValue("SELECT COUNT(id) FROM `".DB_PREFIX."_profiles` WHERE `available` = 1") < 2) {
|
||||
throw new NotEnoughAvailableUsersException();
|
||||
return;
|
||||
}
|
||||
|
||||
$chief_result = $db->selectRow("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 AND `available` = 1 AND `chief` = 1 ORDER BY services ASC, trainings DESC, availability_minutes ASC, name ASC LIMIT 1");
|
||||
if(is_null($chief_result)) {
|
||||
throw new NoChiefAvailableException();
|
||||
return;
|
||||
}
|
||||
$crew[] = $chief_result;
|
||||
if($chief_result["driver"]) {
|
||||
$result = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 AND `available` = 1 ORDER BY chief ASC, services ASC, trainings DESC, availability_minutes ASC, name ASC");
|
||||
foreach ($result as $row) {
|
||||
if(!in_array($row["id"], array_column($crew, 'id'))) {
|
||||
$crew[] = $row;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$driver_result = $db->selectRow("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 AND `available` = 1 AND `driver` = 1 ORDER BY chief ASC, services ASC, trainings DESC, availability_minutes ASC, name ASC");
|
||||
if(is_null($driver_result)) {
|
||||
throw new NoDriverAvailableException();
|
||||
return;
|
||||
}
|
||||
foreach ($driver_result as $row) {
|
||||
if(!in_array($row["id"], array_column($crew, 'id'))) {
|
||||
$crew[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == 'full') {
|
||||
$result = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 AND `available` = 1 ORDER BY chief ASC, services ASC, trainings DESC, availability_minutes ASC, name ASC");
|
||||
foreach ($result as $row) {
|
||||
if(!in_array($row["id"], array_column($crew, 'id'))) {
|
||||
$crew[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $crew;
|
||||
}
|
||||
|
||||
function loadCrewMemberData($input) {
|
||||
global $db;
|
||||
$result = $db->selectRow("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `id` = ?", [$input["id"]]);
|
||||
if(is_null($result)) {
|
||||
throw new Exception("Crew member not found");
|
||||
return;
|
||||
}
|
||||
return array_merge($input, $result);
|
||||
}
|
||||
|
||||
function updateAlertMessages($alert, $crew=null, $alertDeleted = false) {
|
||||
global $Bot, $users, $db;
|
||||
|
||||
if(is_null($Bot)) initializeBot(NONE);
|
||||
|
||||
if(is_null($crew)) {
|
||||
$crew = json_decode($alert["crew"], true);
|
||||
}
|
||||
|
||||
$notification_messages = json_decode($alert["notification_messages"], true);
|
||||
$notification_text = generateAlertReportMessage($alert["type"], $crew, $alert["enabled"], $alert["notes"], $alert["created_by"], $alertDeleted);
|
||||
foreach($notification_messages as $chat_id => $message_id) {
|
||||
try {
|
||||
$Bot->editMessageText([
|
||||
"chat_id" => $chat_id,
|
||||
"message_id" => $message_id,
|
||||
"text" => $notification_text
|
||||
]);
|
||||
} catch(skrtdev\Telegram\BadRequestException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if($alertDeleted) {
|
||||
foreach($crew as &$member) {
|
||||
$message_id = $member["telegram_message_id"];
|
||||
$chat_id = $member["telegram_chat_id"];
|
||||
|
||||
if(!is_null($message_id) && !is_null($chat_id)) {
|
||||
$Bot->sendMessage([
|
||||
"chat_id" => $chat_id,
|
||||
"text" => "Allerta rimossa.\nPartecipazione non più richiesta.",
|
||||
"reply_to_message_id" => $message_id
|
||||
]);
|
||||
try {
|
||||
$Bot->editMessageReplyMarkup([
|
||||
"chat_id" => $chat_id,
|
||||
"message_id" => $message_id,
|
||||
"reply_markup" => [
|
||||
'inline_keyboard' => [
|
||||
]
|
||||
]
|
||||
]);
|
||||
} catch(skrtdev\Telegram\BadRequestException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$available_users_count = 0;
|
||||
$drivers_count = 0;
|
||||
$chiefs_count = 0;
|
||||
foreach($crew as &$member) {
|
||||
if($member["response"] === true) {
|
||||
$user = $users->getUserById($member["id"]);
|
||||
$available_users_count++;
|
||||
if($user["driver"]) $drivers_count++;
|
||||
if($user["chief"]) $chiefs_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(
|
||||
($alert["type"] === "support" && $available_users_count >= 2 && $chiefs_count >= 1 && $drivers_count >= 1) ||
|
||||
($alert["type"] === "full" && $available_users_count >= 5 && $chiefs_count >= 1 && $drivers_count >= 1)
|
||||
) {
|
||||
$db->update(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"enabled" => 0
|
||||
],
|
||||
[
|
||||
"id" => $alert["id"]
|
||||
]
|
||||
);
|
||||
|
||||
$notification_text = generateAlertReportMessage($alert["type"], $crew, false, $alert["notes"], $alert["created_by"], $alertDeleted);
|
||||
foreach($notification_messages as $chat_id => $message_id) {
|
||||
try {
|
||||
$Bot->editMessageText([
|
||||
"chat_id" => $chat_id,
|
||||
"message_id" => $message_id,
|
||||
"text" => $notification_text
|
||||
]);
|
||||
} catch(skrtdev\Telegram\BadRequestException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
foreach($crew as &$member) {
|
||||
$message_id = $member["telegram_message_id"];
|
||||
$chat_id = $member["telegram_chat_id"];
|
||||
|
||||
if((!is_null($message_id) || !is_null($chat_id)) && $member["response"] === "waiting") {
|
||||
$Bot->sendMessage([
|
||||
"chat_id" => $chat_id,
|
||||
"text" => "Numero minimo vigili richiesti raggiunto.\nPartecipazione non più richiesta.",
|
||||
"reply_to_message_id" => $message_id
|
||||
]);
|
||||
try {
|
||||
$Bot->editMessageReplyMarkup([
|
||||
"chat_id" => $chat_id,
|
||||
"message_id" => $message_id,
|
||||
"reply_markup" => [
|
||||
'inline_keyboard' => [
|
||||
]
|
||||
]
|
||||
]);
|
||||
} catch(skrtdev\Telegram\BadRequestException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAlertResponse($response, $userId, $alertId) {
|
||||
global $db, $users, $Bot;
|
||||
|
||||
if(is_null($Bot)) initializeBot(NONE);
|
||||
|
||||
$alert = $db->selectRow(
|
||||
"SELECT * FROM `".DB_PREFIX."_alerts` WHERE `id` = ?", [$alertId]
|
||||
);
|
||||
|
||||
if(!$alert["enabled"]) return;
|
||||
|
||||
$crew = json_decode($alert["crew"], true);
|
||||
$messageText = $response ? "🟢 Partecipazione accettata." : "🔴 Partecipazione rifiutata.";
|
||||
|
||||
foreach($crew as &$member) {
|
||||
if($member["id"] == $userId) {
|
||||
if($member["response"] === $response) return;
|
||||
|
||||
$message_id = $member["telegram_message_id"];
|
||||
$chat_id = $member["telegram_chat_id"];
|
||||
|
||||
if(!is_null($message_id) || !is_null($chat_id)) {
|
||||
$Bot->sendMessage([
|
||||
"chat_id" => $chat_id,
|
||||
"text" => $messageText,
|
||||
"reply_to_message_id" => $message_id
|
||||
]);
|
||||
try {
|
||||
$Bot->editMessageReplyMarkup([
|
||||
"chat_id" => $chat_id,
|
||||
"message_id" => $message_id,
|
||||
"reply_markup" => [
|
||||
'inline_keyboard' => [
|
||||
]
|
||||
]
|
||||
]);
|
||||
} catch(skrtdev\Telegram\BadRequestException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
$member["response"] = $response;
|
||||
$member["response_time"] = get_timestamp();
|
||||
}
|
||||
}
|
||||
$db->update(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"crew" => json_encode($crew)
|
||||
],
|
||||
[
|
||||
"id" => $alertId
|
||||
]
|
||||
);
|
||||
|
||||
updateAlertMessages($alert, $crew);
|
||||
}
|
||||
|
||||
function alertsRouter (FastRoute\RouteCollector $r) {
|
||||
$r->addRoute(
|
||||
'GET',
|
||||
'',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin();
|
||||
$alerts = $db->select("SELECT * FROM `".DB_PREFIX."_alerts` WHERE `enabled` = 1");
|
||||
if(is_null($alerts)) $alerts = [];
|
||||
foreach($alerts as &$alert) {
|
||||
if(isset($_GET["load_less"])) {
|
||||
$alert = [
|
||||
"id" => $alert["id"],
|
||||
"created_at" => $alert["created_at"]
|
||||
];
|
||||
} else {
|
||||
$alert["crew"] = json_decode($alert["crew"], true);
|
||||
$alert["crew"] = array_map(function($crew_member) {
|
||||
return loadCrewMemberData($crew_member);
|
||||
}, $alert["crew"]);
|
||||
}
|
||||
}
|
||||
apiResponse($alerts);
|
||||
}
|
||||
);
|
||||
|
||||
$r->addRoute(
|
||||
'POST',
|
||||
'',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR)) {
|
||||
apiResponse(["status" => "error", "message" => "Access denied"]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$crew_members = callsList($_POST["type"]);
|
||||
} catch (NoChiefAvailableException) {
|
||||
apiResponse(["status" => "error", "message" => "Nessun caposquadra disponibile. Contattare i vigili manualmente."]);
|
||||
return;
|
||||
} catch (NoDriverAvailableException) {
|
||||
apiResponse(["status" => "error", "message" => "Nessun autista disponibile. Contattare i vigili manualmente."]);
|
||||
return;
|
||||
} catch (NotEnoughAvailableUsersException) {
|
||||
apiResponse(["status" => "error", "message" => "Nessun utente disponibile. Distaccamento non operativo."]);
|
||||
return;
|
||||
}
|
||||
|
||||
$crew = [];
|
||||
foreach($crew_members as $member) {
|
||||
$crew[] = [
|
||||
"id" => $member["id"],
|
||||
"response" => "waiting"
|
||||
];
|
||||
}
|
||||
|
||||
$notifications = sendAlertReportMessage($_POST["type"], $crew, true, "", $users->auth->getUserId());
|
||||
|
||||
$db->insert(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"crew" => json_encode($crew),
|
||||
"type" => $_POST["type"],
|
||||
"created_at" => get_timestamp(),
|
||||
"created_by" => $users->auth->getUserId(),
|
||||
"notification_messages" => json_encode($notifications)
|
||||
]
|
||||
);
|
||||
$alertId = $db->getLastInsertId();
|
||||
|
||||
foreach($crew as &$member) {
|
||||
[$member["telegram_message_id"], $member["telegram_chat_id"]] = sendAlertRequestMessage($_POST["type"], $member["id"], $alertId, "", $users->auth->getUserId());
|
||||
}
|
||||
|
||||
$db->update(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"crew" => json_encode($crew)
|
||||
],
|
||||
[
|
||||
"id" => $alertId
|
||||
]
|
||||
);
|
||||
|
||||
apiResponse([
|
||||
"crew" => $crew,
|
||||
"id" => $alertId
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
$r->addRoute(
|
||||
'GET',
|
||||
'/{id:\d+}',
|
||||
function ($vars) {
|
||||
global $db;
|
||||
requireLogin();
|
||||
$alert = $db->selectRow("SELECT * FROM `".DB_PREFIX."_alerts` WHERE `id` = :id", [":id" => $vars["id"]]);
|
||||
if(is_null($alert)) {
|
||||
apiResponse(["error" => "alert not found"]);
|
||||
return;
|
||||
}
|
||||
$alert["crew"] = json_decode($alert["crew"], true);
|
||||
$alert["crew"] = array_map(function($crew_member) {
|
||||
return loadCrewMemberData($crew_member);
|
||||
}, $alert["crew"]);
|
||||
apiResponse($alert);
|
||||
}
|
||||
);
|
||||
|
||||
$r->addRoute(
|
||||
'POST',
|
||||
'/{id:\d+}/settings',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR)) {
|
||||
apiResponse(["status" => "error", "message" => "Access denied"]);
|
||||
return;
|
||||
}
|
||||
$db->update(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"notes" => $_POST["notes"]
|
||||
],
|
||||
[
|
||||
"id" => $vars["id"]
|
||||
]
|
||||
);
|
||||
|
||||
$alert = $db->selectRow(
|
||||
"SELECT * FROM `".DB_PREFIX."_alerts` WHERE `id` = :id",
|
||||
[
|
||||
":id" => $vars["id"]
|
||||
]
|
||||
);
|
||||
updateAlertMessages($alert);
|
||||
}
|
||||
);
|
||||
|
||||
$r->addRoute(
|
||||
'DELETE',
|
||||
'/{id:\d+}',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR)) {
|
||||
apiResponse(["status" => "error", "message" => "Access denied"]);
|
||||
return;
|
||||
}
|
||||
$db->update(
|
||||
DB_PREFIX."_alerts",
|
||||
[
|
||||
"enabled" => 0
|
||||
],
|
||||
[
|
||||
"id" => $vars["id"]
|
||||
]
|
||||
);
|
||||
|
||||
$alert = $db->selectRow(
|
||||
"SELECT * FROM `".DB_PREFIX."_alerts` WHERE `id` = :id",
|
||||
[
|
||||
":id" => $vars["id"]
|
||||
]
|
||||
);
|
||||
updateAlertMessages($alert, null, true);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -2,12 +2,17 @@
|
|||
require_once 'utils.php';
|
||||
require_once 'telegramBotRouter.php';
|
||||
require_once 'cronRouter.php';
|
||||
require_once 'alerts.php';
|
||||
|
||||
function apiRouter (FastRoute\RouteCollector $r) {
|
||||
$r->addGroup('/cron', function (FastRoute\RouteCollector $r) {
|
||||
cronRouter($r);
|
||||
});
|
||||
|
||||
$r->addGroup('/alerts', function (FastRoute\RouteCollector $r) {
|
||||
alertsRouter($r);
|
||||
});
|
||||
|
||||
$r->addRoute(
|
||||
['GET', 'POST'],
|
||||
'/bot/telegram',
|
||||
|
@ -94,9 +99,9 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/list',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
if($users->hasRole(Role::FULL_VIEWER)) {
|
||||
if($users->hasRole(Role::SUPER_EDITOR)) {
|
||||
$response = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 ORDER BY available DESC, chief DESC, services ASC, trainings DESC, availability_minutes ASC, name ASC");
|
||||
} else {
|
||||
$response = $db->select("SELECT `id`, `chief`, `online_time`, `available`, `availability_minutes`, `name`, `driver`, `services` FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0 ORDER BY available DESC, chief DESC, services ASC, trainings DESC, availability_minutes ASC, name ASC");
|
||||
|
@ -112,7 +117,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/logs',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$response = $db->select("SELECT * FROM `".DB_PREFIX."_log` ORDER BY `timestamp` DESC");
|
||||
if(!is_null($response)) {
|
||||
|
@ -132,7 +137,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/services',
|
||||
function ($vars) {
|
||||
global $services, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse($services->list());
|
||||
}
|
||||
|
@ -142,7 +147,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/services',
|
||||
function ($vars) {
|
||||
global $services, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse(["response" => $services->add($_POST["start"], $_POST["end"], $_POST["code"], $_POST["chief"], $_POST["drivers"], $_POST["crew"], $_POST["place"], $_POST["notes"], $_POST["type"], $users->auth->getUserId())]);
|
||||
}
|
||||
|
@ -153,7 +158,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/services/{id}',
|
||||
function ($vars) {
|
||||
global $services, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse($services->get($vars['id']));
|
||||
}
|
||||
|
@ -163,7 +168,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/services/{id}',
|
||||
function ($vars) {
|
||||
global $services, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse(["response" => $services->delete($vars["id"])]);
|
||||
}
|
||||
|
@ -174,7 +179,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/place_details',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$response = $db->selectRow("SELECT * FROM `".DB_PREFIX."_places_info` WHERE `lat` = ? and `lng` = ? LIMIT 0,1;", [$_GET["lat"], $_GET["lng"]]);
|
||||
apiResponse(!is_null($response) ? $response : []);
|
||||
|
@ -186,7 +191,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/trainings',
|
||||
function ($vars) {
|
||||
global $db, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$response = $db->select("SELECT * FROM `".DB_PREFIX."_trainings` ORDER BY date DESC, beginning desc");
|
||||
apiResponse(
|
||||
|
@ -200,7 +205,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/users',
|
||||
function ($vars) {
|
||||
global $users, $users;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse($users->get_users());
|
||||
}
|
||||
|
@ -210,8 +215,8 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/users',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin() || accessDenied();
|
||||
if(!$users->hasRole(Role::FULL_VIEWER) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
requireLogin();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
exit;
|
||||
}
|
||||
apiResponse(["userId" => $users->add_user($_POST["email"], $_POST["name"], $_POST["username"], $_POST["password"], $_POST["phone_number"], $_POST["birthday"], $_POST["chief"], $_POST["driver"], $_POST["hidden"], $_POST["disabled"], "unknown")]);
|
||||
|
@ -222,11 +227,11 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/users/{userId}',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin() || accessDenied();
|
||||
if(!$users->hasRole(Role::FULL_VIEWER) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
requireLogin();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
exit;
|
||||
}
|
||||
apiResponse($users->get_user($vars["userId"]));
|
||||
apiResponse($users->getUserById($vars["userId"]));
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
|
@ -234,8 +239,8 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/users/{userId}',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin() || accessDenied();
|
||||
if(!$users->hasRole(Role::FULL_VIEWER) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
requireLogin();
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
exit;
|
||||
}
|
||||
$users->remove_user($vars["userId"], "unknown");
|
||||
|
@ -248,7 +253,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/availability',
|
||||
function ($vars) {
|
||||
global $users, $db;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$row = $db->selectRow(
|
||||
"SELECT `available`, `manual_mode` FROM `".DB_PREFIX."_profiles` WHERE `id` = ?",
|
||||
|
@ -265,10 +270,12 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/availability',
|
||||
function ($vars) {
|
||||
global $users, $availability;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
if(!$users->hasRole(Role::FULL_VIEWER) && $_POST["id"] !== $users->auth->getUserId()){
|
||||
exit;
|
||||
if(!$users->hasRole(Role::SUPER_EDITOR) && (int) $_POST["id"] !== $users->auth->getUserId()){
|
||||
statusCode(401);
|
||||
apiResponse(["status" => "error", "message" => "You don't have permission to change other users availability", "t" => $users->auth->getUserId()]);
|
||||
return;
|
||||
}
|
||||
$user_id = is_numeric($_POST["id"]) ? $_POST["id"] : $users->auth->getUserId();
|
||||
apiResponse([
|
||||
|
@ -283,7 +290,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
"/manual_mode",
|
||||
function ($vars) {
|
||||
global $users, $availability;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$availability->change_manual_mode($_POST["manual_mode"]);
|
||||
apiResponse(["status" => "success"]);
|
||||
|
@ -295,7 +302,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/schedules',
|
||||
function ($vars) {
|
||||
global $users, $schedules;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
apiResponse($schedules->get());
|
||||
}
|
||||
|
@ -305,7 +312,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/schedules',
|
||||
function ($vars) {
|
||||
global $users, $schedules;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$new_schedules = !is_string($_POST["schedules"]) ? json_encode($_POST["schedules"]) : $_POST["schedules"];
|
||||
apiResponse([
|
||||
|
@ -319,7 +326,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/service_types',
|
||||
function ($vars) {
|
||||
global $users, $db;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$response = $db->select("SELECT * FROM `".DB_PREFIX."_type`");
|
||||
apiResponse(is_null($response) ? [] : $response);
|
||||
|
@ -330,7 +337,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/service_types',
|
||||
function ($vars) {
|
||||
global $users, $db;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$response = $db->insert(DB_PREFIX."_type", ["name" => $_POST["name"]]);
|
||||
apiResponse($response);
|
||||
|
@ -342,7 +349,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/places/search',
|
||||
function ($vars) {
|
||||
global $places;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
apiResponse($places->search($_GET["q"]));
|
||||
}
|
||||
);
|
||||
|
@ -352,7 +359,7 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
'/telegram_login_token',
|
||||
function ($vars) {
|
||||
global $users, $db;
|
||||
requireLogin() || accessDenied();
|
||||
requireLogin();
|
||||
$users->online_time_update();
|
||||
$token = bin2hex(random_bytes(16));
|
||||
apiResponse([
|
||||
|
@ -405,6 +412,64 @@ function apiRouter (FastRoute\RouteCollector $r) {
|
|||
}
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
['POST'],
|
||||
'/impersonate',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin();
|
||||
|
||||
if(!$users->hasRole(Role::SUPER_ADMIN)) {
|
||||
statusCode(401);
|
||||
apiResponse(["status" => "error", "message" => "You don't have permission to impersonate"]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$token = $users->loginAsUserIdAndReturnToken($_POST["user_id"]);
|
||||
apiResponse(["status" => "success", "access_token" => $token]);
|
||||
}
|
||||
catch (\Delight\Auth\UnknownIdException $e) {
|
||||
statusCode(400);
|
||||
apiResponse(["status" => "error", "message" => "Wrong user ID"]);
|
||||
}
|
||||
catch (\Delight\Auth\EmailNotVerifiedException $e) {
|
||||
statusCode(400);
|
||||
apiResponse(["status" => "error", "message" => "Email not verified"]);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
statusCode(400);
|
||||
apiResponse(["status" => "error", "message" => "Unknown error", "error" => $e]);
|
||||
}
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
['POST'],
|
||||
'/stop_impersonating',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin();
|
||||
|
||||
if(array_key_exists("impersonating_user", $users->auth->user_info) && array_key_exists("precedent_user_id", $users->auth->user_info)) {
|
||||
$precedent_user_id = $users->auth->user_info["precedent_user_id"];
|
||||
$users->auth->logOut();
|
||||
$token = $users->loginAsUserIdAndReturnToken($precedent_user_id);
|
||||
apiResponse(["status" => "success", "access_token" => $token, "user_id" => $users->auth->getUserId()]);
|
||||
}
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
['GET', 'POST'],
|
||||
'/refreshToken',
|
||||
function ($vars) {
|
||||
global $users;
|
||||
requireLogin(false);
|
||||
|
||||
apiResponse([
|
||||
"token" => $users->generateToken()
|
||||
]);
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
['GET', 'POST'],
|
||||
'/validateToken',
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
"delight-im/auth": "dev-master",
|
||||
"ulrichsg/getopt-php": "4.0.1",
|
||||
"nikic/fast-route": "^2.0@dev",
|
||||
"spatie/array-to-xml": "3.1.0",
|
||||
"spatie/array-to-xml": "3.1.1",
|
||||
"ezyang/htmlpurifier": "4.14.0",
|
||||
"brick/phonenumber": "0.4.0",
|
||||
"sentry/sdk": "3.1.1",
|
||||
"azuyalabs/yasumi": "2.4.0",
|
||||
"azuyalabs/yasumi": "2.5.0",
|
||||
"ministryofweb/php-osm-tiles": "2.0.0",
|
||||
"delight-im/db": "1.3.1",
|
||||
"phpfastcache/phpfastcache": "9.0.1",
|
||||
|
|
|
@ -4,34 +4,34 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9193956804bd765f7fe3f29d0c61472e",
|
||||
"content-hash": "e757f460b505a5b23599ce4866a7a957",
|
||||
"packages": [
|
||||
{
|
||||
"name": "azuyalabs/yasumi",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/azuyalabs/yasumi.git",
|
||||
"reference": "083a0d0579fee17e68d688d463bc01098ac2691f"
|
||||
"reference": "5fd99815e8bf480fd0e6b76527d5413767e98930"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/083a0d0579fee17e68d688d463bc01098ac2691f",
|
||||
"reference": "083a0d0579fee17e68d688d463bc01098ac2691f",
|
||||
"url": "https://api.github.com/repos/azuyalabs/yasumi/zipball/5fd99815e8bf480fd0e6b76527d5413767e98930",
|
||||
"reference": "5fd99815e8bf480fd0e6b76527d5413767e98930",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=7.3"
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"infection/infection": "^0.17 | ^0.22",
|
||||
"friendsofphp/php-cs-fixer": "v2.19 | v3.5",
|
||||
"infection/infection": "^0.17 | ^0.26",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"phan/phan": "^4.0",
|
||||
"phpstan/phpstan": "^0.12.66",
|
||||
"phpunit/phpunit": "^8.5 | ^9.4",
|
||||
"vimeo/psalm": "^4"
|
||||
"phan/phan": "^5.2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^8.5 | ^9.5",
|
||||
"vimeo/psalm": "^4.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-calendar": "For calculating the date of Easter"
|
||||
|
@ -77,7 +77,7 @@
|
|||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2021-05-09T09:03:34+00:00"
|
||||
"time": "2022-01-30T07:43:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/phonenumber",
|
||||
|
@ -157,12 +157,12 @@
|
|||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Clue\\StreamFilter\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Clue\\StreamFilter\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -207,12 +207,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/allerta-vvf/PHP-Auth-JWT",
|
||||
"reference": "a39b9e746d056145c31bb9c72f613c751d85e105"
|
||||
"reference": "f5a99a4502ed05a4707de610114fd2426b2629ef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/allerta-vvf/PHP-Auth-JWT/zipball/a39b9e746d056145c31bb9c72f613c751d85e105",
|
||||
"reference": "a39b9e746d056145c31bb9c72f613c751d85e105",
|
||||
"url": "https://api.github.com/repos/allerta-vvf/PHP-Auth-JWT/zipball/f5a99a4502ed05a4707de610114fd2426b2629ef",
|
||||
"reference": "f5a99a4502ed05a4707de610114fd2426b2629ef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -240,7 +240,7 @@
|
|||
"login",
|
||||
"security"
|
||||
],
|
||||
"time": "2022-01-09T13:37:14+00:00"
|
||||
"time": "2022-02-14T10:21:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "delight-im/base64",
|
||||
|
@ -385,16 +385,16 @@
|
|||
},
|
||||
{
|
||||
"name": "giggsey/libphonenumber-for-php",
|
||||
"version": "8.12.41",
|
||||
"version": "8.12.43",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/giggsey/libphonenumber-for-php.git",
|
||||
"reference": "c7b9f89a25e37e8bb650a378c3eabcbdafedeafd"
|
||||
"reference": "27bc97a4941f42d320fb6da3de0dcaaf7db69f5d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/c7b9f89a25e37e8bb650a378c3eabcbdafedeafd",
|
||||
"reference": "c7b9f89a25e37e8bb650a378c3eabcbdafedeafd",
|
||||
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/27bc97a4941f42d320fb6da3de0dcaaf7db69f5d",
|
||||
"reference": "27bc97a4941f42d320fb6da3de0dcaaf7db69f5d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -454,7 +454,7 @@
|
|||
"issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
|
||||
"source": "https://github.com/giggsey/libphonenumber-for-php"
|
||||
},
|
||||
"time": "2022-01-11T10:10:37+00:00"
|
||||
"time": "2022-02-09T07:46:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "giggsey/locale",
|
||||
|
@ -1208,12 +1208,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FastRoute\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"FastRoute\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -1443,16 +1443,16 @@
|
|||
},
|
||||
{
|
||||
"name": "php-http/message",
|
||||
"version": "1.12.0",
|
||||
"version": "1.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-http/message.git",
|
||||
"reference": "39eb7548be982a81085fe5a6e2a44268cd586291"
|
||||
"reference": "7886e647a30a966a1a8d1dad1845b71ca8678361"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-http/message/zipball/39eb7548be982a81085fe5a6e2a44268cd586291",
|
||||
"reference": "39eb7548be982a81085fe5a6e2a44268cd586291",
|
||||
"url": "https://api.github.com/repos/php-http/message/zipball/7886e647a30a966a1a8d1dad1845b71ca8678361",
|
||||
"reference": "7886e647a30a966a1a8d1dad1845b71ca8678361",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1469,7 +1469,7 @@
|
|||
"ext-zlib": "*",
|
||||
"guzzlehttp/psr7": "^1.0",
|
||||
"laminas/laminas-diactoros": "^2.0",
|
||||
"phpspec/phpspec": "^5.1 || ^6.3",
|
||||
"phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
|
||||
"slim/slim": "^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -1485,12 +1485,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Http\\Message\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/filters.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -1511,9 +1511,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-http/message/issues",
|
||||
"source": "https://github.com/php-http/message/tree/1.12.0"
|
||||
"source": "https://github.com/php-http/message/tree/1.13.0"
|
||||
},
|
||||
"time": "2021-08-29T09:13:12+00:00"
|
||||
"time": "2022-02-11T13:41:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-http/message-factory",
|
||||
|
@ -2336,13 +2336,13 @@
|
|||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"skrtdev\\async\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/range.php",
|
||||
"src/helpers.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"skrtdev\\async\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -2424,16 +2424,16 @@
|
|||
},
|
||||
{
|
||||
"name": "spatie/array-to-xml",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/array-to-xml.git",
|
||||
"reference": "3090918cb441ad707660dd8bccc6dc46beb34380"
|
||||
"reference": "18d474f5d53d3ff8d98e7ca00781d84c9c98d286"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/3090918cb441ad707660dd8bccc6dc46beb34380",
|
||||
"reference": "3090918cb441ad707660dd8bccc6dc46beb34380",
|
||||
"url": "https://api.github.com/repos/spatie/array-to-xml/zipball/18d474f5d53d3ff8d98e7ca00781d84c9c98d286",
|
||||
"reference": "18d474f5d53d3ff8d98e7ca00781d84c9c98d286",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2472,7 +2472,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/array-to-xml/issues",
|
||||
"source": "https://github.com/spatie/array-to-xml/tree/3.1.0"
|
||||
"source": "https://github.com/spatie/array-to-xml/tree/3.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2484,7 +2484,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-09-12T17:08:24+00:00"
|
||||
"time": "2021-11-22T19:44:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
|
@ -2555,16 +2555,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v6.0.2",
|
||||
"version": "v6.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "7f1cbd44590cb0acc6208c1711a52733e9a91663"
|
||||
"reference": "45b95017f6a20d564584bdee6a376c9a79caa316"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/7f1cbd44590cb0acc6208c1711a52733e9a91663",
|
||||
"reference": "7f1cbd44590cb0acc6208c1711a52733e9a91663",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/45b95017f6a20d564584bdee6a376c9a79caa316",
|
||||
"reference": "45b95017f6a20d564584bdee6a376c9a79caa316",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2619,7 +2619,7 @@
|
|||
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v6.0.2"
|
||||
"source": "https://github.com/symfony/http-client/tree/v6.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2635,7 +2635,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-12-29T10:14:09+00:00"
|
||||
"time": "2022-01-22T06:58:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
|
@ -2717,16 +2717,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v6.0.0",
|
||||
"version": "v6.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "be0facf48a42a232d6c0daadd76e4eb5657a4798"
|
||||
"reference": "51f7006670febe4cbcbae177cbffe93ff833250d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/be0facf48a42a232d6c0daadd76e4eb5657a4798",
|
||||
"reference": "be0facf48a42a232d6c0daadd76e4eb5657a4798",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/51f7006670febe4cbcbae177cbffe93ff833250d",
|
||||
"reference": "51f7006670febe4cbcbae177cbffe93ff833250d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2764,7 +2764,7 @@
|
|||
"options"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v6.0.0"
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v6.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2780,7 +2780,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-11-23T19:05:29+00:00"
|
||||
"time": "2022-01-02T09:55:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
|
@ -2816,12 +2816,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
@ -2893,12 +2893,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
|
@ -2982,12 +2982,12 @@
|
|||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Uuid\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Uuid\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
|
|
|
@ -173,7 +173,7 @@ function job_send_notification_if_manual_mode() {
|
|||
$profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `manual_mode` = 1");
|
||||
$notified_users = [];
|
||||
foreach ($profiles as $profile) {
|
||||
$notified_users[] = $profiles["id"];
|
||||
$notified_users[] = $profile["id"];
|
||||
$stato = $profile["available"] ? "disponibile" : "non disponibile";
|
||||
sendTelegramNotificationToUser("⚠️ Attenzione! La tua disponibilità <b>non segue la programmazione oraria</b>.\nAttualmente sei <b>{$stato}</b>.\nScrivi \"/programma\" se vuoi ripristinare la programmazione.", $profile["id"]);
|
||||
}
|
||||
|
|
|
@ -130,12 +130,33 @@ function getBearerToken() {
|
|||
return null;
|
||||
}
|
||||
|
||||
function requireLogin()
|
||||
function requireLogin($validate_token_version=true)
|
||||
{
|
||||
global $users;
|
||||
$token = getBearerToken();
|
||||
if($users->auth->isTokenValid($token)) {
|
||||
$users->auth->authenticateWithToken($token);
|
||||
if($users->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
|
||||
//Migrate to new user roles
|
||||
$users->auth->admin()->removeRoleForUserById($users->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
|
||||
$users->auth->admin()->addRoleForUserById($users->auth->getUserId(), Role::SUPER_EDITOR);
|
||||
|
||||
$users->auth->authenticateWithToken($token);
|
||||
}
|
||||
|
||||
if($validate_token_version) {
|
||||
if(!array_key_exists("v", $users->auth->user_info)) {
|
||||
statusCode(400);
|
||||
apiResponse(["status" => "error", "message" => "JWT client version is not supported", "type" => "jwt_update_required"]);
|
||||
exit();
|
||||
}
|
||||
if((int) $users->auth->user_info["v"] !== 2) {
|
||||
statusCode(400);
|
||||
apiResponse(["status" => "error", "message" => "JWT client version ".$users->auth->user_info["v"]." is not supported", "type" => "jwt_update_required"]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if(defined('SENTRY_LOADED')) {
|
||||
\Sentry\configureScope(function (\Sentry\State\Scope $scope) use ($users): void {
|
||||
$scope->setUser([
|
||||
|
@ -147,15 +168,11 @@ function requireLogin()
|
|||
]);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function accessDenied()
|
||||
{
|
||||
return;
|
||||
}
|
||||
statusCode(401);
|
||||
apiResponse(["error" => "Access denied"]);
|
||||
apiResponse(["status" => "error", "message" => "Access denied"]);
|
||||
exit();
|
||||
}
|
||||
|
||||
|
@ -195,7 +212,7 @@ try {
|
|||
break;
|
||||
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
|
||||
$allowedMethods = $routeInfo[1];
|
||||
http_response_code(405);
|
||||
statusCode(405);
|
||||
apiResponse(["status" => "error", "message" => "Method not allowed", "usedMethod" => $_SERVER['REQUEST_METHOD']]);
|
||||
break;
|
||||
case FastRoute\Dispatcher::FOUND:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
use skrtdev\NovaGram\Bot;
|
||||
use skrtdev\Telegram\Message;
|
||||
use skrtdev\Telegram\CallbackQuery;
|
||||
|
||||
require_once 'utils.php';
|
||||
|
||||
|
@ -32,14 +33,21 @@ function initializeBot($mode = WEBHOOK) {
|
|||
}
|
||||
}
|
||||
|
||||
function getUserIdByMessage(Message $message)
|
||||
function getUserIdByFrom($from_id)
|
||||
{
|
||||
global $db;
|
||||
return $db->selectValue("SELECT user FROM `".DB_PREFIX."_bot_telegram` WHERE `chat_id` = ?", [$message->from->id]);
|
||||
return $db->selectValue("SELECT user FROM `".DB_PREFIX."_bot_telegram` WHERE `chat_id` = ?", [$from_id]);
|
||||
}
|
||||
|
||||
function getUserIdByMessage(Message $message)
|
||||
{
|
||||
return getUserIdByFrom($message->from->id);
|
||||
}
|
||||
|
||||
function requireBotLogin(Message $message)
|
||||
{
|
||||
global $users;
|
||||
|
||||
$userId = getUserIdByMessage($message);
|
||||
if ($userId === null) {
|
||||
$message->reply(
|
||||
|
@ -47,23 +55,31 @@ function requireBotLogin(Message $message)
|
|||
"\nPer farlo, premere su <strong>\"Collega l'account al bot Telegram\"</strong>."
|
||||
);
|
||||
exit();
|
||||
} else {
|
||||
if($users->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
|
||||
//Migrate to new user roles
|
||||
$users->auth->admin()->removeRoleForUserById($users->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
|
||||
$users->auth->admin()->addRoleForUserById($users->auth->getUserId(), Role::SUPER_EDITOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendTelegramNotification($message)
|
||||
function sendTelegramNotification($message, $do_not_send_if_same=true)
|
||||
{
|
||||
global $Bot, $db;
|
||||
|
||||
if(is_null($Bot)) initializeBot(NONE);
|
||||
|
||||
$sentMessages = [];
|
||||
|
||||
//TODO: implement different types of notifications
|
||||
//TODO: add command for subscribing to notifications
|
||||
$chats = $db->select("SELECT * FROM `".DB_PREFIX."_bot_telegram_notifications`");
|
||||
if(!is_null($chats)) {
|
||||
foreach ($chats as $chat) {
|
||||
if(urldecode($chat['last_notification']) === $message) continue;
|
||||
if($do_not_send_if_same && urldecode($chat['last_notification']) === $message) continue;
|
||||
$chat = $chat['chat_id'];
|
||||
$Bot->sendMessage([
|
||||
$sendMessage = $Bot->sendMessage([
|
||||
"chat_id" => $chat,
|
||||
"text" => $message
|
||||
]);
|
||||
|
@ -72,11 +88,13 @@ function sendTelegramNotification($message)
|
|||
["last_notification" => urlencode($message)],
|
||||
["chat_id" => $chat]
|
||||
);
|
||||
$sentMessages[$chat] = $sendMessage->message_id;
|
||||
}
|
||||
}
|
||||
return $sentMessages;
|
||||
}
|
||||
|
||||
function sendTelegramNotificationToUser($message, $userId)
|
||||
function sendTelegramNotificationToUser($message, $userId, $options = [])
|
||||
{
|
||||
global $Bot, $db;
|
||||
|
||||
|
@ -84,18 +102,100 @@ function sendTelegramNotificationToUser($message, $userId)
|
|||
|
||||
$chat = $db->selectValue("SELECT `chat_id` FROM `".DB_PREFIX."_bot_telegram` WHERE `user` = ?", [$userId]);
|
||||
if(!is_null($chat)) {
|
||||
$Bot->sendMessage([
|
||||
$message_response = $Bot->sendMessage(array_merge([
|
||||
"chat_id" => $chat,
|
||||
"text" => $message
|
||||
]);
|
||||
], $options));
|
||||
return [$message_response->message_id, $chat];
|
||||
}
|
||||
}
|
||||
|
||||
function generateAlertMessage($alertType, $alertEnabled, $alertNotes, $alertCreatedBy, $alertDeleted=false) {
|
||||
global $users;
|
||||
|
||||
$message =
|
||||
"<b><i><u>".($alertEnabled ? "Allertamento in corso" : ($alertDeleted ? "Allertamento completato" : "Allerta rimossa")).":</u></i></b> ".
|
||||
($alertType === "full" ? "Richiesta <b>squadra completa 🚒</b>" : "<b>Supporto 🧯</b>\n");
|
||||
|
||||
if(!is_null($alertNotes) && $alertNotes !== "") {
|
||||
$message .= "Note:\n<b>".$alertNotes."</b>\n";
|
||||
}
|
||||
if(!is_null($alertCreatedBy)) {
|
||||
$message .= "Lanciata da: <b>".$users->getName($alertCreatedBy)."</b>\n";
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
function generateAlertReportMessage($alertType, $crew, $alertEnabled, $alertNotes, $alertCreatedBy, $alertDeleted=false) {
|
||||
global $users;
|
||||
|
||||
$message = generateAlertMessage($alertType, $alertEnabled, $alertNotes, $alertCreatedBy);
|
||||
$message .= "\nSquadra:\n";
|
||||
|
||||
foreach($crew as $member) {
|
||||
if((!$alertEnabled || $alertDeleted) && $member["response"] === "waiting") continue;
|
||||
$user = $users->getUserById($member['id']);
|
||||
$message .= "<i>".$user["name"]."</i> ";
|
||||
if($user["chief"]) $message .= "CS";
|
||||
if($user["driver"]) $message .= "🚒";
|
||||
$message .= "- ";
|
||||
if($member["response"] === "waiting") {
|
||||
$message .= "In attesa 🟡";
|
||||
} else if($member["response"] === true) {
|
||||
$message .= "Presente 🟢";
|
||||
} else if($member["response"] === false) {
|
||||
$message .= "Assente 🔴";
|
||||
}
|
||||
$message .= "\n";
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
function sendAlertReportMessage($alertType, $crew, $alertEnabled, $alertNotes, $alertCreatedBy, $alertDeleted = false) {
|
||||
$message = generateAlertReportMessage($alertType, $crew, $alertEnabled, $alertNotes, $alertCreatedBy, $alertDeleted);
|
||||
|
||||
return sendTelegramNotification($message, false);
|
||||
}
|
||||
|
||||
function sendAlertRequestMessage($alertType, $userId, $alertId, $alertNotes, $alertCreatedBy, $alertDeleted = false) {
|
||||
return sendTelegramNotificationToUser(generateAlertMessage($alertType, true, $alertNotes, $alertCreatedBy, $alertDeleted), $userId, [
|
||||
'reply_markup' => [
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
[
|
||||
'text' => '✅ Partecipo',
|
||||
'callback_data' => "alert_yes_".$alertId
|
||||
],
|
||||
[
|
||||
'text' => 'Non partecipo ❌',
|
||||
'callback_data' => "alert_no_".$alertId
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
function yesOrNo($value)
|
||||
{
|
||||
return ($value === 1 || $value) ? '<b>SI</b>' : '<b>NO</b>';
|
||||
}
|
||||
|
||||
function sendLongMessage($text, $userId) {
|
||||
global $Bot;
|
||||
if(strlen($text) > 4096) {
|
||||
$message_json = wordwrap($text, 4096, "<@MESSAGE_SEPARATOR@>", true);
|
||||
$message_json = explode("<@MESSAGE_SEPARATOR@>", $message_json);
|
||||
foreach($message_json as $segment) {
|
||||
sendLongMessage($segment, $userId);
|
||||
}
|
||||
} else {
|
||||
$Bot->sendMessage($userId, $text);
|
||||
}
|
||||
}
|
||||
|
||||
function telegramBotRouter() {
|
||||
global $Bot;
|
||||
|
||||
|
@ -158,13 +258,42 @@ function telegramBotRouter() {
|
|||
);
|
||||
});
|
||||
|
||||
$Bot->onCommand('debug_userid', function (Message $message) {
|
||||
global $Bot;
|
||||
|
||||
$messageText = "🔎 ID utente Telegram: <b>".$message->from->id."</b>";
|
||||
if(isset($message->from->username)) {
|
||||
$messageText .= "\n💬 Username: <b>".$message->from->username."</b>";
|
||||
}
|
||||
if(isset($message->from->first_name)) {
|
||||
$messageText .= "\n🔎 Nome: <b>".$message->from->first_name."</b>";
|
||||
}
|
||||
if(isset($message->from->last_name)) {
|
||||
$messageText .= "\n🔎 Cognome: <b>".$message->from->last_name."</b>";
|
||||
}
|
||||
if(isset($message->from->language_code)) {
|
||||
$messageText .= "\n🌐 Lingua: <b>".$message->from->language_code."</b>";
|
||||
}
|
||||
if(isset($message->from->is_bot)) {
|
||||
$messageText .= "\n🤖 Bot: <b>".yesOrNo($message->from->is_bot)."</b>";
|
||||
}
|
||||
$message->reply($messageText);
|
||||
|
||||
if(defined("BOT_TELEGRAM_DEBUG_USER") && BOT_TELEGRAM_DEBUG_USER !== $message->from->id){
|
||||
$messageText .= "\n\n🔎 JSON del messaggio:";
|
||||
$Bot->sendMessage(BOT_TELEGRAM_DEBUG_USER, $messageText);
|
||||
$message_json = json_encode($message, JSON_PRETTY_PRINT);
|
||||
sendLongMessage($message_json, BOT_TELEGRAM_DEBUG_USER);
|
||||
}
|
||||
});
|
||||
|
||||
$Bot->onCommand('info', function (Message $message) {
|
||||
global $users;
|
||||
$user_id = getUserIdByMessage($message);
|
||||
if(is_null($user_id)) {
|
||||
$message->chat->sendMessage('⚠️ Questo account Telegram non è associato a nessun utente di Allerta.');
|
||||
} else {
|
||||
$user = $users->get_user($user_id);
|
||||
$user = $users->getUserById($user_id);
|
||||
$message->chat->sendMessage(
|
||||
"ℹ️ Informazioni sul profilo:".
|
||||
"\n<i>Nome:</i> <b>".$user["name"]."</b>".
|
||||
|
@ -240,6 +369,20 @@ function telegramBotRouter() {
|
|||
}
|
||||
$message->reply($msg);
|
||||
});
|
||||
|
||||
$Bot->onCallbackQuery(function (CallbackQuery $callback_query) use ($Bot) {
|
||||
$user = $callback_query->from;
|
||||
$message = $callback_query->message;
|
||||
$chat = $message->chat;
|
||||
|
||||
if(strpos($callback_query->data, 'alert_') === 0) {
|
||||
$data = explode("_", str_replace("alert_", "", $callback_query->data));
|
||||
$alert_id = $data[1];
|
||||
|
||||
setAlertResponse($data[0] === "yes", getUserIdByFrom($user->id), $alert_id);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
$Bot->start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,15 +74,14 @@ $auth = new \Delight\Auth\Auth($db, $JWTconfig, get_ip(), DB_PREFIX."_");
|
|||
|
||||
final class Role
|
||||
{
|
||||
//https://github.com/delight-im/PHP-Auth/blob/master/src/Role.php
|
||||
const GUEST = \Delight\Auth\Role::AUTHOR;
|
||||
const BASIC_VIEWER = \Delight\Auth\Role::COLLABORATOR;
|
||||
const FULL_VIEWER = \Delight\Auth\Role::CONSULTANT;
|
||||
const EDITOR = \Delight\Auth\Role::CONSUMER;
|
||||
const SUPER_EDITOR = \Delight\Auth\Role::CONTRIBUTOR;
|
||||
const EDITOR = \Delight\Auth\Role::EDITOR;
|
||||
const SUPER_EDITOR = \Delight\Auth\Role::SUPER_EDITOR;
|
||||
|
||||
const DEVELOPER = \Delight\Auth\Role::DEVELOPER;
|
||||
const TESTER = \Delight\Auth\Role::CREATOR;
|
||||
|
||||
const GUEST = \Delight\Auth\Role::SUBSCRIBER;
|
||||
const EXTERNAL_VIEWER = \Delight\Auth\Role::REVIEWER;
|
||||
|
||||
const ADMIN = \Delight\Auth\Role::ADMIN;
|
||||
const SUPER_ADMIN = \Delight\Auth\Role::SUPER_ADMIN;
|
||||
|
||||
|
@ -92,6 +91,10 @@ final class Role
|
|||
|
||||
}
|
||||
|
||||
function get_timestamp() {
|
||||
return round(microtime(true) * 1000);
|
||||
}
|
||||
|
||||
function logger($action, $changed=null, $editor=null, $timestamp=null, $source_type="api")
|
||||
{
|
||||
global $db, $users;
|
||||
|
@ -191,7 +194,7 @@ class Users
|
|||
["hidden" => $hidden, "disabled" => $disabled, "name" => $name, "phone_number" => $phone_number, "chief" => $chief, "driver" => $driver]
|
||||
);
|
||||
if($chief == 1) {
|
||||
$this->auth->admin()->addRoleForUserById($userId, Role::FULL_VIEWER);
|
||||
$this->auth->admin()->addRoleForUserById($userId, Role::SUPER_EDITOR);
|
||||
}
|
||||
logger("User added", $userId, $inserted_by);
|
||||
return $userId;
|
||||
|
@ -205,7 +208,7 @@ class Users
|
|||
return $this->db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `hidden` = 0");
|
||||
}
|
||||
|
||||
public function get_user($id)
|
||||
public function getUserById($id)
|
||||
{
|
||||
return $this->db->selectRow("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `id` = ?", [$id]);
|
||||
}
|
||||
|
@ -233,16 +236,61 @@ class Users
|
|||
);
|
||||
}
|
||||
|
||||
public function generateToken($precedent_user_id = null)
|
||||
{
|
||||
$token_params = [
|
||||
"roles" => $this->auth->getRoles(),
|
||||
"name" => $this->getName(),
|
||||
"v" => 2
|
||||
];
|
||||
if(!is_null($precedent_user_id)) {
|
||||
$token_params["impersonating_user"] = true;
|
||||
$token_params["precedent_user_id"] = $precedent_user_id;
|
||||
}
|
||||
$token = $this->auth->generateJWTtoken($token_params);
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function loginAndReturnToken($username, $password)
|
||||
{
|
||||
$this->auth->loginWithUsername($username, $password);
|
||||
$token = $this->auth->generateJWTtoken([
|
||||
"full_viewer" => $this->hasRole(Role::FULL_VIEWER),
|
||||
"name" => $this->getName(),
|
||||
]);
|
||||
return $token;
|
||||
|
||||
if($this->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
|
||||
//Migrate to new user roles
|
||||
$this->auth->admin()->removeRoleForUserById($this->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
|
||||
$this->auth->admin()->addRoleForUserById($this->auth->getUserId(), Role::SUPER_EDITOR);
|
||||
|
||||
$this->auth->loginWithUsername($username, $password);
|
||||
}
|
||||
|
||||
return $this->generateToken();
|
||||
}
|
||||
|
||||
public function loginAsUserIdAndReturnToken($userId)
|
||||
{
|
||||
$precedent_user_id = null;
|
||||
if(!is_null($this->auth->getUserId())) {
|
||||
if((int) $userId === (int) $this->auth->getUserId()) {
|
||||
return $this->generateToken();
|
||||
}
|
||||
$precedent_user_id = $this->auth->getUserId();
|
||||
$this->auth->logOut();
|
||||
}
|
||||
|
||||
$this->auth->admin()->logInAsUserById($userId);
|
||||
|
||||
if($this->auth->hasRole(\Delight\Auth\Role::CONSULTANT)) {
|
||||
//Migrate to new user roles
|
||||
$this->auth->admin()->removeRoleForUserById($this->auth->getUserId(), \Delight\Auth\Role::CONSULTANT);
|
||||
$this->auth->admin()->addRoleForUserById($this->auth->getUserId(), Role::SUPER_EDITOR);
|
||||
|
||||
$this->auth->admin()->logInAsUserById($userId);
|
||||
}
|
||||
|
||||
return $this->generateToken($precedent_user_id);
|
||||
}
|
||||
|
||||
|
||||
public function isHidden($id=null)
|
||||
{
|
||||
if(is_null($id)) $id = $this->auth->getUserId();
|
||||
|
@ -303,10 +351,10 @@ class Availability {
|
|||
$available_users_count = $this->db->selectValue("SELECT COUNT(id) FROM `".DB_PREFIX."_profiles` WHERE `available` = 1 AND `hidden` = 0");
|
||||
if($available_users_count === 5) {
|
||||
sendTelegramNotification("🚒 Distaccamento operativo con squadra completa");
|
||||
} else if($available_users_count === 2) {
|
||||
sendTelegramNotification("🧯 Distaccamento operativo per supporto");
|
||||
} else if($available_users_count === 1 && !$availability) {
|
||||
} else if($available_users_count < 2) {
|
||||
sendTelegramNotification("⚠️ Distaccamento non operativo");
|
||||
} else if($available_users_count < 5) {
|
||||
sendTelegramNotification("🧯 Distaccamento operativo per supporto");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"@asymmetrik/ngx-leaflet": "^8.1.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "11.0.0",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"leaflet": "^1.7.1",
|
||||
|
@ -2467,6 +2469,31 @@
|
|||
"webpack": "^5.30.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ngx-translate/core": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz",
|
||||
"integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": ">=13.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ngx-translate/http-loader": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz",
|
||||
"integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=13.0.0",
|
||||
"@ngx-translate/core": ">=14.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -14331,6 +14358,22 @@
|
|||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@ngx-translate/core": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz",
|
||||
"integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@ngx-translate/http-loader": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz",
|
||||
"integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==",
|
||||
"requires": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
"@asymmetrik/ngx-leaflet": "^8.1.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@ng-bootstrap/ng-bootstrap": "11.0.0",
|
||||
"@ngx-translate/core": "^14.0.0",
|
||||
"@ngx-translate/http-loader": "^7.0.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"leaflet": "^1.7.1",
|
||||
|
|
|
@ -1 +1 @@
|
|||
<button (click)="locationBackService.goBack()" id="backBtn" title="Go back"><i class="fas fa-arrow-left"></i> Torna indietro</button>
|
||||
<button (click)="locationBackService.goBack()" id="backBtn" [title]="'go_back'|translate|titlecase"><i class="fas fa-arrow-left"></i> {{ 'go_back'|translate|titlecase }}</button>
|
|
@ -1,5 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { BackBtnComponent } from './back-btn.component';
|
||||
|
||||
|
@ -8,7 +9,8 @@ import { BackBtnComponent } from './back-btn.component';
|
|||
BackBtnComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule
|
||||
CommonModule,
|
||||
TranslationModule
|
||||
],
|
||||
exports: [
|
||||
BackBtnComponent
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="input-group">
|
||||
<input type="text" [disabled]="disabled" placeholder="Premi per selezionare data" class="form-control date-picker"
|
||||
<input type="text" [disabled]="disabled" [placeholder]="'press_to_select_a_date'|translate|titlecase" class="form-control date-picker"
|
||||
bsDatepicker [bsConfig]="{ adaptivePosition: true, dateInputFormat: 'DD/MM/YYYY' }" [(ngModel)]="date" (ngModelChange)=updateValue()>
|
||||
<input type="time" [disabled]="disabled" class="form-control" [(ngModel)]="time" (change)=updateValue()>
|
||||
</div>
|
|
@ -27,7 +27,7 @@ export class DatetimePickerComponent implements OnInit, ControlValueAccessor {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.localeService.use('it');
|
||||
this.localeService.use(window.navigator.language.split("-")[0]);
|
||||
}
|
||||
|
||||
get value(): Date {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { DatetimePickerComponent } from './datetime-picker.component';
|
||||
|
||||
|
@ -13,7 +14,8 @@ import { DatetimePickerComponent } from './datetime-picker.component';
|
|||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
BsDatepickerModule.forRoot()
|
||||
BsDatepickerModule.forRoot(),
|
||||
TranslationModule
|
||||
],
|
||||
exports: [
|
||||
DatetimePickerComponent
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
<div class="text-center">
|
||||
<h3 *ngIf="available !== undefined">Attualmente sei: <b>{{ available ? "Disponibile" : "Non disponibile" }}{{ manual_mode ? "" : " (programmato)" }}</b></h3>
|
||||
<div id="availability-btn-group">
|
||||
<button (click)="changeAvailibility(1)" type="button" [delay]="1000" tooltip="Cambia la tua disponibilità in 'attivo'" id="activate-btn" class="btn btn-lg btn-success me-1">Attiva</button>
|
||||
<button (click)="changeAvailibility(0)" type="button" [delay]="1000" tooltip="Cambia la tua disponibilità in 'non attivo'" id="deactivate-btn" class="btn btn-lg btn-danger">Disattiva</button>
|
||||
</div>
|
||||
<ng-container *ngIf="manual_mode !== undefined">
|
||||
<button type="button" class="btn btn-secondary" *ngIf="manual_mode" (click)="updateManualMode(0)">
|
||||
Attiva programmazione oraria
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" *ngIf="!manual_mode" (click)="updateManualMode(1)">
|
||||
Disattiva programmazione oraria
|
||||
</button>
|
||||
<br>
|
||||
</ng-container>
|
||||
<button type="button" class="btn btn-lg" (click)="openScheduleModal()">
|
||||
Modifica orari disponibilità
|
||||
</button>
|
||||
</div>
|
||||
<owner-image></owner-image>
|
||||
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" #table></app-table>
|
||||
<div class="text-center">
|
||||
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">Collega l'account al bot Telegram</button>
|
||||
</div>
|
|
@ -3,8 +3,8 @@
|
|||
</div>
|
||||
<div id="search" class="mt-2 mb-3">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" placeholder="Luogo" [(ngModel)]="placeName" (keyup.enter)="searchPlace()">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="searchPlace()">Cerca</button>
|
||||
<input type="text" class="form-control" [placeholder]="'place'|translate|titlecase" [(ngModel)]="placeName" (keyup.enter)="searchPlace()">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="searchPlace()">{{ 'search'|translate|titlecase }}</button>
|
||||
</div>
|
||||
<div id="results" *ngIf="isPlaceSearchResultsOpen">
|
||||
<li *ngFor="let result of placeSearchResults" (click)="selectPlace(result)">{{ result.display_name }}</li>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { LatLng, latLng, tileLayer, Marker, Map } from 'leaflet';
|
||||
import "leaflet.locatecontrol";
|
||||
|
@ -10,8 +11,11 @@ import "leaflet.locatecontrol";
|
|||
styleUrls: ['./map-picker.component.scss']
|
||||
})
|
||||
export class MapPickerComponent implements OnInit {
|
||||
@Input() lat = 45.88283872530;
|
||||
@Input() lng = 10.18226623535;
|
||||
lat = 45.88283872530;
|
||||
lng = 10.18226623535;
|
||||
|
||||
@Input() selectLat = "";
|
||||
@Input() selectLng = "";
|
||||
|
||||
@Output() onMarkerSet = new EventEmitter<any>();
|
||||
|
||||
|
@ -31,12 +35,17 @@ export class MapPickerComponent implements OnInit {
|
|||
isPlaceSearchResultsOpen = false;
|
||||
placeSearchResults: any[] = [];
|
||||
|
||||
constructor(private toastr: ToastrService, private api: ApiClientService) {
|
||||
constructor(private toastr: ToastrService, private api: ApiClientService, private translate: TranslateService) {
|
||||
this.marker = (window as any).L.marker(latLng(0,0));
|
||||
this.map = undefined as unknown as Map;
|
||||
}
|
||||
|
||||
ngOnInit(): void { }
|
||||
ngOnInit() {
|
||||
if(this.selectLat !== "" && this.selectLng !== "") {
|
||||
console.log(this.selectLat, this.selectLng);
|
||||
this.setMarker(latLng(parseFloat(this.selectLat), parseFloat(this.selectLng)));
|
||||
}
|
||||
}
|
||||
|
||||
setMarker(latLng: LatLng) {
|
||||
this.onMarkerSet.emit({
|
||||
|
@ -87,7 +96,9 @@ export class MapPickerComponent implements OnInit {
|
|||
|
||||
searchPlace() {
|
||||
if(this.placeName.length < 3) {
|
||||
this.toastr.error("Il nome della località deve essere di almeno 3 caratteri");
|
||||
this.translate.get('map_picker.place_min_length').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.api.get("places/search", {
|
||||
|
@ -97,7 +108,9 @@ export class MapPickerComponent implements OnInit {
|
|||
this.placeSearchResults = places;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.toastr.error("Errore di caricamento dei risultati della ricerca. Riprovare più tardi");
|
||||
this.translate.get('map_picker.loading_error').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { MapPickerComponent } from './map-picker.component';
|
||||
|
||||
|
@ -13,7 +14,8 @@ import { MapPickerComponent } from './map-picker.component';
|
|||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
LeafletModule
|
||||
LeafletModule,
|
||||
TranslationModule
|
||||
],
|
||||
exports: [
|
||||
MapPickerComponent
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title pull-left">Stato dell'allerta</h4>
|
||||
<button type="button" class="btn-close close pull-right" [attr.aria-label]="'close'|translate|titlecase" (click)="bsModalRef.hide()">
|
||||
<span aria-hidden="true" class="visually-hidden">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="id == 0">
|
||||
<div class="d-flex justify-content-center mt-2 pt-2 mb-3">
|
||||
<div class="spinner spinner-border"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="id !== 0">
|
||||
<table class="table table-border table-striped w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Nome</td>
|
||||
<td colspan="2">Stato risposta</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let user of users">
|
||||
<tr *ngIf="alertEnabled || user.response !== 'waiting'">
|
||||
<td>
|
||||
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="user.chief">
|
||||
<img alt="red helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!user.chief">
|
||||
{{ user.name }}
|
||||
<img alt="driver" src="./assets/icons/wheel.png" width="20px" *ngIf="user.driver">
|
||||
</td>
|
||||
<ng-container *ngIf="user.response == 'waiting'">
|
||||
<td style="width: 1px;"><i class="fas fa-spinner fa-spin"></i></td>
|
||||
<td>In attesa di risposta</td>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="user.response == true">
|
||||
<td style="width: 1px;"><i class="fa fa-check" style="color:green"></i></td>
|
||||
<td>Presente</td>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="user.response == false">
|
||||
<td style="width: 1px;"><i class="fa fa-times" style="color:red"></i></td>
|
||||
<td>Non presente</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-container *ngIf="auth.profile.hasRole('SUPER_EDITOR') && alertEnabled">
|
||||
<button type="button" class="btn btn-primary mb-2" (click)="isAdvancedCollapsed = !isAdvancedCollapsed"
|
||||
[attr.aria-expanded]="!isAdvancedCollapsed" aria-controls="collapseBasic">
|
||||
<ng-container *ngIf="isAdvancedCollapsed">Mostra impostazioni avanzate</ng-container>
|
||||
<ng-container *ngIf="!isAdvancedCollapsed">Nascondi impostazioni avanzate</ng-container>
|
||||
</button>
|
||||
<div [collapse]="isAdvancedCollapsed" [isAnimated]="true">
|
||||
<div class="well well-lg card card-block card-header">
|
||||
<label for="details" class="form-label">Dettagli allerta</label>
|
||||
<textarea class="form-control" id="details" rows="3" [(ngModel)]="notes"></textarea>
|
||||
<button class="btn btn-secondary mt-2" (click)="saveAlertSettings()">Salva</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(!auth.profile.hasRole('SUPER_EDITOR') && notes !== '') || !alertEnabled">
|
||||
<div class="well well-lg card card-block card-header">
|
||||
<h5>Dettagli allerta</h5>
|
||||
<h2>{{ notes }}</h2>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger" (click)="deleteAlert()" *ngIf="auth.profile.hasRole('SUPER_EDITOR') && alertEnabled">Rimuovi allerta corrente <i class="fas fa-exclamation-triangle"></i></button>
|
||||
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">{{ 'close'|translate }}</button>
|
||||
</div>
|
|
@ -0,0 +1,101 @@
|
|||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { AuthService } from 'src/app/_services/auth.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
const isEqual = (...objects: any[]) => objects.every(obj => JSON.stringify(obj) === JSON.stringify(objects[0]));
|
||||
|
||||
@Component({
|
||||
selector: 'modal-alert',
|
||||
templateUrl: './modal-alert.component.html',
|
||||
styleUrls: ['./modal-alert.component.scss']
|
||||
})
|
||||
export class ModalAlertComponent implements OnInit, OnDestroy {
|
||||
id = 0;
|
||||
|
||||
users: any[] = [];
|
||||
|
||||
isAdvancedCollapsed = true;
|
||||
loadDataInterval: NodeJS.Timer | undefined = undefined;
|
||||
|
||||
notes = "";
|
||||
|
||||
alertEnabled = true;
|
||||
|
||||
constructor(
|
||||
public bsModalRef: BsModalRef,
|
||||
private api: ApiClientService,
|
||||
public auth: AuthService,
|
||||
private toastr: ToastrService
|
||||
) { }
|
||||
|
||||
loadResponsesData() {
|
||||
this.api.get(`alerts/${this.id}`).then((response) => {
|
||||
if(this.alertEnabled !== response.enabled) this.alertEnabled = response.enabled;
|
||||
if(!isEqual(this.users, response.crew)) this.users = response.crew;
|
||||
if (this.notes === "" || this.notes === null) {
|
||||
if(!isEqual(this.notes, response.notes)) this.notes = response.notes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadDataInterval = setInterval(() => {
|
||||
if (typeof (window as any).skipTableReload !== 'undefined' && (window as any).skipTableReload) {
|
||||
return;
|
||||
}
|
||||
console.log("Refreshing responses data...");
|
||||
this.loadResponsesData();
|
||||
}, 2000);
|
||||
this.loadResponsesData();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.loadDataInterval) {
|
||||
console.log("Clearing interval...");
|
||||
clearInterval(this.loadDataInterval);
|
||||
}
|
||||
}
|
||||
|
||||
saveAlertSettings() {
|
||||
if(!this.auth.profile.hasRole('SUPER_EDITOR')) return;
|
||||
this.api.post(`alerts/${this.id}/settings`, {
|
||||
notes: this.notes
|
||||
}).then((response) => {
|
||||
this.toastr.success("Impostazioni salvate con successo");
|
||||
});
|
||||
}
|
||||
|
||||
deleteAlert() {
|
||||
if(!this.auth.profile.hasRole('SUPER_EDITOR')) return;
|
||||
Swal.fire({
|
||||
title: "Sei sicuro di voler ritirare l'allarme?",
|
||||
text: "I vigili verranno avvisati dell'azione",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: "Si, rimuovi",
|
||||
cancelButtonText: "Annulla"
|
||||
}).then((result: any) => {
|
||||
if (result.isConfirmed) {
|
||||
this.api.delete(`alerts/${this.id}`).then((response) => {
|
||||
console.log(response);
|
||||
this.bsModalRef.hide();
|
||||
this.api.alertsChanged.next();
|
||||
/*
|
||||
this.translate.get('table.service_deleted_successfully').subscribe((res: string) => {
|
||||
this.toastr.success(res);
|
||||
});
|
||||
this.loadTableData();
|
||||
}).catch((e) => {
|
||||
this.translate.get('table.service_deleted_error').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
*/
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title pull-left">Modifica orari disponibilità</h4>
|
||||
<button type="button" class="btn-close close pull-right" aria-label="Close" (click)="bsModalRef.hide()">
|
||||
<h4 class="modal-title pull-left">{{ 'update_availability_schedule'|translate }}</h4>
|
||||
<button type="button" class="btn-close close pull-right" [attr.aria-label]="'close'|translate|titlecase" (click)="bsModalRef.hide()">
|
||||
<span aria-hidden="true" class="visually-hidden">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -11,7 +11,7 @@
|
|||
<td style="background-color: white;"></td>
|
||||
<ng-container *ngIf="orientation === 'portrait'">
|
||||
<ng-container *ngFor="let day of days; let i = index">
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short }}</td>
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short|translate }}</td>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orientation === 'landscape'">
|
||||
|
@ -34,7 +34,7 @@
|
|||
<tbody id="scheduler_body" *ngIf="orientation === 'landscape'">
|
||||
<ng-container *ngFor="let day of days; let i = index">
|
||||
<tr>
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short }}</td>
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short|translate }}</td>
|
||||
<ng-container *ngFor="let hour of hours">
|
||||
<td class="hour-cell" [class.highlighted] = "isCellSelected(i, hour)" (mousedown)="mouseDownCell(i, hour)" (mouseup)="mouseUpCell()" (mouseover)="mouseOverCell(i, hour)"></td>
|
||||
</ng-container>
|
||||
|
@ -44,6 +44,6 @@
|
|||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" (click)="saveChanges()">Salva le modifiche</button>
|
||||
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">Chiudi</button>
|
||||
<button type="button" class="btn btn-primary" (click)="saveChanges()">{{ 'save_changes'|translate }}</button>
|
||||
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">{{ 'close'|translate }}</button>
|
||||
</div>
|
|
@ -13,32 +13,32 @@ export class ModalAvailabilityScheduleComponent implements OnInit {
|
|||
|
||||
public days = [
|
||||
{
|
||||
name: 'Lunedì',
|
||||
short: 'Lun'
|
||||
name: 'monday',
|
||||
short: 'monday_short'
|
||||
},
|
||||
{
|
||||
name: 'Martedì',
|
||||
short: 'Mar'
|
||||
name: 'tuesday',
|
||||
short: 'tuesday_short'
|
||||
},
|
||||
{
|
||||
name: 'Mercoledì',
|
||||
short: 'Mer'
|
||||
name: 'wednesday',
|
||||
short: 'wednesday_short'
|
||||
},
|
||||
{
|
||||
name: 'Giovedì',
|
||||
short: 'Gio'
|
||||
name: 'thursday',
|
||||
short: 'thursday_short'
|
||||
},
|
||||
{
|
||||
name: 'Venerdì',
|
||||
short: 'Ven'
|
||||
name: 'friday',
|
||||
short: 'friday_short'
|
||||
},
|
||||
{
|
||||
name: 'Sabato',
|
||||
short: 'Sab'
|
||||
name: 'saturday',
|
||||
short: 'saturday_short'
|
||||
},
|
||||
{
|
||||
name: 'Domenica',
|
||||
short: 'Dom'
|
||||
name: 'sunday',
|
||||
short: 'sunday_short'
|
||||
}
|
||||
];
|
||||
public hours = [
|
||||
|
|
|
@ -2,19 +2,20 @@
|
|||
<table *ngIf="sourceType === 'list'" id="table" class="table table-striped table-bordered dt-responsive nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Disponibile</th>
|
||||
<th>Autista</th>
|
||||
<ng-container *ngIf="auth.profile.full_viewer">
|
||||
<th>Chiama</th>
|
||||
<th>{{ 'name'|translate|titlecase }}</th>
|
||||
<th>{{ 'available'|translate|titlecase }}</th>
|
||||
<th>{{ 'driver'|translate|titlecase }}</th>
|
||||
<ng-container *ngIf="auth.profile.hasRole('SUPER_EDITOR')">
|
||||
<th>{{ 'call'|translate|titlecase }}</th>
|
||||
</ng-container>
|
||||
<th>Interventi</th>
|
||||
<th>Minuti disponibilità</th>
|
||||
<th>{{ 'services'|translate|titlecase }}</th>
|
||||
<th>{{ 'availability_minutes'|translate|titlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
<tr *ngFor="let row of data">
|
||||
<td>
|
||||
<i *ngIf="auth.profile.hasRole('SUPER_ADMIN') && row.id !== auth.profile.auth_user_id" class="fa fa-user me-2" (click)="onUserImpersonate(row.id)"></i>
|
||||
<img alt="red helmet" src="./assets/icons/red_helmet.png" width="20px" *ngIf="row.chief">
|
||||
<img alt="red helmet" src="./assets/icons/black_helmet.png" width="20px" *ngIf="!row.chief">
|
||||
<ng-container *ngIf="(getTime() - row.online_time) < 30"><u>{{ row.name }}</u></ng-container>
|
||||
|
@ -27,7 +28,7 @@
|
|||
<td>
|
||||
<img alt="driver" src="./assets/icons/wheel.png" width="20px" *ngIf="row.driver">
|
||||
</td>
|
||||
<td *ngIf="auth.profile.full_viewer">
|
||||
<td *ngIf="auth.profile.hasRole('SUPER_EDITOR')">
|
||||
<a href="tel:{{row.phone_number}}"><i class="fa fa-phone"></i></a>
|
||||
</td>
|
||||
<td>{{ row.services }}</td>
|
||||
|
@ -38,10 +39,10 @@
|
|||
<table *ngIf="sourceType === 'logs'" id="table" class="table table-striped table-bordered dt-responsive nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Azione</th>
|
||||
<th>Interessato</th>
|
||||
<th>Fatto da</th>
|
||||
<th>Data e ora</th>
|
||||
<th>{{ 'action'|translate|titlecase }}</th>
|
||||
<th>{{ 'changed'|translate|titlecase }}</th>
|
||||
<th>{{ 'editor'|translate|titlecase }}</th>
|
||||
<th>{{ 'datetime'|translate|titlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
|
@ -56,31 +57,36 @@
|
|||
<table *ngIf="sourceType === 'services'" id="table" class="table table-striped table-bordered dt-responsive nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Inizio</th>
|
||||
<th>Fine</th>
|
||||
<th>Codice</th>
|
||||
<th>Caposquadra</th>
|
||||
<th>Autisti</th>
|
||||
<th>Altre persone</th>
|
||||
<th>Luogo</th>
|
||||
<th>Note</th>
|
||||
<th>Tipo</th>
|
||||
<th hidden>Modifica</th>
|
||||
<th>Rimuovi</th>
|
||||
<th>#</th>
|
||||
<th>{{ 'start'|translate|titlecase }}</th>
|
||||
<th>{{ 'end'|translate|titlecase }}</th>
|
||||
<th>{{ 'code'|translate|titlecase }}</th>
|
||||
<th>{{ 'chief'|translate|titlecase }}</th>
|
||||
<th>{{ 'drivers'|translate|titlecase }}</th>
|
||||
<th>{{ 'crew'|translate|titlecase }}</th>
|
||||
<th>{{ 'place'|translate|titlecase }}</th>
|
||||
<th>{{ 'notes'|translate|titlecase }}</th>
|
||||
<th>{{ 'type'|translate|titlecase }}</th>
|
||||
<th>{{ 'update'|translate|titlecase }}</th>
|
||||
<th>{{ 'remove'|translate|titlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
<tr *ngFor="let row of data">
|
||||
<tr *ngFor="let row of data; index as i">
|
||||
<td>{{ data.length - i }}</td>
|
||||
<td>{{ row.start | date:'dd/MM/YYYY, HH:mm' }}</td>
|
||||
<td>{{ row.end | date:'dd/MM/YYYY, HH:mm' }}</td>
|
||||
<td>{{ row.code }}</td>
|
||||
<td>{{ row.chief }}</td>
|
||||
<td>{{ row.drivers }}</td>
|
||||
<td>{{ row.crew }}</td>
|
||||
<td>{{ row.place_name }}</td>
|
||||
<td>
|
||||
{{ row.place_name }}<br>
|
||||
<a class="place_details_link cursor-pointer" (click)="openPlaceDetails(row.lat, row.lng)">{{ 'more details'|translate|titlecase }}</a>
|
||||
</td>
|
||||
<td>{{ row.notes }}</td>
|
||||
<td>{{ row.type }}</td>
|
||||
<td hidden><i class="fa fa-edit"></i></td>
|
||||
<td (click)="editService(row.id)"><i class="fa fa-edit"></i></td>
|
||||
<td (click)="deleteService(row.id)"><i class="fa fa-trash"></i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -88,21 +94,21 @@
|
|||
<table *ngIf="sourceType === 'trainings'" id="table" class="table table-striped table-bordered dt-responsive nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Nome</th>
|
||||
<th>Tempo inizio</th>
|
||||
<th>Tempo fine</th>
|
||||
<th>Caposquadra</th>
|
||||
<th>Altre persone</th>
|
||||
<th>Luogo</th>
|
||||
<th>Note</th>
|
||||
<th hidden>Modifica</th>
|
||||
<th hidden>Rimuovi</th>
|
||||
<td>#</td>
|
||||
<th>{{ 'name'|translate|titlecase }}</th>
|
||||
<th>{{ 'start'|translate|titlecase }}</th>
|
||||
<th>{{ 'end'|translate|titlecase }}</th>
|
||||
<th>{{ 'chief'|translate|titlecase }}</th>
|
||||
<th>{{ 'crew'|translate|titlecase }}</th>
|
||||
<th>{{ 'place'|translate|titlecase }}</th>
|
||||
<th>{{ 'notes'|translate|titlecase }}</th>
|
||||
<th>{{ 'update'|translate|titlecase }}</th>
|
||||
<th>{{ 'remove'|translate|titlecase }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table_body">
|
||||
<tr *ngFor="let row of data">
|
||||
<td>{{ row.date | date: 'MM/dd/yyyy HH:mm' }}</td>
|
||||
<tr *ngFor="let row of data; index as i">
|
||||
<td>{{ data.length - i }}</td>
|
||||
<td>{{ row.name }}</td>
|
||||
<td>{{ row.beginning }}</td>
|
||||
<td>{{ row.end }}</td>
|
||||
|
@ -110,8 +116,8 @@
|
|||
<td>{{ row.crew }}</td>
|
||||
<td>{{ row.place }}</td>
|
||||
<td>{{ row.notes }}</td>
|
||||
<td hidden><i class="fa fa-edit"></i></td>
|
||||
<td hidden><i class="fa fa-trash"></i></td>
|
||||
<td><i class="fa fa-edit"></i></td>
|
||||
<td><i class="fa fa-trash"></i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -27,3 +27,8 @@ img {
|
|||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.place_details_link {
|
||||
text-decoration: underline;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Router } from '@angular/router';
|
|||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { AuthService } from '../../_services/auth.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
@Component({
|
||||
|
@ -16,6 +17,7 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
@Input() refreshInterval?: number;
|
||||
|
||||
@Output() changeAvailability: EventEmitter<{user: number, newState: 0|1}> = new EventEmitter<{user: number, newState: 0|1}>();
|
||||
@Output() userImpersonate: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
||||
public data: any = [];
|
||||
|
||||
|
@ -25,7 +27,8 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
private api: ApiClientService,
|
||||
public auth: AuthService,
|
||||
private router: Router,
|
||||
private toastr: ToastrService
|
||||
private toastr: ToastrService,
|
||||
private translate: TranslateService
|
||||
) { }
|
||||
|
||||
getTime() {
|
||||
|
@ -33,12 +36,13 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
loadTableData() {
|
||||
this.api.get(this.sourceType || "list").then((data: any) => {
|
||||
if(!this.sourceType) this.sourceType = "list";
|
||||
this.api.get(this.sourceType).then((data: any) => {
|
||||
console.log(data);
|
||||
this.data = data.filter((row: any) => {
|
||||
if(typeof row.hidden !== 'undefined') return !row.hidden;
|
||||
return true;
|
||||
});
|
||||
this.data = data.filter((row: any) => typeof row.hidden !== 'undefined' ? !row.hidden : true);
|
||||
if(this.sourceType === 'list') {
|
||||
this.api.availableUsers = this.data.filter((row: any) => row.available).length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -52,6 +56,9 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
console.log("Refreshing data...");
|
||||
this.loadTableData();
|
||||
}, this.refreshInterval || 10000);
|
||||
this.auth.authChanged.subscribe({
|
||||
next: () => this.loadTableData()
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -61,35 +68,55 @@ export class TableComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
onChangeAvailability(user: number, newState: 0|1) {
|
||||
if(this.auth.profile.full_viewer) {
|
||||
if(this.auth.profile.hasRole('SUPER_EDITOR')) {
|
||||
this.changeAvailability.emit({user, newState});
|
||||
}
|
||||
}
|
||||
|
||||
onUserImpersonate(user: number) {
|
||||
if(this.auth.profile.hasRole('SUPER_ADMIN')) {
|
||||
this.auth.impersonate(user).then((user_id) => {
|
||||
this.loadTableData();
|
||||
this.userImpersonate.emit(user_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openPlaceDetails(lat: number, lng: number) {
|
||||
this.router.navigate(['/place-details', lat, lng]);
|
||||
}
|
||||
|
||||
editService(id: number) {
|
||||
this.router.navigate(['/services', id]);
|
||||
}
|
||||
|
||||
deleteService(id: number) {
|
||||
console.log(id);
|
||||
Swal.fire({
|
||||
title: 'Sei del tutto sicuro di voler rimuovere l\'intervento?',
|
||||
text: "Gli interventi eliminati non si possono recuperare.",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: 'Si, rimuovilo',
|
||||
cancelButtonText: 'Annulla'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
this.api.delete(`services/${id}`).then((response) => {
|
||||
this.toastr.success('Intervento rimosso con successo.');
|
||||
this.loadTableData();
|
||||
}).catch((e) => {
|
||||
this.toastr.error('Errore durante la rimozione dell\'intervento.');
|
||||
});
|
||||
}
|
||||
})
|
||||
this.translate.get(['table.yes_remove', 'table.cancel', 'table.remove_service_confirm', 'table.remove_service_text']).subscribe((res: { [key: string]: string; }) => {
|
||||
console.log(res);
|
||||
Swal.fire({
|
||||
title: res['table.remove_service_confirm'],
|
||||
text: res['table.remove_service_confirm_text'],
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
cancelButtonColor: '#d33',
|
||||
confirmButtonText: res['table.yes_remove'],
|
||||
cancelButtonText: res['table.cancel']
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
this.api.delete(`services/${id}`).then((response) => {
|
||||
this.translate.get('table.service_deleted_successfully').subscribe((res: string) => {
|
||||
this.toastr.success(res);
|
||||
});
|
||||
this.loadTableData();
|
||||
}).catch((e) => {
|
||||
this.translate.get('table.service_deleted_error').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<owner-image></owner-image>
|
||||
<div class="text-center mb-4">
|
||||
<button type="button" class="btn btn-primary" disabled>Aggiungi esercitazione</button>
|
||||
</div>
|
||||
<app-table [sourceType]="'trainings'" [refreshInterval]="1200000"></app-table>
|
|
@ -0,0 +1,72 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||
import { AuthService } from '../_services/auth.service';
|
||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { catchError, filter, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject: BehaviorSubject<string|undefined> = new BehaviorSubject<string|undefined>(undefined);
|
||||
|
||||
constructor(private auth: AuthService) { }
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
|
||||
const token = this.auth.getToken();
|
||||
let authReq = this.addHeaders(req, token);
|
||||
|
||||
return next.handle(authReq).pipe(catchError(error => {
|
||||
if (error instanceof HttpErrorResponse && !authReq.url.includes('login')) {
|
||||
if(error.status === 400) {
|
||||
return this.handle400Error(authReq, next);
|
||||
} else if (error.status === 401) {
|
||||
this.auth.logout();
|
||||
}
|
||||
}
|
||||
return throwError(() => new Error(error));
|
||||
}));
|
||||
}
|
||||
|
||||
private handle400Error(request: HttpRequest<any>, next: HttpHandler) {
|
||||
if (!this.isRefreshing) {
|
||||
this.isRefreshing = true;
|
||||
this.refreshTokenSubject.next(undefined);
|
||||
return this.auth.refreshToken().pipe(
|
||||
switchMap((token: string) => {
|
||||
this.isRefreshing = false;
|
||||
this.refreshTokenSubject.next(token);
|
||||
|
||||
return next.handle(this.addHeaders(request, token));
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.isRefreshing = false;
|
||||
this.auth.logout();
|
||||
return throwError(() => new Error(err));
|
||||
})
|
||||
);
|
||||
}
|
||||
return this.refreshTokenSubject.pipe(
|
||||
filter(token => token !== undefined),
|
||||
take(1),
|
||||
switchMap((token) => {
|
||||
return next.handle(this.addHeaders(request, token));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private addHeaders(request: HttpRequest<any>, token: string|undefined) {
|
||||
if (typeof token === 'string' && token.length > 10) {
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': `Bearer ${token}`
|
||||
});
|
||||
return request.clone({ headers });
|
||||
} else {
|
||||
const headers = new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
});
|
||||
return request.clone({ headers });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { AuthService } from '../_services/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class UnauthorizedInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) { }
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(request).pipe( tap({
|
||||
next: () => {},
|
||||
error: (err: any) => {
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
if (err.status !== 401 || request.url.includes('/login')) {
|
||||
return;
|
||||
}
|
||||
console.log("Login required");
|
||||
this.auth.logout();
|
||||
}
|
||||
}}));
|
||||
}
|
||||
}
|
|
@ -3,29 +3,29 @@
|
|||
<form method="post" [formGroup]="serviceForm" (ngSubmit)="formSubmit()">
|
||||
<div class="container">
|
||||
<div class="form-group has-validation">
|
||||
<label for="date-picker">Inizio</label>
|
||||
<label for="date-picker">{{ 'start'|translate|titlecase }}</label>
|
||||
<datetime-picker formControlName="start" [class.is-invalid]="!isFieldValid('start')"></datetime-picker>
|
||||
<div class="invalid-feedback" *ngIf="start.errors?.['required']">
|
||||
Seleziona data e ora di inizio dell'intervento
|
||||
<div class="invalid-feedback" *ngIf="start.errors?.['required']" translate>
|
||||
edit_service.select_start_datetime
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group has-validation">
|
||||
<label for="date-picker">Fine</label>
|
||||
<label for="date-picker">{{ 'end'|translate|titlecase }}</label>
|
||||
<datetime-picker formControlName="end" [class.is-invalid]="!isFieldValid('end')"></datetime-picker>
|
||||
<div class="invalid-feedback" *ngIf="end.errors?.['required']">
|
||||
Seleziona data e ora di fine dell'intervento
|
||||
<div class="invalid-feedback" *ngIf="end.errors?.['required']" translate>
|
||||
edit_service.select_end_datetime
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group has-validation">
|
||||
<label for="progressivo">Progressivo</label>
|
||||
<input formControlName="code" [class.is-invalid]="!isFieldValid('code')" id="progressivo" class="form-control"
|
||||
<label for="code">{{ 'code'|translate|titlecase }}</label>
|
||||
<input formControlName="code" [class.is-invalid]="!isFieldValid('code')" id="code" class="form-control"
|
||||
type="text" placeholder="1234/5">
|
||||
<div class="invalid-feedback" *ngIf="code.errors?.['required']">
|
||||
Inserisci il progressivo dell'intervento
|
||||
<div class="invalid-feedback" *ngIf="code.errors?.['required']" translate>
|
||||
edit_service.insert_code
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group has-validation" [class.is-invalid-div]="!isFieldValid('chief')">
|
||||
<label>Caposquadra</label>
|
||||
<label>{{ 'chief'|translate|titlecase }}</label>
|
||||
<br>
|
||||
<ng-container *ngFor="let user of users">
|
||||
<div class="form-check">
|
||||
|
@ -38,7 +38,7 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
<div class="form-group has-validation" [class.is-invalid-div]="!isFieldValid('drivers')">
|
||||
<label>Autisti</label>
|
||||
<label>{{ 'drivers'|translate|titlecase }}</label>
|
||||
<br>
|
||||
<ng-container *ngFor="let user of users">
|
||||
<div class="form-check" *ngIf="user.driver">
|
||||
|
@ -51,7 +51,7 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
<div class="form-group has-validation" [class.is-invalid-div]="!isFieldValid('crew')">
|
||||
<label>Altri membri della squadra</label>
|
||||
<label translate>edit_service.other_crew_members</label>
|
||||
<br>
|
||||
<ng-container *ngFor="let user of users">
|
||||
<div class="form-check">
|
||||
|
@ -64,38 +64,39 @@
|
|||
</ng-container>
|
||||
</div>
|
||||
<div [class.is-invalid-div]="!isFieldValid('place')" class="mb-2">
|
||||
<label>Luogo dell'intervento</label>
|
||||
<map-picker (onMarkerSet)="setPlace($event.lat, $event.lng)"></map-picker>
|
||||
<label>{{ 'place'|translate|titlecase }}</label>
|
||||
<map-picker *ngIf="addingService" (onMarkerSet)="setPlace($event.lat, $event.lng)"></map-picker>
|
||||
<map-picker *ngIf="!addingService && loadedServiceLat !== ''" (onMarkerSet)="setPlace($event.lat, $event.lng)" [selectLat]="loadedServiceLat" [selectLng]="loadedServiceLng"></map-picker>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">Note (es. altre informazioni)</label><br>
|
||||
<label for="notes">{{ 'notes'|translate|titlecase }}</label><br>
|
||||
<textarea formControlName="notes" class="form-control" id="notes"></textarea>
|
||||
</div>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label>Tipologia</label>
|
||||
<label>{{ 'type'|translate|titlecase }}</label>
|
||||
<br>
|
||||
<div class="input-group has-validation">
|
||||
<select formControlName="type" [class.is-invalid]="!isFieldValid('type')" class="form-control mr-2">
|
||||
<option selected disabled>Seleziona tipologia..</option>
|
||||
<option selected disabled translate>edit_service.select_type</option>
|
||||
<option *ngFor="let service_type of types" value="{{ service_type.id }}">{{ service_type.name }}</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" type="button" tabindex="-1" (click)="addingType = true">
|
||||
Aggiungi
|
||||
{{ 'add'|translate|titlecase }}
|
||||
</button>
|
||||
<div class="invalid-feedback" *ngIf="type.errors?.['required']">
|
||||
Seleziona una tipologia di intervento
|
||||
edit_service.select_service_type
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-2 mt-2" *ngIf="addingType">
|
||||
<input type="text" class="form-control" placeholder="Nome della tipologia" [(ngModel)]="newType"
|
||||
<input type="text" class="form-control" [placeholder]="'type'|translate" [(ngModel)]="newType"
|
||||
[ngModelOptions]="{standalone: true}">
|
||||
<button class="btn btn-secondary" type="button" (click)="addType()">Invia</button>
|
||||
<button class="btn btn-secondary" type="button" (click)="addType()">{{ 'chief'|translate|titlecase }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button id="submit_button" type="submit" class="btn btn-primary" [disabled]="submittingForm">Invia</button>
|
||||
<button class="btn" type="button" (click)="formReset()" [disabled]="submittingForm">Reset</button>
|
||||
<button id="submit_button" type="submit" class="btn btn-primary" [disabled]="submittingForm">{{ 'submit'|translate|titlecase }}</button>
|
||||
<button class="btn" type="button" (click)="formReset()" [disabled]="submittingForm">{{ 'reset'|translate|titlecase }}</button>
|
||||
<div class="d-flex justify-content-center mt-2 pt-2 mb-3" *ngIf="submittingForm">
|
||||
<div class="spinner spinner-border"></div>
|
||||
</div>
|
|
@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-service',
|
||||
|
@ -23,6 +24,8 @@ export class EditServiceComponent implements OnInit {
|
|||
notes: '',
|
||||
type: ''
|
||||
};
|
||||
loadedServiceLat = "";
|
||||
loadedServiceLng = "";
|
||||
|
||||
users: any[] = [];
|
||||
types: any[] = [];
|
||||
|
@ -64,7 +67,8 @@ export class EditServiceComponent implements OnInit {
|
|||
private route: ActivatedRoute,
|
||||
private api: ApiClientService,
|
||||
private toastr: ToastrService,
|
||||
private fb: FormBuilder
|
||||
private fb: FormBuilder,
|
||||
private translate: TranslateService
|
||||
) {
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.serviceId = params.get('id') || undefined;
|
||||
|
@ -73,6 +77,8 @@ export class EditServiceComponent implements OnInit {
|
|||
} else {
|
||||
this.api.get(`services/${this.serviceId}`).then((service) => {
|
||||
this.loadedService = service;
|
||||
this.loadedServiceLat = service.lat;
|
||||
this.loadedServiceLng = service.lng;
|
||||
|
||||
let patch = Object.assign({}, service);
|
||||
patch.start = new Date(parseInt(patch.start));
|
||||
|
@ -100,11 +106,15 @@ export class EditServiceComponent implements OnInit {
|
|||
|
||||
addType() {
|
||||
if(this.newType.length < 2) {
|
||||
this.toastr.error("Il nome della tipologia deve essere lungo almeno 2 caratteri");
|
||||
this.translate.get('edit_service.type_must_be_two_characters_long').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(this.types.find(t => t.name == this.newType)) {
|
||||
this.toastr.error("Il nome della tipologia è già in uso");
|
||||
this.translate.get('edit_service.type_already_exists').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.api.post("service_types", {
|
||||
|
@ -113,8 +123,12 @@ export class EditServiceComponent implements OnInit {
|
|||
this.addingType = false;
|
||||
this.newType = "";
|
||||
console.log(type);
|
||||
if(type === 1) this.toastr.success("Tipologia di servizio aggiunta con successo.");
|
||||
this.loadTypes();
|
||||
if(type == 1) {
|
||||
this.translate.get('edit_service.type_added_successfully').subscribe((res: string) => {
|
||||
this.toastr.success(res);
|
||||
});
|
||||
this.loadTypes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -165,11 +179,15 @@ export class EditServiceComponent implements OnInit {
|
|||
console.log(values);
|
||||
this.api.post("services", values).then((res) => {
|
||||
console.log(res);
|
||||
this.toastr.success("Intervento aggiunto con successo.");
|
||||
this.translate.get('edit_service.service_added_successfully').subscribe((res: string) => {
|
||||
this.toastr.success(res);
|
||||
});
|
||||
this.submittingForm = false;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
this.toastr.error("Errore durante l'aggiunta dell'intervento");
|
||||
this.translate.get('edit_service.service_add_failed').subscribe((res: string) => {
|
||||
this.toastr.error(res);
|
||||
});
|
||||
this.submittingForm = false;
|
||||
});
|
||||
}
|
|
@ -3,9 +3,10 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
|
||||
import { MapPickerModule } from '../map-picker/map-picker.module';
|
||||
import { DatetimePickerModule } from '../datetime-picker/datetime-picker.module';
|
||||
import { BackBtnModule } from '../back-btn/back-btn.module';
|
||||
import { MapPickerModule } from '../../_components/map-picker/map-picker.module';
|
||||
import { DatetimePickerModule } from '../../_components/datetime-picker/datetime-picker.module';
|
||||
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { EditServiceRoutingModule } from './edit-service-routing.module';
|
||||
import { EditServiceComponent } from './edit-service.component';
|
||||
|
@ -22,7 +23,8 @@ import { EditServiceComponent } from './edit-service.component';
|
|||
BsDatepickerModule.forRoot(),
|
||||
MapPickerModule,
|
||||
DatetimePickerModule,
|
||||
BackBtnModule
|
||||
BackBtnModule,
|
||||
TranslationModule
|
||||
]
|
||||
})
|
||||
export class EditServiceModule { }
|
|
@ -0,0 +1,34 @@
|
|||
<div class="text-center">
|
||||
<h3 *ngIf="available !== undefined">{{ 'list.your_availability_is'|translate }} <b>{{ available ? ("available"|translate|uppercase) : ("unavailable"|translate|uppercase) }}{{ manual_mode ? "" : " ("+('programmed'|translate)+")" }}</b></h3>
|
||||
<div id="availability-btn-group">
|
||||
<button (click)="changeAvailibility(1)" type="button" [delay]="1000" [tooltip]="'tooltip_change_availability'|translate:{state: 'available'|translate}" id="activate-btn" class="btn btn-lg btn-success me-1">{{ 'set_available'|translate|titlecase }}</button>
|
||||
<button (click)="changeAvailibility(0)" type="button" [delay]="1000" [tooltip]="'tooltip_change_availability'|translate:{state: 'unavailable'|translate}" id="deactivate-btn" class="btn btn-lg btn-danger">{{ 'set_unavailable'|translate|titlecase }}</button>
|
||||
</div>
|
||||
<ng-container *ngIf="manual_mode !== undefined">
|
||||
<button type="button" class="btn btn-secondary" *ngIf="manual_mode" (click)="updateManualMode(0)">
|
||||
{{ 'list.enable_schedules'|translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" *ngIf="!manual_mode" (click)="updateManualMode(1)">
|
||||
{{ 'list.disable_schedules'|translate }}
|
||||
</button>
|
||||
<br>
|
||||
</ng-container>
|
||||
<button type="button" class="btn btn-lg" (click)="openScheduleModal()">
|
||||
{{ 'list.update_schedules'|translate }}
|
||||
</button>
|
||||
</div>
|
||||
<owner-image></owner-image>
|
||||
<div class="text-center" *ngIf="auth.profile.hasRole('SUPER_EDITOR')">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-danger" (click)="addAlertFull()" [disabled]="!api?.availableUsers || api.availableUsers! < 5 || alertLoading">
|
||||
🚒 Richiedi squadra completa
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning" (click)="addAlertSupport()" [disabled]="!api?.availableUsers || api.availableUsers! < 2 || alertLoading">
|
||||
Richiedi squadra di supporto 🧯
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" #table></app-table>
|
||||
<div class="text-center">
|
||||
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
|
||||
</div>
|
|
@ -1,9 +1,11 @@
|
|||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { TableComponent } from '../table/table.component';
|
||||
import { ModalAvailabilityScheduleComponent } from '../modal-availability-schedule/modal-availability-schedule.component';
|
||||
import { TableComponent } from '../../_components/table/table.component';
|
||||
import { ModalAvailabilityScheduleComponent } from '../../_components/modal-availability-schedule/modal-availability-schedule.component';
|
||||
import { ModalAlertComponent } from 'src/app/_components/modal-alert/modal-alert.component';
|
||||
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { BsModalService, BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AuthService } from 'src/app/_services/auth.service';
|
||||
|
||||
@Component({
|
||||
|
@ -11,8 +13,9 @@ import { AuthService } from 'src/app/_services/auth.service';
|
|||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.scss']
|
||||
})
|
||||
export class ListComponent implements OnInit {
|
||||
export class ListComponent implements OnInit, OnDestroy {
|
||||
scheduleModalRef?: BsModalRef;
|
||||
alertModalRef?: BsModalRef;
|
||||
@ViewChild('table') table!: TableComponent;
|
||||
|
||||
public loadAvailabilityInterval: NodeJS.Timer | undefined = undefined;
|
||||
|
@ -20,11 +23,14 @@ export class ListComponent implements OnInit {
|
|||
public available: boolean | undefined = undefined;
|
||||
public manual_mode: boolean | undefined = undefined;
|
||||
|
||||
public alertLoading = false;
|
||||
|
||||
constructor(
|
||||
private api: ApiClientService,
|
||||
private auth: AuthService,
|
||||
public api: ApiClientService,
|
||||
public auth: AuthService,
|
||||
private toastr: ToastrService,
|
||||
private modalService: BsModalService
|
||||
private modalService: BsModalService,
|
||||
private translate: TranslateService
|
||||
) {
|
||||
this.loadAvailability();
|
||||
}
|
||||
|
@ -33,11 +39,13 @@ export class ListComponent implements OnInit {
|
|||
this.api.get("availability").then((response) => {
|
||||
this.available = response.available;
|
||||
this.manual_mode = response.manual_mode;
|
||||
console.log(this.available, this.manual_mode);
|
||||
});
|
||||
}
|
||||
|
||||
changeAvailibility(available: 0|1, id?: number|undefined) {
|
||||
if(typeof id === 'undefined') {
|
||||
id = this.auth.profile.auth_user_id;
|
||||
}
|
||||
this.api.post("availability", {
|
||||
id: id,
|
||||
available: available
|
||||
|
@ -54,7 +62,9 @@ export class ListComponent implements OnInit {
|
|||
this.api.post("manual_mode", {
|
||||
manual_mode: manual_mode
|
||||
}).then((response) => {
|
||||
this.toastr.success("Modalità manuale aggiornata con successo.");
|
||||
this.translate.get('list.manual_mode_updated_successfully').subscribe((res: string) => {
|
||||
this.toastr.success(res);
|
||||
});
|
||||
this.loadAvailability();
|
||||
});
|
||||
}
|
||||
|
@ -63,11 +73,58 @@ export class ListComponent implements OnInit {
|
|||
this.scheduleModalRef = this.modalService.show(ModalAvailabilityScheduleComponent, Object.assign({}, { class: 'modal-custom' }));
|
||||
}
|
||||
|
||||
addAlertFull() {
|
||||
this.alertLoading = true;
|
||||
if(!this.auth.profile.hasRole('SUPER_EDITOR')) return;
|
||||
this.api.post("alerts", {
|
||||
type: "full"
|
||||
}).then((response) => {
|
||||
this.alertLoading = false;
|
||||
if(response?.status === "error") {
|
||||
this.toastr.error(response.message, undefined, {
|
||||
timeOut: 5000
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.alertModalRef = this.modalService.show(ModalAlertComponent, {
|
||||
initialState: {
|
||||
id: response.id
|
||||
}
|
||||
});
|
||||
this.api.alertsChanged.next();
|
||||
});
|
||||
}
|
||||
|
||||
addAlertSupport() {
|
||||
this.alertLoading = true;
|
||||
if(!this.auth.profile.hasRole('SUPER_EDITOR')) return;
|
||||
this.api.post("alerts", {
|
||||
type: "support"
|
||||
}).then((response) => {
|
||||
this.alertLoading = false;
|
||||
if(response?.status === "error") {
|
||||
this.toastr.error(response.message, undefined, {
|
||||
timeOut: 5000
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.alertModalRef = this.modalService.show(ModalAlertComponent, {
|
||||
initialState: {
|
||||
id: response.id
|
||||
}
|
||||
});
|
||||
this.api.alertsChanged.next();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadAvailabilityInterval = setInterval(() => {
|
||||
console.log("Refreshing availability...");
|
||||
this.loadAvailability();
|
||||
}, 10000);
|
||||
this.auth.authChanged.subscribe({
|
||||
next: () => this.loadAvailability()
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
|
@ -3,15 +3,15 @@
|
|||
<owner-image></owner-image>
|
||||
<div class="my-2 text-danger" *ngIf="!loginResponse.loginOk">{{ loginResponse.message }}</div>
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" (keydown.enter)="inputPassword.focus()" [(ngModel)]="username" id="username" placeholder="Username">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" (keydown.enter)="inputPassword.focus()" [(ngModel)]="username" id="username" [placeholder]="'username'|translate">
|
||||
<label for="username" translate>login.username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" (keydown.enter)="login()" [(ngModel)]="password" id="password" placeholder="Password" #inputPassword>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" (keydown.enter)="login()" [(ngModel)]="password" id="password" [placeholder]="'password'|translate" #inputPassword>
|
||||
<label for="password" translate>login.password</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" (click)="login()" [disabled]="loading">
|
||||
<span *ngIf="!loading">Login</span>
|
||||
<span *ngIf="!loading" translate>login.submit_btn</span>
|
||||
<div class="spinner-border spinner-border-sm text-white" *ngIf="loading"></div>
|
||||
</button>
|
||||
</main>
|
|
@ -8,28 +8,28 @@
|
|||
</div>
|
||||
<div class="place_info" *ngIf="place_loaded">
|
||||
<h3>
|
||||
<a href="https://www.google.com/maps/@?api=1&map_action=map¢er={{ lat }},{{ lng }}&zoom=19&basemap=satellite" target="_blank">Apri il luogo in Google Maps</a>
|
||||
<a href="https://www.google.com/maps/@?api=1&map_action=map¢er={{ lat }},{{ lng }}&zoom=19&basemap=satellite" target="_blank">{{ 'place_details.open_in_google_maps'|translate }}</a>
|
||||
</h3>
|
||||
<br>
|
||||
<h4 *ngIf="place_info.place_name">
|
||||
Nome: <b>{{ place_info.place_name }}</b>
|
||||
{{ 'name'|translate|titlecase }}: <b>{{ place_info.place_name }}</b>
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.building_service_name">
|
||||
Nome del luogo: <b>{{ place_info.building_service_name }}</b>
|
||||
{{ 'place_details.place_name'|translate|titlecase }}: <b>{{ place_info.building_service_name }}</b>
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.house_number">
|
||||
Numero civico: <b>{{ place_info.house_number }}</b>
|
||||
{{ 'place_details.house_number'|translate|titlecase }}: <b>{{ place_info.house_number }}</b>
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.road">
|
||||
Strada: <b>{{ place_info.road }}</b>
|
||||
{{ 'place_details.road'|translate|titlecase }}: <b>{{ place_info.road }}</b>
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.village">
|
||||
Comune: <b>{{ place_info.village }}</b> (CAP <b>{{ place_info.postcode }}</b>)
|
||||
{{ 'place_details.village'|translate|titlecase }}: <b>{{ place_info.village }}</b> ({{ 'place_details.postcode'|translate }} <b>{{ place_info.postcode }}</b>)
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.hamlet">
|
||||
Frazione: <b>{{ place_info.hamlet }}</b>
|
||||
{{ 'place_details.hamlet'|translate|titlecase }}: <b>{{ place_info.hamlet }}</b>
|
||||
</h4>
|
||||
<h4 *ngIf="place_info.municipality">
|
||||
Raggruppamento del Comune: <b>{{ place_info.municipality }}</b>
|
||||
{{ 'place_details.municipality'|translate|titlecase }}: <b>{{ place_info.municipality }}</b>
|
||||
</h4>
|
||||
</div>
|
|
@ -1,8 +1,9 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslationModule } from '../../translation.module';
|
||||
|
||||
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
|
||||
import { BackBtnModule } from '../back-btn/back-btn.module';
|
||||
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
|
||||
|
||||
import { PlaceDetailsRoutingModule } from './place-details-routing.module';
|
||||
import { PlaceDetailsComponent } from './place-details.component';
|
||||
|
@ -15,7 +16,8 @@ import { PlaceDetailsComponent } from './place-details.component';
|
|||
CommonModule,
|
||||
PlaceDetailsRoutingModule,
|
||||
LeafletModule,
|
||||
BackBtnModule
|
||||
BackBtnModule,
|
||||
TranslationModule
|
||||
]
|
||||
})
|
||||
export class PlaceDetailsModule { }
|
|
@ -1,5 +1,5 @@
|
|||
<owner-image></owner-image>
|
||||
<div class="text-center mb-4">
|
||||
<button type="button" class="btn btn-primary" (click)="addService()">Aggiungi intervento</button>
|
||||
<button type="button" class="btn btn-primary" (click)="addService()">{{ 'add'|translate|titlecase }} {{ 'service'|translate }}</button>
|
||||
</div>
|
||||
<app-table [sourceType]="'services'" [refreshInterval]="1200000"></app-table>
|
|
@ -0,0 +1,5 @@
|
|||
<owner-image></owner-image>
|
||||
<div class="text-center mb-4">
|
||||
<button type="button" class="btn btn-primary" disabled>{{ 'add'|translate|titlecase }} {{ 'training'|translate }}</button>
|
||||
</div>
|
||||
<app-table [sourceType]="'trainings'" [refreshInterval]="1200000"></app-table>
|
|
@ -1,29 +1,17 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiClientService {
|
||||
private apiRoot = 'api/';
|
||||
public requestOptions = {};
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.requestOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
}
|
||||
}
|
||||
public alertsChanged = new Subject<void>();
|
||||
public availableUsers: undefined | number = undefined;
|
||||
|
||||
public setToken(token: string) {
|
||||
this.requestOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
}
|
||||
}
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public apiEndpoint(endpoint: string): string {
|
||||
if(endpoint.startsWith('https')) {
|
||||
|
@ -45,42 +33,37 @@ export class ApiClientService {
|
|||
public get(endpoint: string, data: any = {}) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.http.get(this.apiEndpoint(endpoint), {
|
||||
...this.requestOptions,
|
||||
params: new HttpParams({ fromObject: data })
|
||||
}).subscribe((data: any) => {
|
||||
resolve(data);
|
||||
}, (err) => {
|
||||
reject(err);
|
||||
}).subscribe({
|
||||
next: (v) => resolve(v),
|
||||
error: (e) => reject(e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public post(endpoint: string, data: any) {
|
||||
public post(endpoint: string, data: any = {}) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.http.post(this.apiEndpoint(endpoint), this.dataToParams(data), this.requestOptions).subscribe((data: any) => {
|
||||
resolve(data);
|
||||
}, (err) => {
|
||||
reject(err);
|
||||
this.http.post(this.apiEndpoint(endpoint), this.dataToParams(data)).subscribe({
|
||||
next: (v) => resolve(v),
|
||||
error: (e) => reject(e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public put(endpoint: string, data: any) {
|
||||
public put(endpoint: string, data: any = {}) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.http.put(this.apiEndpoint(endpoint), this.dataToParams(data), this.requestOptions).subscribe((data: any) => {
|
||||
resolve(data);
|
||||
}, (err) => {
|
||||
reject(err);
|
||||
this.http.put(this.apiEndpoint(endpoint), this.dataToParams(data)).subscribe({
|
||||
next: (v) => resolve(v),
|
||||
error: (e) => reject(e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public delete(endpoint: string) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.http.delete(this.apiEndpoint(endpoint), this.requestOptions).subscribe((data: any) => {
|
||||
resolve(data);
|
||||
}, (err) => {
|
||||
reject(err);
|
||||
this.http.delete(this.apiEndpoint(endpoint)).subscribe({
|
||||
next: (v) => resolve(v),
|
||||
error: (e) => reject(e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApiClientService } from './api-client.service';
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import jwt_decode from 'jwt-decode';
|
||||
|
||||
export interface LoginResponse {
|
||||
|
@ -13,13 +14,15 @@ export interface LoginResponse {
|
|||
})
|
||||
export class AuthService {
|
||||
public profile: any = undefined;
|
||||
private access_token = '';
|
||||
private access_token: string | undefined = undefined;
|
||||
public authChanged = new Subject<void>();
|
||||
|
||||
public loadProfile() {
|
||||
try{
|
||||
console.log("Loading profile", this.access_token);
|
||||
let now = Date.now().valueOf() / 1000;
|
||||
(window as any).jwt_decode = jwt_decode;
|
||||
if(typeof(this.access_token) !== "string") return;
|
||||
let decoded: any = jwt_decode(this.access_token);
|
||||
if (typeof decoded.exp !== 'undefined' && decoded.exp < now) {
|
||||
return false;
|
||||
|
@ -29,7 +32,12 @@ export class AuthService {
|
|||
}
|
||||
this.profile = decoded.user_info;
|
||||
|
||||
this.profile.hasRole = (role: string) => {
|
||||
return Object.values(this.profile.roles).includes(role);
|
||||
}
|
||||
|
||||
console.log(this.profile);
|
||||
this.authChanged.next();
|
||||
return true;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
@ -42,19 +50,22 @@ export class AuthService {
|
|||
constructor(private api: ApiClientService, private router: Router) {
|
||||
if(localStorage.getItem("access_token") !== null) {
|
||||
this.access_token = localStorage.getItem("access_token") as string;
|
||||
this.api.setToken(this.access_token);
|
||||
this.loadProfile();
|
||||
}
|
||||
}
|
||||
|
||||
private setToken(value: string) {
|
||||
public setToken(value: string) {
|
||||
localStorage.setItem("access_token", value);
|
||||
this.access_token = value;
|
||||
this.api.setToken(this.access_token);
|
||||
this.loadProfile();
|
||||
}
|
||||
|
||||
public getToken(): string | undefined {
|
||||
return this.access_token;
|
||||
}
|
||||
|
||||
private removeToken() {
|
||||
this.access_token = '';
|
||||
localStorage.removeItem("access_token");
|
||||
}
|
||||
|
||||
|
@ -97,12 +108,54 @@ export class AuthService {
|
|||
})
|
||||
}
|
||||
|
||||
public impersonate(user_id: number): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log("final", user_id);
|
||||
this.api.post("impersonate", {
|
||||
user_id: user_id
|
||||
}).then((response) => {
|
||||
this.setToken(response.access_token);
|
||||
resolve(user_id);
|
||||
}).catch((err) => {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public stop_impersonating(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.api.post("stop_impersonating").then((response) => {
|
||||
this.setToken(response.access_token);
|
||||
resolve(response.user_id);
|
||||
}).catch((err) => {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logout(routerDestination?: string[] | undefined) {
|
||||
this.removeToken();
|
||||
this.profile = undefined;
|
||||
if(routerDestination === undefined) {
|
||||
routerDestination = ["login", "list"];
|
||||
if(this.profile.impersonating_user) {
|
||||
this.stop_impersonating().then((user_id) => {
|
||||
});
|
||||
} else {
|
||||
this.removeToken();
|
||||
this.profile = undefined;
|
||||
if(routerDestination === undefined) {
|
||||
routerDestination = ["login", "list"];
|
||||
}
|
||||
this.router.navigate(routerDestination);
|
||||
}
|
||||
this.router.navigate(routerDestination);
|
||||
}
|
||||
|
||||
public refreshToken() {
|
||||
return new Observable<string>((observer) => {
|
||||
this.api.post("refreshToken").then((data: any) => {
|
||||
this.setToken(data.token);
|
||||
observer.next(data.token);
|
||||
observer.complete();
|
||||
}).catch((err) => {
|
||||
observer.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { ListComponent } from './_components/list/list.component';
|
||||
import { LogsComponent } from './_components/logs/logs.component';
|
||||
import { ServicesComponent } from './_components/services/services.component';
|
||||
import { TrainingsComponent } from './_components/trainings/trainings.component';
|
||||
import { ListComponent } from './_routes/list/list.component';
|
||||
import { LogsComponent } from './_routes/logs/logs.component';
|
||||
import { ServicesComponent } from './_routes/services/services.component';
|
||||
import { TrainingsComponent } from './_routes/trainings/trainings.component';
|
||||
|
||||
import { AuthorizeGuard } from './_guards/authorize.guard';
|
||||
import { LoginComponent } from './_components/login/login.component';
|
||||
import { LoginComponent } from './_routes/login/login.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'list', component: ListComponent, canActivate: [AuthorizeGuard] },
|
||||
|
@ -15,12 +15,12 @@ const routes: Routes = [
|
|||
{ path: 'services', component: ServicesComponent, canActivate: [AuthorizeGuard] },
|
||||
{
|
||||
path: 'place-details',
|
||||
loadChildren: () => import('./_components/place-details/place-details.module').then(m => m.PlaceDetailsModule),
|
||||
loadChildren: () => import('./_routes/place-details/place-details.module').then(m => m.PlaceDetailsModule),
|
||||
canActivate: [AuthorizeGuard]
|
||||
},
|
||||
{
|
||||
path: 'services/:id',
|
||||
loadChildren: () => import('./_components/edit-service/edit-service.module').then(m => m.EditServiceModule),
|
||||
loadChildren: () => import('./_routes/edit-service/edit-service.module').then(m => m.EditServiceModule),
|
||||
canActivate: [AuthorizeGuard]
|
||||
},
|
||||
{ path: 'trainings', component: TrainingsComponent, canActivate: [AuthorizeGuard] },
|
||||
|
|
|
@ -1,19 +1,36 @@
|
|||
<div [className]="menuButtonClicked ? 'topnav responsive' : 'topnav'" id="topNavBar" *ngIf="auth.profile !== undefined">
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/list">Lista disponibilità</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/services">Interventi</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/trainings">Esercitazioni</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/logs">Logs</a>
|
||||
<a style="float: right;" id="logout">Ciao, {{ auth.profile.name }}. <b id="logout-text" (click)="auth.logout()">Logout</b></a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/list" translate>menu.list</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/services" translate>menu.services</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/trainings" translate>menu.trainings</a>
|
||||
<a routerLinkActive="active" (click)="menuButtonClicked = false" routerLink="/logs" translate>menu.logs</a>
|
||||
<a style="float: right;" id="logout">{{ 'menu.hi'|translate|titlecase }}, {{ auth.profile.name }}. <b id="logout-text" (click)="auth.logout()" translate>menu.logout</b></a>
|
||||
<a class="icon" id="menuButton" (click)="menuButtonClicked = !menuButtonClicked">☰</a>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center mt-4 pt-4 mb-3" *ngIf="loadingRoute">
|
||||
<div class="spinner spinner-border"></div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-center mt-4 pt-4 mb-3" *ngIf="loadingRoute">
|
||||
<div class="spinner spinner-border"></div>
|
||||
</div>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<alert type="danger" *ngIf="alerts.length > 0">
|
||||
<strong>Attenzione!</strong> Allertamento in corso.<br>
|
||||
<ng-container *ngIf="alerts.length == 1">
|
||||
Emergenza attuale: <a (click)="openAlert(alerts[0]['id'])"><b>{{ alerts[0]["created_at"] | date:'dd/MM/YYYY, HH:mm:ss' }}</b> (premere per ulteriori informazioni)</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="alerts.length > 1">
|
||||
Emergenze attuali:
|
||||
<ul>
|
||||
<li *ngFor="let alert of alerts">
|
||||
<a (click)="openAlert(alert['id'])"><b>{{ alert["created_at"] | date:'dd/MM/YYYY, HH:mm:ss' }}</b> (premere per ulteriori informazioni)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
</alert>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="footer text-center p-3">
|
||||
Allerta-VVF, software libero realizzato per i Vigili del Fuoco volontari.<br>
|
||||
<p>Revisione {{ versions.revision }} ({{ revision_datetime_string }})</p>
|
||||
{{ 'footer_text' | translate }}<br>
|
||||
<p>{{ 'revision' | translate | titlecase }} {{ versions.revision }} ({{ revision_datetime_string }})</p>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,9 @@ import { AuthService } from './_services/auth.service';
|
|||
import { LocationBackService } from 'src/app/_services/locationBack.service';
|
||||
import { versions } from 'src/environments/versions';
|
||||
import { Router, RouteConfigLoadStart, RouteConfigLoadEnd } from '@angular/router';
|
||||
import { ApiClientService } from './_services/api-client.service';
|
||||
import { ModalAlertComponent } from 'src/app/_components/modal-alert/modal-alert.component';
|
||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -14,15 +17,27 @@ export class AppComponent {
|
|||
public revision_datetime_string;
|
||||
public versions = versions;
|
||||
public loadingRoute = false;
|
||||
private loadAlertsInterval: NodeJS.Timer | undefined = undefined;
|
||||
public alerts = [];
|
||||
|
||||
constructor(
|
||||
public auth: AuthService,
|
||||
private locationBackService: LocationBackService,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private api: ApiClientService,
|
||||
private modalService: BsModalService
|
||||
) {
|
||||
this.revision_datetime_string = new Date(versions.revision_timestamp).toLocaleString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
|
||||
}
|
||||
|
||||
loadAlerts() {
|
||||
if(this.auth.profile) {
|
||||
this.api.get("alerts").then((response) => {
|
||||
this.alerts = response;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof RouteConfigLoadStart) {
|
||||
|
@ -31,5 +46,23 @@ export class AppComponent {
|
|||
this.loadingRoute = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.loadAlertsInterval = setInterval(() => {
|
||||
console.log("Refreshing alerts...");
|
||||
this.loadAlerts();
|
||||
}, 15000);
|
||||
this.loadAlerts();
|
||||
|
||||
this.api.alertsChanged.subscribe(() => {
|
||||
this.loadAlerts();
|
||||
});
|
||||
}
|
||||
|
||||
openAlert(id: number) {
|
||||
this.modalService.show(ModalAlertComponent, {
|
||||
initialState: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { TranslationModule } from './translation.module';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { ModalModule } from 'ngx-bootstrap/modal';
|
||||
import { TooltipModule } from 'ngx-bootstrap/tooltip';
|
||||
import { CollapseModule } from 'ngx-bootstrap/collapse';
|
||||
import { AlertModule } from 'ngx-bootstrap/alert';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
@ -14,16 +19,17 @@ import { environment } from '../environments/environment';
|
|||
|
||||
import { TableComponent } from './_components/table/table.component';
|
||||
import { ModalAvailabilityScheduleComponent } from './_components/modal-availability-schedule/modal-availability-schedule.component';
|
||||
import { ModalAlertComponent } from './_components/modal-alert/modal-alert.component';
|
||||
import { OwnerImageComponent } from './_components/owner-image/owner-image.component';
|
||||
|
||||
import { LoginComponent } from './_components/login/login.component';
|
||||
import { LoginComponent } from './_routes/login/login.component';
|
||||
|
||||
import { ListComponent } from './_components/list/list.component';
|
||||
import { LogsComponent } from './_components/logs/logs.component';
|
||||
import { ServicesComponent } from './_components/services/services.component';
|
||||
import { TrainingsComponent } from './_components/trainings/trainings.component';
|
||||
import { ListComponent } from './_routes/list/list.component';
|
||||
import { LogsComponent } from './_routes/logs/logs.component';
|
||||
import { ServicesComponent } from './_routes/services/services.component';
|
||||
import { TrainingsComponent } from './_routes/trainings/trainings.component';
|
||||
|
||||
import { UnauthorizedInterceptor } from './_providers/unauthorized-interceptor.provider';
|
||||
import { AuthInterceptor } from './_providers/auth-interceptor.provider';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -31,6 +37,7 @@ import { UnauthorizedInterceptor } from './_providers/unauthorized-interceptor.p
|
|||
//
|
||||
TableComponent,
|
||||
ModalAvailabilityScheduleComponent,
|
||||
ModalAlertComponent,
|
||||
OwnerImageComponent,
|
||||
//
|
||||
LoginComponent,
|
||||
|
@ -54,18 +61,32 @@ import { UnauthorizedInterceptor } from './_providers/unauthorized-interceptor.p
|
|||
}),
|
||||
ModalModule.forRoot(),
|
||||
TooltipModule.forRoot(),
|
||||
CollapseModule.forRoot(),
|
||||
AlertModule.forRoot(),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||
enabled: false && environment.production,
|
||||
// Register the ServiceWorker as soon as the app is stable
|
||||
// or after 30 seconds (whichever comes first).
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
})
|
||||
}),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
TranslationModule
|
||||
],
|
||||
providers: [{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: UnauthorizedInterceptor,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
}],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
exports: [
|
||||
CommonModule,
|
||||
TranslateModule
|
||||
]
|
||||
})
|
||||
export class TranslationModule {
|
||||
constructor(private translate: TranslateService) {
|
||||
this.translate.setDefaultLang('en');
|
||||
this.translate.use(window.navigator.language.split("-")[0]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"menu": {
|
||||
"list": "List",
|
||||
"services": "Services",
|
||||
"trainings": "Trainings",
|
||||
"logs": "Logs",
|
||||
"logout": "Logout",
|
||||
"hi": "hi"
|
||||
},
|
||||
"table": {
|
||||
"yes_remove": "Yes, remove",
|
||||
"cancel": "Cancel",
|
||||
"remove_service_confirm": "Are you sure you want to remove this service?",
|
||||
"remove_service_confirm_text": "This action cannot be undone.",
|
||||
"service_deleted_successfully": "Service deleted successfully",
|
||||
"service_deleted_error": "Service could not be deleted. Please try again."
|
||||
},
|
||||
"list": {
|
||||
"your_availability_is": "You are:",
|
||||
"enable_schedules": "Enable hour schedules",
|
||||
"disable_schedules": "Disable hour schedules",
|
||||
"update_schedules": "Update availability schedules",
|
||||
"connect_telegram_bot": "Connect your account to the Telegram bot",
|
||||
"tooltip_change_availability": "Change your availability to {{state}}",
|
||||
"manual_mode_updated_successfully": "Manual mode updated successfully"
|
||||
},
|
||||
"login": {
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"submit_btn": "Login"
|
||||
},
|
||||
"place_details": {
|
||||
"open_in_google_maps": "Open in Google Maps",
|
||||
"place_name": "Place name",
|
||||
"house_number": "house number",
|
||||
"road": "road",
|
||||
"village": "village",
|
||||
"postcode": "postcode",
|
||||
"hamlet": "hamlet",
|
||||
"municipality": "municipality"
|
||||
},
|
||||
"map_picker": {
|
||||
"place_min_length": "Place name must be at least 3 characters long",
|
||||
"loading_error": "Error loading search results. Please try again later"
|
||||
},
|
||||
"edit_service": {
|
||||
"select_start_datetime": "Select start date and time for the service",
|
||||
"select_end_datetime": "Select end date and time for the service",
|
||||
"insert_code": "Insert service code",
|
||||
"other_crew_members": "Other crew members",
|
||||
"select_type": "Select a type",
|
||||
"select_service_type": "Select a service type",
|
||||
"type_must_be_two_characters_long": "Type must be at least 2 characters long",
|
||||
"type_already_exists": "Type already exists",
|
||||
"type_added_successfully": "Type added successfully",
|
||||
"service_added_successfully": "Service added successfully",
|
||||
"service_add_error": "Service could not be added. Please try again"
|
||||
},
|
||||
"update_availability_schedule": "Update availability schedule",
|
||||
"save_changes": "Save changes",
|
||||
"close": "Close",
|
||||
"monday": "Monday",
|
||||
"monday_short": "Mon",
|
||||
"tuesday": "Tuesday",
|
||||
"tuesday_short": "Tue",
|
||||
"wednesday": "Wednesday",
|
||||
"wednesday_short": "Wed",
|
||||
"thursday": "Thursday",
|
||||
"thursday_short": "Thu",
|
||||
"friday": "Friday",
|
||||
"friday_short": "Fri",
|
||||
"saturday": "Saturday",
|
||||
"saturday_short": "Sat",
|
||||
"sunday": "Sunday",
|
||||
"sunday_short": "Sun",
|
||||
"programmed": "programmed",
|
||||
"available": "available",
|
||||
"unavailable": "unavailable",
|
||||
"set_available": "available",
|
||||
"set_unavailable": "unavailable",
|
||||
"name": "name",
|
||||
"driver": "driver",
|
||||
"drivers": "drivers",
|
||||
"call": "call",
|
||||
"service": "service",
|
||||
"services": "services",
|
||||
"training": "training",
|
||||
"trainings": "trainings",
|
||||
"user": "user",
|
||||
"users": "users",
|
||||
"availability_minutes": "availability_minutes",
|
||||
"action": "action",
|
||||
"changed": "changed",
|
||||
"editor": "editor",
|
||||
"datetime": "datetime",
|
||||
"start": "start",
|
||||
"end": "end",
|
||||
"code": "code",
|
||||
"chief": "chief",
|
||||
"crew": "crew",
|
||||
"place": "place",
|
||||
"notes": "notes",
|
||||
"type": "type",
|
||||
"add": "add",
|
||||
"update": "update",
|
||||
"remove": "remove",
|
||||
"more details": "more details",
|
||||
"search": "search",
|
||||
"submit": "invia",
|
||||
"reset": "reset",
|
||||
"go_back": "go back",
|
||||
"press_to_select_a_date": "press to select a date",
|
||||
"footer_text": "Allerta-VVF, free software developed for volunteer firefighters brigades.",
|
||||
"revision": "revision"
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"menu": {
|
||||
"list": "Lista disponibilità",
|
||||
"services": "Interventi",
|
||||
"trainings": "Esercitazioni",
|
||||
"logs": "Logs",
|
||||
"logout": "Logout",
|
||||
"hi": "Ciao"
|
||||
},
|
||||
"table": {
|
||||
"yes_remove": "Si, rimuovi",
|
||||
"cancel": "Annulla",
|
||||
"remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
|
||||
"remove_service_confirm_text": "Questa operazione non può essere annullata.",
|
||||
"service_deleted_successfully": "Intervento rimosso con successo",
|
||||
"service_deleted_error": "Errore durante la rimozione dell'intervento. Riprova più tardi"
|
||||
},
|
||||
"list": {
|
||||
"your_availability_is": "Attualmente sei:",
|
||||
"enable_schedules": "Abilita programmazione oraria",
|
||||
"disable_schedules": "Disattiva programmazione oraria",
|
||||
"update_schedules": "Modifica orari disponibilità",
|
||||
"connect_telegram_bot": "Collega l'account al bot Telegram",
|
||||
"tooltip_change_availability": "Cambia la tua disponibilità in {{state}}",
|
||||
"manual_mode_updated_successfully": "Modalità manuale aggiornata con successo"
|
||||
},
|
||||
"login": {
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"submit_btn": "Login"
|
||||
},
|
||||
"place_details": {
|
||||
"open_in_google_maps": "Apri il luogo in Google Maps",
|
||||
"place_name": "Nome del luogo",
|
||||
"house_number": "numero civico",
|
||||
"road": "strada",
|
||||
"village": "comune",
|
||||
"postcode": "CAP",
|
||||
"hamlet": "frazione",
|
||||
"municipality": "raggruppamento del comune"
|
||||
},
|
||||
"map_picker": {
|
||||
"place_min_length": "Il nome della località deve essere di almeno 3 caratteri",
|
||||
"loading_error": "Errore di caricamento dei risultati della ricerca. Riprovare più tardi"
|
||||
},
|
||||
"edit_service": {
|
||||
"select_start_datetime": "Seleziona data e ora di inizio dell'intervento",
|
||||
"select_end_datetime": "Seleziona data e ora di fine dell'intervento",
|
||||
"insert_code": "Inserisci il progressivo dell'intervento",
|
||||
"other_crew_members": "Altri membri della squadra",
|
||||
"select_type": "Seleziona una tipologia",
|
||||
"select_service_type": "Seleziona una tipologia di intervento",
|
||||
"type_must_be_two_characters_long": "La tipologia deve essere di almeno 2 caratteri",
|
||||
"type_already_exists": "La tipologia è già presente",
|
||||
"type_added_successfully": "Tipologia aggiunta con successo",
|
||||
"service_added_successfully": "Intervento aggiunto con successo",
|
||||
"service_add_error": "Errore durante l'aggiunta dell'intervento. Riprovare più tardi"
|
||||
},
|
||||
"update_availability_schedule": "Aggiorna programmazione disponibilità",
|
||||
"save_changes": "Salva modifiche",
|
||||
"close": "Chiudi",
|
||||
"monday": "Lunedì",
|
||||
"monday_short": "Lun",
|
||||
"tuesday": "Martedì",
|
||||
"tuesday_short": "Mar",
|
||||
"wednesday": "Mercoledì",
|
||||
"wednesday_short": "Mer",
|
||||
"thursday": "Giovedì",
|
||||
"thursday_short": "Gio",
|
||||
"friday": "Venerdì",
|
||||
"friday_short": "Ven",
|
||||
"saturday": "Sabato",
|
||||
"saturday_short": "Sab",
|
||||
"sunday": "Domenica",
|
||||
"sunday_short": "Dom",
|
||||
"programmed": "programmata",
|
||||
"available": "disponibile",
|
||||
"unavailable": "non disponibile",
|
||||
"set_available": "attiva",
|
||||
"set_unavailable": "disattiva",
|
||||
"name": "nome",
|
||||
"driver": "autista",
|
||||
"drivers": "autisti",
|
||||
"call": "chiama",
|
||||
"service": "intervento",
|
||||
"services": "interventi",
|
||||
"training": "esercitazione",
|
||||
"trainings": "esercitazioni",
|
||||
"user": "utente",
|
||||
"users": "utenti",
|
||||
"availability_minutes": "minuti di disponibilità",
|
||||
"action": "azione",
|
||||
"changed": "interessato",
|
||||
"editor": "fatto da",
|
||||
"datetime": "data e ora",
|
||||
"start": "inizio",
|
||||
"end": "fine",
|
||||
"code": "codice",
|
||||
"chief": "caposquadra",
|
||||
"crew": "squadra",
|
||||
"place": "luogo",
|
||||
"notes": "note",
|
||||
"type": "tipologia",
|
||||
"add": "aggiungi",
|
||||
"update": "modifica",
|
||||
"remove": "rimuovi",
|
||||
"more details": "altri dettagli",
|
||||
"search": "cerca",
|
||||
"submit": "invia",
|
||||
"reset": "reset",
|
||||
"go_back": "torna indietro",
|
||||
"press_to_select_a_date": "premi per selezionare una data",
|
||||
"footer_text": "Allerta-VVF, software libero realizzato per i Vigili del Fuoco volontari.",
|
||||
"revision": "revisione"
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
<IfModule mod_php4.c>
|
||||
php_flag display_errors Off
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_flag display_errors Off
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php7.c>
|
||||
php_flag display_errors Off
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header unset X-Powered-By
|
||||
Header unset X-Pingback
|
||||
Header unset SERVER
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|TRACK) [NC]
|
||||
RewriteRule ^(.*)$ - [F,L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule .* error_page.php?error=404 [L]
|
||||
</IfModule>
|
||||
|
||||
<FilesMatch "(config|config.old|config-sample|core|ui)\.php">
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
<FilesMatch "\.log$">
|
||||
Order Allow,Deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
<FilesMatch "^\.ht">
|
||||
Order Allow,Deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
# From https://htaccessbook.com/useful-htaccess-rules/
|
||||
|
||||
# JAVASCRIPT
|
||||
AddType application/javascript js jsonp
|
||||
AddType application/json json
|
||||
|
||||
<FilesMatch "manifest.webmanifest">
|
||||
AddType application/manifest+json webmanifest
|
||||
</FilesMatch>
|
||||
|
||||
# FONTS
|
||||
AddType font/opentype otf
|
||||
AddType application/font-woff woff
|
||||
AddType application/x-font-woff woff
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
AddType application/x-font-ttf ttc ttf
|
||||
AddType image/svg+xml svg svgz
|
||||
AddType application/wasm wasm.gz wasm
|
||||
AddEncoding gzip gz
|
||||
|
||||
# AUDIO
|
||||
AddType audio/mp4 m4a f4a f4b
|
||||
AddType audio/ogg oga ogg
|
||||
|
||||
# VIDEO
|
||||
AddType video/mp4 mp4 m4v f4v f4p
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
|
||||
# OTHERS
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-shockwave-flash swf
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType application/xml atom rdf rss xml
|
||||
AddType application/vnd.openxmlformats .docx .pptx .xlsx .xltx . xltm .dotx .potx .ppsx
|
||||
AddType text/cache-manifest appcache manifest
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
AddType text/x-vcard vcf
|
||||
AddType image/webp webp
|
||||
AddType image/x-icon ico
|
||||
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(js|css|woff|woff2|ttf|eot)$">
|
||||
Header set Cache-Control "max-age=2592000, public"
|
||||
</FilesMatch>
|
||||
<FilesMatch "\.(jpe?g|png|gif|swf|flv|pdf|svg|ico)$">
|
||||
Header set Cache-Control "max-age=604800, public"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
ServerSignature Off
|
|
@ -1,277 +0,0 @@
|
|||
<?php
|
||||
define('REQUEST_USING_API', true);
|
||||
require 'core.php';
|
||||
use Spatie\ArrayToXml\ArrayToXml;
|
||||
use Brick\PhoneNumber\PhoneNumber;
|
||||
use Brick\PhoneNumber\PhoneNumberFormat;
|
||||
use Brick\PhoneNumber\PhoneNumberParseException;
|
||||
|
||||
$user_info = [];
|
||||
|
||||
$dispatcher = FastRoute\simpleDispatcher(
|
||||
function (FastRoute\RouteCollector $r) {
|
||||
$r->addRoute(
|
||||
'GET', '/healthcheck', function ($vars) {
|
||||
return ["state" => "SUCCESS", "description" => ""];
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
['GET', 'POST'], '/requestDebug', function ($vars) {
|
||||
return ["get" => $_GET, "post" => $_POST, "server" => $_SERVER];
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'POST', '/login', function ($vars) {
|
||||
global $tools, $db, $user;
|
||||
try {
|
||||
$user->auth->loginWithUsername($_POST['username'], $_POST['password']);
|
||||
$apiKey = $tools->createKey();
|
||||
$db->insert(
|
||||
DB_PREFIX."_api_keys",
|
||||
["apikey" => $apiKey, "user" => $user->auth->getUserId(), "permissions" => "all"]
|
||||
);
|
||||
return ["status" => "ok", "apiKey" => $apiKey];
|
||||
}
|
||||
catch (\Delight\Auth\UnknownUsernameException $e) {
|
||||
http_response_code(401);
|
||||
return ["status" => "error", "message" => "Username unknown"];
|
||||
}
|
||||
catch (\Delight\Auth\AmbiguousUsernameException $e) {
|
||||
http_response_code(401);
|
||||
return ["status" => "error", "message" => "Ambiguous Username"];
|
||||
}
|
||||
catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
http_response_code(401);
|
||||
return ["status" => "error", "message" => "Wrong password"];
|
||||
}
|
||||
catch (\Delight\Auth\EmailNotVerifiedException $e) {
|
||||
http_response_code(401);
|
||||
return ["status" => "error", "message" => "Email not verified"];
|
||||
}
|
||||
catch (\Delight\Auth\TooManyRequestsException $e) {
|
||||
http_response_code(429);
|
||||
return ["status" => "error", "message" => "Too many requests"];
|
||||
}
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/users', function ($vars) {
|
||||
requireToken();
|
||||
global $db;
|
||||
$users = $db->select("SELECT * FROM `".DB_PREFIX."_users`");
|
||||
$users_profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles`");
|
||||
foreach ($users_profiles as $key=>$value){
|
||||
if(is_null($users_profiles[$key]["name"])) {
|
||||
$users_profiles[$key]["name"] = $users[$key]["username"];
|
||||
}
|
||||
$users_profiles[$key]["email"] = $users[$key]["email"];
|
||||
}
|
||||
return $users_profiles;
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/user', function ($vars) {
|
||||
requireToken();
|
||||
global $db, $user_info;
|
||||
$users = $db->select("SELECT * FROM `".DB_PREFIX."_users` WHERE id = :id", ["id" => $user_info["id"]])[0];
|
||||
$users_profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE id = :id", ["id" => $user_info["id"]])[0];
|
||||
if(is_null($users_profiles["name"])) {
|
||||
$users_profiles["name"] = $users["username"];
|
||||
}
|
||||
$users_profiles["email"] = $users["email"];
|
||||
return $users_profiles;
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/user/{id:\d+}', function ($vars) {
|
||||
requireToken();
|
||||
global $db;
|
||||
$users = $db->select("SELECT * FROM `".DB_PREFIX."_users` WHERE id = :id", ["id" => $vars["id"]])[0];
|
||||
$users_profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE id = :id", ["id" => $vars["id"]])[0];
|
||||
if(is_null($users_profiles["name"])) {
|
||||
$users_profiles["name"] = $users["username"];
|
||||
}
|
||||
$users_profiles["email"] = $users["email"];
|
||||
return $users_profiles;
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'POST', '/user', function ($vars) {
|
||||
requireToken();
|
||||
global $user, $user_info;
|
||||
$chief = isset($_POST["chief"]) ? $_POST["chief"]==1 : false;
|
||||
$driver = isset($_POST["driver"]) ? $_POST["driver"]==1 : false;
|
||||
$hidden = isset($_POST["hidden"]) ? $_POST["hidden"]==1 : false;
|
||||
$disabled = isset($_POST["disabled"]) ? $_POST["disabled"]==1 : false;
|
||||
if(isset($_POST["mail"], $_POST["name"], $_POST["username"], $_POST["password"], $_POST["phone_number"], $_POST["birthday"])) {
|
||||
try {
|
||||
$phone_number = PhoneNumber::parse($_POST["phone_number"]);
|
||||
if (!$phone_number->isValidNumber()) {
|
||||
return ["status" => "error", "message" => "Bad phone number"];
|
||||
} else {
|
||||
$phone_number = $phone_number->format(PhoneNumberFormat::E164);
|
||||
}
|
||||
} catch (PhoneNumberParseException $e) {
|
||||
return ["status" => "error", "message" => "Bad phone number"];
|
||||
}
|
||||
try{
|
||||
$userId = $user->add_user($_POST["mail"], $_POST["name"], $_POST["username"], $_POST["password"], $phone_number, $_POST["birthday"], $chief, $driver, $hidden, $disabled, $user_info["id"]);
|
||||
} catch (\Delight\Auth\InvalidEmailException $e) {
|
||||
return ["status" => "error", "message" => "Invalid email address"];
|
||||
} catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
return ["status" => "error", "message" => "Invalid password"];
|
||||
} catch (\Delight\Auth\UserAlreadyExistsException $e) {
|
||||
return ["status" => "error", "message" => "User already exists"];
|
||||
}
|
||||
if($userId) {
|
||||
return ["userId" => $userId];
|
||||
} else {
|
||||
return ["status" => "error", "message" => "Unknown error"];
|
||||
}
|
||||
} else {
|
||||
return ["status" => "error", "message" => "User info required"];
|
||||
}
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/availability', function ($vars) {
|
||||
requireToken();
|
||||
global $db, $user_info;
|
||||
return $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE id = :id", ["id" => $user_info["id"]])[0]["available"];
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/availability/{id:\d+}', function ($vars) {
|
||||
requireToken();
|
||||
global $db;
|
||||
return $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE id = :id", ["id" => $vars["id"]])[0]["available"];
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/changeAvailability/{available:\d+}', function ($vars) {
|
||||
requireToken();
|
||||
global $user, $db, $user_info;
|
||||
$vars["available"] = (int) $vars["available"];
|
||||
if($vars["available"] !== 0 && $vars["available"] !== 1) {
|
||||
return ["status" => "error", "message" => "Availability code not allowed"];
|
||||
}
|
||||
$log_message = $vars["available"] ? "Status changed to 'available'" : "Status changed to 'not available'";
|
||||
$db->select("UPDATE `".DB_PREFIX."_profiles` SET `available` = :available WHERE `id` = :id", ["id" => $user_info["id"], "available" => $vars["available"]]);
|
||||
$user->log($log_message);
|
||||
}
|
||||
);
|
||||
$r->addRoute(
|
||||
'GET', '/changeAvailability/{id:\d+}/{available:\d+}', function ($vars) {
|
||||
requireToken();
|
||||
global $user, $db, $user_info;
|
||||
$vars["available"] = (int) $vars["available"];
|
||||
if($vars["available"] !== 0 && $vars["available"] !== 1) {
|
||||
return ["status" => "error", "message" => "Availability code not allowed"];
|
||||
}
|
||||
$log_message = $vars["available"] ? "Status changed to 'available'" : "Status changed to 'not available'";
|
||||
$db->select("UPDATE `".DB_PREFIX."_profiles` SET `available` = :available WHERE `id` = :id", ["id" => $vars["id"], "available" => $vars["available"]]);
|
||||
$user->log($log_message, $vars["id"], $user_info["id"]);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$httpMethod = $_SERVER['REQUEST_METHOD'];
|
||||
$uri = $_SERVER['REQUEST_URI'];
|
||||
$uri = str_replace($_SERVER['SCRIPT_NAME'], "", $uri);
|
||||
$uri = str_replace("///", "/", $uri);
|
||||
$uri = str_replace("//", "/", $uri);
|
||||
$uri = "/" . trim($uri, "/");
|
||||
|
||||
// Strip query string (?foo=bar) and decode URI
|
||||
if (false !== $pos = strpos($uri, '?')) {
|
||||
$uri = substr($uri, 0, $pos);
|
||||
}
|
||||
$uri = rawurldecode($uri);
|
||||
|
||||
// Get response format
|
||||
if (isset($_GET["xml"])) {
|
||||
$responseFormat = "xml";
|
||||
$responseFormatType = "application/xml";
|
||||
} else if (isset($_GET["json"])) {
|
||||
$responseFormat = "json";
|
||||
$responseFormatType = "application/json";
|
||||
} else if (false !== strpos($uri, 'xml')) {
|
||||
$responseFormat = "xml";
|
||||
$responseFormatType = "application/xml";
|
||||
$uri = str_replace(".xml", "", $uri);
|
||||
} else if (false !== strpos($uri, 'json')) {
|
||||
$responseFormat = "json";
|
||||
$responseFormatType = "application/json";
|
||||
$uri = str_replace(".json", "", $uri);
|
||||
} else {
|
||||
$responseFormat = "json";
|
||||
$responseFormatType = "application/json";
|
||||
}
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Headers: *");
|
||||
header("Access-Control-Allow-Methods: *");
|
||||
header("Access-Control-Max-Age: *");
|
||||
header("Content-type: ".$responseFormatType);
|
||||
init_class(false, false); //initialize classes after Content-type header
|
||||
|
||||
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
|
||||
|
||||
function responseApi($content, $status_code=200)
|
||||
{
|
||||
global $responseFormat;
|
||||
if($status_code !== 200) {
|
||||
http_response_code($status_code);
|
||||
}
|
||||
if($responseFormat == "json") {
|
||||
echo(json_encode($content));
|
||||
} else {
|
||||
echo(ArrayToXml::convert($content));
|
||||
}
|
||||
}
|
||||
|
||||
function validToken()
|
||||
{
|
||||
global $db, $user_info;
|
||||
$token = isset($_REQUEST['apiKey']) ? $_REQUEST['apiKey'] : (isset($_REQUEST['apikey']) ? $_REQUEST['apikey'] : (isset($_SERVER['HTTP_APIKEY']) ? $_SERVER['HTTP_APIKEY'] : false));
|
||||
if($token == false) {
|
||||
return false;
|
||||
}
|
||||
if(!empty($api_key_row = $db->select("SELECT * FROM `".DB_PREFIX."_api_keys` WHERE apikey = :apikey", ["apikey" => $token]))) {
|
||||
$user_info["id"] = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE id = :id", ["id" => $api_key_row[0]["user"]])[0]["id"];
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function requireToken()
|
||||
{
|
||||
if(!validToken()) {
|
||||
responseApi(["status" => "error", "message" => "Access Denied"], 401);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] == "OPTIONS"){
|
||||
exit();
|
||||
}
|
||||
|
||||
switch ($routeInfo[0]) {
|
||||
case FastRoute\Dispatcher::NOT_FOUND:
|
||||
http_response_code(404);
|
||||
responseApi(["status" => "error", "message" => "Route not found"]);
|
||||
break;
|
||||
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
|
||||
$allowedMethods = $routeInfo[1];
|
||||
http_response_code(405);
|
||||
responseApi(["status" => "error", "message" => "Method not allowed", "usedMethod" => $_SERVER['REQUEST_METHOD']]);
|
||||
break;
|
||||
case FastRoute\Dispatcher::FOUND:
|
||||
$handler = $routeInfo[1];
|
||||
$vars = $routeInfo[2];
|
||||
responseApi($handler($vars));
|
||||
bdump($vars);
|
||||
break;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<?php
|
||||
require("../core.php");
|
||||
init_class();
|
||||
$tools->rickroll();
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
// ** Database settings ** //
|
||||
/* The name of the database for Allerta-vvf */
|
||||
define('DB_NAME', '@@db@@');
|
||||
|
||||
/* Database username */
|
||||
define('DB_USER', '@@user@@');
|
||||
|
||||
/* Database password */
|
||||
define('DB_PASSWORD', '@@password@@');
|
||||
|
||||
/* Database hostname */
|
||||
define('DB_HOST', '@@host@@');
|
||||
|
||||
/* Database hostname */
|
||||
define('DB_PREFIX', '@@prefix@@');
|
||||
|
||||
/* Telegram bot options */
|
||||
define('BOT_TELEGRAM_API_KEY', '');
|
||||
define('BOT_TELEGRAM_USERNAME', '');
|
||||
|
||||
/* Sentry options */
|
||||
define('SENTRY_CSP_REPORT_URI', '');
|
||||
define('SENTRY_ENABLED', false);
|
||||
define('SENTRY_DSN', '');
|
||||
define('SENTRY_ENV', 'prod');
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
header("Content-type: application/json");
|
||||
try {
|
||||
$start_time = microtime(true);
|
||||
include "core.php";
|
||||
init_class(false, false);
|
||||
$exec_time = microtime(true) - $start_time;
|
||||
$server_side = ["status" => "ok", "status_msg" => null, "exec_time" => $exec_time, "user_info" => $user->info()];
|
||||
} catch (Exception $e) {
|
||||
$server_side = ["status" => "error", "status_msg" => $e];
|
||||
}
|
||||
try {
|
||||
$client_side = ["status" => "ok", "status_msg" => null, "ip" => $tools->get_ip()];
|
||||
} catch (Exception $e) {
|
||||
$server_side = ["status" => "error", "status_msg" => $e];
|
||||
}
|
||||
echo(json_encode(["client" => $client_side, "server" => $server_side]));
|
1038
old_version/core.php
1038
old_version/core.php
File diff suppressed because it is too large
Load Diff
|
@ -1,175 +0,0 @@
|
|||
<?php
|
||||
require_once 'core.php';
|
||||
|
||||
init_class(false);
|
||||
error_reporting(-1);
|
||||
|
||||
list($cronJobDay, $cronJobTime) = explode(";", get_option("cron_job_time"));
|
||||
|
||||
$execDateTime = [
|
||||
"day" => date("d"),
|
||||
"month" => date("m"),
|
||||
"year" => date("Y"),
|
||||
"hour" => date("H"),
|
||||
"minutes" => date("i")
|
||||
];
|
||||
|
||||
$cronJobDateTime = [
|
||||
"day" => $cronJobDay,
|
||||
"month" => date("m"),
|
||||
"year" => date("Y"),
|
||||
"hour" => explode(":", $cronJobTime)[0],
|
||||
"minutes" => explode(":", $cronJobTime)[1]
|
||||
];
|
||||
|
||||
$start = get_option("cron_job_enabled") && ((isset($_POST['cron']) && $_POST['cron'] == "cron_job-".get_option("cron_job_code")) || (isset($_SERVER['HTTP_CRON']) && $_SERVER['HTTP_CRON'] == "cron_job-".get_option("cron_job_code")));
|
||||
$start_reset = ( $execDateTime["day"] == $cronJobDateTime["day"] &&
|
||||
$execDateTime["day"] == $cronJobDateTime["day"] &&
|
||||
$execDateTime["month"] == $cronJobDateTime["month"] &&
|
||||
$execDateTime["year"] == $cronJobDateTime["year"] &&
|
||||
$execDateTime["hour"] == $cronJobDateTime["hour"] &&
|
||||
$execDateTime["minutes"] - $cronJobDateTime["minutes"] < 5 );
|
||||
|
||||
$action = "Availability Minutes ";
|
||||
if($start) {
|
||||
if($start_reset) {
|
||||
$action .= "reset and ";
|
||||
$profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `available` = 1 ");
|
||||
if(count($profiles) > 0) {
|
||||
$list = [];
|
||||
foreach($profiles as $profile){
|
||||
$list[] = [$profile["id"] => $profile["availability_minutes"]];
|
||||
}
|
||||
$db->insert(
|
||||
DB_PREFIX."_minutes",
|
||||
["month" => $execDateTime["month"], "year" => $execDateTime["year"], "list"=>json_encode($list)]
|
||||
);
|
||||
$db->exec("UPDATE `".DB_PREFIX."_profiles` SET `availability_minutes` = 0");
|
||||
}
|
||||
}
|
||||
$action .= "update";
|
||||
|
||||
$profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `available` = 1");
|
||||
if(count($profiles) > 0) {
|
||||
$output = [];
|
||||
$output[] = $profiles;
|
||||
$output_status = "ok";
|
||||
$queries = [];
|
||||
foreach ($profiles as $row) {
|
||||
$value = (int)$row["availability_minutes"]+5;
|
||||
$id = $row["id"];
|
||||
$increment[$id] = $value;
|
||||
$count = $db->update(
|
||||
DB_PREFIX."_profiles",
|
||||
["availability_minutes" => $value],
|
||||
["id" => $id]
|
||||
);
|
||||
$tmp = $id . " - " . $value . " ";
|
||||
$tmp .= $count == 1 ? "success" : "fail";
|
||||
$queries[] = $tmp;
|
||||
}
|
||||
$output[] = $queries;
|
||||
} else {
|
||||
$output = ["profiles array empty"];
|
||||
$output_status = "ok";
|
||||
}
|
||||
|
||||
$result = $db->select("SELECT * FROM `".DB_PREFIX."_schedules`;");
|
||||
$schedules_check = [];
|
||||
$schedules_users = [];
|
||||
$schedules_check["schedules"] = [];
|
||||
$schedules_check["users"] = [];
|
||||
if(!empty($result)){
|
||||
foreach ($result as $key => $value) {
|
||||
$result[$key]["schedules"] = json_decode($result[$key]["schedules"]);
|
||||
}
|
||||
$schedules_check["table"] = $result;
|
||||
foreach ($result as $key => $row) {
|
||||
$last_exec = $row["last_exec"];
|
||||
$last_exec = [
|
||||
"day" => (int) explode(";",$row["last_exec"])[0],
|
||||
"hour" => (int) explode(":",explode(";",$row["last_exec"])[1])[0],
|
||||
"minutes" => (int) explode(":",$row["last_exec"])[1]
|
||||
];
|
||||
$id = $row["id"];
|
||||
$user_id = $row["user"];
|
||||
$selected_holidays = json_decode($row["holidays"]);
|
||||
$selected_holidays_dates = [];
|
||||
foreach ($selected_holidays as $holiday){
|
||||
$selected_holidays_dates[] = $user->holidays->getHoliday($holiday)->format('Y-m-d');
|
||||
}
|
||||
foreach ($row["schedules"] as $key => $value) {
|
||||
$schedule = [
|
||||
"day" => (int) $value[0]+1,
|
||||
"hour" => (int) explode(":",$value[1])[0],
|
||||
"minutes" => (int) explode(":",$value[1])[1]
|
||||
];
|
||||
$now = [
|
||||
"day" => (int) date("N"),
|
||||
"hour" => (int) date("H"),
|
||||
"minutes" => (int) date("i")
|
||||
];
|
||||
|
||||
if(
|
||||
$schedule["day"] == $now["day"] &&
|
||||
$schedule["hour"] == $now["hour"] &&
|
||||
$schedule["minutes"] <= $now["minutes"] &&
|
||||
$now["minutes"] - $schedule["minutes"] <= 30
|
||||
){
|
||||
if(!in_array($user_id,$schedules_users)) $schedules_users[] = $user_id;
|
||||
if($schedule["hour"] == $last_exec["hour"] ? $schedule["minutes"] !== $last_exec["minutes"] : true && !in_array(date('Y-m-d'), $selected_holidays_dates)){
|
||||
$last_exec_new = $schedule["day"].";".sprintf("%02d", $schedule["hour"]).":".sprintf("%02d", $schedule["minutes"]);
|
||||
$db->update(
|
||||
DB_PREFIX."_schedules",
|
||||
["last_exec" => $last_exec_new],
|
||||
["id" => $id]
|
||||
);
|
||||
$db->update(
|
||||
DB_PREFIX."_profiles",
|
||||
["available" => '1', "availability_last_change" => "cron"],
|
||||
["id" => $user_id]
|
||||
);
|
||||
$schedules_check["schedules"][] = [
|
||||
"schedule" => $schedule,
|
||||
"now" => $now,
|
||||
"exec" => $last_exec,
|
||||
"last_exec_new" => $last_exec_new,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
$schedules_check["users"] = $schedules_users;
|
||||
$profiles = $db->select("SELECT id FROM `".DB_PREFIX."_profiles`");
|
||||
foreach ($profiles as $profile) {
|
||||
if(!in_array($profile["id"],$schedules_users)){
|
||||
$db->update(
|
||||
DB_PREFIX."_profiles",
|
||||
["available" => 0],
|
||||
["availability_last_change" => "cron", "id" => $profile["id"]]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
require("error_page.php");
|
||||
show_error_page(405, "Method not allowed", "Allowed methods: POST");
|
||||
exit();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo(json_encode(
|
||||
[
|
||||
"start" => $start,
|
||||
"start_reset" => $start_reset,
|
||||
"execDateTime" => $execDateTime,
|
||||
"cronJobDateTime" => $cronJobDateTime,
|
||||
"action" => $action,
|
||||
"schedules_check" => $schedules_check,
|
||||
"output" => [
|
||||
"status" => $output_status,
|
||||
"message" => $output
|
||||
]
|
||||
]
|
||||
));
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"baseUrl": "http://127.0.0.1:8080/",
|
||||
"projectId": "4ty9gy",
|
||||
"env": {
|
||||
"DB_PASSWORD": "password"
|
||||
},
|
||||
"experimentalStudio": true,
|
||||
"chromeWebSecurity": false,
|
||||
"defaultCommandTimeout": 40000,
|
||||
"requestTimeout": 60000
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
|
@ -1 +0,0 @@
|
|||
[{"place_id":257830288,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":44882,"boundingbox":["45.2042549","46.3548531","9.8362928","10.842675"],"lat":"45.77958045","lon":"10.4258729694612","display_name":"Brescia, Lombardia, Italia","class":"boundary","type":"administrative","importance":0.7354640205264362,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":257390216,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":45144,"boundingbox":["45.4807171","45.5899752","10.1470684","10.3002186"],"lat":"45.5398022","lon":"10.2200214","display_name":"Brescia, Lombardia, Italia","class":"boundary","type":"administrative","importance":0.7271074461208031,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":54063101,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":4553822557,"boundingbox":["45.5273342","45.5373342","10.2078404","10.2178404"],"lat":"45.5323342","lon":"10.2128404","display_name":"Brescia, Piazzale Stazione, Centro Storico Sud, Zona Centro, Brescia, Lombardia, 25122, Italia","class":"railway","type":"station","importance":0.48614554653236075,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//transport_train_station2.p.20.png"},{"place_id":23143065,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":2409443276,"boundingbox":["44.4687976","44.4688976","8.6048681","8.6049681"],"lat":"44.4688476","lon":"8.6049181","display_name":"Brescia, Urbe, Savona, Liguria, 17048, Italia","class":"place","type":"isolated_dwelling","importance":0.29999999999999993},{"place_id":163311426,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"way","osm_id":307938666,"boundingbox":["-29.9019023","-29.9008417","-71.2670192","-71.2669167"],"lat":"-29.9014479","lon":"-71.2669709","display_name":"Brescia, Condominio Capri, La Serena, Provincia de Elqui, Coquimbo Region, 17000000, Cile","class":"highway","type":"residential","importance":0.19999999999999998}]
|
|
@ -1 +0,0 @@
|
|||
[{"place_id":258244980,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":44915,"boundingbox":["45.3867381","45.5358482","9.0408867","9.2781103"],"lat":"45.4668","lon":"9.1905","display_name":"Milano, Lombardia, Italia","class":"boundary","type":"administrative","importance":0.8390652078701348,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":258789516,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":344499,"boundingbox":["41.0766967","41.1206314","-6.6489375","-6.566364"],"lat":"41.0923837","lon":"-6.5984217","display_name":"El Milano, Salamanca, Castiglia e León, Spagna","class":"boundary","type":"administrative","importance":0.6058586680411897,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":258556780,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":44881,"boundingbox":["45.1614258","45.6434829","8.7060961","9.551457"],"lat":"45.454211900000004","lon":"9.111350961779705","display_name":"Milano, Lombardia, Italia","class":"boundary","type":"administrative","importance":0.6027406455537151,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":259530334,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"relation","osm_id":6605766,"boundingbox":["30.696897","30.718944","-96.881245","-96.841162"],"lat":"30.7106407","lon":"-96.864745","display_name":"Milano, Milam County, Texas, 76556, Stati Uniti d'America","class":"boundary","type":"administrative","importance":0.5332195319778348,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png"},{"place_id":5770611,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","osm_type":"node","osm_id":650721553,"boundingbox":["42.7623859","42.8023859","12.5780599","12.6180599"],"lat":"42.7823859","lon":"12.5980599","display_name":"Milano, Spoleto, Perugia, Umbria, 06049, Italia","class":"place","type":"hamlet","importance":0.5316717311890431,"icon":"https://nominatim.openstreetmap.org/ui/mapicons//poi_place_village.p.20.png"}]
|
|
@ -1,81 +0,0 @@
|
|||
[{
|
||||
"email": "user1@mail.local",
|
||||
"name": "Kathy Jordan",
|
||||
"username": "kathy.jordan",
|
||||
"password": "4jpbly8g6a",
|
||||
"birthday": "08/08/1995",
|
||||
"chief": false,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user2@mail.local",
|
||||
"name": "Natalie Jordan",
|
||||
"username": "natalie.jordan",
|
||||
"password": "kf343di74s",
|
||||
"birthday": "04/05/2011",
|
||||
"chief": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user3@mail.local",
|
||||
"name": "Keith Li",
|
||||
"username": "keith.li",
|
||||
"password": "3ojvgk4fpv",
|
||||
"birthday": "11/04/2019",
|
||||
"chief": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user4@mail.local",
|
||||
"name": "Pellegrino Scotto",
|
||||
"username": "pellegrino.scotto",
|
||||
"password": "e0ou92taw3",
|
||||
"birthday": "20/10/1983",
|
||||
"chief": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user5@mail.local",
|
||||
"name": "William Torres",
|
||||
"username": "william.torres",
|
||||
"password": "0fso8sxxe0",
|
||||
"birthday": "16/07/2000",
|
||||
"chief": false,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user6@mail.local",
|
||||
"name": "Napoleone Tomasetti",
|
||||
"username": "napoleone.tomasetti",
|
||||
"password": "so7ykv8a7g",
|
||||
"birthday": "27/12/1978",
|
||||
"chief": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user7@mail.local",
|
||||
"name": "Gelsomina Murray",
|
||||
"username": "gelsomina.murray",
|
||||
"password": "x1js0s6zao",
|
||||
"birthday": "22/10/1994",
|
||||
"chief": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user8@mail.local",
|
||||
"name": "Letizia Petrucelli",
|
||||
"username": "letizia.petrucelli",
|
||||
"password": "k1hsbdt3cv",
|
||||
"birthday": "24/04/1981",
|
||||
"chief": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user9@mail.local",
|
||||
"name": "Giampaolo Surian",
|
||||
"username": "giampaolo.surian",
|
||||
"password": "et52m65s4g",
|
||||
"birthday": "10/06/1972",
|
||||
"chief": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user10@mail.local",
|
||||
"name": "Cassandra Jensen",
|
||||
"username": "cassandra.jensen",
|
||||
"password": "9h3fb37ccw",
|
||||
"birthday": "28/10/1985",
|
||||
"chief": true,
|
||||
"driver": false
|
||||
}]
|
|
@ -1,116 +0,0 @@
|
|||
describe("Installation", () => {
|
||||
before(() => {
|
||||
cy.exec("rm config.old.php", {failOnNonZeroExit: false});
|
||||
cy.exec("mv config.php config.old.php", {failOnNonZeroExit: false});
|
||||
cy.visit("/");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setCookie("forceLanguage", "en");
|
||||
})
|
||||
|
||||
it('Write wrong DB pwd and user', function () {
|
||||
cy.get("input[name='dbname']")
|
||||
.clear()
|
||||
.type("allerta_db_"+Date.now())
|
||||
|
||||
cy.get("input[name='uname']")
|
||||
.clear()
|
||||
.type("root_wrongpwd_"+Date.now())
|
||||
|
||||
cy.get("input[name='pwd']")
|
||||
.clear()
|
||||
.should('have.value', '')
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Error establishing a database connection");
|
||||
cy.visit("/");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
|
||||
it('Write correct DB pwd and user', function () {
|
||||
cy.get("input[name='dbname']")
|
||||
.clear()
|
||||
.type("allerta_db_"+Date.now())
|
||||
|
||||
cy.get("input[name='uname']")
|
||||
.clear()
|
||||
.type("root")
|
||||
.should('have.value', 'root')
|
||||
|
||||
cy.get("input[name='pwd']")
|
||||
.clear()
|
||||
.type(Cypress.env("DB_PASSWORD"))
|
||||
.should('have.value', Cypress.env("DB_PASSWORD"))
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Great job, man!");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
|
||||
it('Finish installation', function () {
|
||||
cy.get("input[name='user_name']")
|
||||
.clear()
|
||||
.type("admin")
|
||||
.should('have.value', 'admin')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("password")
|
||||
.should('have.value', 'password')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Very weak')
|
||||
.should('have.class', 'short')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("passsword")
|
||||
.should('have.value', 'passsword')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Weak')
|
||||
.should('have.class', 'bad')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("Tr0ub4dour&3")
|
||||
.should('have.value', 'Tr0ub4dour&3')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Good')
|
||||
.should('have.class', 'good')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("#Tr0ub4dour&3#")
|
||||
.should('have.value', '#Tr0ub4dour&3#')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Strong')
|
||||
.should('have.class', 'strong')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("correcthorsebatterystaple")
|
||||
.should('have.value', 'correcthorsebatterystaple')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Very strong')
|
||||
.should('have.class', 'strong')
|
||||
|
||||
cy.get("input[name='admin_visible']").check()
|
||||
|
||||
cy.get("input[name='admin_email']")
|
||||
.clear()
|
||||
.type("admin_email@mail.local")
|
||||
.should('have.value', 'admin_email@mail.local')
|
||||
|
||||
cy.get("input[name='owner']")
|
||||
.clear()
|
||||
.type("owner")
|
||||
.should('have.value', 'owner')
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Great job, man!");
|
||||
cy.exec("touch install/runInstall.php", {failOnNonZeroExit: false});
|
||||
cy.get(".login").click();
|
||||
cy.contains("Login");
|
||||
})
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
describe("Login and logout", () => {
|
||||
it('Login', function () {
|
||||
cy.login()
|
||||
cy.contains("Logs")
|
||||
cy.contains("Logs").click()
|
||||
cy.wait('@ajax_log')
|
||||
cy.get("#list").contains("Login")
|
||||
})
|
||||
|
||||
it('Logout', function () {
|
||||
cy.login()
|
||||
cy.visit("/logout.php")
|
||||
cy.contains("Login")
|
||||
cy.login()
|
||||
cy.contains("Logs")
|
||||
cy.contains("Logs").click()
|
||||
cy.wait('@ajax_log')
|
||||
cy.get("#list").contains("Logout")
|
||||
})
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
describe("Availability", () => {
|
||||
beforeEach(() => {
|
||||
cy.login()
|
||||
})
|
||||
it('Change availability to available', function () {
|
||||
cy.contains('Activate').click()
|
||||
cy.wait("@ajax_change_availability")
|
||||
cy.get(".toast-message").should('be.visible').contains('Thanks, admin, you have given your availability in case of alert.');
|
||||
cy.wait("@ajax_list")
|
||||
cy.get(".fa-check").should('be.visible')
|
||||
cy.visit("/log.php")
|
||||
cy.wait("@ajax_log")
|
||||
cy.contains("Status changed to 'available'")
|
||||
cy.visit("/")
|
||||
})
|
||||
|
||||
it('Change availability to not available', function () {
|
||||
cy.contains('Deactivate').click()
|
||||
cy.wait("@ajax_change_availability")
|
||||
cy.get(".toast-message").should('be.visible').contains('Thanks, admin, you have removed your availability in case of alert.');
|
||||
cy.wait("@ajax_list")
|
||||
cy.get(".fa-times").should('be.visible')
|
||||
cy.visit("/log.php")
|
||||
cy.wait("@ajax_log")
|
||||
cy.contains("Status changed to 'not available'")
|
||||
cy.visit("/")
|
||||
})
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
describe("User management", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.fixture('users')
|
||||
.as('users');
|
||||
})
|
||||
it('Create users', () => {
|
||||
cy.get('@users')
|
||||
.then((users) => {
|
||||
cy.getApiKey().then((apiKey) => {
|
||||
var i = 1
|
||||
var phone_number = "+1-800-555-0199";
|
||||
users.forEach(user => {
|
||||
console.log("User '"+user.name+"' number "+i);
|
||||
if(i == 1){
|
||||
console.log("Adding user via gui...");
|
||||
name = user.name
|
||||
console.log(user)
|
||||
cy.wait(1000)
|
||||
cy.contains("Add user").click()
|
||||
cy.get("input[name='mail']")
|
||||
.clear()
|
||||
.type(user.email)
|
||||
.should('have.value', user.email)
|
||||
cy.get("input[name='name']")
|
||||
.clear()
|
||||
.type(user.name)
|
||||
.should('have.value', user.name)
|
||||
cy.get("input[name='username']")
|
||||
.clear()
|
||||
.type(user.username)
|
||||
.should('have.value', user.username)
|
||||
cy.get("input[name='password']")
|
||||
.clear()
|
||||
.type(user.password)
|
||||
.should('have.value', user.password)
|
||||
cy.get("input[name='phone_number']")
|
||||
.clear()
|
||||
.type(phone_number)
|
||||
.should('have.value', phone_number)
|
||||
cy.get("input[name='birthday']")
|
||||
.clear()
|
||||
.type(user.birthday)
|
||||
.should('have.value', user.birthday)
|
||||
if(user.chief){
|
||||
cy.get("input[name='chief']")
|
||||
.check({force: true})
|
||||
}
|
||||
if(user.driver){
|
||||
cy.get("input[name='driver']")
|
||||
.check({force: true})
|
||||
}
|
||||
cy.contains("Submit").click()
|
||||
cy.wait('@ajax_list')
|
||||
cy.contains(user.name)
|
||||
cy.visit("/log.php")
|
||||
cy.wait('@ajax_log')
|
||||
cy.contains("User added")
|
||||
cy.contains(user.name)
|
||||
cy.visit("/")
|
||||
} else {
|
||||
console.log("Adding user via api...");
|
||||
cy.request({ method: 'POST', url: '/api.php/user', form: true,
|
||||
body: {
|
||||
apiKey: apiKey,
|
||||
mail: user.email,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
phone_number: phone_number,
|
||||
birthday: user.birthday,
|
||||
chief: user.chief | 0,
|
||||
driver: user.driver | 0,
|
||||
hidden: 0,
|
||||
disabled: 0
|
||||
}})
|
||||
.then((response) => {
|
||||
console.log(response.body)
|
||||
expect(response.status).to.eq(200)
|
||||
expect(response.body).to.have.property('userId')
|
||||
cy.visit("/log.php")
|
||||
cy.wait('@ajax_log')
|
||||
cy.contains("User added")
|
||||
cy.contains(user.name)
|
||||
})
|
||||
}
|
||||
i+=1;
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
|
@ -1,110 +0,0 @@
|
|||
describe("Service management", () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
})
|
||||
|
||||
it('Add Service with new type', function () {
|
||||
cy.get('tr:has(> td:has(> [data-user="11"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="4"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="9"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="7"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="2"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="6"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.visit("/edit_service.php?add", {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win, 'prompt').returns('test');
|
||||
}
|
||||
});
|
||||
cy.get('#date-picker').clear();
|
||||
cy.get('#date-picker').type('07/12/2020');
|
||||
cy.window().then(win => win.$('.datepicker').remove());
|
||||
cy.get('#progressivo').clear();
|
||||
cy.get('#progressivo').type('1234/5');
|
||||
cy.get('#timePicker1').clear();
|
||||
cy.get('#timePicker1').type('10:10');
|
||||
cy.get('#timePicker2').clear();
|
||||
cy.get('#timePicker2').type('23:59');
|
||||
cy.get('.chief-11').check();
|
||||
cy.get('.drivers-7').check();
|
||||
cy.get('.drivers-6').check();
|
||||
cy.get('.crew-2').check();
|
||||
cy.get('.crew-4').check();
|
||||
cy.get('.crew-9').check();
|
||||
cy.get('#addr').clear();
|
||||
cy.get('#addr').type('brescia');
|
||||
cy.get('#search_button').click();
|
||||
cy.get('#search').click();
|
||||
cy.get('.results-list > :nth-child(1) > a').click();
|
||||
cy.get('#notes').click();
|
||||
cy.get('#types').select('add_new');
|
||||
cy.wait('@ajax_add_type');
|
||||
cy.get('[type="submit"]').click();
|
||||
cy.wait('@ajax_services');
|
||||
cy.contains("2020-07-12");
|
||||
cy.contains("1234/5");
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Service added");
|
||||
cy.visit("/list.php");
|
||||
cy.wait('@ajax_list');
|
||||
cy.get('tr:has(> td:has(> [data-user="11"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="4"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="9"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="7"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="2"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="6"])) > :nth-child(6)').should('contain', '1');
|
||||
});
|
||||
|
||||
it('Edit service', function() {
|
||||
cy.visit("/services.php");
|
||||
cy.wait('@ajax_services');
|
||||
cy.get('.dtr-details a[data-action="edit"]').first().click();
|
||||
cy.get('#progressivo').clear();
|
||||
cy.get('#progressivo').type('4321/5');
|
||||
cy.get('.chief-11').uncheck();
|
||||
cy.get('.chief-8').check();
|
||||
cy.get('.crew-4').uncheck();
|
||||
cy.get('.crew-9').uncheck();
|
||||
cy.get('.crew-3').check();
|
||||
cy.get('.crew-5').check();
|
||||
cy.get('#addr').clear();
|
||||
cy.get('#addr').type('milano');
|
||||
cy.get('#search_button').click();
|
||||
cy.get('.results-list > :nth-child(1) > a').click();
|
||||
cy.get('[type="submit"]').click();
|
||||
cy.wait('@ajax_services');
|
||||
cy.contains("2020-07-12");
|
||||
cy.contains("4321/5");
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Service edited");
|
||||
cy.visit("/list.php");
|
||||
cy.wait('@ajax_list');
|
||||
cy.get('tr:has(> td:has(> [data-user="11"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="8"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="4"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="9"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="3"])) > :nth-child(6)').should('contain', '1');
|
||||
cy.get('tr:has(> td:has(> [data-user="5"])) > :nth-child(6)').should('contain', '1');
|
||||
});
|
||||
|
||||
it('Delete Service', function() {
|
||||
cy.visit("/services.php");
|
||||
cy.wait('@ajax_services');
|
||||
cy.get('.dtr-details a[data-action="delete"]').first().click();
|
||||
cy.get('#remove').click();
|
||||
cy.wait('@ajax_services');
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Service removed");
|
||||
cy.visit("/list.php");
|
||||
cy.wait('@ajax_list');
|
||||
cy.get('tr:has(> td:has(> [data-user="8"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="3"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="5"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="7"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="2"])) > :nth-child(6)').should('contain', '0');
|
||||
cy.get('tr:has(> td:has(> [data-user="6"])) > :nth-child(6)').should('contain', '0');
|
||||
});
|
||||
|
||||
})
|
|
@ -1,78 +0,0 @@
|
|||
describe("Training management", () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
})
|
||||
|
||||
it('Add Training', function () {
|
||||
cy.visit("/edit_training.php?add", {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win, 'prompt').returns('test')
|
||||
}
|
||||
});
|
||||
cy.get('#date-picker').clear();
|
||||
cy.get('#date-picker').type('07/12/2020');
|
||||
cy.window().then(win => win.$('.datepicker').remove());
|
||||
cy.get('#name').clear();
|
||||
cy.get('#name').type('Test Training');
|
||||
cy.get('#timePicker1').clear();
|
||||
cy.get('#timePicker1').type('10:10');
|
||||
cy.get('#timePicker2').clear();
|
||||
cy.get('#timePicker2').type('23:59');
|
||||
cy.get('.chief-5').check();
|
||||
cy.get('.crew-2').check();
|
||||
cy.get('.crew-4').check();
|
||||
cy.get('.crew-3').check();
|
||||
cy.get('.crew-6').check();
|
||||
cy.get('#addr').clear();
|
||||
cy.get('#addr').type('brescia');
|
||||
cy.get('#search_button').click();
|
||||
cy.get('#search').click();
|
||||
cy.get('.results-list > :nth-child(1) > a').click();
|
||||
cy.get('[type="submit"]').click();
|
||||
cy.wait('@ajax_trainings');
|
||||
cy.contains("2020-07-12");
|
||||
cy.contains("Test Training");
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Training added");
|
||||
cy.visit("/list.php");
|
||||
cy.wait('@ajax_list');
|
||||
});
|
||||
|
||||
it('Edit Training', function() {
|
||||
cy.visit("/trainings.php");
|
||||
cy.wait('@ajax_trainings');
|
||||
cy.get('.dtr-details a[data-action="edit"]').first().click();
|
||||
cy.get('#name').clear();
|
||||
cy.get('#name').type('Training 1 test');
|
||||
cy.get('.chief-5').uncheck();
|
||||
cy.get('.chief-7').check();
|
||||
cy.get('.crew-3').uncheck();
|
||||
cy.get('.crew-6').uncheck();
|
||||
cy.get('.crew-9').check();
|
||||
cy.get('.crew-8').check();
|
||||
cy.get('#addr').clear();
|
||||
cy.get('#addr').type('milano');
|
||||
cy.get('#search_button').click();
|
||||
cy.get('.results-list > :nth-child(1) > a').click();
|
||||
cy.get('[type="submit"]').click();
|
||||
cy.wait('@ajax_trainings');
|
||||
cy.contains("2020-07-12");
|
||||
cy.contains("Training 1 test");
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Training edited");
|
||||
});
|
||||
|
||||
it('Delete Training', function() {
|
||||
cy.visit("/trainings.php");
|
||||
cy.wait('@ajax_trainings');
|
||||
cy.get('.dtr-details a[data-action="delete"]').first().click();
|
||||
cy.get('#remove').click();
|
||||
cy.wait('@ajax_trainings');
|
||||
cy.visit("/log.php");
|
||||
cy.wait('@ajax_log');
|
||||
cy.contains("Training removed");
|
||||
});
|
||||
|
||||
})
|
|
@ -1,47 +0,0 @@
|
|||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// for some reasons, the test fails without this in certain conditions...
|
||||
return false
|
||||
})
|
||||
|
||||
//TODO: login remember me and better language support
|
||||
Cypress.Commands.add("login", (username="admin", password="correcthorsebatterystaple") => {
|
||||
cy.setCookie("forceLanguage", "en");
|
||||
cy.setCookie('disableServiceWorkerInstallation', '1');
|
||||
|
||||
cy.visit("/");
|
||||
cy.get("input[name='name']")
|
||||
.clear()
|
||||
.type(username)
|
||||
.should('have.value', username)
|
||||
|
||||
cy.get("input[name='password']")
|
||||
.clear()
|
||||
.type(password)
|
||||
.should('have.value', password)
|
||||
|
||||
cy.get("input[name='login']").click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add("getApiKey", (username="admin", password="correcthorsebatterystaple") => {
|
||||
cy.request({ method: 'POST', url: '/api.php/login', form: true, body: { username: username, password: password }})
|
||||
.then((response) => {
|
||||
expect(response.status).to.eq(200)
|
||||
expect(response.body).to.have.property('apiKey')
|
||||
console.log(response.body)
|
||||
return response.body.apiKey
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('https://nominatim.openstreetmap.org/search?format=json&limit=5&q=brescia', { fixture: 'nominatim_brescia.json' });
|
||||
cy.intercept('https://nominatim.openstreetmap.org/search?format=json&limit=5&q=milano', { fixture: 'nominatim_milano.json' });
|
||||
cy.intercept('https://a.tile.openstreetmap.org/*/*/*.png', { fixture: 'map_frame_A.png' });
|
||||
cy.intercept('https://b.tile.openstreetmap.org/*/*/*.png', { fixture: 'map_frame_B.png' });
|
||||
cy.intercept('https://c.tile.openstreetmap.org/*/*/*.png', { fixture: 'map_frame_C.png' });
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_add_type.php**').as('ajax_add_type');
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_change_availability.php**').as('ajax_change_availability');
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_list.php**').as('ajax_list');
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_log.php**').as('ajax_log');
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_services.php**').as('ajax_services');
|
||||
cy.intercept(Cypress.config('baseUrl')+'resources/ajax/ajax_trainings.php**').as('ajax_trainings');
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
require("core.php");
|
||||
init_class();
|
||||
if($user->authenticated()){
|
||||
if($user->hasRole(Role::DEVELOPER)){
|
||||
if(!isset($_REQUEST["op"]) || !isset($_REQUEST["id"])) $tools->rickroll();
|
||||
$openHandler = new DebugBar\OpenHandler($debugbar);
|
||||
$response = $openHandler->handle();
|
||||
} else {
|
||||
$tools->rickroll();
|
||||
}
|
||||
} else {
|
||||
$tools->rickroll();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
nothing (see this directory in file explorer or delete this file)
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
require_once 'ui.php';
|
||||
function debug(){
|
||||
echo("<pre>"); var_dump($_POST); echo("</pre>"); exit();
|
||||
}
|
||||
if($tools->validate_form("mod", "add")) {
|
||||
if($tools->validate_form(['date', 'code', 'beginning', 'end', 'place', 'notes', 'type', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("adding service");
|
||||
$place = $tools->checkPlaceParam($_POST["place"]);
|
||||
$crud->add_service($_POST["date"], $_POST["code"], $_POST["beginning"], $_POST["end"], $_POST["chief"][0], $tools->extract_unique($_POST["drivers"]), $tools->extract_unique($_POST["crew"]), $place, $_POST["notes"], $_POST["type"], $tools->extract_unique([$_POST["chief"],$_POST["drivers"],$_POST["crew"]]), $user->name());
|
||||
$tools->redirect("services.php");
|
||||
} else {
|
||||
debug(); //TODO: remove debug info
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} elseif($tools->validate_form("mod", "edit")) {
|
||||
if($tools->validate_form(['id', 'date', 'code', 'beginning', 'end', 'place', 'notes', 'type', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump($_POST);
|
||||
bdump("editing service");
|
||||
$place = $tools->checkPlaceParam($_POST["place"]);
|
||||
$crud->edit_service($_POST["id"], $_POST["date"], $_POST["code"], $_POST["beginning"], $_POST["end"], $_POST["chief"][0], $tools->extract_unique($_POST["drivers"]), $tools->extract_unique($_POST["crew"]), $place, $_POST["notes"], $_POST["type"], $tools->extract_unique([$_POST["chief"],$_POST["drivers"],$_POST["crew"]]), $user->name());
|
||||
$tools->redirect("services.php");
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} elseif($tools->validate_form("mod", "delete")) {
|
||||
bdump("removing service");
|
||||
if($tools->validate_form(['id', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("removing service");
|
||||
$crud->remove_service($_POST["id"]);
|
||||
$tools->redirect("services.php");
|
||||
} else {
|
||||
echo("1");
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
echo("2");
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
if(isset($_GET["add"])||isset($_GET["edit"])||isset($_GET["delete"])||isset($_GET["mod"])) {
|
||||
$_SESSION["token"] = bin2hex(random_bytes(64));
|
||||
}
|
||||
$crew = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` ORDER BY name ASC");
|
||||
$types = $db->select("SELECT `name` FROM `".DB_PREFIX."_type` ORDER BY name ASC");
|
||||
$modalità = (isset($_GET["add"])) ? "add" : ((isset($_GET["edit"])) ? "edit" : ((isset($_GET["delete"])) ? "delete" : "add"));
|
||||
bdump($modalità, "modalità");
|
||||
bdump($types, "types");
|
||||
bdump($crew, "crew");
|
||||
$id = "";
|
||||
if(isset($_GET["id"])) {
|
||||
$id = $_GET["id"];
|
||||
bdump($crud->exists("services", $id));
|
||||
$values = $db->select("SELECT * FROM `".DB_PREFIX."_services` WHERE `id` = :id", [":id" => $id])[0];
|
||||
bdump($values);
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
if($modalità=="edit" || $modalità=="delete") {
|
||||
if(empty($id)) {
|
||||
echo("<pre>"); var_dump($_POST); echo("</pre>");
|
||||
} elseif (!$crud->exists("services", $id)) {
|
||||
echo("<pre>"); var_dump($_POST); echo("</pre>");
|
||||
}
|
||||
}
|
||||
loadtemplate('edit_service.html', ['service' => ['id' => $id, 'token' => $_SESSION['token'], 'modalità' => $modalità, 'crew' => $crew, 'types' => $types], 'values' => $values, 'title' => t(ucfirst($modalità) . " service", false)]);
|
||||
bdump($_SESSION['token'], "token");
|
||||
}
|
||||
?>
|
|
@ -1,73 +0,0 @@
|
|||
<?php
|
||||
require_once 'ui.php';
|
||||
function debug(){
|
||||
echo("<pre>"); var_dump($_POST); echo("</pre>"); exit();
|
||||
}
|
||||
if($tools->validate_form("mod", "add")) {
|
||||
if($tools->validate_form(['date', 'name', 'start_time', 'end_time', 'place', 'notes', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("adding training");
|
||||
$place = $tools->checkPlaceParam($_POST["place"]);
|
||||
$crud->add_training($_POST["date"], $_POST["name"], $_POST["start_time"], $_POST["end_time"], $_POST["chief"][0], $tools->extract_unique($_POST["crew"]), $place, $_POST["notes"], $tools->extract_unique([$_POST["chief"],$_POST["crew"]]), $user->name());
|
||||
$tools->redirect("trainings.php");
|
||||
} else {
|
||||
debug(); //TODO: remove debug info
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} elseif($tools->validate_form("mod", "edit")) {
|
||||
if($tools->validate_form(['id', 'date', 'name', 'start_time', 'end_time', 'chief', 'place', 'notes', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump($_POST);
|
||||
bdump("editing training");
|
||||
$place = $tools->checkPlaceParam($_POST["place"]);
|
||||
$crud->edit_training($_POST["id"], $_POST["date"], $_POST["name"], $_POST["start_time"], $_POST["end_time"], $_POST["chief"][0], $tools->extract_unique($_POST["crew"]), $place, $_POST["notes"], $tools->extract_unique([$_POST["chief"],$_POST["crew"]]), $user->name());
|
||||
$tools->redirect("trainings.php");
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} elseif($tools->validate_form("mod", "delete")) {
|
||||
bdump("removing training");
|
||||
if($tools->validate_form(['id', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("removing training");
|
||||
$crud->remove_training($_POST["id"]);
|
||||
$tools->redirect("trainings.php");
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
if(isset($_GET["add"])||isset($_GET["edit"])||isset($_GET["delete"])||isset($_GET["mod"])) {
|
||||
$_SESSION["token"] = bin2hex(random_bytes(64));
|
||||
}
|
||||
$crew = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` ORDER BY name ASC");
|
||||
$modalità = (isset($_GET["add"])) ? "add" : ((isset($_GET["edit"])) ? "edit" : ((isset($_GET["delete"])) ? "delete" : "add"));
|
||||
bdump($modalità, "modalità");
|
||||
bdump($crew, "crew");
|
||||
$id = "";
|
||||
if(isset($_GET["id"])) {
|
||||
$id = $_GET["id"];
|
||||
bdump($crud->exists("trainings", $id));
|
||||
$values = $db->select("SELECT * FROM `".DB_PREFIX."_trainings` WHERE `id` = :id", [":id" => $id])[0];
|
||||
bdump($values);
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
if($modalità=="edit" || $modalità=="delete") {
|
||||
if(empty($id)) {
|
||||
$tools->redirect("accessdenied.php");
|
||||
} elseif (!$crud->exists("trainings", $id)) {
|
||||
//$tools->redirect("accessdenied.php");
|
||||
}
|
||||
}
|
||||
loadtemplate('edit_training.html', ['training' => ['id' => $id, 'token' => $_SESSION['token'], 'modalità' => $modalità, 'crew' => $crew], 'values' => $values, 'title' => t(ucfirst($modalità) . " training", false)]);
|
||||
bdump($_SESSION['token'], "token");
|
||||
}
|
||||
?>
|
|
@ -1,95 +0,0 @@
|
|||
<?php
|
||||
require_once 'ui.php';
|
||||
use Brick\PhoneNumber\PhoneNumber;
|
||||
use Brick\PhoneNumber\PhoneNumberFormat;
|
||||
use Brick\PhoneNumber\PhoneNumberParseException;
|
||||
|
||||
if(!$user->hasRole(Role::SUPER_ADMIN)){
|
||||
require("error_page.php");
|
||||
show_error_page(401, t("You are not authorized to perform this action.",false), "");
|
||||
exit();
|
||||
}
|
||||
|
||||
function debug(){
|
||||
echo("<pre>"); var_dump($_POST); echo("</pre>"); exit();
|
||||
}
|
||||
if($tools->validate_form("mod", "add")) {
|
||||
if($tools->validate_form(['mail', 'name', 'username', 'password', 'phone_number', 'birthday', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("adding user");
|
||||
bdump($_POST);
|
||||
$chief = isset($_POST["chief"]) ? 1 : 0;
|
||||
$driver = isset($_POST["driver"]) ? 1 : 0;
|
||||
$hidden = isset($_POST["visible"]) ? 0 : 1;
|
||||
$disabled = isset($_POST["enabled"]) ? 0 : 1;
|
||||
try {
|
||||
$phone_number = PhoneNumber::parse($_POST["phone_number"]);
|
||||
if (!$phone_number->isValidNumber()) {
|
||||
echo("Bad phone number. <a href='javascript:window.history.back()'>Go back</a>"); //TODO: better form validation
|
||||
exit();
|
||||
} else {
|
||||
$phone_number = $phone_number->format(PhoneNumberFormat::E164);
|
||||
}
|
||||
} catch (PhoneNumberParseException $e) {
|
||||
echo("Bad phone number. <a href='javascript:window.history.back()'>Go back</a>"); //TODO: better form validation
|
||||
exit();
|
||||
}
|
||||
$user->add_user($_POST["mail"], $_POST["name"], $_POST["username"], $_POST["password"], $phone_number, $_POST["birthday"], $chief, $driver, $hidden, $disabled, $user->name());
|
||||
$tools->redirect("list.php");
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
/*} elseif($tools->validate_form("mod", "edit")) {
|
||||
if($tools->validate_form(['mail', 'name', 'username', 'password', 'birthday', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']){
|
||||
bdump($_POST);
|
||||
bdump("editing service");
|
||||
$crud->edit_service($_POST["id"], $_POST["date"], $_POST["code"], $_POST["beginning"], $_POST["end"], $_POST["chief"], $tools->extract_unique($_POST["drivers"]), $tools->extract_unique($_POST["crew"]), $_POST["place"], $_POST["notes"], $_POST["type"], $tools->extract_unique([$_POST["chief"],$_POST["drivers"],$_POST["crew"]]), $user->name());
|
||||
$tools->redirect("services.php");
|
||||
} else {
|
||||
$tools->redirect("accessdenied.php");
|
||||
}
|
||||
}
|
||||
*/
|
||||
} elseif($tools->validate_form("mod", "delete")) {
|
||||
bdump("removing service");
|
||||
if($tools->validate_form(['id', 'token'])) {
|
||||
if($_POST["token"] == $_SESSION['token']) {
|
||||
bdump("removing user");
|
||||
$user->remove_user($_POST["id"]);
|
||||
$tools->redirect("list.php");
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
debug();
|
||||
}
|
||||
} else {
|
||||
if(isset($_GET["add"])||isset($_GET["edit"])||isset($_GET["delete"])||isset($_GET["mod"])) {
|
||||
$_SESSION["token"] = bin2hex(random_bytes(64));
|
||||
}
|
||||
$modalità = (isset($_GET["add"])) ? "add" : ((isset($_GET["edit"])) ? "edit" : ((isset($_GET["delete"])) ? "delete" : "add"));
|
||||
bdump($modalità, "modalità");
|
||||
$id = "";
|
||||
if(isset($_GET["id"])) {
|
||||
$id = $_GET["id"];
|
||||
bdump($crud->exists("profiles", $id));
|
||||
$values = $db->select("SELECT * FROM `".DB_PREFIX."_profiles` WHERE `id` = :id", [":id" => $id])[0];
|
||||
bdump($values);
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
if($modalità=="edit" || $modalità=="delete") {
|
||||
if(empty($id)) {
|
||||
$tools->redirect("accessdenied.php");
|
||||
} elseif (!$crud->exists("profiles", $id)) {
|
||||
$tools->redirect("accessdenied.php");
|
||||
}
|
||||
}
|
||||
loadtemplate('edit_user.html', ['id' => $id, 'token' => $_SESSION["token"], 'modalità' => $modalità, 'values' => $values, 'title' => t(ucfirst($modalità) . " user", false)]);
|
||||
bdump($_SESSION['token'], "token");
|
||||
}
|
||||
?>
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
function show_error_page($error=null, $error_message=null, $error_message_advanced=null){
|
||||
global $tools, $webpack_manifest_path;
|
||||
$error = !is_null($error) ? $error : (isset($_GET["error"]) ? $_GET["error"] : 404);
|
||||
if(is_null($error_message)){
|
||||
switch ($error){
|
||||
case 404:
|
||||
$error_message = "Page not found";
|
||||
$error_message_advanced = "Please check the url";
|
||||
break;
|
||||
|
||||
case 500:
|
||||
$error_message = "Server error";
|
||||
$error_message_advanced = "Please retry later";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$main_script_url = null;
|
||||
$game_script_url = null;
|
||||
try{
|
||||
$webpack_manifest_path = isset($webpack_manifest_path) ? $webpack_manifest_path : realpath("resources/dist/assets-manifest.json");
|
||||
if(!empty($webpack_manifest_path)){
|
||||
$webpack_manifest = json_decode(
|
||||
file_get_contents($webpack_manifest_path),
|
||||
true
|
||||
);
|
||||
$main_script_url = "resources/dist/".$webpack_manifest["main.js"]["src"];
|
||||
$game_script_url = "resources/dist/".$webpack_manifest["games.js"]["src"];
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
|
||||
$error_templates = [
|
||||
<<<EOT
|
||||
<div id="error">
|
||||
<div id="box"></div>
|
||||
<h3>ERROR $error</h3>
|
||||
<p>$error_message</p>
|
||||
<p>$error_message_advanced</p>
|
||||
</div>
|
||||
<style>
|
||||
body,html{height:100%}body{display:grid;width:100%;font-family:Inconsolata,monospace}body div#error{position:relative;margin:auto;padding:20px;z-index:2}body div#error div#box{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #000}body div#error div#box:after,body div#error div#box:before{content:'';position:absolute;top:0;left:0;width:100%;height:100%;box-shadow:inset 0 0 0 1px #000;mix-blend-mode:multiply;animation:dance 2s infinite steps(1)}body div#error div#box:before{clip-path:polygon(0 0,65% 0,35% 100%,0 100%);box-shadow:inset 0 0 0 1px currentColor;color:#f0f}body div#error div#box:after{clip-path:polygon(65% 0,100% 0,100% 100%,35% 100%);animation-duration:.5s;animation-direction:alternate;box-shadow:inset 0 0 0 1px currentColor;color:#0ff}body div#error h3{position:relative;font-size:5vw;font-weight:700;text-transform:uppercase;animation:blink 1.3s infinite steps(1)}body div#error h3:after,body div#error h3:before{content:'ERROR $error';position:absolute;top:-1px;left:0;mix-blend-mode:soft-light;animation:dance 2s infinite steps(2)}body div#error h3:before{clip-path:polygon(0 0,100% 0,100% 50%,0 50%);color:#f0f;animation:shiftright 2s steps(2) infinite}body div#error h3:after{clip-path:polygon(0 100%,100% 100%,100% 50%,0 50%);color:#0ff;animation:shiftleft 2s steps(2) infinite}body div#error p{position:relative;margin-bottom:8px}body div#error p span{position:relative;display:inline-block;font-weight:700;color:#000;animation:blink 3s steps(1) infinite}body div#error p span:after,body div#error p span:before{content:'unstable';position:absolute;top:-1px;left:0;mix-blend-mode:multiply}body div#error p span:before{clip-path:polygon(0 0,100% 0,100% 50%,0 50%);color:#f0f;animation:shiftright 1.5s steps(2) infinite}body div#error p span:after{clip-path:polygon(0 100%,100% 100%,100% 50%,0 50%);color:#0ff;animation:shiftleft 1.7s steps(2) infinite}@-moz-keyframes dance{0%,84%,94%{transform:skew(0)}85%{transform:skew(5deg)}90%{transform:skew(-5deg)}98%{transform:skew(3deg)}}@-webkit-keyframes dance{0%,84%,94%{transform:skew(0)}85%{transform:skew(5deg)}90%{transform:skew(-5deg)}98%{transform:skew(3deg)}}@-o-keyframes dance{0%,84%,94%{transform:skew(0)}85%{transform:skew(5deg)}90%{transform:skew(-5deg)}98%{transform:skew(3deg)}}@keyframes dance{0%,84%,94%{transform:skew(0)}85%{transform:skew(5deg)}90%{transform:skew(-5deg)}98%{transform:skew(3deg)}}@-moz-keyframes shiftleft{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(-8px,0) skew(20deg)}}@-webkit-keyframes shiftleft{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(-8px,0) skew(20deg)}}@-o-keyframes shiftleft{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(-8px,0) skew(20deg)}}@keyframes shiftleft{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(-8px,0) skew(20deg)}}@-moz-keyframes shiftright{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(8px,0) skew(20deg)}}@-webkit-keyframes shiftright{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(8px,0) skew(20deg)}}@-o-keyframes shiftright{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(8px,0) skew(20deg)}}@keyframes shiftright{0%,100%,87%{transform:translate(0,0) skew(0)}84%,90%{transform:translate(8px,0) skew(20deg)}}@-moz-keyframes blink{0%,100%,50%,85%{color:#000}87%,95%{color:transparent}}@-webkit-keyframes blink{0%,100%,50%,85%{color:#000}87%,95%{color:transparent}}@-o-keyframes blink{0%,100%,50%,85%{color:#000}87%,95%{color:transparent}}@keyframes blink{0%,100%,50%,85%{color:#000}87%,95%{color:transparent}}
|
||||
</style>
|
||||
EOT,
|
||||
<<<EOT
|
||||
<h1 aria-label="$error Error" style="font-size: calc(3em + 9vw);">$error</h1>
|
||||
<h2>$error_message</h2>
|
||||
<h3>$error_message_advanced</h3>
|
||||
<style>
|
||||
body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: 'Open Sans', Arial, sans-serif;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-image: linear-gradient(-225deg, #cf2778, #7c64d5, #4cc3ff);
|
||||
}
|
||||
</style>
|
||||
EOT
|
||||
];
|
||||
$credits_list = [
|
||||
"<a target='_blank' href='https://codepen.io/yexx'>Yeshua Emanuel Braz</a>",
|
||||
"<a target='_blank' href='https://codepen.io/GeorgePark'>George W. Park</a>"
|
||||
];
|
||||
$key = isset($_GET["force_page"]) ? $_GET["force_page"] : array_rand($error_templates);
|
||||
$credits = $credits_list[$key];
|
||||
echo($error_templates[$key]);
|
||||
|
||||
$nonce = false;
|
||||
try {
|
||||
if (is_object($tools)) $nonce = $tools->script_nonce;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
?>
|
||||
<br><br>
|
||||
<?php
|
||||
if(!is_null($game_script_url)){
|
||||
?>
|
||||
<div class="games_list" style="margin-left: 20px; text-align: left;">
|
||||
While you are waiting, you can play some games:
|
||||
<ul>
|
||||
<li hidden><a data-game="pong" class="playGame">Pong</a></li>
|
||||
<li><a data-game="ld46" class="playGame">What the firetruck</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="credits" style="position:absolute;opacity: 0.5;bottom: 5px;right: 5px;">
|
||||
Error page based on work by <?php echo($credits); ?>.
|
||||
</div>
|
||||
<script src="<?php echo($main_script_url); ?>"<?php if($nonce){ echo("nonce='{$nonce}'"); } ?>></script>
|
||||
<script src="<?php echo($game_script_url); ?>"<?php if($nonce){ echo("nonce='{$nonce}'"); } ?>></script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
show_error_page();
|
||||
}
|
||||
?>
|
|
@ -1,199 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Test this using following command
|
||||
// php -S localhost:8080 ./graphql.php &
|
||||
// curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
||||
// curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
|
||||
$users = [[
|
||||
'email' => "email",
|
||||
'username' => "username",
|
||||
'name' => "Name",
|
||||
'available' => true,
|
||||
'chief' => true,
|
||||
'driver' => true,
|
||||
'phoneNumber' => "+11234567891",
|
||||
'services' => 0,
|
||||
'trainings' => 0,
|
||||
'availabilityMinutes' => 0,
|
||||
'verified' => true,
|
||||
'hidden' => false,
|
||||
'disabled' => false,
|
||||
],[
|
||||
'email' => "email2",
|
||||
'username' => "username2",
|
||||
'name' => "Name2",
|
||||
'available' => true,
|
||||
'chief' => true,
|
||||
'driver' => true,
|
||||
'phoneNumber' => "+11234567892",
|
||||
'services' => 0,
|
||||
'trainings' => 0,
|
||||
'availabilityMinutes' => 0,
|
||||
'verified' => true,
|
||||
'hidden' => false,
|
||||
'disabled' => false,
|
||||
]];
|
||||
|
||||
try {
|
||||
$userType = new ObjectType([
|
||||
'name' => 'User',
|
||||
'description' => 'Allerta User',
|
||||
'fields' => [
|
||||
'id' => Type::int(),
|
||||
'email' => Type::string(),
|
||||
'username' => Type::string(),
|
||||
'name' => Type::string(),
|
||||
'available' => Type::boolean(),
|
||||
'chief' => Type::boolean(),
|
||||
'driver' => Type::boolean(),
|
||||
'phoneNumber' => Type::string(), //TODO: custom type
|
||||
'services' => Type::int(),
|
||||
'trainings' => Type::int(),
|
||||
'availabilityMinutes' => Type::int(),
|
||||
'verified' => Type::boolean(),
|
||||
'hidden' => Type::boolean(),
|
||||
'disabled' => Type::boolean(),
|
||||
]
|
||||
]);
|
||||
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'echo' => [
|
||||
'type' => Type::string(),
|
||||
'args' => [
|
||||
'message' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => static function ($rootValue, array $args): string {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
},
|
||||
],
|
||||
'user' => [
|
||||
'type' => $userType,
|
||||
'args' => [
|
||||
'id' => [
|
||||
'type' => Type::int(),
|
||||
]
|
||||
],
|
||||
'resolve' => function ($rootValue, array $args) {
|
||||
global $users;
|
||||
return $users[0];
|
||||
},
|
||||
],
|
||||
'Users' => [
|
||||
'type' => Type::listOf($userType),
|
||||
'args' => [
|
||||
'id' => [
|
||||
'type' => Type::int(),
|
||||
],
|
||||
'username' => [
|
||||
'type' => Type::string(),
|
||||
],
|
||||
'available' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'chief' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'driver' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'services' => [
|
||||
'type' => Type::int(),
|
||||
],
|
||||
'trainings' => [
|
||||
'type' => Type::int(),
|
||||
],
|
||||
'availabilityMinutes' => [
|
||||
'type' => Type::int(),
|
||||
],
|
||||
'verified' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'hidden' => [
|
||||
'type' => Type::boolean(),
|
||||
],
|
||||
'disabled' => [
|
||||
'type' => Type::boolean()
|
||||
]
|
||||
],
|
||||
'resolve' => function ($rootValue, array $args) {
|
||||
global $db, $user;
|
||||
$profiles = $db->select("SELECT * FROM `".DB_PREFIX."_profiles`");
|
||||
$users = $db->select("SELECT * FROM `".DB_PREFIX."_users`");
|
||||
$result = [];
|
||||
for ($i=0; $i < sizeof($profiles); $i++) {
|
||||
$result[] = [
|
||||
"id" => $users["id"],
|
||||
"email" => $profiles["email"],
|
||||
"username" => $users["username"],
|
||||
"name" => $user->nameById($users["id"]),
|
||||
"available" => $profiles["available"],
|
||||
"chief" => $profiles["chief"],
|
||||
"driver" => $profiles["driver"],
|
||||
"phoneNumber" => $profiles["phone_number"],
|
||||
"services" => $profiles["services"],
|
||||
"trainings" => $profiles["trainings"],
|
||||
"availabilityMinutes" => $profiles["availabilityMinutes"],
|
||||
"verified" => $users["verified"],
|
||||
"hidden" => $profiles["hidden"],
|
||||
"disabled" => $profiles["disabled"],
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
},
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$mutationType = new ObjectType([
|
||||
'name' => 'Calc',
|
||||
'fields' => [
|
||||
'sum' => [
|
||||
'type' => Type::int(),
|
||||
'args' => [
|
||||
'x' => ['type' => Type::int()],
|
||||
'y' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => static function ($calc, array $args): int {
|
||||
return $args['x'] + $args['y'];
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// See docs on schema options:
|
||||
// https://webonyx.github.io/graphql-php/type-system/schema/#configuration-options
|
||||
$schema = new Schema([
|
||||
'query' => $queryType,
|
||||
'mutation' => $mutationType,
|
||||
]);
|
||||
|
||||
$rawInput = file_get_contents('php://input');
|
||||
$input = json_decode($rawInput, true);
|
||||
$query = $input['query'];
|
||||
$variableValues = $input['variables'] ?? null;
|
||||
|
||||
$rootValue = ['prefix' => 'You said: '];
|
||||
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
|
||||
$output = $result->toArray();
|
||||
} catch (Throwable $e) {
|
||||
$output = [
|
||||
'error' => [
|
||||
'message' => $e->getMessage(),
|
||||
"object" => $e
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode($output);
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
require_once 'ui.php';
|
||||
if($user->authenticated) {
|
||||
$tools->redirect("list.php");
|
||||
}
|
||||
$error = false;
|
||||
if(isset($_POST['name']) & isset($_POST['password'])) {
|
||||
$login = $user->login($_POST['name'], $_POST['password'], isset($_POST["remember_me"]));
|
||||
if($login===true) {
|
||||
$tools->redirect("list.php");
|
||||
} else {
|
||||
$error = $login;
|
||||
bdump($error);
|
||||
}
|
||||
}
|
||||
loadtemplate('index.html', ['error' => $error, 'title' => t('Login', false)], false);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue