Optimize and refactor latest changes

This commit is contained in:
Bernd Bestel
2018-07-25 19:28:15 +02:00
parent 4685ff4145
commit 42c1709633
33 changed files with 315 additions and 254 deletions

27
app.php
View File

@@ -6,17 +6,38 @@ use \Psr\Http\Message\ResponseInterface as Response;
use \Grocy\Helpers\UrlManager; use \Grocy\Helpers\UrlManager;
use \Grocy\Controllers\LoginController; use \Grocy\Controllers\LoginController;
// Definitions for embedded mode
if (file_exists(__DIR__ . '/embedded.txt')) if (file_exists(__DIR__ . '/embedded.txt'))
{ {
define('GROCY_IS_EMBEDDED_INSTALL', true);
define('GROCY_DATAPATH', file_get_contents(__DIR__ . '/embedded.txt')); define('GROCY_DATAPATH', file_get_contents(__DIR__ . '/embedded.txt'));
define('GROCY_USER_ID', 1); define('GROCY_USER_ID', 1);
} }
else else
{ {
define('GROCY_IS_EMBEDDED_INSTALL', false);
define('GROCY_DATAPATH', __DIR__ . '/data'); define('GROCY_DATAPATH', __DIR__ . '/data');
} }
// Definitions for demo mode
if (file_exists(GROCY_DATAPATH . '/demo.txt'))
{
define('GROCY_IS_DEMO_INSTALL', true);
if (!defined('GROCY_USER_ID'))
{
define('GROCY_USER_ID', 1);
}
}
else
{
define('GROCY_IS_DEMO_INSTALL', false);
define('GROCY_DATAPATH', __DIR__ . '/data');
}
// Load composer dependencies
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
// Load config fils
require_once GROCY_DATAPATH . '/config.php'; require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones
@@ -45,11 +66,7 @@ $appContainer = new \Slim\Container([
]); ]);
$app = new \Slim\App($appContainer); $app = new \Slim\App($appContainer);
if (PHP_SAPI === 'cli') // Load routes from separate file
{
$app->add(\pavlakis\cli\CliRequest::class);
}
require_once __DIR__ . '/routes.php'; require_once __DIR__ . '/routes.php';
$app->run(); $app->run();

View File

@@ -3,7 +3,6 @@
"php": ">=7.2", "php": ">=7.2",
"slim/slim": "^3.8", "slim/slim": "^3.8",
"morris/lessql": "^0.3.4", "morris/lessql": "^0.3.4",
"pavlakis/slim-cli": "^1.0",
"rubellum/slim-blade-view": "^0.1.1", "rubellum/slim-blade-view": "^0.1.1",
"tuupola/cors-middleware": "^0.7.0" "tuupola/cors-middleware": "^0.7.0"
}, },

