Tweaks to database logic, create slot
This commit is contained in:
+2
-1
@@ -22,7 +22,8 @@
|
||||
"symfony/cache": "^7.2",
|
||||
"psr/log": "^3.0",
|
||||
"monolog/monolog": "^3.8",
|
||||
"scienta/doctrine-json-functions": "^6.3"
|
||||
"scienta/doctrine-json-functions": "^6.3",
|
||||
"symfony/yaml": "^7.2"
|
||||
},
|
||||
"bin": [
|
||||
"bin/slotdbd"
|
||||
|
||||
Generated
+73
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f841aaa059a224972f8fd4701e4e2387",
|
||||
"content-hash": "8c809e4306164e200a0b53086633b24b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
@@ -3014,6 +3014,78 @@
|
||||
}
|
||||
],
|
||||
"time": "2025-02-13T10:27:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "ac238f173df0c9c1120f862d0f599e17535a87ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/ac238f173df0c9c1120f862d0f599e17535a87ec",
|
||||
"reference": "ac238f173df0c9c1120f862d0f599e17535a87ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3.0",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<6.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^6.4|^7.0"
|
||||
},
|
||||
"bin": [
|
||||
"Resources/bin/yaml-lint"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Yaml\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.2.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-07T12:55:42+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
@@ -16,6 +16,48 @@ slotdbd - SlotDB database daemon
|
||||
**\-h**, **\-\-help**
|
||||
: Display help
|
||||
|
||||
**\-v**,**\-\-verbose**
|
||||
: Include debug level logging to STDOUT
|
||||
|
||||
**\-V**,**\-\-version**
|
||||
: Print version and exit
|
||||
|
||||
**\-l**,**\-\-listen** *ADDR*
|
||||
: Set listen address for HTTP requests (env: SLOTDB_LISTEN)
|
||||
|
||||
**\-c**,**\-\-config** *FILE*
|
||||
: Read configuration from file (env: SLOTDB_CONFIG)
|
||||
|
||||
**\-\-db** *URI*
|
||||
:Use custom database connection URI (env: SLOTDB_DATABASE)
|
||||
|
||||
**\-\-init-db**
|
||||
:Initialize database schema
|
||||
|
||||
**\-\-upgrade-db**
|
||||
:Upgrade database schema
|
||||
|
||||
**\-\-root** *FILE*
|
||||
:Set root CA certificate for SSL (env: SLOTDB_SSL_ROOT)
|
||||
|
||||
**\-\-cert** *FILE*
|
||||
:Set certificate for SSL (env: SLOTDB_SSL_CERT)
|
||||
|
||||
**\-\-key** *FILE*
|
||||
:Set private key file for SSL (env: SLOTDB_SSL_KEY)
|
||||
|
||||
**\-\-jwt-secret** *SECRET*
|
||||
:Use JWT auth with secret (env: SLOTDB_JWT_SECRET)
|
||||
|
||||
**\-\-jwt-claim** *CLAIM*
|
||||
:Key in JWT claims that contains ACLs (env: SLOTDB_JWT_CLAIM)
|
||||
|
||||
**\-\-tokens** *FILE*
|
||||
:Read access tokens from file (env: SLOTDB_TOKENS_FILE)
|
||||
|
||||
**\-\-http-log** *FILE*
|
||||
:Log HTTP requests to file (env: SLOTDB_HTTP_LOG)
|
||||
|
||||
|
||||
# EXAMPLES
|
||||
**slotdbd**
|
||||
|
||||
+3
-1
@@ -41,6 +41,7 @@ class Daemon
|
||||
?string $dataDir = null,
|
||||
?LoggerInterface $logger = null,
|
||||
?LoggerInterface $httpLogger = null,
|
||||
array $securityOpts = [],
|
||||
)
|
||||
{
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
@@ -52,6 +53,7 @@ class Daemon
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => $this->dataDir."/slots.db",
|
||||
]);
|
||||
|
||||
|
||||
$this->database = new Database($conn, $logger);
|
||||
$this->database->updateSchema($upgradeDb);
|
||||
@@ -68,7 +70,7 @@ class Daemon
|
||||
|
||||
$this->server = new HttpServer(
|
||||
new Http\Middleware\LoggingMiddleware($httpLogger ?? $logger),
|
||||
new SecurityMiddleware(),
|
||||
new SecurityMiddleware(...$securityOpts),
|
||||
new RoutingMiddleware($routes),
|
||||
new RequestHandler(),
|
||||
function (ServerRequestInterface $request) {
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
namespace SlotDb\SlotDb\Data\Hook;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use SlotDb\SlotDb\Data\Group\HookRepository;
|
||||
|
||||
#[ORM\Table(name:"hooks")]
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Entity(repositoryClass: HookRepository::class)]
|
||||
class Hook
|
||||
{
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace SlotDb\SlotDb\Data\Group;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class HookRepository extends EntityRepository
|
||||
{
|
||||
|
||||
|
||||
public function flush(...$objs): void
|
||||
{
|
||||
foreach ($objs as $obj) $this->getEntityManager()->persist($obj);
|
||||
$this->getEntityManager()->flush();
|
||||
$this->getEntityManager()->clear();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace SlotDb\SlotDb\Data\Slot;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
class SlotRepository extends EntityRepository
|
||||
@@ -13,8 +14,6 @@ class SlotRepository extends EntityRepository
|
||||
slotId: $id,
|
||||
name: $name??$id,
|
||||
);
|
||||
$this->getEntityManager()->persist($slot);
|
||||
$this->getEntityManager()->flush();
|
||||
return $slot;
|
||||
}
|
||||
|
||||
@@ -27,6 +26,15 @@ class SlotRepository extends EntityRepository
|
||||
return $slot;
|
||||
}
|
||||
|
||||
public function hasSlot(string $id): bool
|
||||
{
|
||||
$slot = $this->findOneBy([ 'slotId' => $id ]);
|
||||
if (!$slot) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function findSlots(int $limit = 0, int $page = 0): array
|
||||
{
|
||||
$builder = $this->createQueryBuilder('s');
|
||||
@@ -87,7 +95,15 @@ class SlotRepository extends EntityRepository
|
||||
public function flush(...$slots): void
|
||||
{
|
||||
foreach ($slots as $slot) $this->getEntityManager()->persist($slot);
|
||||
$this->getEntityManager()->flush();
|
||||
$this->getEntityManager()->clear();
|
||||
try {
|
||||
$this->getEntityManager()->flush();
|
||||
$this->getEntityManager()->clear();
|
||||
}
|
||||
catch (UniqueConstraintViolationException $e) {
|
||||
throw new \Exception("Duplicate index", previous: $e);
|
||||
}
|
||||
catch (\Throwable $t) {
|
||||
fwrite(STDERR, $t."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace SlotDb\SlotDb\Http\Controller;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use NoccyLabs\React\Http\Attributes\Route;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
@@ -67,14 +68,21 @@ class SlotsController extends Controller
|
||||
foreach ($data as $item) {
|
||||
$slotId = $item->slot;
|
||||
$slotName = $item->name??null;
|
||||
try {
|
||||
$slot = $this->slots->createSlot($slotId, $slotName);
|
||||
$props = $item->props ?? [];
|
||||
if (!$this->slots->hasSlot($slotId)) {
|
||||
$slot = new Slot($slotId, $slotName);
|
||||
$slot->setProperties($props);
|
||||
$slots[] = $slot;
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return Response::plaintext((string)$e)->withStatus(500);
|
||||
}
|
||||
}
|
||||
try {
|
||||
$this->slots->flush(...$slots);
|
||||
}
|
||||
catch (UniqueConstraintViolationException $e) {
|
||||
return Response::json([ 'error'=>"Duplicate slot" ])->withStatus(Response::STATUS_CONFLICT);
|
||||
}
|
||||
catch (\Throwable $t) {
|
||||
return Response::json([ 'error'=>$t->getMessage() ])->withStatus(Response::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
return Response::json($slots);
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace SlotDb\SlotDb\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Promise\PromiseInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class SecurityMiddleware
|
||||
{
|
||||
@@ -14,13 +16,18 @@ class SecurityMiddleware
|
||||
|
||||
private bool $useToken = false;
|
||||
|
||||
private array $tokens = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly ?string $jwtSecret = null,
|
||||
private readonly ?string $jwtClaim = "slotdb",
|
||||
private readonly ?string $tokenFile = null,
|
||||
)
|
||||
{
|
||||
|
||||
if ($tokenFile) {
|
||||
$this->readTokenFile($tokenFile);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ?callable $next = null)
|
||||
@@ -31,6 +38,7 @@ class SecurityMiddleware
|
||||
$request = $request->withAttribute("acl", [ "*/rw" ]);
|
||||
} else {
|
||||
if ($this->useJwt && $request->hasHeader('authorization')) {
|
||||
// JWT authenticaton
|
||||
$auth = $request->getHeader('authorization')[0];
|
||||
[$type,$token] = explode(" ", $auth, 2);
|
||||
$acl = $this->checkJwt($token);
|
||||
@@ -39,12 +47,15 @@ class SecurityMiddleware
|
||||
}
|
||||
$request = $request->withAttribute("acl", $acl);
|
||||
} elseif ($this->useToken && $request->hasHeader('x-token')) {
|
||||
// Token-based authentication
|
||||
$token = $request->getHeader('x-token')[0];
|
||||
$acl = $this->checkToken($token);
|
||||
if ($acl === null) {
|
||||
return Response::json(['error'=>'Unauthorized'])->withStatus(Response::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
$request = $request->withAttribute("acl", $acl);
|
||||
} else {
|
||||
return Response::json(['error'=>'Unauthorized'])->withStatus(Response::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +63,35 @@ class SecurityMiddleware
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function readTokenFile(string $filename): void
|
||||
{
|
||||
// Set this so that the server does not become open if the token file is
|
||||
// missing.
|
||||
$this->useToken = true;
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
$this->logger->warning("Token file not found: {$filename} - no tokens read!");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info("Reading tokens from file: {$filename}");
|
||||
|
||||
$tokens = Yaml::parseFile($filename);
|
||||
$this->tokens = [];
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$tok = $token['token'] ?? null;
|
||||
$acl = $token['acl'] ?? null;
|
||||
if (!($acl && $tok)) {
|
||||
$this->logger->warning("Invalid token in configuration file: {$filename}");
|
||||
} else {
|
||||
$this->tokens[$tok] = $acl;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->debug("Read ".count($this->tokens)." tokens from file.");
|
||||
}
|
||||
|
||||
private function checkJwt(string $token): ?array
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -6,6 +6,7 @@ use Monolog\Formatter\JsonFormatter;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Psr\Log\LogLevel;
|
||||
use React\EventLoop\Loop;
|
||||
use SlotDb\SlotDb\Daemon;
|
||||
|
||||
$buildInfo = (file_exists(__DIR__."/build.php"))
|
||||
@@ -19,6 +20,7 @@ $opts = getopt("hl:vV", [
|
||||
"db:", "init-db", "upgrade-db",
|
||||
"root:", "cert:", "key:",
|
||||
"jwt-secret:", "jwt-claim:",
|
||||
"tokens:",
|
||||
"http-log:", "version"
|
||||
]);
|
||||
|
||||
@@ -100,6 +102,11 @@ $httpLog = $config->httpLog
|
||||
?: ($opts['http-log']
|
||||
?? null ));
|
||||
|
||||
$tokenFile = $config->tokenFile
|
||||
?? (getenv("SLOTDB_TOKENS_FILE")
|
||||
?: ($opts['tokens']
|
||||
?? null ));
|
||||
|
||||
$consoleHandler = new Monolog\Handler\StreamHandler(STDOUT);
|
||||
|
||||
$logFormat = "[%datetime%] %channel%.%level_name%: %message%\n";
|
||||
@@ -124,11 +131,18 @@ if ($verbose) {
|
||||
|
||||
$logger = new Monolog\Logger("main", $logHandlers);
|
||||
$httpLogger = new Monolog\Logger("http", $httpLogHandlers);
|
||||
$securityLogger = new Monolog\Logger("security", $logHandlers);
|
||||
|
||||
$daemon = new Daemon(
|
||||
listen: $listen ?? '0.0.0.0:8080',
|
||||
logger: $logger,
|
||||
httpLogger: $httpLogger,
|
||||
securityOpts: [
|
||||
'tokenFile' => $tokenFile,
|
||||
'logger' => $securityLogger,
|
||||
]
|
||||
);
|
||||
|
||||
$daemon->start();
|
||||
|
||||
Loop::run();
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*.db
|
||||
/*/*.json
|
||||
*.db
|
||||
*.json
|
||||
|
||||
Reference in New Issue
Block a user