Added edit calendar events and fixed timezone
This commit is contained in:
parent
85ea87201d
commit
ac995b1b83
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -3,5 +3,6 @@
|
||||
"phpstan.configFile": "backend/phpstan.dist.neon",
|
||||
"phpstan.checkValidity": true,
|
||||
"phpstan.showTypeOnHover": false,
|
||||
"phpstan.showProgress": true
|
||||
"phpstan.showProgress": true,
|
||||
"php.version": "8.4"
|
||||
}
|
||||
@ -26,5 +26,5 @@ APP_SECRET=71bf50bfb778d456b3a376ff60d5dcd8
|
||||
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
|
||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://postgres:postgres@calendi-postgres:5432/postgres?serverVersion=16&charset=utf8"
|
||||
DATABASE_URL="postgresql://postgres:postgres@calendi-postgres.test:5432/postgres?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"doctrine/annotations": "^2.0",
|
||||
@ -20,6 +20,7 @@
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
@ -83,6 +84,8 @@
|
||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"symfony/maker-bundle": "^1.62"
|
||||
"symfony/maker-bundle": "^1.62",
|
||||
"symfony/stopwatch": "6.4.*",
|
||||
"symfony/web-profiler-bundle": "6.4.*"
|
||||
}
|
||||
}
|
||||
|
||||
349
backend/composer.lock
generated
349
backend/composer.lock
generated
@ -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": "f41287711c3c1d476ebbca47f5b529b5",
|
||||
"content-hash": "7ec99e86c547c32beef698aea0e9e346",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
@ -1202,6 +1202,109 @@
|
||||
},
|
||||
"time": "2025-01-24T11:45:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Seldaek/monolog.git",
|
||||
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
|
||||
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"psr/log": "^2.0 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"aws/aws-sdk-php": "^3.0",
|
||||
"doctrine/couchdb": "~1.0@dev",
|
||||
"elasticsearch/elasticsearch": "^7 || ^8",
|
||||
"ext-json": "*",
|
||||
"graylog2/gelf-php": "^1.4.2 || ^2.0",
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"guzzlehttp/psr7": "^2.2",
|
||||
"mongodb/mongodb": "^1.8",
|
||||
"php-amqplib/php-amqplib": "~2.4 || ^3",
|
||||
"php-console/php-console": "^3.1.8",
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpstan/phpstan-deprecation-rules": "^2",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
|
||||
"predis/predis": "^1.1 || ^2",
|
||||
"rollbar/rollbar": "^4.0",
|
||||
"ruflin/elastica": "^7 || ^8",
|
||||
"symfony/mailer": "^5.4 || ^6",
|
||||
"symfony/mime": "^5.4 || ^6"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
|
||||
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
|
||||
"ext-mbstring": "Allow to work properly with unicode symbols",
|
||||
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
|
||||
"ext-openssl": "Required to send log messages using SSL",
|
||||
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
|
||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
|
||||
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/Monolog"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "https://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||
"homepage": "https://github.com/Seldaek/monolog",
|
||||
"keywords": [
|
||||
"log",
|
||||
"logging",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Seldaek/monolog/issues",
|
||||
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Seldaek",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-24T10:02:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nelmio/api-doc-bundle",
|
||||
"version": "v5.0.1",
|
||||
@ -3485,6 +3588,166 @@
|
||||
],
|
||||
"time": "2025-03-28T13:27:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/monolog-bridge",
|
||||
"version": "v6.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/monolog-bridge.git",
|
||||
"reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/9d14621e59f22c2b6d030d92d37ffe5ae1e60452",
|
||||
"reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"monolog/monolog": "^1.25.1|^2|^3",
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/http-kernel": "^5.4|^6.0|^7.0",
|
||||
"symfony/service-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<5.4",
|
||||
"symfony/http-foundation": "<5.4",
|
||||
"symfony/security-core": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-client": "^5.4|^6.0|^7.0",
|
||||
"symfony/mailer": "^5.4|^6.0|^7.0",
|
||||
"symfony/messenger": "^5.4|^6.0|^7.0",
|
||||
"symfony/mime": "^5.4|^6.0|^7.0",
|
||||
"symfony/security-core": "^5.4|^6.0|^7.0",
|
||||
"symfony/var-dumper": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"type": "symfony-bridge",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bridge\\Monolog\\": ""
|
||||
},
|
||||
"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": "Provides integration for Monolog with various Symfony components",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/monolog-bridge/tree/v6.4.13"
|
||||
},
|
||||
"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": "2024-10-14T08:49:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/monolog-bundle",
|
||||
"version": "v3.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/monolog-bundle.git",
|
||||
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
|
||||
"reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"monolog/monolog": "^1.25.1 || ^2.0 || ^3.0",
|
||||
"php": ">=7.2.5",
|
||||
"symfony/config": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/phpunit-bridge": "^6.3 || ^7.0",
|
||||
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bundle\\MonologBundle\\": ""
|
||||
},
|
||||
"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": "Symfony MonologBundle",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"log",
|
||||
"logging"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/monolog-bundle/issues",
|
||||
"source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0"
|
||||
},
|
||||
"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": "2023-11-06T17:08:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v6.4.16",
|
||||
@ -6027,6 +6290,88 @@
|
||||
}
|
||||
],
|
||||
"time": "2025-03-10T17:11:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/web-profiler-bundle",
|
||||
"version": "v6.4.19",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/web-profiler-bundle.git",
|
||||
"reference": "7d1026a8e950d416cb5148ae88ac23db5d264839"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/7d1026a8e950d416cb5148ae88ac23db5d264839",
|
||||
"reference": "7d1026a8e950d416cb5148ae88ac23db5d264839",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/config": "^5.4|^6.0|^7.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/routing": "^5.4|^6.0|^7.0",
|
||||
"symfony/twig-bundle": "^5.4|^6.0",
|
||||
"twig/twig": "^2.13|^3.0.4"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/form": "<5.4",
|
||||
"symfony/mailer": "<5.4",
|
||||
"symfony/messenger": "<5.4",
|
||||
"symfony/twig-bundle": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/browser-kit": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^5.4|^6.0|^7.0",
|
||||
"symfony/css-selector": "^5.4|^6.0|^7.0",
|
||||
"symfony/stopwatch": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Bundle\\WebProfilerBundle\\": ""
|
||||
},
|
||||
"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": "Provides a development tool that gives detailed information about the execution of any request",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"dev"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.19"
|
||||
},
|
||||
"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-02-14T12:21:59+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
@ -6035,7 +6380,7 @@
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
|
||||
@ -8,4 +8,6 @@ return [
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
62
backend/config/packages/monolog.yaml
Normal file
62
backend/config/packages/monolog.yaml
Normal file
@ -0,0 +1,62 @@
|
||||
monolog:
|
||||
channels:
|
||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
|
||||
when@prod:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: [deprecation]
|
||||
path: php://stderr
|
||||
formatter: monolog.formatter.json
|
||||
11
backend/config/packages/web_profiler.yaml
Normal file
11
backend/config/packages/web_profiler.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
8
backend/config/routes/web_profiler.yaml
Normal file
8
backend/config/routes/web_profiler.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
||||
@ -4,6 +4,7 @@
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
app.timezone: 'Europe/Berlin'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
||||
@ -15,6 +15,7 @@ use Nelmio\ApiDocBundle\Attribute\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[Route('/api/events', name: 'api_events_')]
|
||||
#[OA\Tag(name: 'Events')]
|
||||
class GetEventsController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
@ -23,7 +24,6 @@ class GetEventsController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('', name: 'list', methods: ['GET'])]
|
||||
#[OA\Tag(name: 'Events')]
|
||||
#[OA\Response(
|
||||
response: 200,
|
||||
description: 'Returns list of events',
|
||||
@ -39,7 +39,6 @@ class GetEventsController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'get', methods: ['GET'])]
|
||||
#[OA\Tag(name: 'Events')]
|
||||
#[OA\Parameter(
|
||||
name: 'id',
|
||||
description: 'Event ID',
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Application\Controller\Event;
|
||||
|
||||
use App\Application\DTO\PersistEventDTO;
|
||||
use App\Domain\Event\PersistEventHandler;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[Route('/api/events', name: 'api_events_persist', methods: ['POST'])]
|
||||
#[OA\Tag(name: 'Events')]
|
||||
class PersistEventController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PersistEventHandler $persistEventHandler,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('', name: 'persist', methods: ['POST'])]
|
||||
public function persist(#[MapRequestPayload] PersistEventDTO $dto): JsonResponse
|
||||
{
|
||||
$this->persistEventHandler->handle($dto->toDomain());
|
||||
return $this->json(['message' => 'Event persisted']);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'update', methods: ['PUT'])]
|
||||
public function update(#[MapRequestPayload] PersistEventDTO $dto, string $id): JsonResponse
|
||||
{
|
||||
$this->persistEventHandler->handle($dto->toDomain()->withId($id));
|
||||
return $this->json(['message' => 'Event updated']);
|
||||
}
|
||||
}
|
||||
@ -5,36 +5,48 @@ declare(strict_types=1);
|
||||
namespace App\Application\DTO;
|
||||
|
||||
use App\Domain\Model\EventDraft;
|
||||
use DateTimeInterface;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Schema]
|
||||
final readonly class EventDraftDTO
|
||||
{
|
||||
public function __construct(
|
||||
#[OA\Property(type: 'string')]
|
||||
public string $id,
|
||||
#[OA\Property(type: 'string')]
|
||||
public string $title,
|
||||
#[OA\Property(type: 'string')]
|
||||
public string $description,
|
||||
#[OA\Property(type: 'string')]
|
||||
public ?string $start,
|
||||
#[OA\Property(type: 'string')]
|
||||
public ?string $end,
|
||||
#[OA\Property(type: 'boolean')]
|
||||
public bool $allDay
|
||||
#[OA\Property(type: 'string', nullable: true)]
|
||||
public ?string $title = null,
|
||||
#[OA\Property(type: 'string', nullable: true)]
|
||||
public ?string $description = null,
|
||||
#[OA\Property(type: 'string', nullable: true)]
|
||||
public ?string $location = null,
|
||||
#[OA\Property(type: 'datetime', nullable: true)]
|
||||
public ?DateTimeInterface $start = null,
|
||||
#[OA\Property(type: 'datetime', nullable: true)]
|
||||
public ?DateTimeInterface $end = null,
|
||||
#[OA\Property(type: 'boolean', nullable: true)]
|
||||
public ?bool $allDay = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toDomain(): EventDraft
|
||||
{
|
||||
return new EventDraft(
|
||||
$this->title,
|
||||
$this->description,
|
||||
$this->location,
|
||||
$this->start,
|
||||
$this->end,
|
||||
$this->allDay,
|
||||
);
|
||||
}
|
||||
public static function fromDraft(EventDraft $draft): self
|
||||
{
|
||||
return new self(
|
||||
$draft->title(),
|
||||
$draft->description(),
|
||||
$draft->location(),
|
||||
$draft->start()?->format('Y-m-d H:i:s'),
|
||||
$draft->end()?->format('Y-m-d H:i:s'),
|
||||
$draft->allDay()
|
||||
$draft->title,
|
||||
$draft->description,
|
||||
$draft->location,
|
||||
$draft->start,
|
||||
$draft->end,
|
||||
$draft->allDay,
|
||||
);
|
||||
}
|
||||
}
|
||||
24
backend/src/Application/DTO/PersistEventDTO.php
Normal file
24
backend/src/Application/DTO/PersistEventDTO.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Application\DTO;
|
||||
|
||||
use App\Domain\Event\PersistEvent;
|
||||
use OpenApi\Attributes as OA;
|
||||
use App\Application\DTO\EventDraftDTO;
|
||||
|
||||
#[OA\Schema]
|
||||
final readonly class PersistEventDTO
|
||||
{
|
||||
public function __construct(
|
||||
#[OA\Property]
|
||||
public readonly EventDraftDTO $draft,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toDomain(): PersistEvent
|
||||
{
|
||||
return new PersistEvent(
|
||||
$this->draft->toDomain(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,12 +6,14 @@ use App\Domain\Chat\ChatProviderInterface;
|
||||
use App\Domain\Chat\ChatSession;
|
||||
use App\Domain\Model\EventDraft;
|
||||
use App\Domain\User\UserContextProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class GenerateDraftHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ChatProviderInterface $chatProvider,
|
||||
private readonly UserContextProvider $userContextProvider,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -19,14 +21,20 @@ class GenerateDraftHandler
|
||||
{
|
||||
$userContext = $this->userContextProvider->getUserContext();
|
||||
$chat = new ChatSession($this->chatProvider);
|
||||
$chat->system(<<<PROMPT
|
||||
You are a helpful assistant that generates event drafts based on user input.
|
||||
|
||||
$systemPrompt = <<<PROMPT
|
||||
You are a helpful assistant that generates calendar event drafts based on user input.
|
||||
The user input can be anything from a very detailed description to a simple sentence or just a snippet from another source (Chat, email, etc.).
|
||||
You should always generate a draft even if the user input is not very detailed.
|
||||
Go Step by Step by step:
|
||||
- First, analyze the user input and analyze the context of the user input.
|
||||
- First, analyze the user input and asking yourself what the user wants to achieve:
|
||||
a. What is the title of the event?
|
||||
b. What is the description of the event?
|
||||
c. What is the location of the event?
|
||||
d. What is the start datetime of the event?
|
||||
e. What is the end datetime of the event?
|
||||
f. Is the event all day?
|
||||
- Then, generate a draft of the event.
|
||||
- Finally, return the draft in the following format:
|
||||
|
||||
The event draft should be in the following format:
|
||||
```json
|
||||
@ -42,9 +50,14 @@ class GenerateDraftHandler
|
||||
|
||||
This is the current context:
|
||||
- Time: {$userContext->now()->format('Y-m-d H:i:s')}
|
||||
- Timezone: {$userContext->now()->getTimezone()->getName()}
|
||||
|
||||
You will only respond with the JSON object in ```json``` tags, nothing else.
|
||||
PROMPT);
|
||||
PROMPT;
|
||||
|
||||
$chat->system($systemPrompt);
|
||||
|
||||
$this->logger->info('Generating draft for input: ' . $generateDraft->input() . " System prompt: " . $systemPrompt);
|
||||
|
||||
$chat->user($generateDraft->input());
|
||||
$chat->commit(reasoning: false);
|
||||
|
||||
19
backend/src/Domain/Event/PersistEvent.php
Normal file
19
backend/src/Domain/Event/PersistEvent.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Event;
|
||||
|
||||
use App\Domain\Model\EventDraft;
|
||||
|
||||
class PersistEvent
|
||||
{
|
||||
public function __construct(
|
||||
public readonly EventDraft $draft,
|
||||
public readonly ?string $id = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function withId(string $id): self
|
||||
{
|
||||
return new self($this->draft, $id);
|
||||
}
|
||||
}
|
||||
28
backend/src/Domain/Event/PersistEventHandler.php
Normal file
28
backend/src/Domain/Event/PersistEventHandler.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Event;
|
||||
|
||||
use App\Domain\Event\PersistEvent;
|
||||
use App\Domain\Model\Persisted\PersistedEvent;
|
||||
use App\Infrastructure\Repository\EventRepository;
|
||||
|
||||
class PersistEventHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EventRepository $eventRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(PersistEvent $event): void
|
||||
{
|
||||
$persistedEvent = new PersistedEvent();
|
||||
if ($event->id !== null) {
|
||||
$persistedEvent = $this->eventRepository->find($event->id);
|
||||
if (!$persistedEvent) {
|
||||
throw new \Exception('Event not found');
|
||||
}
|
||||
}
|
||||
|
||||
$this->eventRepository->save($event->draft->mergeIntoPersisted($persistedEvent));
|
||||
}
|
||||
}
|
||||
@ -2,47 +2,49 @@
|
||||
|
||||
namespace App\Domain\Model;
|
||||
|
||||
use App\Domain\Model\Persisted\PersistedEvent;
|
||||
use DateTimeInterface;
|
||||
|
||||
class EventDraft
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $title,
|
||||
private readonly string $description,
|
||||
private readonly string $location,
|
||||
private readonly ?DateTimeInterface $start,
|
||||
private readonly ?DateTimeInterface $end,
|
||||
private readonly bool $allDay,
|
||||
public readonly ?string $title,
|
||||
public readonly ?string $description,
|
||||
public readonly ?string $location,
|
||||
public readonly ?DateTimeInterface $start,
|
||||
public readonly ?DateTimeInterface $end,
|
||||
public readonly ?bool $allDay,
|
||||
) {
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
public function mergeIntoPersisted(PersistedEvent $persistedEvent = new PersistedEvent()): PersistedEvent
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
if (!$this->start instanceof \DateTimeImmutable) {
|
||||
throw new \Exception('Start date must be a DateTimeImmutable');
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
if (!$this->end instanceof \DateTimeImmutable) {
|
||||
throw new \Exception('End date must be a DateTimeImmutable');
|
||||
}
|
||||
|
||||
public function location(): string
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
if ($this->allDay === null) {
|
||||
throw new \Exception('All day must be a boolean');
|
||||
}
|
||||
|
||||
public function start(): ?DateTimeInterface
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
if ($this->title === null) {
|
||||
throw new \Exception('Title must be a string');
|
||||
}
|
||||
|
||||
public function end(): ?DateTimeInterface
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
if ($this->description === null) {
|
||||
throw new \Exception('Description must be a string');
|
||||
}
|
||||
|
||||
public function allDay(): bool
|
||||
{
|
||||
return $this->allDay;
|
||||
return $persistedEvent
|
||||
->setTitle($this->title)
|
||||
->setDescription($this->description)
|
||||
->setLocation($this->location)
|
||||
->setStart($this->start)
|
||||
->setEnd($this->end)
|
||||
->setAllDay($this->allDay);
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,9 @@ class PersistedEvent
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $description = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
private ?string $location = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'event_from')]
|
||||
#[Assert\NotNull]
|
||||
private \DateTimeImmutable $from;
|
||||
@ -128,4 +131,16 @@ class PersistedEvent
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocation(): ?string
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
public function setLocation(?string $location): self
|
||||
{
|
||||
$this->location = $location;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Infrastructure\EventSubscriber;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
final class TimezoneSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ParameterBagInterface $params,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => ['setTimezone', 100],
|
||||
];
|
||||
}
|
||||
|
||||
public function setTimezone(RequestEvent $event): void
|
||||
{
|
||||
if (!$event->isMainRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timezone = $this->params->get('app.timezone');
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,12 @@ class EventRepository extends ServiceEntityRepository
|
||||
parent::__construct($registry, PersistedEvent::class);
|
||||
}
|
||||
|
||||
public function save(PersistedEvent $event): void
|
||||
{
|
||||
$this->getEntityManager()->persist($event);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<PersistedEvent>
|
||||
*/
|
||||
|
||||
@ -121,6 +121,18 @@
|
||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||
}
|
||||
},
|
||||
"symfony/monolog-bundle": {
|
||||
"version": "3.10",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.7",
|
||||
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/monolog.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
@ -170,5 +182,18 @@
|
||||
"files": [
|
||||
"config/packages/validator.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/web-profiler-bundle": {
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.1",
|
||||
"ref": "8b51135b84f4266e3b4c8a6dc23c9d1e32e543b7"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/web_profiler.yaml",
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
services:
|
||||
app:
|
||||
image: app:latest
|
||||
hostname: calendi.test
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
@ -17,7 +18,7 @@ services:
|
||||
- proxy
|
||||
|
||||
postgres:
|
||||
hostname: calendi-postgres
|
||||
hostname: calendi-postgres.test
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
@ -29,6 +30,11 @@ services:
|
||||
- ./var/postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.postgres.entrypoints=postgres"
|
||||
- "traefik.http.routers.postgres.rule=Host(`calendi-postgres.test`)"
|
||||
- "traefik.http.services.postgres.loadbalancer.server.port=5432"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
|
||||
@ -10,7 +10,13 @@ RUN apt-get update && apt-get install -y \
|
||||
apt-transport-https \
|
||||
software-properties-common \
|
||||
nginx \
|
||||
unzip
|
||||
unzip \
|
||||
tzdata
|
||||
|
||||
# Set timezone to Germany
|
||||
RUN ln -fs /usr/share/zoneinfo/Europe/Berlin /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata && \
|
||||
echo "Europe/Berlin" > /etc/timezone
|
||||
|
||||
# Add PHP repository
|
||||
RUN wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg \
|
||||
|
||||
@ -9,6 +9,11 @@ server {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
# PHP Backend API
|
||||
location /_profiler {
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
|
||||
@ -26,19 +26,16 @@ export type Event = {
|
||||
allDay: boolean;
|
||||
};
|
||||
|
||||
export type CreateEventRequest = {
|
||||
title: string;
|
||||
description?: string;
|
||||
start: string;
|
||||
end: string;
|
||||
allDay?: boolean;
|
||||
export type PersistEventRequest = {
|
||||
draft: EventDraft;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
// Event endpoints
|
||||
export const getEvents = () => get<Event[]>('/api/events');
|
||||
export const getEvent = (id: string) => get<Event>(`/api/events/${id}`);
|
||||
export const createEvent = (data: CreateEventRequest) => post<Event>('/api/events', data);
|
||||
export const updateEvent = (id: string, data: Partial<CreateEventRequest>) => put<Event>(`/api/events/${id}`, data);
|
||||
export const persistEvent = (data: PersistEventRequest) => post<Event>('/api/events', data);
|
||||
export const updateEvent = (id: string, data: Partial<PersistEventRequest>) => put<Event>(`/api/events/${id}`, data);
|
||||
export const deleteEvent = (id: string) => del<void>(`/api/events/${id}`);
|
||||
|
||||
// Event draft types
|
||||
|
||||
@ -43,25 +43,4 @@ export function useApi<T, P extends unknown[]>(
|
||||
);
|
||||
|
||||
return [execute, state];
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
//
|
||||
// function UserList() {
|
||||
// const [fetchUsers, { data: users, loading, error }] = useApi(getUsers, []);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// fetchUsers();
|
||||
// }, [fetchUsers]);
|
||||
//
|
||||
// if (loading) return <div>Loading...</div>;
|
||||
// if (error) return <div>Error: {error}</div>;
|
||||
//
|
||||
// return (
|
||||
// <ul>
|
||||
// {users?.map(user => (
|
||||
// <li key={user.id}>{user.name}</li>
|
||||
// ))}
|
||||
// </ul>
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { EventDraft, createEvent } from '../../lib/api/endpoints';
|
||||
import { EventDraft, persistEvent } from '../../lib/api/endpoints';
|
||||
import LoadingSpinner from '../../components/ui/LoadingSpinner';
|
||||
import './EditEvent.css';
|
||||
|
||||
@ -85,14 +85,17 @@ const EditEvent: React.FC = () => {
|
||||
setLoading(true);
|
||||
|
||||
const eventData = {
|
||||
title,
|
||||
description,
|
||||
start: startDate ? formatForServer(startDate, startTime || '00:00') : new Date().toISOString(),
|
||||
end: endDate ? formatForServer(endDate, endTime || '00:00') : new Date().toISOString(),
|
||||
allDay
|
||||
draft: {
|
||||
id: id || '',
|
||||
title,
|
||||
description,
|
||||
start: startDate ? formatForServer(startDate, startTime || '00:00') : new Date().toISOString(),
|
||||
end: endDate ? formatForServer(endDate, endTime || '00:00') : new Date().toISOString(),
|
||||
allDay
|
||||
}
|
||||
};
|
||||
|
||||
const savedEvent = await createEvent(eventData);
|
||||
const savedEvent = await persistEvent(eventData);
|
||||
console.log('Event created:', savedEvent);
|
||||
|
||||
// Clear the draft from sessionStorage
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user