85
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "131ab83ecb1ea3d1a431cc70b5092448", "content-hash": "c1bc4c17739e9d0ee8b33628f6d4b9a4",
"packages": [ "packages": [
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
@@ -158,7 +158,7 @@
}, },
{ {
"name": "illuminate/container", "name": "illuminate/container",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/container.git", "url": "https://github.com/illuminate/container.git",
@@ -202,7 +202,7 @@
}, },
{ {
"name": "illuminate/contracts", "name": "illuminate/contracts",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/contracts.git", "url": "https://github.com/illuminate/contracts.git",
@@ -246,7 +246,7 @@
}, },
{ {
"name": "illuminate/events", "name": "illuminate/events",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/events.git", "url": "https://github.com/illuminate/events.git",
@@ -291,7 +291,7 @@
}, },
{ {
"name": "illuminate/filesystem", "name": "illuminate/filesystem",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/filesystem.git", "url": "https://github.com/illuminate/filesystem.git",
@@ -343,7 +343,7 @@
}, },
{ {
"name": "illuminate/support", "name": "illuminate/support",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/support.git", "url": "https://github.com/illuminate/support.git",
@@ -401,7 +401,7 @@
}, },
{ {
"name": "illuminate/view", "name": "illuminate/view",
"version": "v5.6.27", "version": "v5.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/view.git", "url": "https://github.com/illuminate/view.git",
@@ -651,55 +651,6 @@
], ],
"time": "2018-02-13T20:26:39+00:00" "time": "2018-02-13T20:26:39+00:00"
}, },
{
"name": "pavlakis/slim-cli",
"version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/pavlakis/slim-cli.git",
"reference": "603933a54e391b3c70c573206cce543b75d8b1db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pavlakis/slim-cli/zipball/603933a54e391b3c70c573206cce543b75d8b1db",
"reference": "603933a54e391b3c70c573206cce543b75d8b1db",
"shasum": ""
},
"require": {
"php": "^5.5|^5.6|^7.0|^7.1"
},
"require-dev": {
"phpunit/phpunit": "^4.0",
"slim/slim": "^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"pavlakis\\cli\\tests\\": "tests/phpunit",
"pavlakis\\cli\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Antonis Pavlakis",
"email": "adoni@pavlakis.info",
"homepage": "http://pavlakis.info"
}
],
"description": "Making a mock GET request through the CLI and enabling the same application entry point on CLI scripts.",
"homepage": "http://github.com/pavlakis/slim-cli",
"keywords": [
"cli",
"framework",
"middleware",
"slim"
],
"time": "2017-01-30T22:50:06+00:00"
},
{ {
"name": "philo/laravel-blade", "name": "philo/laravel-blade",
"version": "v3.1", "version": "v3.1",
@@ -1214,16 +1165,16 @@
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v4.1.1", "version": "v4.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d" "reference": "a1f2118cedb8731c45e945cdd2b808ca82abc4b5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/dbe0fad88046a755dcf9379f2964c61a02f5ae3d", "url": "https://api.github.com/repos/symfony/debug/zipball/a1f2118cedb8731c45e945cdd2b808ca82abc4b5",
"reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d", "reference": "a1f2118cedb8731c45e945cdd2b808ca82abc4b5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1266,11 +1217,11 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-06-08T09:39:36+00:00" "time": "2018-07-06T14:52:28+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v4.1.1", "version": "v4.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
@@ -1378,16 +1329,16 @@
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v4.1.1", "version": "v4.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854" "reference": "2dd74d6b2dcbd46a93971e6ce7d245cf3123e957"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/b6d8164085ee0b6debcd1b7a131fd6f63bb04854", "url": "https://api.github.com/repos/symfony/translation/zipball/2dd74d6b2dcbd46a93971e6ce7d245cf3123e957",
"reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854", "reference": "2dd74d6b2dcbd46a93971e6ce7d245cf3123e957",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1443,7 +1394,7 @@
], ],
"description": "Symfony Translation Component", "description": "Symfony Translation Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2018-06-22T08:59:39+00:00" "time": "2018-07-23T08:20:20+00:00"
}, },
{ {
"name": "tuupola/callable-handler", "name": "tuupola/callable-handler",

View File

@@ -19,7 +19,6 @@ class BaseController
$versionInfo = $applicationService->GetInstalledVersion(); $versionInfo = $applicationService->GetInstalledVersion();
$container->view->set('version', $versionInfo->Version); $container->view->set('version', $versionInfo->Version);
$container->view->set('releaseDate', $versionInfo->ReleaseDate); $container->view->set('releaseDate', $versionInfo->ReleaseDate);
$container->view->set('isEmbeddedInstallation', $applicationService->IsEmbeddedInstallation());
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations()); $container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService) $container->view->set('L', function($text, ...$placeholderValues) use($localizationService)

View File

@@ -1,19 +0,0 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\DatabaseMigrationService;
class CliController extends BaseController
{
public function RecreateDemo(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$applicationService = new ApplicationService();
if ($applicationService->IsDemoInstallation())
{
$databaseMigrationService = new DatabaseMigrationService();
$databaseMigrationService->RecreateDemo();
}
}
}

View File

@@ -3,7 +3,6 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use \Grocy\Services\SessionService; use \Grocy\Services\SessionService;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\DatabaseMigrationService; use \Grocy\Services\DatabaseMigrationService;
use \Grocy\Services\DemoDataGeneratorService; use \Grocy\Services\DemoDataGeneratorService;
@@ -31,8 +30,6 @@ class LoginController extends BaseController
{ {
$sessionKey = $this->SessionService->CreateSession($user->id); $sessionKey = $this->SessionService->CreateSession($user->id);
setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
define('GROCY_USER_USERNAME', $user->username);
define('GROCY_USER_ID', $user->id);
if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
{ {
@@ -71,8 +68,7 @@ class LoginController extends BaseController
$databaseMigrationService = new DatabaseMigrationService(); $databaseMigrationService = new DatabaseMigrationService();
$databaseMigrationService->MigrateDatabase(); $databaseMigrationService->MigrateDatabase();
$applicationService = new ApplicationService(); if (GROCY_IS_DEMO_INSTALL)
if ($applicationService->IsDemoInstallation())
{ {
$demoDataGeneratorService = new DemoDataGeneratorService(); $demoDataGeneratorService = new DemoDataGeneratorService();
$demoDataGeneratorService->PopulateDemoData(); $demoDataGeneratorService->PopulateDemoData();
@@ -81,30 +77,6 @@ class LoginController extends BaseController
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview')); return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
} }
public function UsersList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'users', [
'users' => $this->Database->users()->orderBy('username')
]);
}
public function UserEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['userId'] == 'new')
{
return $this->AppContainer->view->render($response, 'userform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'userform', [
'user' => $this->Database->users($args['userId']),
'mode' => 'edit'
]);
}
}
public function GetSessionCookieName() public function GetSessionCookieName()
{ {
return $this->SessionCookieName; return $this->SessionCookieName;

View File

@@ -35,7 +35,8 @@ class OpenApiController extends BaseApiController
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
return $this->AppContainer->view->render($response, 'manageapikeys', [ return $this->AppContainer->view->render($response, 'manageapikeys', [
'apiKeys' => $this->Database->api_keys() 'apiKeys' => $this->Database->api_keys(),
'users' => $this->Database->users()
]); ]);
} }

