Phase 0 : entités, migrations, API Platform, PostGIS
- API Platform + Doctrine ORM + jsor/doctrine-postgis installés - 6 entités Symfony (SargassumObservation, SargassumForecast, DataIngestionJob, CoastalPoint, ImpactScore, UserFeedback) - Migration initiale manuelle avec CREATE EXTENSION postgis, toutes les tables, index GIST et BTREE - doctrine.yaml configuré pour PostgreSQL 16 + types PostGIS - Roadmap Phase 0 complète Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
7
backend/compose.override.yaml
Normal file
7
backend/compose.override.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
ports:
|
||||
- "5432"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
25
backend/compose.yaml
Normal file
25
backend/compose.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: postgres:${POSTGRES_VERSION:-16}-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-app}
|
||||
# You should definitely change the password in production
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-app}
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"]
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
volumes:
|
||||
- database_data:/var/lib/postgresql/data:rw
|
||||
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
|
||||
# - ./docker/db/data:/var/lib/postgresql/data:rw
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
volumes:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database_data:
|
||||
###< doctrine/doctrine-bundle ###
|
||||
@@ -7,15 +7,31 @@
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"api-platform/doctrine-orm": "^4.3",
|
||||
"api-platform/symfony": "^4.3",
|
||||
"doctrine/doctrine-bundle": "^2.18",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.7",
|
||||
"doctrine/orm": "^3.6",
|
||||
"jsor/doctrine-postgis": "^2.4",
|
||||
"nelmio/cors-bundle": "^2.6",
|
||||
"phpdocumentor/reflection-docblock": "^6.0",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
"symfony/asset": "7.4.*",
|
||||
"symfony/console": "7.4.*",
|
||||
"symfony/dotenv": "7.4.*",
|
||||
"symfony/expression-language": "7.4.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "7.4.*",
|
||||
"symfony/property-access": "7.4.*",
|
||||
"symfony/property-info": "7.4.*",
|
||||
"symfony/runtime": "7.4.*",
|
||||
"symfony/security-bundle": "7.4.*",
|
||||
"symfony/serializer": "7.4.*",
|
||||
"symfony/twig-bundle": "7.4.*",
|
||||
"symfony/uid": "7.4.*",
|
||||
"symfony/validator": "7.4.*",
|
||||
"symfony/yaml": "7.4.*"
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
|
||||
5106
backend/composer.lock
generated
5106
backend/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,10 @@
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
7
backend/config/packages/api_platform.yaml
Normal file
7
backend/config/packages/api_platform.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
api_platform:
|
||||
title: Hello API Platform
|
||||
version: 1.0.0
|
||||
defaults:
|
||||
stateless: true
|
||||
cache_headers:
|
||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
53
backend/config/packages/doctrine.yaml
Normal file
53
backend/config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
server_version: '16'
|
||||
profiling_collect_backtrace: '%kernel.debug%'
|
||||
use_savepoints: true
|
||||
types:
|
||||
geometry: Jsor\Doctrine\PostGIS\Types\GeometryType
|
||||
geography: Jsor\Doctrine\PostGIS\Types\GeographyType
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
identity_generation_preferences:
|
||||
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
dbal:
|
||||
# "TEST_TOKEN" is typically set by ParaTest
|
||||
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
|
||||
|
||||
when@prod:
|
||||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
||||
6
backend/config/packages/doctrine_migrations.yaml
Normal file
6
backend/config/packages/doctrine_migrations.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
||||
enable_profiler: false
|
||||
10
backend/config/packages/nelmio_cors.yaml
Normal file
10
backend/config/packages/nelmio_cors.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
nelmio_cors:
|
||||
defaults:
|
||||
origin_regex: true
|
||||
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
allow_headers: ['Content-Type', 'Authorization']
|
||||
expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
paths:
|
||||
'^/': null
|
||||
3
backend/config/packages/property_info.yaml
Normal file
3
backend/config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
39
backend/config/packages/security.yaml
Normal file
39
backend/config/packages/security.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
# Ensure dev tools and static assets are always allowed
|
||||
pattern: ^/(_profiler|_wdt|assets|build)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: users_in_memory
|
||||
|
||||
# Activate different ways to authenticate:
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Note: Only the *first* matching rule is applied
|
||||
access_control:
|
||||
# - { path: ^/admin, roles: ROLE_ADMIN }
|
||||
# - { path: ^/profile, roles: ROLE_USER }
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# Password hashers are resource-intensive by design to ensure security.
|
||||
# In tests, it's safe to reduce their cost to improve performance.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
6
backend/config/packages/twig.yaml
Normal file
6
backend/config/packages/twig.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
11
backend/config/packages/validator.yaml
Normal file
11
backend/config/packages/validator.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
framework:
|
||||
validation:
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
||||
File diff suppressed because it is too large
Load Diff
4
backend/config/routes/api_platform.yaml
Normal file
4
backend/config/routes/api_platform.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
api_platform:
|
||||
resource: .
|
||||
type: api_platform
|
||||
prefix: /api
|
||||
3
backend/config/routes/security.yaml
Normal file
3
backend/config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
0
backend/migrations/.gitignore
vendored
Normal file
0
backend/migrations/.gitignore
vendored
Normal file
147
backend/migrations/Version20260401000000.php
Normal file
147
backend/migrations/Version20260401000000.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260401000000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Schéma initial — toutes les entités + extension PostGIS + index spatiaux';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE EXTENSION IF NOT EXISTS postgis');
|
||||
|
||||
// SargassumObservation
|
||||
$this->addSql('
|
||||
CREATE TABLE sargassum_observation (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
geometry geometry(MULTIPOLYGON, 4326) NOT NULL,
|
||||
bbox geometry(POLYGON, 4326),
|
||||
detected_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
source VARCHAR(50) NOT NULL,
|
||||
tile_id VARCHAR(100) NOT NULL,
|
||||
cloud_coverage DOUBLE PRECISION NOT NULL,
|
||||
confidence DOUBLE PRECISION NOT NULL,
|
||||
afai_mean DOUBLE PRECISION NOT NULL,
|
||||
afai_std DOUBLE PRECISION NOT NULL,
|
||||
coverage_area DOUBLE PRECISION,
|
||||
processing_version VARCHAR(50),
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_observation_detected_at ON sargassum_observation (detected_at)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_observation_source ON sargassum_observation (source)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_observation_geometry ON sargassum_observation USING GIST (geometry)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_observation_bbox ON sargassum_observation USING GIST (bbox)');
|
||||
|
||||
// SargassumForecast
|
||||
$this->addSql('
|
||||
CREATE TABLE sargassum_forecast (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
source_observation_id UUID NOT NULL,
|
||||
geometry geometry(MULTIPOLYGON, 4326) NOT NULL,
|
||||
valid_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
computed_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
model_version VARCHAR(50) NOT NULL,
|
||||
time_horizon INT NOT NULL,
|
||||
confidence DOUBLE PRECISION NOT NULL,
|
||||
drift_vector_avg geometry(POINT, 4326),
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('ALTER TABLE sargassum_forecast ADD CONSTRAINT fk_forecast_observation FOREIGN KEY (source_observation_id) REFERENCES sargassum_observation (id) ON DELETE CASCADE');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_valid_at ON sargassum_forecast (valid_at)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_horizon ON sargassum_forecast (time_horizon)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_geometry ON sargassum_forecast USING GIST (geometry)');
|
||||
|
||||
// DataIngestionJob
|
||||
$this->addSql('
|
||||
CREATE TABLE data_ingestion_job (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
source VARCHAR(50) NOT NULL,
|
||||
tile_id VARCHAR(100) NOT NULL,
|
||||
requested_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
processed_at TIMESTAMP(0) WITHOUT TIME ZONE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT \'pending\',
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
error_message TEXT,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_job_status ON data_ingestion_job (status)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_job_requested_at ON data_ingestion_job (requested_at)');
|
||||
|
||||
// CoastalPoint
|
||||
$this->addSql('
|
||||
CREATE TABLE coastal_point (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(150) NOT NULL,
|
||||
geometry geometry(POINT, 4326) NOT NULL,
|
||||
type VARCHAR(30) NOT NULL,
|
||||
region VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_type ON coastal_point (type)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_region ON coastal_point (region)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_geometry ON coastal_point USING GIST (geometry)');
|
||||
|
||||
// ImpactScore
|
||||
$this->addSql('
|
||||
CREATE TABLE impact_score (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
coastal_point_id UUID NOT NULL,
|
||||
timestamp TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
score INT NOT NULL,
|
||||
level VARCHAR(20) NOT NULL,
|
||||
distance_to_nearest_sargassum DOUBLE PRECISION,
|
||||
density_estimate DOUBLE PRECISION,
|
||||
trend VARCHAR(20),
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('ALTER TABLE impact_score ADD CONSTRAINT fk_score_coastal_point FOREIGN KEY (coastal_point_id) REFERENCES coastal_point (id) ON DELETE CASCADE');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_score_timestamp ON impact_score (timestamp)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_score_level ON impact_score (level)');
|
||||
|
||||
// UserFeedback
|
||||
$this->addSql('
|
||||
CREATE TABLE user_feedback (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
coastal_point_id UUID,
|
||||
location geometry(POINT, 4326) NOT NULL,
|
||||
timestamp TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
has_seaweed BOOLEAN NOT NULL,
|
||||
density VARCHAR(20),
|
||||
source VARCHAR(100),
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
');
|
||||
$this->addSql('ALTER TABLE user_feedback ADD CONSTRAINT fk_feedback_coastal_point FOREIGN KEY (coastal_point_id) REFERENCES coastal_point (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_feedback_timestamp ON user_feedback (timestamp)');
|
||||
$this->addSql('CREATE INDEX CONCURRENTLY idx_feedback_location ON user_feedback USING GIST (location)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sargassum_forecast DROP CONSTRAINT fk_forecast_observation');
|
||||
$this->addSql('ALTER TABLE impact_score DROP CONSTRAINT fk_score_coastal_point');
|
||||
$this->addSql('ALTER TABLE user_feedback DROP CONSTRAINT fk_feedback_coastal_point');
|
||||
|
||||
$this->addSql('DROP TABLE IF EXISTS user_feedback');
|
||||
$this->addSql('DROP TABLE IF EXISTS impact_score');
|
||||
$this->addSql('DROP TABLE IF EXISTS sargassum_forecast');
|
||||
$this->addSql('DROP TABLE IF EXISTS sargassum_observation');
|
||||
$this->addSql('DROP TABLE IF EXISTS coastal_point');
|
||||
$this->addSql('DROP TABLE IF EXISTS data_ingestion_job');
|
||||
}
|
||||
}
|
||||
0
backend/src/ApiResource/.gitignore
vendored
Normal file
0
backend/src/ApiResource/.gitignore
vendored
Normal file
0
backend/src/Entity/.gitignore
vendored
Normal file
0
backend/src/Entity/.gitignore
vendored
Normal file
71
backend/src/Entity/CoastalPoint.php
Normal file
71
backend/src/Entity/CoastalPoint.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Repository\CoastalPointRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: CoastalPointRepository::class)]
|
||||
#[ORM\Index(columns: ['type'], name: 'idx_coastal_type')]
|
||||
#[ORM\Index(columns: ['region'], name: 'idx_coastal_region')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(),
|
||||
new Get(),
|
||||
]
|
||||
)]
|
||||
class CoastalPoint
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(length: 150)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||
private mixed $geometry = null;
|
||||
|
||||
#[ORM\Column(length: 30)]
|
||||
private ?string $type = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $region = null;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: ImpactScore::class, mappedBy: 'coastalPoint')]
|
||||
private Collection $impactScores;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: UserFeedback::class, mappedBy: 'coastalPoint')]
|
||||
private Collection $feedbacks;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->impactScores = new ArrayCollection();
|
||||
$this->feedbacks = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getName(): ?string { return $this->name; }
|
||||
public function setName(string $name): static { $this->name = $name; return $this; }
|
||||
public function getGeometry(): mixed { return $this->geometry; }
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
public function getType(): ?string { return $this->type; }
|
||||
public function setType(string $type): static { $this->type = $type; return $this; }
|
||||
public function getRegion(): ?string { return $this->region; }
|
||||
public function setRegion(string $region): static { $this->region = $region; return $this; }
|
||||
|
||||
/** @return Collection<int, ImpactScore> */
|
||||
public function getImpactScores(): Collection { return $this->impactScores; }
|
||||
|
||||
/** @return Collection<int, UserFeedback> */
|
||||
public function getFeedbacks(): Collection { return $this->feedbacks; }
|
||||
}
|
||||
61
backend/src/Entity/DataIngestionJob.php
Normal file
61
backend/src/Entity/DataIngestionJob.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\DataIngestionJobRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DataIngestionJobRepository::class)]
|
||||
#[ORM\Index(columns: ['status'], name: 'idx_job_status')]
|
||||
#[ORM\Index(columns: ['requested_at'], name: 'idx_job_requested_at')]
|
||||
class DataIngestionJob
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $source = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $tileId = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $requestedAt = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $processedAt = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private string $status = 'pending';
|
||||
|
||||
#[ORM\Column]
|
||||
private int $retryCount = 0;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $errorMessage = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->requestedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getSource(): ?string { return $this->source; }
|
||||
public function setSource(string $source): static { $this->source = $source; return $this; }
|
||||
public function getTileId(): ?string { return $this->tileId; }
|
||||
public function setTileId(string $tileId): static { $this->tileId = $tileId; return $this; }
|
||||
public function getRequestedAt(): ?\DateTimeImmutable { return $this->requestedAt; }
|
||||
public function getProcessedAt(): ?\DateTimeImmutable { return $this->processedAt; }
|
||||
public function setProcessedAt(?\DateTimeImmutable $processedAt): static { $this->processedAt = $processedAt; return $this; }
|
||||
public function getStatus(): string { return $this->status; }
|
||||
public function setStatus(string $status): static { $this->status = $status; return $this; }
|
||||
public function getRetryCount(): int { return $this->retryCount; }
|
||||
public function incrementRetry(): static { $this->retryCount++; return $this; }
|
||||
public function getErrorMessage(): ?string { return $this->errorMessage; }
|
||||
public function setErrorMessage(?string $errorMessage): static { $this->errorMessage = $errorMessage; return $this; }
|
||||
}
|
||||
72
backend/src/Entity/ImpactScore.php
Normal file
72
backend/src/Entity/ImpactScore.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Repository\ImpactScoreRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ImpactScoreRepository::class)]
|
||||
#[ORM\Index(columns: ['timestamp'], name: 'idx_score_timestamp')]
|
||||
#[ORM\Index(columns: ['level'], name: 'idx_score_level')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(),
|
||||
new Get(),
|
||||
]
|
||||
)]
|
||||
class ImpactScore
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'impactScores')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?CoastalPoint $coastalPoint = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $timestamp = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $score = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private ?string $level = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $distanceToNearestSargassum = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $densityEstimate = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $trend = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->timestamp = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getTimestamp(): ?\DateTimeImmutable { return $this->timestamp; }
|
||||
public function setTimestamp(\DateTimeImmutable $timestamp): static { $this->timestamp = $timestamp; return $this; }
|
||||
public function getScore(): ?int { return $this->score; }
|
||||
public function setScore(int $score): static { $this->score = $score; return $this; }
|
||||
public function getLevel(): ?string { return $this->level; }
|
||||
public function setLevel(string $level): static { $this->level = $level; return $this; }
|
||||
public function getDistanceToNearestSargassum(): ?float { return $this->distanceToNearestSargassum; }
|
||||
public function setDistanceToNearestSargassum(?float $d): static { $this->distanceToNearestSargassum = $d; return $this; }
|
||||
public function getDensityEstimate(): ?float { return $this->densityEstimate; }
|
||||
public function setDensityEstimate(?float $d): static { $this->densityEstimate = $d; return $this; }
|
||||
public function getTrend(): ?string { return $this->trend; }
|
||||
public function setTrend(?string $trend): static { $this->trend = $trend; return $this; }
|
||||
}
|
||||
82
backend/src/Entity/SargassumForecast.php
Normal file
82
backend/src/Entity/SargassumForecast.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Repository\SargassumForecastRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: SargassumForecastRepository::class)]
|
||||
#[ORM\Index(columns: ['valid_at'], name: 'idx_forecast_valid_at')]
|
||||
#[ORM\Index(columns: ['time_horizon'], name: 'idx_forecast_horizon')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(),
|
||||
new Get(),
|
||||
]
|
||||
)]
|
||||
class SargassumForecast
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'forecasts')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?SargassumObservation $sourceObservation = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'MULTIPOLYGON', 'srid' => 4326])]
|
||||
private mixed $geometry = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $validAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $computedAt = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $modelVersion = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $timeHorizon = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $confidence = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', nullable: true, options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||
private mixed $driftVectorAvg = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->computedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getSourceObservation(): ?SargassumObservation { return $this->sourceObservation; }
|
||||
public function setSourceObservation(?SargassumObservation $sourceObservation): static { $this->sourceObservation = $sourceObservation; return $this; }
|
||||
public function getGeometry(): mixed { return $this->geometry; }
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
public function getValidAt(): ?\DateTimeImmutable { return $this->validAt; }
|
||||
public function setValidAt(\DateTimeImmutable $validAt): static { $this->validAt = $validAt; return $this; }
|
||||
public function getComputedAt(): ?\DateTimeImmutable { return $this->computedAt; }
|
||||
public function setComputedAt(\DateTimeImmutable $computedAt): static { $this->computedAt = $computedAt; return $this; }
|
||||
public function getModelVersion(): ?string { return $this->modelVersion; }
|
||||
public function setModelVersion(string $modelVersion): static { $this->modelVersion = $modelVersion; return $this; }
|
||||
public function getTimeHorizon(): ?int { return $this->timeHorizon; }
|
||||
public function setTimeHorizon(int $timeHorizon): static { $this->timeHorizon = $timeHorizon; return $this; }
|
||||
public function getConfidence(): ?float { return $this->confidence; }
|
||||
public function setConfidence(float $confidence): static { $this->confidence = $confidence; return $this; }
|
||||
public function getDriftVectorAvg(): mixed { return $this->driftVectorAvg; }
|
||||
public function setDriftVectorAvg(mixed $driftVectorAvg): static { $this->driftVectorAvg = $driftVectorAvg; return $this; }
|
||||
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
|
||||
}
|
||||
105
backend/src/Entity/SargassumObservation.php
Normal file
105
backend/src/Entity/SargassumObservation.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Repository\SargassumObservationRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Jsor\Doctrine\PostGIS\Types\PostGISType;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: SargassumObservationRepository::class)]
|
||||
#[ORM\Index(columns: ['detected_at'], name: 'idx_observation_detected_at')]
|
||||
#[ORM\Index(columns: ['source'], name: 'idx_observation_source')]
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(),
|
||||
new Get(),
|
||||
]
|
||||
)]
|
||||
class SargassumObservation
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'MULTIPOLYGON', 'srid' => 4326])]
|
||||
private mixed $geometry = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', nullable: true, options: ['geometry_type' => 'POLYGON', 'srid' => 4326])]
|
||||
private mixed $bbox = null;
|
||||
|
||||
#[ORM\Column(immutable: true)]
|
||||
private ?\DateTimeImmutable $detectedAt = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $source = null;
|
||||
|
||||
#[ORM\Column(length: 100)]
|
||||
private ?string $tileId = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $cloudCoverage = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $confidence = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $afaiMean = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?float $afaiStd = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $coverageArea = null;
|
||||
|
||||
#[ORM\Column(length: 50, nullable: true)]
|
||||
private ?string $processingVersion = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: SargassumForecast::class, mappedBy: 'sourceObservation')]
|
||||
private Collection $forecasts;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->forecasts = new ArrayCollection();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getGeometry(): mixed { return $this->geometry; }
|
||||
public function setGeometry(mixed $geometry): static { $this->geometry = $geometry; return $this; }
|
||||
public function getBbox(): mixed { return $this->bbox; }
|
||||
public function setBbox(mixed $bbox): static { $this->bbox = $bbox; return $this; }
|
||||
public function getDetectedAt(): ?\DateTimeImmutable { return $this->detectedAt; }
|
||||
public function setDetectedAt(\DateTimeImmutable $detectedAt): static { $this->detectedAt = $detectedAt; return $this; }
|
||||
public function getSource(): ?string { return $this->source; }
|
||||
public function setSource(string $source): static { $this->source = $source; return $this; }
|
||||
public function getTileId(): ?string { return $this->tileId; }
|
||||
public function setTileId(string $tileId): static { $this->tileId = $tileId; return $this; }
|
||||
public function getCloudCoverage(): ?float { return $this->cloudCoverage; }
|
||||
public function setCloudCoverage(float $cloudCoverage): static { $this->cloudCoverage = $cloudCoverage; return $this; }
|
||||
public function getConfidence(): ?float { return $this->confidence; }
|
||||
public function setConfidence(float $confidence): static { $this->confidence = $confidence; return $this; }
|
||||
public function getAfaiMean(): ?float { return $this->afaiMean; }
|
||||
public function setAfaiMean(float $afaiMean): static { $this->afaiMean = $afaiMean; return $this; }
|
||||
public function getAfaiStd(): ?float { return $this->afaiStd; }
|
||||
public function setAfaiStd(float $afaiStd): static { $this->afaiStd = $afaiStd; return $this; }
|
||||
public function getCoverageArea(): ?float { return $this->coverageArea; }
|
||||
public function setCoverageArea(?float $coverageArea): static { $this->coverageArea = $coverageArea; return $this; }
|
||||
public function getProcessingVersion(): ?string { return $this->processingVersion; }
|
||||
public function setProcessingVersion(?string $processingVersion): static { $this->processingVersion = $processingVersion; return $this; }
|
||||
public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; }
|
||||
|
||||
/** @return Collection<int, SargassumForecast> */
|
||||
public function getForecasts(): Collection { return $this->forecasts; }
|
||||
}
|
||||
57
backend/src/Entity/UserFeedback.php
Normal file
57
backend/src/Entity/UserFeedback.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserFeedbackRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Types\UuidType;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: UserFeedbackRepository::class)]
|
||||
#[ORM\Index(columns: ['timestamp'], name: 'idx_feedback_timestamp')]
|
||||
class UserFeedback
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: UuidType::NAME, unique: true)]
|
||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
|
||||
private ?Uuid $id = null;
|
||||
|
||||
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'POINT', 'srid' => 4326])]
|
||||
private mixed $location = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'feedbacks')]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?CoastalPoint $coastalPoint = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $timestamp = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $hasSeaweed = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $density = null;
|
||||
|
||||
#[ORM\Column(length: 100, nullable: true)]
|
||||
private ?string $source = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->timestamp = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?Uuid { return $this->id; }
|
||||
public function getLocation(): mixed { return $this->location; }
|
||||
public function setLocation(mixed $location): static { $this->location = $location; return $this; }
|
||||
public function getCoastalPoint(): ?CoastalPoint { return $this->coastalPoint; }
|
||||
public function setCoastalPoint(?CoastalPoint $coastalPoint): static { $this->coastalPoint = $coastalPoint; return $this; }
|
||||
public function getTimestamp(): ?\DateTimeImmutable { return $this->timestamp; }
|
||||
public function setTimestamp(\DateTimeImmutable $timestamp): static { $this->timestamp = $timestamp; return $this; }
|
||||
public function getHasSeaweed(): ?bool { return $this->hasSeaweed; }
|
||||
public function setHasSeaweed(bool $hasSeaweed): static { $this->hasSeaweed = $hasSeaweed; return $this; }
|
||||
public function getDensity(): ?string { return $this->density; }
|
||||
public function setDensity(?string $density): static { $this->density = $density; return $this; }
|
||||
public function getSource(): ?string { return $this->source; }
|
||||
public function setSource(?string $source): static { $this->source = $source; return $this; }
|
||||
}
|
||||
0
backend/src/Repository/.gitignore
vendored
Normal file
0
backend/src/Repository/.gitignore
vendored
Normal file
15
backend/src/Repository/CoastalPointRepository.php
Normal file
15
backend/src/Repository/CoastalPointRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\CoastalPoint;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class CoastalPointRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, CoastalPoint::class);
|
||||
}
|
||||
}
|
||||
15
backend/src/Repository/DataIngestionJobRepository.php
Normal file
15
backend/src/Repository/DataIngestionJobRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\DataIngestionJob;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class DataIngestionJobRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, DataIngestionJob::class);
|
||||
}
|
||||
}
|
||||
15
backend/src/Repository/ImpactScoreRepository.php
Normal file
15
backend/src/Repository/ImpactScoreRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\ImpactScore;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class ImpactScoreRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ImpactScore::class);
|
||||
}
|
||||
}
|
||||
15
backend/src/Repository/SargassumForecastRepository.php
Normal file
15
backend/src/Repository/SargassumForecastRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\SargassumForecast;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class SargassumForecastRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SargassumForecast::class);
|
||||
}
|
||||
}
|
||||
15
backend/src/Repository/SargassumObservationRepository.php
Normal file
15
backend/src/Repository/SargassumObservationRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\SargassumObservation;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class SargassumObservationRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SargassumObservation::class);
|
||||
}
|
||||
}
|
||||
15
backend/src/Repository/UserFeedbackRepository.php
Normal file
15
backend/src/Repository/UserFeedbackRepository.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\UserFeedback;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class UserFeedbackRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserFeedback::class);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,75 @@
|
||||
{
|
||||
"api-platform/symfony": {
|
||||
"version": "4.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "4.0",
|
||||
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/api_platform.yaml",
|
||||
"config/routes/api_platform.yaml",
|
||||
"src/ApiResource/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/deprecations": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "fdd756167454623e21f1d769c5b814b243782a67"
|
||||
}
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.18",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.13",
|
||||
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
"src/Entity/.gitignore",
|
||||
"src/Repository/.gitignore"
|
||||
]
|
||||
},
|
||||
"doctrine/doctrine-migrations-bundle": {
|
||||
"version": "3.7",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine_migrations.yaml",
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"jsor/doctrine-postgis": {
|
||||
"version": "2.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.7",
|
||||
"ref": "211979f7917bf6edb8fc5006a17b2e84e8e84d50"
|
||||
}
|
||||
},
|
||||
"nelmio/cors-bundle": {
|
||||
"version": "2.6",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.5",
|
||||
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/nelmio_cors.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
@@ -44,6 +115,18 @@
|
||||
".editorconfig"
|
||||
]
|
||||
},
|
||||
"symfony/property-info": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.3",
|
||||
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/property_info.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
@@ -56,5 +139,52 @@
|
||||
"config/packages/routing.yaml",
|
||||
"config/routes.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/security-bundle": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.4",
|
||||
"ref": "c42fee7802181cdd50f61b8622715829f5d2335c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/security.yaml",
|
||||
"config/routes/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "f250159ebe99153d0c640a3e7742876fc7453f2c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/twig.yaml",
|
||||
"templates/base.html.twig"
|
||||
]
|
||||
},
|
||||
"symfony/uid": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
||||
}
|
||||
},
|
||||
"symfony/validator": {
|
||||
"version": "7.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/validator.yaml"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
23
backend/templates/base.html.twig
Normal file
23
backend/templates/base.html.twig
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% endblock %}
|
||||
|
||||
{% set frankenphpHotReload = app.request.server.get('FRANKENPHP_HOT_RELOAD') %}
|
||||
{% if frankenphpHotReload %}
|
||||
<meta name="frankenphp-hot-reload:url" content="{{ frankenphpHotReload }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
|
||||
{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user