Add settings-driven theme and version metadata

This commit is contained in:
Micha
2025-12-25 14:35:01 +01:00
parent b5d689dd4d
commit 882ef26982
16 changed files with 525 additions and 29 deletions

View File

@@ -1,5 +1,6 @@
{
"type": "project",
"version": "25.00.1",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
@@ -79,6 +80,9 @@
"symfony/symfony": "*"
},
"extra": {
"speedbb": {
"build": 3
},
"symfony": {
"allow-contrib": false,
"require": "8.0.*"

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251224184500 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add settings table with version and build metadata.';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE settings (id INT AUTO_INCREMENT NOT NULL, version VARCHAR(20) NOT NULL, build INT NOT NULL, PRIMARY KEY(id))');
$this->addSql("INSERT INTO settings (id, version, build) VALUES (1, '25.00.1', 3)");
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE settings');
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251224191500 extends AbstractMigration
{
public function getDescription(): string
{
return 'Convert settings table to key/value rows for version/build.';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE settings_new (id INT AUTO_INCREMENT NOT NULL, `key` VARCHAR(100) NOT NULL, value LONGTEXT NOT NULL, UNIQUE INDEX UNIQ_SETTINGS_KEY (`key`), PRIMARY KEY(id))');
$this->addSql("INSERT INTO settings_new (`key`, value) SELECT 'version', version FROM settings LIMIT 1");
$this->addSql("INSERT INTO settings_new (`key`, value) SELECT 'build', CAST(build AS CHAR) FROM settings LIMIT 1");
$this->addSql('DROP TABLE settings');
$this->addSql('RENAME TABLE settings_new TO settings');
}
public function down(Schema $schema): void
{
$this->addSql('CREATE TABLE settings_old (id INT AUTO_INCREMENT NOT NULL, version VARCHAR(20) NOT NULL, build INT NOT NULL, PRIMARY KEY(id))');
$this->addSql("INSERT INTO settings_old (id, version, build) VALUES (1, (SELECT value FROM settings WHERE `key` = 'version' LIMIT 1), CAST((SELECT value FROM settings WHERE `key` = 'build' LIMIT 1) AS UNSIGNED))");
$this->addSql('DROP TABLE settings');
$this->addSql('RENAME TABLE settings_old TO settings');
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251224193000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add indexes for forum parent ordering and type filters.';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE INDEX idx_forum_parent_position ON forum (parent_id, position)');
$this->addSql('CREATE INDEX idx_forum_type ON forum (type)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX idx_forum_parent_position ON forum');
$this->addSql('DROP INDEX idx_forum_type ON forum');
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251224194000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add accent color setting.';
}
public function up(Schema $schema): void
{
$this->addSql("INSERT IGNORE INTO settings (`key`, value) VALUES ('accent_color', '#f29b3f')");
}
public function down(Schema $schema): void
{
$this->addSql("DELETE FROM settings WHERE `key` = 'accent_color'");
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Controller;
use App\Entity\Settings;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
final class VersionController
{
#[Route('/api/version', name: 'api_version', methods: ['GET'])]
public function __invoke(EntityManagerInterface $entityManager): JsonResponse
{
$repository = $entityManager->getRepository(Settings::class);
$version = $repository->findOneBy(['key' => 'version']);
$build = $repository->findOneBy(['key' => 'build']);
return new JsonResponse([
'version' => $version?->getValue(),
'build' => $build ? (int) $build->getValue() : null,
]);
}
}

View File

@@ -21,6 +21,10 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
#[ORM\Table(indexes: [
new ORM\Index(name: 'idx_forum_parent_position', columns: ['parent_id', 'position']),
new ORM\Index(name: 'idx_forum_type', columns: ['type']),
])]
#[ApiFilter(SearchFilter::class, properties: ['parent' => 'exact', 'type' => 'exact'])]
#[ApiFilter(ExistsFilter::class, properties: ['parent'])]
#[ApiResource(

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity]
#[ApiFilter(SearchFilter::class, properties: ['key' => 'exact'])]
#[ApiResource(
operations : [
new Get(),
new GetCollection(),
new Patch(security: "is_granted('ROLE_ADMIN')")
],
normalizationContext : ['groups' => ['settings:read']],
denormalizationContext: ['groups' => ['settings:write']]
)]
class Settings
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['settings:read'])]
private ?int $id = null;
#[ORM\Column(length: 100, unique: true)]
#[Groups(['settings:read', 'settings:write'])]
private string $key = '';
#[ORM\Column(type: 'text')]
#[Groups(['settings:read', 'settings:write'])]
private string $value = '';
public function getId(): ?int
{
return $this->id;
}
public function getKey(): string
{
return $this->key;
}
public function setKey(string $key): self
{
$this->key = $key;
return $this;
}
public function getValue(): string
{
return $this->value;
}
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
}

View File

@@ -21,6 +21,18 @@ msgstr "Abmelden"
msgid "nav.language"
msgstr "Sprache"
msgid "nav.theme"
msgstr "Design"
msgid "nav.theme_auto"
msgstr "Auto"
msgid "nav.theme_light"
msgstr "Hell"
msgid "nav.theme_dark"
msgstr "Dunkel"
msgid "home.hero_title"
msgstr "Foren"
@@ -115,7 +127,7 @@ msgid "auth.register_hint"
msgstr "Registriere dich mit E-Mail und einem eindeutigen Benutzernamen."
msgid "footer.copy"
msgstr "speedBB Forum. Powered by API Platform und React-Bootstrap."
msgstr "speedBB"
msgid "form.title"
msgstr "Titel"

View File

@@ -21,6 +21,18 @@ msgstr "Logout"
msgid "nav.language"
msgstr "Language"
msgid "nav.theme"
msgstr "Theme"
msgid "nav.theme_auto"
msgstr "Auto"
msgid "nav.theme_light"
msgstr "Light"
msgid "nav.theme_dark"
msgstr "Dark"
msgid "home.hero_title"
msgstr "Forums"
@@ -115,7 +127,7 @@ msgid "auth.register_hint"
msgstr "Register with an email and a unique username."
msgid "footer.copy"
msgstr "speedBB forum. Powered by API Platform and React-Bootstrap."
msgstr "speedBB"
msgid "form.title"
msgstr "Title"