View File

@@ -14,6 +14,18 @@ class UsersApiController extends BaseApiController
protected $UsersService; protected $UsersService;
public function GetUsers(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->UsersService->GetUsersAsDto());
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function CreateUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function CreateUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
$requestBody = $request->getParsedBody(); $requestBody = $request->getParsedBody();
@@ -33,8 +45,8 @@ class UsersApiController extends BaseApiController
{ {
try try
{ {
$success = $this->UsersService->DeleteUser($args['userId']); $this->UsersService->DeleteUser($args['userId']);
return $this->ApiResponse(array('success' => $success)); return $this->ApiResponse(array('success' => true));
} }
catch (\Exception $ex) catch (\Exception $ex)
{ {

View File

@@ -0,0 +1,30 @@
<?php
namespace Grocy\Controllers;
class UsersController extends BaseController
{
public function UsersList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'users', [
'users' => $this->Database->users()->orderBy('username')
]);
}
public function UserEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['userId'] == 'new')
{
return $this->AppContainer->view->render($response, 'userform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'userform', [
'user' => $this->Database->users($args['userId']),
'mode' => 'edit'
]);
}
}
}

View File

@@ -370,6 +370,39 @@
} }
} }
}, },
"/users/get": {
"get": {
"description": "Returns all users",
"tags": [
"User management"
],
"responses": {
"200": {
"description": "A list of user objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/UserDto"
}
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
}
}
}
}
}
}
},
"/users/create": { "/users/create": {
"post": { "post": {
"description": "Creates a new user", "description": "Creates a new user",
@@ -910,6 +943,15 @@
"schema": { "schema": {
"type": "date-time" "type": "date-time"
} }
},
{
"in": "query",
"name": "done_by",
"required": false,
"description": "A valid user id of who executed this habit, when omitted, the currently authenticated user will be used",
"schema": {
"type": "integer"
}
} }
], ],
"responses": { "responses": {
@@ -1273,6 +1315,9 @@
"track_count": { "track_count": {
"type": "integer", "type": "integer",
"description": "How often this habit was tracked so far" "description": "How often this habit was tracked so far"
},
"last_done_by": {
"$ref": "#/components/schemas/UserDto"
} }
} }
}, },
@@ -1340,6 +1385,31 @@
} }
} }
}, },
"UserDto": {
"type": "object",
"description": "A user object without the *password* and with an additional *display_name* property",
"properties": {
"id": {
"type": "integer"
},
"username": {
"type": "string"
},
"first_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"display_name": {
"type": "string"
},
"row_created_timestamp": {
"type": "string",
"format": "date-time"
}
}
},
"ApiKey": { "ApiKey": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -170,6 +170,7 @@ return array(
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Sicher alle fehlenden Zutaten für Rezept "#1" auf die Einkaufsliste zu setzen?', 'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Sicher alle fehlenden Zutaten für Rezept "#1" auf die Einkaufsliste zu setzen?',
'Added for recipe #1' => 'Hinzugefügt für Rezept #1', 'Added for recipe #1' => 'Hinzugefügt für Rezept #1',
'Manage users' => 'Benutzer verwalten', 'Manage users' => 'Benutzer verwalten',
'User' => 'Benutzer',
'Users' => 'Benutzer', 'Users' => 'Benutzer',
'Are you sure to delete user "#1"?' => 'Benutzer "#1" wirklich löschen?', 'Are you sure to delete user "#1"?' => 'Benutzer "#1" wirklich löschen?',
'Create user' => 'Benutzer erstellen', 'Create user' => 'Benutzer erstellen',
@@ -181,6 +182,8 @@ return array(
'Passwords do not match' => 'Passwörter stimmen nicht überein', 'Passwords do not match' => 'Passwörter stimmen nicht überein',
'Change password' => 'Passwort ändern', 'Change password' => 'Passwort ändern',
'Done by' => 'Ausgeführt von', 'Done by' => 'Ausgeführt von',
'Last done by' => 'Zuletzt ausgeführt von',
'Unknown' => 'Unbekannt',
//Constants //Constants
'manually' => 'Manuell', 'manually' => 'Manuell',

View File

@@ -22,8 +22,9 @@ class ApiKeyAuthMiddleware extends BaseMiddleware
$route = $request->getAttribute('route'); $route = $request->getAttribute('route');
$routeName = $route->getName(); $routeName = $route->getName();
if ($this->ApplicationService->IsDemoInstallation() || $this->ApplicationService->IsEmbeddedInstallation()) if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
{ {
define('GROCY_AUTHENTICATED', true);
$response = $next($request, $response); $response = $next($request, $response);
} }
else else
@@ -45,10 +46,15 @@ class ApiKeyAuthMiddleware extends BaseMiddleware
if (!$validSession && !$validApiKey) if (!$validSession && !$validApiKey)
{ {
define('GROCY_AUTHENTICATED', false);
$response = $response->withStatus(401); $response = $response->withStatus(401);
} }
else else
{ {
$user = $apiKeyService->GetUserByApiKey($request->getHeaderLine($this->ApiKeyHeaderName));
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
$response = $next($request, $response); $response = $next($request, $response);
} }
} }

View File

@@ -1,20 +0,0 @@
<?php
namespace Grocy\Middleware;
class CliMiddleware extends BaseMiddleware
{
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{
if (PHP_SAPI !== 'cli')
{
$response->write('Please call this only from CLI');
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
}
else
{
$response = $next($request, $response);
return $response->withHeader('Content-Type', 'text/plain');
}
}
}

View File

@@ -19,23 +19,23 @@ class SessionAuthMiddleware extends BaseMiddleware
{ {
$route = $request->getAttribute('route'); $route = $request->getAttribute('route');
$routeName = $route->getName(); $routeName = $route->getName();
$sessionService = new SessionService();
if ($routeName === 'root' || $this->ApplicationService->IsDemoInstallation() || $this->ApplicationService->IsEmbeddedInstallation()) if ($routeName === 'root')
{ {
if ($this->ApplicationService->IsDemoInstallation() || $this->ApplicationService->IsEmbeddedInstallation()) $response = $next($request, $response);
{ }
define('GROCY_AUTHENTICATED', true); elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
{
$localizationService = new LocalizationService(GROCY_CULTURE); $user = $sessionService->GetDefaultUser();
define('GROCY_USER_USERNAME', $localizationService->Localize('Demo User')); define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', -1); define('GROCY_USER_USERNAME', $user->username);
} define('GROCY_USER_ID', $user->id);
$response = $next($request, $response); $response = $next($request, $response);
} }
else else
{ {
$sessionService = new SessionService();
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login') if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
{ {
define('GROCY_AUTHENTICATED', false); define('GROCY_AUTHENTICATED', false);

View File

@@ -1,2 +1,13 @@
ALTER TABLE habits_log ALTER TABLE habits_log
ADD done_by_user_id ADD done_by_user_id;
DROP TABLE api_keys;
CREATE TABLE api_keys (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
api_key TEXT NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
expires DATETIME,
last_used DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);

View File

@@ -7,7 +7,7 @@ Grocy.Components.BatteryCard.Refresh = function(batteryId)
{ {
$('#batterycard-battery-name').text(batteryDetails.battery.name); $('#batterycard-battery-name').text(batteryDetails.battery.name);
$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in); $('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || 'never')); $('#batterycard-battery-last-charged').text((batteryDetails.last_charged || L('never')));
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || '')); $('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0')); $('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));

View File

@@ -6,9 +6,10 @@ Grocy.Components.HabitCard.Refresh = function(habitId)
function(habitDetails) function(habitDetails)
{ {
$('#habitcard-habit-name').text(habitDetails.habit.name); $('#habitcard-habit-name').text(habitDetails.habit.name);
$('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || 'never')); $('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || L('never')));
$('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || '')); $('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
$('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0')); $('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0'));
$('#habitcard-habit-last-done-by').text((habitDetails.last_done_by.display_name || L('Unknown')));
EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', L('timeago_nan')); EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', L('timeago_nan'));
}, },

View File

@@ -42,7 +42,7 @@ $(document).on('click', '.user-delete-button', function (e)
{ {
if (result === true) if (result === true)
{ {
Grocy.Api.Get('users/delete' + objectId, Grocy.Api.Get('users/delete/' + objectId,
function(result) function(result)
{ {
window.location.href = U('/users'); window.location.href = U('/users');

View File

@@ -1,7 +1,6 @@
<?php <?php
use \Grocy\Middleware\JsonMiddleware; use \Grocy\Middleware\JsonMiddleware;
use \Grocy\Middleware\CliMiddleware;
use \Grocy\Middleware\SessionAuthMiddleware; use \Grocy\Middleware\SessionAuthMiddleware;
use \Grocy\Middleware\ApiKeyAuthMiddleware; use \Grocy\Middleware\ApiKeyAuthMiddleware;
use \Tuupola\Middleware\CorsMiddleware; use \Tuupola\Middleware\CorsMiddleware;
@@ -11,86 +10,92 @@ $app->group('', function()
// Base route // Base route
$this->get('/', 'LoginControllerInstance:Root')->setName('root'); $this->get('/', 'LoginControllerInstance:Root')->setName('root');
// Login/user routes // Login routes
$this->get('/login', 'LoginControllerInstance:LoginPage')->setName('login'); $this->get('/login', 'LoginControllerInstance:LoginPage')->setName('login');
$this->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login'); $this->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login');
$this->get('/logout', 'LoginControllerInstance:Logout'); $this->get('/logout', 'LoginControllerInstance:Logout');
$this->get('/users', 'LoginControllerInstance:UsersList');
$this->get('/user/{userId}', 'LoginControllerInstance:UserEditForm'); // User routes
$this->get('/users', '\Grocy\Controllers\UsersController:UsersList');
$this->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm');
// Stock routes // Stock routes
$this->get('/stockoverview', 'Grocy\Controllers\StockController:Overview'); $this->get('/stockoverview', '\Grocy\Controllers\StockController:Overview');
$this->get('/purchase', 'Grocy\Controllers\StockController:Purchase'); $this->get('/purchase', '\Grocy\Controllers\StockController:Purchase');
$this->get('/consume', 'Grocy\Controllers\StockController:Consume'); $this->get('/consume', '\Grocy\Controllers\StockController:Consume');
$this->get('/inventory', 'Grocy\Controllers\StockController:Inventory'); $this->get('/inventory', '\Grocy\Controllers\StockController:Inventory');
$this->get('/products', '\Grocy\Controllers\StockController:ProductsList');
$this->get('/products', 'Grocy\Controllers\StockController:ProductsList'); $this->get('/product/{productId}', '\Grocy\Controllers\StockController:ProductEditForm');
$this->get('/product/{productId}', 'Grocy\Controllers\StockController:ProductEditForm'); $this->get('/locations', '\Grocy\Controllers\StockController:LocationsList');
$this->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
$this->get('/locations', 'Grocy\Controllers\StockController:LocationsList'); $this->get('/quantityunits', '\Grocy\Controllers\StockController:QuantityUnitsList');
$this->get('/location/{locationId}', 'Grocy\Controllers\StockController:LocationEditForm'); $this->get('/quantityunit/{quantityunitId}', '\Grocy\Controllers\StockController:QuantityUnitEditForm');
$this->get('/shoppinglist', '\Grocy\Controllers\StockController:ShoppingList');
$this->get('/quantityunits', 'Grocy\Controllers\StockController:QuantityUnitsList'); $this->get('/shoppinglistitem/{itemId}', '\Grocy\Controllers\StockController:ShoppingListItemEditForm');
$this->get('/quantityunit/{quantityunitId}', 'Grocy\Controllers\StockController:QuantityUnitEditForm');
$this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList');
$this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm');
// Recipe routes // Recipe routes
$this->get('/recipes', 'Grocy\Controllers\RecipesController:Overview'); $this->get('/recipes', '\Grocy\Controllers\RecipesController:Overview');
$this->get('/recipe/{recipeId}', 'Grocy\Controllers\RecipesController:RecipeEditForm'); $this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
$this->get('/recipe/{recipeId}/pos/{recipePosId}', 'Grocy\Controllers\RecipesController:RecipePosEditForm'); $this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
// Habit routes // Habit routes
$this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview'); $this->get('/habitsoverview', '\Grocy\Controllers\HabitsController:Overview');
$this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution'); $this->get('/habittracking', '\Grocy\Controllers\HabitsController:TrackHabitExecution');
$this->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList'); $this->get('/habits', '\Grocy\Controllers\HabitsController:HabitsList');
$this->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm'); $this->get('/habit/{habitId}', '\Grocy\Controllers\HabitsController:HabitEditForm');
// Battery routes // Battery routes
$this->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview'); $this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
$this->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle'); $this->get('/batterytracking', '\Grocy\Controllers\BatteriesController:TrackChargeCycle');
$this->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList'); $this->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList');
$this->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm'); $this->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm');
// Other routes // OpenAPI routes
$this->get('/api', 'Grocy\Controllers\OpenApiController:DocumentationUi'); $this->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
$this->get('/manageapikeys', 'Grocy\Controllers\OpenApiController:ApiKeysList'); $this->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
$this->get('/manageapikeys/new', 'Grocy\Controllers\OpenApiController:CreateNewApiKey'); $this->get('/manageapikeys/new', '\Grocy\Controllers\OpenApiController:CreateNewApiKey');
})->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName())); })->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName()));
$app->group('/api', function() $app->group('/api', function()
{ {
$this->get('/get-openapi-specification', 'Grocy\Controllers\OpenApiController:DocumentationSpec'); // OpenAPI
$this->get('/get-openapi-specification', '\Grocy\Controllers\OpenApiController:DocumentationSpec');
$this->get('/get-objects/{entity}', 'Grocy\Controllers\GenericEntityApiController:GetObjects'); // Generic entity interaction
$this->get('/get-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:GetObject'); $this->get('/get-objects/{entity}', '\Grocy\Controllers\GenericEntityApiController:GetObjects');
$this->post('/add-object/{entity}', 'Grocy\Controllers\GenericEntityApiController:AddObject'); $this->get('/get-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:GetObject');
$this->post('/edit-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:EditObject'); $this->post('/add-object/{entity}', '\Grocy\Controllers\GenericEntityApiController:AddObject');
$this->get('/delete-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:DeleteObject'); $this->post('/edit-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:EditObject');
$this->get('/delete-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:DeleteObject');
$this->post('/users/create', 'Grocy\Controllers\UsersApiController:CreateUser'); // Users
$this->post('/users/edit/{userId}', 'Grocy\Controllers\UsersApiController:EditUser'); $this->get('/users/get', '\Grocy\Controllers\UsersApiController:GetUsers');
$this->get('/users/delete/{userId}', 'Grocy\Controllers\UsersApiController:DeleteUser'); $this->post('/users/create', '\Grocy\Controllers\UsersApiController:CreateUser');
$this->post('/users/edit/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
$this->get('/users/delete/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
$this->get('/stock/add-product/{productId}/{amount}', 'Grocy\Controllers\StockApiController:AddProduct'); // Stock
$this->get('/stock/consume-product/{productId}/{amount}', 'Grocy\Controllers\StockApiController:ConsumeProduct'); $this->get('/stock/add-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:AddProduct');
$this->get('/stock/inventory-product/{productId}/{newAmount}', 'Grocy\Controllers\StockApiController:InventoryProduct'); $this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');
$this->get('/stock/get-product-details/{productId}', 'Grocy\Controllers\StockApiController:ProductDetails'); $this->get('/stock/inventory-product/{productId}/{newAmount}', '\Grocy\Controllers\StockApiController:InventoryProduct');
$this->get('/stock/get-current-stock', 'Grocy\Controllers\StockApiController:CurrentStock'); $this->get('/stock/get-product-details/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails');
$this->get('/stock/add-missing-products-to-shoppinglist', 'Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList'); $this->get('/stock/get-current-stock', '\Grocy\Controllers\StockApiController:CurrentStock');
$this->get('/stock/clear-shopping-list', 'Grocy\Controllers\StockApiController:ClearShoppingList'); $this->get('/stock/add-missing-products-to-shoppinglist', '\Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
$this->get('/stock/external-barcode-lookup/{barcode}', 'Grocy\Controllers\StockApiController:ExternalBarcodeLookup'); $this->get('/stock/clear-shopping-list', '\Grocy\Controllers\StockApiController:ClearShoppingList');
$this->get('/stock/external-barcode-lookup/{barcode}', '\Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', 'Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList'); // Recipes
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
$this->get('/habits/track-habit-execution/{habitId}', 'Grocy\Controllers\HabitsApiController:TrackHabitExecution'); // Habits
$this->get('/habits/get-habit-details/{habitId}', 'Grocy\Controllers\HabitsApiController:HabitDetails'); $this->get('/habits/track-habit-execution/{habitId}', '\Grocy\Controllers\HabitsApiController:TrackHabitExecution');
$this->get('/habits/get-habit-details/{habitId}', '\Grocy\Controllers\HabitsApiController:HabitDetails');
$this->get('/batteries/track-charge-cycle/{batteryId}', 'Grocy\Controllers\BatteriesApiController:TrackChargeCycle'); // Batteries
$this->get('/batteries/get-battery-details/{batteryId}', 'Grocy\Controllers\BatteriesApiController:BatteryDetails'); $this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
$this->get('/batteries/get-battery-details/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
})->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName)) })->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName))
->add(JsonMiddleware::class) ->add(JsonMiddleware::class)
->add(new CorsMiddleware([ ->add(new CorsMiddleware([
@@ -101,8 +106,3 @@ $app->group('/api', function()
'credentials' => false, 'credentials' => false,
'cache' => 0, 'cache' => 0,
])); ]));
$app->group('/cli', function()
{
$this->get('/recreatedemo', 'Grocy\Controllers\CliController:RecreateDemo');
})->add(CliMiddleware::class);

View File

@@ -39,6 +39,7 @@ class ApiKeyService extends BaseService
$apiKeyRow = $this->Database->api_keys()->createRow(array( $apiKeyRow = $this->Database->api_keys()->createRow(array(
'api_key' => $newApiKey, 'api_key' => $newApiKey,
'user_id' => GROCY_USER_ID,
'expires' => '2999-12-31 23:59:59' // Default is that API keys expire never 'expires' => '2999-12-31 23:59:59' // Default is that API keys expire never
)); ));
$apiKeyRow->save(); $apiKeyRow->save();
@@ -57,6 +58,16 @@ class ApiKeyService extends BaseService
return $apiKey->id; return $apiKey->id;
} }
public function GetUserByApiKey($apiKey)
{
$apiKeyRow = $this->Database->api_keys()->where('api_key', $apiKey)->fetch();
if ($apiKeyRow !== null)
{
return $this->Database->users($apiKeyRow->user_id);
}
return null;
}
private function GenerateApiKey() private function GenerateApiKey()
{ {
return RandomString(50); return RandomString(50);

View File

@@ -4,22 +4,6 @@ namespace Grocy\Services;
class ApplicationService extends BaseService class ApplicationService extends BaseService
{ {
/**
* @return boolean
*/
public function IsDemoInstallation()
{
return file_exists(GROCY_DATAPATH . '/demo.txt');
}
/**
* @return boolean
*/
public function IsEmbeddedInstallation()
{
return file_exists(__DIR__ . '/../embedded.txt');
}
private $InstalledVersion; private $InstalledVersion;
public function GetInstalledVersion() public function GetInstalledVersion()
{ {

View File

@@ -16,7 +16,10 @@ class DemoDataGeneratorService extends BaseService
$loremIpsum = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'; $loremIpsum = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.';
$sql = " $sql = "
INSERT INTO users (id, username, password) VALUES (-1, '{$localizationService->Localize('Demo User')}', 'x'); UPDATE users SET username = '{$localizationService->Localize('Demo User')}' WHERE id = 1;
INSERT INTO users (username, password) VALUES ('{$localizationService->Localize('Demo User')} 2', 'x');
INSERT INTO users (username, password) VALUES ('{$localizationService->Localize('Demo User')} 3', 'x');
INSERT INTO users (username, password) VALUES ('{$localizationService->Localize('Demo User')} 4', 'x');
INSERT INTO locations (name) VALUES ('{$localizationService->Localize('Pantry')}'); --2 INSERT INTO locations (name) VALUES ('{$localizationService->Localize('Pantry')}'); --2
INSERT INTO locations (name) VALUES ('{$localizationService->Localize('Candy cupboard')}'); --3 INSERT INTO locations (name) VALUES ('{$localizationService->Localize('Candy cupboard')}'); --3

View File

@@ -45,10 +45,19 @@ class HabitsService extends BaseService
$habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count(); $habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count();
$habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time'); $habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time');
$doneByUserId = $this->Database->habits_log()->where('habit_id = :1 AND tracked_time = :2', $habitId, $habitLastTrackedTime)->fetch()->done_by_user_id;
if ($doneByUserId !== null && !empty($doneByUserId))
{
$usersService = new UsersService();
$users = $usersService->GetUsersAsDto();
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $doneByUserId);
}
return array( return array(
'habit' => $habit, 'habit' => $habit,
'last_tracked' => $habitLastTrackedTime, 'last_tracked' => $habitLastTrackedTime,
'tracked_count' => $habitTrackedCount 'tracked_count' => $habitTrackedCount,
'last_done_by' => $lastDoneByUser
); );
} }

View File

@@ -62,6 +62,11 @@ class SessionService extends BaseService
return null; return null;
} }
public function GetDefaultUser()
{
return $this->Database->users(1);
}
private function GenerateSessionKey() private function GenerateSessionKey()
{ {
return RandomString(50); return RandomString(50);

View File

@@ -33,10 +33,21 @@ class UsersService extends BaseService
public function DeleteUser($userId) public function DeleteUser($userId)
{ {
$row = $this->Database->users($args['userId']); $row = $this->Database->users($userId);
$row->delete(); $row->delete();
$success = $row->isClean(); }
return $this->ApiResponse(array('success' => $success));
public function GetUsersAsDto()
{
$users = $this->Database->users();
$returnUsers = array();
foreach ($users as $user)
{
unset($user->password);
$user->display_name = GetUserDisplayName($user);
$returnUsers[] = $user;
}
return $returnUsers;
} }
private function UserExists($userId) private function UserExists($userId)

0
update.sh Normal file → Executable file
View File

View File

@@ -1,4 +1,4 @@
{ {
"Version": "1.15.0", "Version": "1.16.0",
"ReleaseDate": "2018-07-22" "ReleaseDate": "2018-07-25"
} }

View File

@@ -37,7 +37,7 @@
</thead> </thead>
<tbody> <tbody>
@foreach($current as $curentBatteryEntry) @foreach($current as $curentBatteryEntry)
<tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s')) table-danger @endif"> <tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s', strtotime('+5 days'))) table-warning @endif">
<td class="fit-content"> <td class="fit-content">
<a class="btn btn-success btn-sm track-charge-cycle-button" href="#" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}" <a class="btn btn-success btn-sm track-charge-cycle-button" href="#" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}"
data-battery-id="{{ $curentBatteryEntry->battery_id }}" data-battery-id="{{ $curentBatteryEntry->battery_id }}"

View File

@@ -10,5 +10,6 @@
<h3><span id="habitcard-habit-name"></span></h3> <h3><span id="habitcard-habit-name"></span></h3>
<strong>{{ $L('Tracked count') }}:</strong> <span id="habitcard-habit-tracked-count"></span><br> <strong>{{ $L('Tracked count') }}:</strong> <span id="habitcard-habit-tracked-count"></span><br>
<strong>{{ $L('Last tracked') }}:</strong> <span id="habitcard-habit-last-tracked"></span> <time id="habitcard-habit-last-tracked-timeago" class="timeago timeago-contextual"></time><br> <strong>{{ $L('Last tracked') }}:</strong> <span id="habitcard-habit-last-tracked"></span> <time id="habitcard-habit-last-tracked-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $L('Last done by') }}:</strong> <span id="habitcard-habit-last-done-by"></span>
</div> </div>
</div> </div>

View File

@@ -37,7 +37,7 @@
</thead> </thead>
<tbody> <tbody>
@foreach($currentHabits as $curentHabitEntry) @foreach($currentHabits as $curentHabitEntry)
<tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s')) table-danger @endif"> <tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s', strtotime('+5 days'))) table-warning @endif">
<td class="fit-content"> <td class="fit-content">
<a class="btn btn-success btn-sm track-habit-button" href="#" title="{{ $L('Track execution of habit #1', FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name) }}" <a class="btn btn-success btn-sm track-habit-button" href="#" title="{{ $L('Track execution of habit #1', FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name) }}"
data-habit-id="{{ $curentHabitEntry->habit_id }}" data-habit-id="{{ $curentHabitEntry->habit_id }}"

View File

@@ -164,7 +164,7 @@
</ul> </ul>
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
@if(GROCY_AUTHENTICATED === true && $isEmbeddedInstallation === false) @if(GROCY_AUTHENTICATED === true && !GROCY_IS_EMBEDDED_INSTALL)
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-user"></i> {{ GROCY_USER_USERNAME }}</a> <a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-user"></i> {{ GROCY_USER_USERNAME }}</a>

View File

@@ -34,6 +34,7 @@
<tr> <tr>
<th>#</th> <th>#</th>
<th>{{ $L('API key') }}</th> <th>{{ $L('API key') }}</th>
<th>{{ $L('User') }}</th>
<th>{{ $L('Expires') }}</th> <th>{{ $L('Expires') }}</th>
<th>{{ $L('Last used') }}</th> <th>{{ $L('Last used') }}</th>
<th>{{ $L('Created') }}</th> <th>{{ $L('Created') }}</th>
@@ -50,6 +51,9 @@
<td> <td>
{{ $apiKey->api_key }} {{ $apiKey->api_key }}
</td> </td>
<td>
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $apiKey->user_id)) }}
</td>
<td> <td>
{{ $apiKey->expires }} {{ $apiKey->expires }}
<time class="timeago timeago-contextual" datetime="{{ $apiKey->expires }}"></time> <time class="timeago timeago-contextual" datetime="{{ $apiKey->expires }}"></time>

View File

@@ -7,8 +7,8 @@
resolved "https://github.com/pallidus-fintech/bootstrap-combobox.git#0bd1da781b99d390f1c75315b6025e7d8658b263" resolved "https://github.com/pallidus-fintech/bootstrap-combobox.git#0bd1da781b99d390f1c75315b6025e7d8658b263"
"@fortawesome/fontawesome-free@^5.1.0": "@fortawesome/fontawesome-free@^5.1.0":
version "5.1.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.1.0.tgz#f35f5ba91366b7a58b0b6a4f22ff0907fe002219" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.2.0.tgz#50cd9856774351c56c0b1b0db4efe122d7913e58"
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2": "TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2":
version "3.0.1" version "3.0.1"
@@ -29,8 +29,8 @@ bootstrap@4.0.0:
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"
bootstrap@^4.1.1: bootstrap@^4.1.1:
version "4.1.2" version "4.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.2.tgz#aee2a93472e61c471fc79fb475531dcbc87de326" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
chart.js@2.7.1: chart.js@2.7.1:
version "2.7.1" version "2.7.1"
@@ -185,8 +185,8 @@ startbootstrap-sb-admin@^4.0.0:
jquery.easing "^1.4.1" jquery.easing "^1.4.1"
swagger-ui-dist@^3.17.3: swagger-ui-dist@^3.17.3:
version "3.17.4" version "3.17.5"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.4.tgz#7b4d3842b052cbadebec784265b2e17fdda6a232" resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.5.tgz#ccab9dc35d16a3d244d26b5975e36a684211665a"
tempusdominus-bootstrap-4@^5.0.1: tempusdominus-bootstrap-4@^5.0.1:
version "5.0.1" version "5.0.1"