[backend] from mariadb to postgres
This commit is contained in:
@@ -48,8 +48,8 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
#
|
||||
# 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://root:root@mariadb:3306/app?serverVersion=Mariadb-10.11.11&charset=utf8mb4"
|
||||
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
#DATABASE_URL="mysql://root:root@mariadb:3306/app?serverVersion=Mariadb-10.11.11&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SOURCES=(
|
||||
"africanewsrdc.net"
|
||||
"angazainstitute.ac.cd"
|
||||
"b-onetv.cd"
|
||||
"bukavufm.com"
|
||||
"changement7.net"
|
||||
"congoactu.net"
|
||||
"congoindependant.com"
|
||||
"congoquotidien.com"
|
||||
"cumulard.cd"
|
||||
"environews-rdc.net"
|
||||
"freemediardc.info"
|
||||
"geopolismagazine.org"
|
||||
"habarirdc.net"
|
||||
"infordc.com"
|
||||
"kilalopress.net"
|
||||
"laprosperiteonline.net"
|
||||
"laprunellerdc.cd"
|
||||
"lesmedias.net"
|
||||
"lesvolcansnews.net"
|
||||
"netic-news.net"
|
||||
"objectif-infos.cd"
|
||||
"scooprdc.net"
|
||||
"journaldekinshasa.com"
|
||||
"lepotentiel.cd"
|
||||
"acturdc.com"
|
||||
"matininfos.net"
|
||||
)
|
||||
BASE_CMD="/usr/bin/php /var/www/html/news.devscast.tech/bin/console app:crawl"
|
||||
LOG_DIR="/var/www/html/news.devscast.tech/var"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
rm -f "${LOG_DIR}"/*.log
|
||||
|
||||
for SOURCE in "${SOURCES[@]}"; do
|
||||
LOG_FILE="${LOG_DIR}/crawling-${SOURCE}.log"
|
||||
nohup $BASE_CMD "$SOURCE" -vvv > "$LOG_FILE" 2>&1 &
|
||||
done
|
||||
|
||||
echo "All crawlers started in the background."
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CONSOLE_BIN="$PROJECT_ROOT/bin/console"
|
||||
APPLY_MIGRATIONS=0
|
||||
PGLOADER_EXTRA_ARGS=()
|
||||
SOURCE_DSN=""
|
||||
TARGET_DSN=""
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: $(basename "$0") [options] <mariadb_dsn> <postgres_dsn>
|
||||
|
||||
Environment variables:
|
||||
SOURCE_DATABASE_URL MariaDB connection string (fallback for <mariadb_dsn>)
|
||||
TARGET_DATABASE_URL PostgreSQL connection string (fallback for <postgres_dsn>)
|
||||
|
||||
Options:
|
||||
--apply-migrations Run Doctrine migrations after data transfer (uses local PHP runtime)
|
||||
--pgloader-arg ARG Append a raw argument when calling pgloader (can be provided multiple times)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
SOURCE_DATABASE_URL="mysql://user:pass@host/db" \\
|
||||
TARGET_DATABASE_URL="postgresql://user:pass@host/db" \\
|
||||
$(basename "$0") --apply-migrations
|
||||
|
||||
$(basename "$0") --pgloader-arg "--with no schema" \\
|
||||
mysql://root:root@127.0.0.1:3306/app \\
|
||||
postgresql://app:secret@127.0.0.1:5432/app
|
||||
USAGE
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[migration] %s\n' "$*"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--apply-migrations)
|
||||
APPLY_MIGRATIONS=1
|
||||
;;
|
||||
--pgloader-arg)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "--pgloader-arg requires a value" >&2
|
||||
exit 1
|
||||
fi
|
||||
PGLOADER_EXTRA_ARGS+=("$1")
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$SOURCE_DSN" ]]; then
|
||||
SOURCE_DSN="$1"
|
||||
elif [[ -z "$TARGET_DSN" ]]; then
|
||||
TARGET_DSN="$1"
|
||||
else
|
||||
PGLOADER_EXTRA_ARGS+=("$1")
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -z "$SOURCE_DSN" ]]; then
|
||||
SOURCE_DSN="${SOURCE_DATABASE_URL:-}"
|
||||
fi
|
||||
if [[ -z "$TARGET_DSN" ]]; then
|
||||
TARGET_DSN="${TARGET_DATABASE_URL:-}"
|
||||
fi
|
||||
|
||||
if [[ -z "$SOURCE_DSN" || -z "$TARGET_DSN" ]]; then
|
||||
echo "Source and target DSNs are required (pass as arguments or set SOURCE_DATABASE_URL/TARGET_DATABASE_URL)." >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pgloader >/dev/null 2>&1; then
|
||||
echo "pgloader is required but not available on PATH. Install it (https://pgloader.readthedocs.io) and retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Starting data copy"
|
||||
log " source : $SOURCE_DSN"
|
||||
log " target : $TARGET_DSN"
|
||||
|
||||
pgloader "${PGLOADER_EXTRA_ARGS[@]}" "$SOURCE_DSN" "$TARGET_DSN"
|
||||
|
||||
log "Data copy finished"
|
||||
|
||||
if [[ $APPLY_MIGRATIONS -eq 1 ]]; then
|
||||
if ! command -v php >/dev/null 2>&1; then
|
||||
echo "PHP CLI is required to run Doctrine migrations." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$CONSOLE_BIN" ]]; then
|
||||
echo "Symfony console not found at $CONSOLE_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Running Doctrine migrations"
|
||||
(cd "$PROJECT_ROOT" && php "$CONSOLE_BIN" doctrine:migrations:migrate --no-interaction)
|
||||
fi
|
||||
|
||||
log "Migration helper completed"
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SOURCES=("7sur7.cd" "actualite.cd" "radiookapi.net" "mediacongo.net" "newscd.net")
|
||||
BASE_CMD="/usr/bin/php /var/www/html/news.devscast.tech/bin/console app:open-graph"
|
||||
LOG_DIR="/var/www/html/news.devscast.tech/var"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
rm -f "${LOG_DIR}"/*.log
|
||||
|
||||
for SOURCE in "${SOURCES[@]}"; do
|
||||
LOG_FILE="${LOG_DIR}/${SOURCE}.log"
|
||||
nohup $BASE_CMD "$SOURCE" -vvv --no-interaction > "$LOG_FILE" 2>&1 &
|
||||
done
|
||||
|
||||
echo "All open graph crawlers started in the background."
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ps aux | grep '/bin/console app:' | grep -v grep | awk '{print $2}' | xargs -r kill -9
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SOURCES=("7sur7.cd" "actualite.cd" "radiookapi.net" "mediacongo.net" "newscd.net")
|
||||
BASE_CMD="/usr/bin/php /var/www/html/news.devscast.tech/bin/console app:update"
|
||||
LOG_DIR="/var/www/html/news.devscast.tech/var"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
rm -f "${LOG_DIR}"/*.log
|
||||
|
||||
for SOURCE in "${SOURCES[@]}"; do
|
||||
if [[ "$SOURCE" == "7sur7.cd" ]]; then
|
||||
CATEGORIES=("politique" "economie" "culture" "sport" "societe")
|
||||
|
||||
for CATEGORY in "${CATEGORIES[@]}"; do
|
||||
LOG_FILE="${LOG_DIR}/${SOURCE}.${CATEGORY}.log"
|
||||
nohup $BASE_CMD "$SOURCE" --direction=forward -vvv --category="$CATEGORY" > "$LOG_FILE" 2>&1 &
|
||||
done
|
||||
else
|
||||
LOG_FILE="${LOG_DIR}/${SOURCE}.log"
|
||||
nohup $BASE_CMD "$SOURCE" --direction=forward -vvv > "$LOG_FILE" 2>&1 &
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All crawlers started in the background."
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SOURCES=("7sur7.cd" "actualite.cd" "radiookapi.net" "mediacongo.net" "newscd.net")
|
||||
BASE_CMD="/usr/bin/php /var/www/html/news.devscast.tech/bin/console app:update"
|
||||
LOG_DIR="/var/www/html/news.devscast.tech/var"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
rm -f "${LOG_DIR}"/*.log
|
||||
|
||||
for SOURCE in "${SOURCES[@]}"; do
|
||||
if [[ "$SOURCE" == "7sur7.cd" ]]; then
|
||||
CATEGORIES=("politique" "economie" "culture" "sport" "societe")
|
||||
|
||||
for CATEGORY in "${CATEGORIES[@]}"; do
|
||||
LOG_FILE="${LOG_DIR}/${SOURCE}.${CATEGORY}.log"
|
||||
$BASE_CMD "$SOURCE" --direction=forward -vvv --category="$CATEGORY" 2>&1 | tee "$LOG_FILE"
|
||||
done
|
||||
else
|
||||
LOG_FILE="${LOG_DIR}/${SOURCE}.log"
|
||||
$BASE_CMD "$SOURCE" --direction=forward -vvv 2>&1 | tee "$LOG_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All crawlers finished."
|
||||
@@ -6,6 +6,7 @@
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*",
|
||||
"cweagans/composer-patches": "^1.7.3",
|
||||
"doctrine/dbal": "^3.9.4",
|
||||
@@ -17,6 +18,7 @@
|
||||
"knplabs/knp-paginator-bundle": "^6.7",
|
||||
"league/csv": "^9.21",
|
||||
"lexik/jwt-authentication-bundle": "^3.1",
|
||||
"martin-georgiev/postgresql-for-doctrine": "^3.5",
|
||||
"matomo/device-detector": "^6.4",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.1",
|
||||
@@ -43,8 +45,7 @@
|
||||
"symfony/yaml": "7.2.*",
|
||||
"twig/extra-bundle": "^2.12|^3.19",
|
||||
"twig/twig": "^2.12|^3.19",
|
||||
"webmozart/assert": "^1.11",
|
||||
"ext-dom": "*"
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"behat/behat": "^3.22",
|
||||
|
||||
Generated
+87
-3
@@ -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": "b542c5edda5afefd4907959fe98f9e10",
|
||||
"content-hash": "2629764bb519b7236ab2236acc4fee1d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
@@ -2139,6 +2139,90 @@
|
||||
],
|
||||
"time": "2025-01-06T16:34:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "martin-georgiev/postgresql-for-doctrine",
|
||||
"version": "v3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/martin-georgiev/postgresql-for-doctrine.git",
|
||||
"reference": "5d1621e48edd7c7306cf2b9e73e374727867d6af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/martin-georgiev/postgresql-for-doctrine/zipball/5d1621e48edd7c7306cf2b9e73e374727867d6af",
|
||||
"reference": "5d1621e48edd7c7306cf2b9e73e374727867d6af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/dbal": "~2.10||~3.0||~4.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"deptrac/deptrac": "^4.0",
|
||||
"doctrine/orm": "~2.14||~3.0",
|
||||
"ekino/phpstan-banned-code": "^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.87.1",
|
||||
"phpstan/phpstan": "^2.1.22",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-doctrine": "^2.0.4",
|
||||
"phpstan/phpstan-phpunit": "^2.0.7",
|
||||
"phpunit/phpunit": "^10.5.53",
|
||||
"rector/rector": "^2.1.5",
|
||||
"symfony/cache": "^6.4||^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/orm": "~2.14||~3.0",
|
||||
"php": "^8.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MartinGeorgiev\\": "src/MartinGeorgiev/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Martin Georgiev",
|
||||
"email": "martin.georgiev@gmail.com",
|
||||
"role": "author"
|
||||
}
|
||||
],
|
||||
"description": "Adds PostgreSQL enhancements to Doctrine. Provides support for JSON, JSONB and some array data types. Provides functions, operators and common expressions used when working with JSON data, arrays and features related to text search.",
|
||||
"keywords": [
|
||||
"array data types",
|
||||
"dbal",
|
||||
"doctrine",
|
||||
"json",
|
||||
"jsonb",
|
||||
"martin georgiev",
|
||||
"postgres",
|
||||
"postgresql",
|
||||
"text search",
|
||||
"tsvector"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/martin-georgiev/postgresql-for-doctrine/issues",
|
||||
"source": "https://github.com/martin-georgiev/postgresql-for-doctrine/tree/v3.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/martin-georgiev",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/martin-georgiev",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-12T10:54:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
@@ -11540,8 +11624,8 @@
|
||||
"platform": {
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-dom": "*"
|
||||
"ext-dom": "*",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
|
||||
@@ -11,17 +11,12 @@
|
||||
<id name="id" type="article_id">
|
||||
<generator strategy="NONE" />
|
||||
</id>
|
||||
<indexes>
|
||||
<index fields="hash" />
|
||||
<index fields="publishedAt" />
|
||||
<index name="IDX_PUBLISHED_AT_ID" fields="publishedAt, id" />
|
||||
</indexes>
|
||||
|
||||
<field name="title" length="1024" />
|
||||
<field name="body" type="text" />
|
||||
<embedded name="link" class="Basango\Aggregator\Domain\Model\ValueObject\Link" use-column-prefix="false" />
|
||||
<field name="hash" length="32" />
|
||||
<field name="categories" nullable="true" />
|
||||
<field name="categories" type="text[]" nullable="true" />
|
||||
|
||||
<many-to-one field="source" target-entity="Basango\Aggregator\Domain\Model\Entity\Source">
|
||||
<join-column nullable="false" on-delete="CASCADE" />
|
||||
@@ -39,13 +34,13 @@
|
||||
<field name="image"
|
||||
insertable="false"
|
||||
updatable="false"
|
||||
column-definition="VARCHAR(1024) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.image'))) STORED"
|
||||
column-definition="VARCHAR(1024) GENERATED ALWAYS AS ((metadata->>'image')) STORED"
|
||||
/>
|
||||
<field
|
||||
name="excerpt"
|
||||
insertable="false"
|
||||
updatable="false"
|
||||
column-definition="VARCHAR(255) GENERATED ALWAYS AS (CONCAT(LEFT(body, 200), '...')) STORED"
|
||||
column-definition="VARCHAR(255) GENERATED ALWAYS AS ((left(body, 200) || '...')) STORED"
|
||||
/>
|
||||
|
||||
<field name="publishedAt" type="datetime_immutable" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</id>
|
||||
|
||||
<field name="url" />
|
||||
<field name="name" unique="true" />
|
||||
<field name="name" />
|
||||
|
||||
<embedded name="credibility" class="Basango\Aggregator\Domain\Model\ValueObject\Scoring\Credibility" use-column-prefix="false" />
|
||||
<field name="displayName" nullable="true" />
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<generator strategy="NONE"/>
|
||||
</id>
|
||||
|
||||
<!-- fetching eager cause will always need to check the user's id whenever we deal with a bookmark -->
|
||||
<many-to-one field="user" target-entity="Basango\IdentityAndAccess\Domain\Model\Entity\User" fetch="EAGER">
|
||||
<join-column nullable="false" on-delete="CASCADE" />
|
||||
</many-to-one>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<join-column nullable="false" on-delete="CASCADE" />
|
||||
</many-to-one>
|
||||
|
||||
<field name="ipAddress" nullable="true" length="15" />
|
||||
<field name="ipAddress" type="inet" nullable="true" length="15" />
|
||||
<embedded name="device" class="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\Device" />
|
||||
<embedded name="location" class="Basango\SharedKernel\Domain\Model\ValueObject\Tracking\GeoLocation" />
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
<field name="name"/>
|
||||
<field name="email" type="email" />
|
||||
<field name="password" length="512" />
|
||||
<embedded name="roles" class="Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles" use-column-prefix="false" />
|
||||
|
||||
<field name="isLocked" type="boolean">
|
||||
<options>
|
||||
<option name="default">0</option>
|
||||
<option name="default">false</option>
|
||||
</options>
|
||||
</field>
|
||||
<field name="isConfirmed" type="boolean">
|
||||
<options>
|
||||
<option name="default">0</option>
|
||||
<option name="default">false</option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
<embedded name="roles" class="Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles" use-column-prefix="false" />
|
||||
|
||||
<field name="createdAt" type="datetime_immutable" />
|
||||
<field name="updatedAt" type="datetime_immutable" nullable="true" />
|
||||
</entity>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<embeddable name="Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles">
|
||||
<field name="roles" type="json"/>
|
||||
<field name="roles" type="jsonb"/>
|
||||
</embeddable>
|
||||
</doctrine-mapping>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<field name="device" type="string" nullable="true" />
|
||||
<field name="isBot" type="boolean" nullable="false" >
|
||||
<options>
|
||||
<option name="default">0</option>
|
||||
<option name="default">false</option>
|
||||
</options>
|
||||
</field>
|
||||
</embeddable>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20241008030057.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20241008030057 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add article table';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE article (id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', title VARCHAR(255) NOT NULL, body LONGTEXT NOT NULL, link VARCHAR(255) NOT NULL, source VARCHAR(255) NOT NULL, categories VARCHAR(255) DEFAULT NULL, published_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', crawled_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_23A0E6636AC99F1 (link), INDEX IDX_23A0E665F8A7F73 (source), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE article');
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20241010041217.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20241010041217 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'remove unique index on article link';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX UNIQ_23A0E6636AC99F1 ON article');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_23A0E6636AC99F1 ON article (link)');
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20241010041432.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20241010041432 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'increase link column size';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE article CHANGE link link VARCHAR(2048) NOT NULL');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE article CHANGE link link VARCHAR(255) NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20241010042241.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20241010042241 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add hash column to article';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE article ADD hash VARCHAR(32) NOT NULL');
|
||||
$this->addSql('UPDATE article SET hash = MD5(link)');
|
||||
$this->addSql('CREATE INDEX IDX_23A0E66D1B862B8 ON article (hash)');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX IDX_23A0E66D1B862B8 ON article');
|
||||
$this->addSql('ALTER TABLE article DROP hash');
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250314140326.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250314140326 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add user table';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE user (id BINARY(16) NOT NULL COMMENT \'(DC2Type:user_id)\', name VARCHAR(255) NOT NULL, email VARCHAR(500) NOT NULL, password VARCHAR(4098) NOT NULL, created_at DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', updated_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password_reset_token_token VARCHAR(255) DEFAULT NULL, password_reset_token_generated_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE user');
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250314145254.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250314145254 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add refresh token';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE refresh_tokens (id INT AUTO_INCREMENT NOT NULL, refresh_token VARCHAR(128) NOT NULL, username VARCHAR(255) NOT NULL, valid DATETIME NOT NULL, UNIQUE INDEX UNIQ_9BACE7E1C74F2195 (refresh_token), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE refresh_tokens');
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250315154326 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'fix password_reset_token_generated_at column type';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE user CHANGE password_reset_token_generated_at password_reset_token_generated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\'');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE user CHANGE password_reset_token_generated_at password_reset_token_generated_at DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250423183329.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250423183329 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'refactoring identity and access module';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE login_attempt (id BINARY(16) NOT NULL COMMENT '(DC2Type:login_attempt_id)', user_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', INDEX IDX_8C11C1BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE login_history (id BINARY(16) NOT NULL COMMENT '(DC2Type:login_history_id)', user_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', device_operating_system VARCHAR(255) DEFAULT NULL, device_client VARCHAR(255) DEFAULT NULL, device_device VARCHAR(255) DEFAULT NULL, device_is_bot TINYINT(1) DEFAULT 0 NOT NULL, location_time_zone VARCHAR(255) DEFAULT NULL, location_longitude DOUBLE PRECISION DEFAULT NULL, location_latitude DOUBLE PRECISION DEFAULT NULL, location_accuracy_radius INT DEFAULT NULL, INDEX IDX_37976E36A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE verification_token (id BINARY(16) NOT NULL COMMENT '(DC2Type:verification_token_id)', user_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', purpose VARCHAR(255) NOT NULL, created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', token_token VARCHAR(255) DEFAULT NULL, INDEX IDX_C1CC006BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_attempt ADD CONSTRAINT FK_8C11C1BA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history ADD CONSTRAINT FK_37976E36A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token ADD CONSTRAINT FK_C1CC006BA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE id id BINARY(16) NOT NULL COMMENT '(DC2Type:article_id)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user ADD is_locked TINYINT(1) DEFAULT 0 NOT NULL, ADD is_confirmed TINYINT(1) DEFAULT 0 NOT NULL, DROP password_reset_token_token, DROP password_reset_token_generated_at
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
UPDATE user SET is_locked = 0, is_confirmed = 1
|
||||
SQL);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_attempt DROP FOREIGN KEY FK_8C11C1BA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history DROP FOREIGN KEY FK_37976E36A76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token DROP FOREIGN KEY FK_C1CC006BA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE login_attempt
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE login_history
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE verification_token
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user ADD password_reset_token_token VARCHAR(255) DEFAULT NULL, ADD password_reset_token_generated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)', DROP is_locked, DROP is_confirmed
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE id id BINARY(16) NOT NULL COMMENT '(DC2Type:uuid)'
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250423185205.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250423185205 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add ip to login history';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history ADD ip VARCHAR(45) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history DROP ip
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250423190105 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'remove column prefix';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE token_token token VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE token token_token VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* Class Version20250501041246.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250501041246 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'introduce new source entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE source (name VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)', bias VARCHAR(255) DEFAULT 'neutral' NOT NULL, reliability VARCHAR(255) DEFAULT 'reliable' NOT NULL, transparency VARCHAR(255) DEFAULT 'medium' NOT NULL, PRIMARY KEY(name)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)', ADD bias VARCHAR(255) DEFAULT 'neutral' NOT NULL, ADD reliability VARCHAR(255) DEFAULT 'reliable' NOT NULL, ADD transparency VARCHAR(255) DEFAULT 'medium' NOT NULL
|
||||
SQL);
|
||||
|
||||
$this->write("Fetching sources from crawled articles...");
|
||||
$sources = $this->connection
|
||||
->executeQuery("SELECT DISTINCT source FROM article WHERE source IS NOT NULL")
|
||||
->fetchFirstColumn();
|
||||
|
||||
$this->write(sprintf("%d unique sources found", count($sources)));
|
||||
|
||||
foreach ($sources as $sourceName) {
|
||||
$this->addSql("INSERT INTO source (name, url) VALUES (:name, :url)", [
|
||||
"name" => $sourceName,
|
||||
"url" => 'https://' . $sourceName
|
||||
]);
|
||||
}
|
||||
$this->addSql("UPDATE article SET categories = LOWER(categories)");
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD CONSTRAINT FK_23A0E665F8A7F73 FOREIGN KEY (source) REFERENCES source (name) ON DELETE RESTRICT
|
||||
SQL);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP FOREIGN KEY FK_23A0E665F8A7F73
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE source
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP updated_at, DROP bias, DROP reliability, DROP transparency
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250501041950.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250501041950 extends AbstractMigration
|
||||
{
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'increase title length';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE title title VARCHAR(2048) NOT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE title title VARCHAR(255) NOT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250501143015.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250501143015 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add sentiment score';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD sentiment VARCHAR(255) DEFAULT 'neutral' NOT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP sentiment
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250502181706.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250502181706 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add metadata column to article table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD metadata JSON DEFAULT NULL COMMENT '(DC2Type:open_graph)'
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP metadata
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250502184108.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250502184108 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'relative url to absolue';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('UPDATE article SET link = CONCAT("https://", source, "/", TRIM(BOTH "/" FROM link)) WHERE link NOT LIKE "http%"');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->throwIrreversibleMigrationException(
|
||||
'This migration is irreversible. You cannot revert the link to relative url.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250513081958.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250513081958 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'adding reading time to articles';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD reading_time INT DEFAULT NULL
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
UPDATE article SET reading_time = FLOOR(LENGTH(body) - LENGTH(REPLACE(body, ' ', '')) + 1) / 200
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP reading_time
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250514211949.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250514211949 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '[FeedManagement] add bookmark';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE bookmark (id BINARY(16) NOT NULL COMMENT '(DC2Type:bookmark_id)', user_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', name VARCHAR(255) NOT NULL, description VARCHAR(2048) DEFAULT NULL, is_public TINYINT(1) DEFAULT 0 NOT NULL, created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)', INDEX IDX_DA62921DA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE bookmark_article (bookmark_id BINARY(16) NOT NULL COMMENT '(DC2Type:bookmark_id)', article_id BINARY(16) NOT NULL COMMENT '(DC2Type:article_id)', INDEX IDX_6FE2655D92741D25 (bookmark_id), INDEX IDX_6FE2655D7294869C (article_id), PRIMARY KEY(bookmark_id, article_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark ADD CONSTRAINT FK_DA62921DA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark_article ADD CONSTRAINT FK_6FE2655D92741D25 FOREIGN KEY (bookmark_id) REFERENCES bookmark (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark_article ADD CONSTRAINT FK_6FE2655D7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark DROP FOREIGN KEY FK_DA62921DA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark_article DROP FOREIGN KEY FK_6FE2655D92741D25
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark_article DROP FOREIGN KEY FK_6FE2655D7294869C
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE bookmark
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE bookmark_article
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250515023707.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250515023707 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'date_immutable to datetime_immutable';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE updated_at updated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark CHANGE created_at created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', CHANGE updated_at updated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_attempt CHANGE created_at created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history CHANGE created_at created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source CHANGE updated_at updated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user CHANGE created_at created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', CHANGE updated_at updated_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE created_at created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user CHANGE created_at created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', CHANGE updated_at updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark CHANGE created_at created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)', CHANGE updated_at updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source CHANGE updated_at updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_attempt CHANGE created_at created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE created_at created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history CHANGE created_at created_at DATE NOT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE updated_at updated_at DATE DEFAULT NULL COMMENT '(DC2Type:date_immutable)'
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;final class Version20250516123343 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '[source] add display_name and description';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source ADD display_name VARCHAR(255) DEFAULT NULL, ADD description VARCHAR(2048) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source DROP display_name, DROP description
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250517055913.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250517055913 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '[article] add index on publication date';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_23A0E66E0D4FDE1 ON article (published_at)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_23A0E66E0D4FDE1 ON article
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250522140030.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250522140030 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create FollowedSource table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE followed_source (id BINARY(16) NOT NULL COMMENT '(DC2Type:followed_source_id)', follower_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', source VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX IDX_7A763A3EAC24F853 (follower_id), INDEX IDX_7A763A3E5F8A7F73 (source), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE followed_source ADD CONSTRAINT FK_7A763A3EAC24F853 FOREIGN KEY (follower_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE followed_source ADD CONSTRAINT FK_7A763A3E5F8A7F73 FOREIGN KEY (source) REFERENCES source (name) ON DELETE CASCADE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE followed_source DROP FOREIGN KEY FK_7A763A3EAC24F853
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE followed_source DROP FOREIGN KEY FK_7A763A3E5F8A7F73
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE followed_source
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250525183408.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250525183408 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'optimize data lengths for various fields in the database schema';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE title title VARCHAR(1024) NOT NULL, CHANGE link link VARCHAR(1024) NOT NULL, CHANGE bias bias VARCHAR(30) DEFAULT 'neutral' NOT NULL, CHANGE reliability reliability VARCHAR(30) DEFAULT 'reliable' NOT NULL, CHANGE transparency transparency VARCHAR(30) DEFAULT 'medium' NOT NULL, CHANGE reading_time reading_time INT UNSIGNED DEFAULT 1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark CHANGE description description VARCHAR(512) DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history ADD ip_address VARCHAR(15) DEFAULT NULL, DROP ip
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source CHANGE bias bias VARCHAR(30) DEFAULT 'neutral' NOT NULL, CHANGE reliability reliability VARCHAR(30) DEFAULT 'reliable' NOT NULL, CHANGE transparency transparency VARCHAR(30) DEFAULT 'medium' NOT NULL, CHANGE description description VARCHAR(1024) DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user CHANGE email email VARCHAR(255) NOT NULL, CHANGE password password VARCHAR(512) NOT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE token token VARCHAR(60) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE user CHANGE email email VARCHAR(500) NOT NULL, CHANGE password password VARCHAR(4098) NOT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE bookmark CHANGE description description VARCHAR(2048) DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE source CHANGE description description VARCHAR(2048) DEFAULT NULL, CHANGE bias bias VARCHAR(255) DEFAULT 'neutral' NOT NULL, CHANGE reliability reliability VARCHAR(255) DEFAULT 'reliable' NOT NULL, CHANGE transparency transparency VARCHAR(255) DEFAULT 'medium' NOT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE verification_token CHANGE token token VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE login_history ADD ip VARCHAR(45) DEFAULT NULL, DROP ip_address
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE title title VARCHAR(2048) NOT NULL, CHANGE link link VARCHAR(2048) NOT NULL, CHANGE bias bias VARCHAR(255) DEFAULT 'neutral' NOT NULL, CHANGE reliability reliability VARCHAR(255) DEFAULT 'reliable' NOT NULL, CHANGE transparency transparency VARCHAR(255) DEFAULT 'medium' NOT NULL, CHANGE reading_time reading_time INT DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250526101759.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250526101759 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'add comment table with foreign keys to user and article tables';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE comment (id BINARY(16) NOT NULL COMMENT '(DC2Type:comment_id)', user_id BINARY(16) NOT NULL COMMENT '(DC2Type:user_id)', article_id BINARY(16) NOT NULL COMMENT '(DC2Type:article_id)', content VARCHAR(512) NOT NULL, sentiment VARCHAR(30) DEFAULT 'neutral' NOT NULL, is_spam TINYINT(1) DEFAULT 0 NOT NULL, created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime_immutable)', INDEX IDX_9474526CA76ED395 (user_id), INDEX IDX_9474526C7294869C (article_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE comment ADD CONSTRAINT FK_9474526CA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE comment ADD CONSTRAINT FK_9474526C7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE comment DROP FOREIGN KEY FK_9474526CA76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE comment DROP FOREIGN KEY FK_9474526C7294869C
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE comment
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250526102035.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250526102035 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'optimize sentiment column in article table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE sentiment sentiment VARCHAR(30) DEFAULT 'neutral' NOT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article CHANGE sentiment sentiment VARCHAR(255) DEFAULT 'neutral' NOT NULL
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250526164157.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250526164157 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add image and excerpt columns to article table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article
|
||||
ADD image VARCHAR(1024) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.image'))) STORED,
|
||||
ADD excerpt VARCHAR(255) GENERATED ALWAYS AS (CONCAT(LEFT(body, 200), '...')) STORED
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article DROP image, DROP excerpt
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Basango\Aggregator\Domain\Model\Identity\SourceId;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250526231341.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250526231341 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'move from source_name to source_id in article and followed_source tables';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("SET FOREIGN_KEY_CHECKS = 0");
|
||||
|
||||
// delete the old indexes and foreign keys
|
||||
$this->addSql("DROP INDEX `primary` ON source");
|
||||
$this->addSql("ALTER TABLE article DROP FOREIGN KEY FK_23A0E665F8A7F73");
|
||||
$this->addSql("ALTER TABLE followed_source DROP FOREIGN KEY FK_7A763A3E5F8A7F73");
|
||||
$this->addSql("DROP INDEX IDX_23A0E665F8A7F73 ON article");
|
||||
$this->addSql("DROP INDEX IDX_7A763A3E5F8A7F73 ON followed_source");
|
||||
|
||||
// add the new id column to source table
|
||||
$this->addSql("ALTER TABLE source ADD id BINARY(16) DEFAULT NULL COMMENT '(DC2Type:source_id)' FIRST");
|
||||
$sources = $this->connection
|
||||
->executeQuery("SELECT name FROM source")
|
||||
->fetchFirstColumn();
|
||||
|
||||
foreach ($sources as $source) {
|
||||
$this->addSql("UPDATE source SET id = :id WHERE name = :name", [
|
||||
"id" => new SourceId()->toBinary(),
|
||||
"name" => $source,
|
||||
]);
|
||||
}
|
||||
|
||||
// set the id column as NOT NULL and create a unique index
|
||||
$this->addSql("ALTER TABLE source MODIFY id BINARY(16) NOT NULL COMMENT '(DC2Type:source_id)'");
|
||||
$this->addSql("CREATE UNIQUE INDEX UNIQ_5F8A7F735E237E06 ON source (name)");
|
||||
$this->addSql("ALTER TABLE source ADD PRIMARY KEY (id)");
|
||||
|
||||
// Update article table
|
||||
$this->addSql("ALTER TABLE article ADD source_id BINARY(16) NOT NULL COMMENT '(DC2Type:source_id)'");
|
||||
$this->addSql("UPDATE article JOIN source ON article.source = source.name SET article.source_id = source.id");
|
||||
$this->addSql("ALTER TABLE article DROP source");
|
||||
$this->addSql("ALTER TABLE article ADD CONSTRAINT FK_23A0E66953C1C61 FOREIGN KEY (source_id) REFERENCES source (id) ON DELETE CASCADE");
|
||||
$this->addSql(" CREATE INDEX IDX_23A0E66953C1C61 ON article (source_id)");
|
||||
|
||||
// Update followed_source table
|
||||
$this->addSql("ALTER TABLE followed_source ADD source_id BINARY(16) NOT NULL COMMENT '(DC2Type:source_id)'");
|
||||
$this->addSql("ALTER TABLE followed_source DROP source");
|
||||
$this->addSql("ALTER TABLE followed_source ADD CONSTRAINT FK_7A763A3E953C1C61 FOREIGN KEY (source_id) REFERENCES source (id) ON DELETE CASCADE");
|
||||
$this->addSql("CREATE INDEX IDX_7A763A3E953C1C61 ON followed_source (source_id)");
|
||||
|
||||
// Re-enable foreign key checks
|
||||
$this->addSql("SET FOREIGN_KEY_CHECKS = 1");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->throwIrreversibleMigrationException('This migration is irreversible.');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Class Version20250530121647.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20250530121647 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create index on article table for published_at and id columns';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE article ADD INDEX IDX_PUBLISHED_AT_ID (published_at DESC, id DESC);
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_PUBLISHED_AT_ID ON article
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Doctrine\Migrations\Exception\IrreversibleMigration;
|
||||
|
||||
/**
|
||||
* Class Version20251019151441.
|
||||
*
|
||||
* @author bernard-ng <bernard@devscast.tech>
|
||||
*/
|
||||
final class Version20251019151441 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'initial postgresql schema';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("CREATE EXTENSION IF NOT EXISTS pg_trgm;"); // for trigram indexes (links, titles, etc.)
|
||||
$this->addSql("SET SESSION TIME ZONE 'UTC';");
|
||||
|
||||
// -- ---------- TABLE: article ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE article (
|
||||
id UUID NOT NULL,
|
||||
source_id UUID NOT NULL,
|
||||
title VARCHAR(1024) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
hash VARCHAR(32) NOT NULL,
|
||||
categories TEXT[] DEFAULT NULL,
|
||||
sentiment VARCHAR(30) DEFAULT 'neutral' NOT NULL,
|
||||
metadata JSONB DEFAULT NULL,
|
||||
image VARCHAR(1024) GENERATED ALWAYS AS ((metadata->>'image')) STORED,
|
||||
excerpt VARCHAR(255) GENERATED ALWAYS AS ((LEFT(body, 200) || '...')) STORED,
|
||||
published_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
crawled_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
link VARCHAR(1024) NOT NULL,
|
||||
bias VARCHAR(30) DEFAULT 'neutral' NOT NULL,
|
||||
reliability VARCHAR(30) DEFAULT 'reliable' NOT NULL,
|
||||
transparency VARCHAR(30) DEFAULT 'medium' NOT NULL,
|
||||
reading_time INT DEFAULT 1,
|
||||
CONSTRAINT CHK_ARTICLE_READING_TIME CHECK (reading_time >= 0),
|
||||
CONSTRAINT CHK_ARTICLE_SENTIMENT CHECK (sentiment IN ('positive','neutral','negative')),
|
||||
CONSTRAINT CHK_ARTICLE_METADATA_JSON CHECK (metadata IS NULL OR JSONB_TYPEOF(metadata) IN ('object','array')),
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_23A0E66953C1C61 ON article (source_id)');
|
||||
$this->addSql('CREATE INDEX IDX_ARTICLE_PUBLISHED_AT ON article (published_at DESC)');
|
||||
$this->addSql('CREATE INDEX IDX_ARTICLE_PUBLISHED_ID ON article (published_at DESC, id DESC)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNQ_ARTICLE_HASH ON article (hash)');
|
||||
$this->addSql(<<<SQL
|
||||
ALTER TABLE article ADD COLUMN tsv TSVECTOR GENERATED ALWAYS AS (
|
||||
SETWEIGHT(TO_TSVECTOR('french', COALESCE(title,'')), 'A') ||
|
||||
SETWEIGHT(TO_TSVECTOR('french', COALESCE(body ,'')), 'B')
|
||||
) STORED;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX GIN_ARTICLE_TSV ON article USING GIN(tsv)');
|
||||
$this->addSql('CREATE INDEX GIN_ARTICLE_LINK_TRGM ON article USING GIN (link gin_trgm_ops)');
|
||||
$this->addSql('CREATE INDEX GIN_ARTICLE_TITLE_TRGM ON article USING GIN (title gin_trgm_ops)');
|
||||
$this->addSql('CREATE INDEX GIN_ARTICLE_CATEGORIES ON article USING GIN (categories)');
|
||||
$this->addSql("COMMENT ON COLUMN article.id IS '(DC2Type:article_id)';");
|
||||
$this->addSql("COMMENT ON COLUMN article.source_id IS '(DC2Type:source_id)';");
|
||||
$this->addSql("COMMENT ON COLUMN article.published_at IS '(DC2Type:datetime_immutable)'");
|
||||
$this->addSql("COMMENT ON COLUMN article.crawled_at IS '(DC2Type:datetime_immutable)'");
|
||||
$this->addSql("COMMENT ON COLUMN article.updated_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: bookmark ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE bookmark (
|
||||
id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description VARCHAR(512) DEFAULT NULL,
|
||||
is_public BOOLEAN DEFAULT false NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_DA62921DA76ED395 ON bookmark (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_BOOKMARK_USER_CREATED ON bookmark (user_id, created_at DESC)');
|
||||
$this->addSql("COMMENT ON COLUMN bookmark.id IS '(DC2Type:bookmark_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN bookmark.user_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN bookmark.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
$this->addSql("COMMENT ON COLUMN bookmark.updated_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: bookmark_article ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE bookmark_article (
|
||||
bookmark_id UUID NOT NULL,
|
||||
article_id UUID NOT NULL,
|
||||
PRIMARY KEY(bookmark_id, article_id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_6FE2655D92741D25 ON bookmark_article (bookmark_id)');
|
||||
$this->addSql('CREATE INDEX IDX_6FE2655D7294869C ON bookmark_article (article_id)');
|
||||
$this->addSql("COMMENT ON COLUMN bookmark_article.bookmark_id IS '(DC2Type:bookmark_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN bookmark_article.article_id IS '(DC2Type:article_id)'");
|
||||
|
||||
// -- ---------- TABLE: comment ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE comment (
|
||||
id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
article_id UUID NOT NULL,
|
||||
content VARCHAR(512) NOT NULL,
|
||||
sentiment VARCHAR(30) DEFAULT 'neutral' NOT NULL,
|
||||
is_spam BOOLEAN DEFAULT false NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_9474526CA76ED395 ON comment (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_9474526C7294869C ON comment (article_id)');
|
||||
$this->addSql('CREATE INDEX IDX_COMMENT_ARTICLE_CREATED ON comment (article_id, created_at DESC)');
|
||||
$this->addSql("COMMENT ON COLUMN comment.id IS '(DC2Type:comment_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN comment.user_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN comment.article_id IS '(DC2Type:article_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN comment.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: followed_source ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE followed_source (
|
||||
id UUID NOT NULL,
|
||||
follower_id UUID NOT NULL,
|
||||
source_id UUID NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_7A763A3EAC24F853 ON followed_source (follower_id)');
|
||||
$this->addSql('CREATE INDEX IDX_7A763A3E953C1C61 ON followed_source (source_id)');
|
||||
$this->addSql('CREATE INDEX IDX_FOLLOWED_SOURCE_FOLLOWER_CREATED ON followed_source (follower_id, created_at DESC)');
|
||||
$this->addSql("COMMENT ON COLUMN followed_source.id IS '(DC2Type:followed_source_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN followed_source.follower_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN followed_source.source_id IS '(DC2Type:source_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN followed_source.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: login_attempt ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE login_attempt (
|
||||
id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_8C11C1BA76ED395 ON login_attempt (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_LOGIN_ATTEMPT_CREATED_AT ON login_attempt (created_at DESC)');
|
||||
$this->addSql("COMMENT ON COLUMN login_attempt.id IS '(DC2Type:login_attempt_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN login_attempt.user_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN login_attempt.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: login_history ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE login_history (
|
||||
id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
ip_address INET DEFAULT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
device_operating_system VARCHAR(255) DEFAULT NULL,
|
||||
device_client VARCHAR(255) DEFAULT NULL,
|
||||
device_device VARCHAR(255) DEFAULT NULL,
|
||||
device_is_bot BOOLEAN DEFAULT false NOT NULL,
|
||||
location_time_zone VARCHAR(255) DEFAULT NULL,
|
||||
location_longitude DOUBLE PRECISION DEFAULT NULL,
|
||||
location_latitude DOUBLE PRECISION DEFAULT NULL,
|
||||
location_accuracy_radius INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_37976E36A76ED395 ON login_history (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_LOGIN_HISTORY_CREATED_AT ON login_history (user_id, created_at DESC)');
|
||||
$this->addSql('CREATE INDEX IDX_LOGIN_HISTORY_IP_ADDRESS ON login_history (ip_address)');
|
||||
$this->addSql("COMMENT ON COLUMN login_history.id IS '(DC2Type:login_history_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN login_history.user_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN login_history.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: refresh_tokens ----------
|
||||
$this->addSql('CREATE SEQUENCE refresh_tokens_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE refresh_tokens (
|
||||
id INT NOT NULL,
|
||||
refresh_token VARCHAR(128) NOT NULL,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
valid TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_9BACE7E1C74F2195 ON refresh_tokens (refresh_token)');
|
||||
|
||||
// -- ---------- TABLE: source ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE source (
|
||||
id UUID NOT NULL,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(255) DEFAULT NULL,
|
||||
description VARCHAR(1024) DEFAULT NULL,
|
||||
updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
bias VARCHAR(30) DEFAULT 'neutral' NOT NULL,
|
||||
reliability VARCHAR(30) DEFAULT 'reliable' NOT NULL,
|
||||
transparency VARCHAR(30) DEFAULT 'medium' NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE UNIQUE INDEX UNQ_SOURCE_NAME ON source (LOWER(name))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNQ_SOURCE_URL ON source (LOWER(url))');
|
||||
$this->addSql("COMMENT ON COLUMN source.id IS '(DC2Type:source_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN source.updated_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: user ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE "user" (
|
||||
id UUID NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(512) NOT NULL,
|
||||
is_locked BOOLEAN DEFAULT false NOT NULL,
|
||||
is_confirmed BOOLEAN DEFAULT false NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
roles JSONB NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
CONSTRAINT CHK_USER_ROLES_JSON CHECK (JSONB_TYPEOF(roles) = 'array')
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<SQL
|
||||
CREATE UNIQUE INDEX UNQ_USER_EMAIL ON "user" (LOWER(email));
|
||||
SQL
|
||||
);
|
||||
$this->addSql("COMMENT ON COLUMN \"user\".id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN \"user\".created_at IS '(DC2Type:datetime_immutable)'");
|
||||
$this->addSql("COMMENT ON COLUMN \"user\".updated_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- TABLE: verification_token ----------
|
||||
$this->addSql(<<<SQL
|
||||
CREATE TABLE verification_token (
|
||||
id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
purpose VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
token VARCHAR(60) DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_C1CC006BA76ED395 ON verification_token (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_VERIF_TOKEN_CREATED_AT ON verification_token (created_at DESC)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNQ_VERIF_USER_PURPOSE_TOKEN ON verification_token (user_id, purpose) WHERE token IS NOT NULL');
|
||||
$this->addSql("COMMENT ON COLUMN verification_token.id IS '(DC2Type:verification_token_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN verification_token.user_id IS '(DC2Type:user_id)'");
|
||||
$this->addSql("COMMENT ON COLUMN verification_token.created_at IS '(DC2Type:datetime_immutable)'");
|
||||
|
||||
// -- ---------- FOREIGN KEY CONSTRAINTS ----------
|
||||
$this->addSql('ALTER TABLE article ADD CONSTRAINT FK_23A0E66953C1C61 FOREIGN KEY (source_id) REFERENCES source (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE bookmark ADD CONSTRAINT FK_DA62921DA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE bookmark_article ADD CONSTRAINT FK_6FE2655D92741D25 FOREIGN KEY (bookmark_id) REFERENCES bookmark (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE bookmark_article ADD CONSTRAINT FK_6FE2655D7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526CA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C7294869C FOREIGN KEY (article_id) REFERENCES article (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE followed_source ADD CONSTRAINT FK_7A763A3EAC24F853 FOREIGN KEY (follower_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE followed_source ADD CONSTRAINT FK_7A763A3E953C1C61 FOREIGN KEY (source_id) REFERENCES source (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE login_attempt ADD CONSTRAINT FK_8C11C1BA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE login_history ADD CONSTRAINT FK_37976E36A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE verification_token ADD CONSTRAINT FK_C1CC006BA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
throw new IrreversibleMigration('Sometimes in life you have to accept that you can\'t go back.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
maker:
|
||||
root_namespace: Basango
|
||||
@@ -1,14 +1,67 @@
|
||||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
connections:
|
||||
default:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
server_version: '16'
|
||||
profiling_collect_backtrace: false
|
||||
idle_connection_ttl: 172800
|
||||
use_savepoints: true
|
||||
result_cache: 'cache.dbal'
|
||||
mapping_types:
|
||||
# Array type mappings
|
||||
'bool[]': 'bool[]'
|
||||
_bool: 'bool[]'
|
||||
'smallint[]': 'smallint[]'
|
||||
_int2: 'smallint[]'
|
||||
'integer[]': 'integer[]'
|
||||
_int4: 'integer[]'
|
||||
'bigint[]': 'bigint[]'
|
||||
_int8: 'bigint[]'
|
||||
'double precision[]': 'double precision[]'
|
||||
_float8: 'double precision[]'
|
||||
'real[]': 'real[]'
|
||||
_float4: 'real[]'
|
||||
'text[]': 'text[]'
|
||||
_text: 'text[]'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '16'
|
||||
# JSON type mappings
|
||||
jsonb: jsonb
|
||||
'jsonb[]': 'jsonb[]'
|
||||
_jsonb: 'jsonb[]'
|
||||
|
||||
profiling_collect_backtrace: false
|
||||
use_savepoints: true
|
||||
result_cache: 'cache.dbal'
|
||||
# Network type mappings
|
||||
cidr: cidr
|
||||
'cidr[]': 'cidr[]'
|
||||
_cidr: 'cidr[]'
|
||||
inet: inet
|
||||
'inet[]': 'inet[]'
|
||||
_inet: 'inet[]'
|
||||
macaddr: macaddr
|
||||
'macaddr[]': 'macaddr[]'
|
||||
_macaddr: 'macaddr[]'
|
||||
|
||||
# Spatial type mappings
|
||||
point: point
|
||||
'point[]': 'point[]'
|
||||
_point: 'point[]'
|
||||
geometry: geometry
|
||||
'geometry[]': 'geometry[]'
|
||||
_geometry: 'geometry[]'
|
||||
geography: geography
|
||||
'geography[]': 'geography[]'
|
||||
_geography: 'geography[]'
|
||||
|
||||
# Range type mappings
|
||||
daterange: daterange
|
||||
int4range: int4range
|
||||
int8range: int8range
|
||||
numrange: numrange
|
||||
tsrange: tsrange
|
||||
tstzrange: tstzrange
|
||||
|
||||
# Hierarchical type mappings
|
||||
ltree: ltree
|
||||
types:
|
||||
# Shared Kernel
|
||||
email: Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Types\EmailType
|
||||
@@ -28,34 +81,231 @@ doctrine:
|
||||
bookmark_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\BookmarkIdType
|
||||
followed_source_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\FollowedSourceIdType
|
||||
comment_id: Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Types\CommentIdType
|
||||
|
||||
# PostgreSQL Specific Types
|
||||
# Array types
|
||||
'bool[]': MartinGeorgiev\Doctrine\DBAL\Types\BooleanArray
|
||||
'smallint[]': MartinGeorgiev\Doctrine\DBAL\Types\SmallIntArray
|
||||
'integer[]': MartinGeorgiev\Doctrine\DBAL\Types\IntegerArray
|
||||
'bigint[]': MartinGeorgiev\Doctrine\DBAL\Types\BigIntArray
|
||||
'double precision[]': MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray
|
||||
'real[]': MartinGeorgiev\Doctrine\DBAL\Types\RealArray
|
||||
'text[]': MartinGeorgiev\Doctrine\DBAL\Types\TextArray
|
||||
|
||||
# JSON types
|
||||
jsonb: MartinGeorgiev\Doctrine\DBAL\Types\Jsonb
|
||||
'jsonb[]': MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray
|
||||
|
||||
# Network types
|
||||
cidr: MartinGeorgiev\Doctrine\DBAL\Types\Cidr
|
||||
'cidr[]': MartinGeorgiev\Doctrine\DBAL\Types\CidrArray
|
||||
inet: MartinGeorgiev\Doctrine\DBAL\Types\Inet
|
||||
'inet[]': MartinGeorgiev\Doctrine\DBAL\Types\InetArray
|
||||
macaddr: MartinGeorgiev\Doctrine\DBAL\Types\Macaddr
|
||||
'macaddr[]': MartinGeorgiev\Doctrine\DBAL\Types\MacaddrArray
|
||||
|
||||
# Spatial types
|
||||
point: MartinGeorgiev\Doctrine\DBAL\Types\Point
|
||||
'point[]': MartinGeorgiev\Doctrine\DBAL\Types\PointArray
|
||||
geometry: MartinGeorgiev\Doctrine\DBAL\Types\Geometry
|
||||
'geometry[]': MartinGeorgiev\Doctrine\DBAL\Types\GeometryArray
|
||||
geography: MartinGeorgiev\Doctrine\DBAL\Types\Geography
|
||||
'geography[]': MartinGeorgiev\Doctrine\DBAL\Types\GeographyArray
|
||||
|
||||
# Range types
|
||||
daterange: MartinGeorgiev\Doctrine\DBAL\Types\DateRange
|
||||
int4range: MartinGeorgiev\Doctrine\DBAL\Types\Int4Range
|
||||
int8range: MartinGeorgiev\Doctrine\DBAL\Types\Int8Range
|
||||
numrange: MartinGeorgiev\Doctrine\DBAL\Types\NumRange
|
||||
tsrange: MartinGeorgiev\Doctrine\DBAL\Types\TsRange
|
||||
tstzrange: MartinGeorgiev\Doctrine\DBAL\Types\TstzRange
|
||||
|
||||
# Hierarchical types
|
||||
ltree: MartinGeorgiev\Doctrine\DBAL\Types\Ltree
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: false
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
Aggregator:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/Aggregator'
|
||||
prefix: 'Basango\Aggregator\Domain\Model'
|
||||
IdentityAndAccess:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/IdentityAndAccess'
|
||||
prefix: 'Basango\IdentityAndAccess\Domain\Model'
|
||||
FeedManagement:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/FeedManagement'
|
||||
prefix: 'Basango\FeedManagement\Domain\Model'
|
||||
SharedKernel:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/SharedKernel'
|
||||
prefix: 'Basango\SharedKernel\Domain\Model'
|
||||
entity_managers:
|
||||
default:
|
||||
validate_xml_mapping: false
|
||||
report_fields_where_declared: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
dql:
|
||||
string_functions:
|
||||
# alternative implementation of ALL() and ANY() where subquery is not required, useful for arrays
|
||||
ALL_OF: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\All
|
||||
ANY_OF: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Any
|
||||
|
||||
# operators for working with array and json(b) data
|
||||
GREATEST: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Greatest
|
||||
LEAST: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Least
|
||||
CONTAINS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains # @>
|
||||
IS_CONTAINED_BY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy # <@
|
||||
OVERLAPS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Overlaps # &&
|
||||
RIGHT_EXISTS_ON_LEFT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TheRightExistsOnTheLeft # ?
|
||||
ALL_ON_RIGHT_EXIST_ON_LEFT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AllOnTheRightExistOnTheLeft # ?&
|
||||
ANY_ON_RIGHT_EXISTS_ON_LEFT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AnyOnTheRightExistsOnTheLeft # ?|
|
||||
RETURNS_VALUE_FOR_JSON_VALUE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ReturnsValueForJsonValue # @?
|
||||
DELETE_AT_PATH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DeleteAtPath # #-
|
||||
|
||||
# array and string specific functions
|
||||
IN_ARRAY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\InArray
|
||||
ANY_VALUE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\AnyValue
|
||||
ARRAY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Arr
|
||||
ARRAY_APPEND: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAppend
|
||||
ARRAY_CARDINALITY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCardinality
|
||||
ARRAY_CAT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCat
|
||||
ARRAY_DIMENSIONS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayDimensions
|
||||
ARRAY_LENGTH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayLength
|
||||
ARRAY_NUMBER_OF_DIMENSIONS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayNumberOfDimensions
|
||||
ARRAY_POSITION: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPosition
|
||||
ARRAY_POSITIONS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPositions
|
||||
ARRAY_PREPEND: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPrepend
|
||||
ARRAY_REMOVE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayRemove
|
||||
ARRAY_REPLACE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayReplace
|
||||
ARRAY_SHUFFLE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayShuffle
|
||||
ARRAY_TO_JSON: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToJson
|
||||
ARRAY_TO_STRING: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToString
|
||||
SPLIT_PART: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SplitPart
|
||||
STARTS_WITH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StartsWith
|
||||
STRING_TO_ARRAY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray
|
||||
UNNEST: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unnest
|
||||
|
||||
# json specific functions
|
||||
JSON_ARRAY_LENGTH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength
|
||||
JSON_BUILD_OBJECT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject
|
||||
JSON_EACH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach
|
||||
JSON_EACH_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText
|
||||
JSON_EXISTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonExists
|
||||
JSON_GET_FIELD: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField
|
||||
JSON_GET_FIELD_AS_INTEGER: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger
|
||||
JSON_GET_FIELD_AS_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText
|
||||
JSON_GET_OBJECT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObject
|
||||
JSON_GET_OBJECT_AS_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObjectAsText
|
||||
JSON_OBJECT_KEYS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectKeys
|
||||
JSON_QUERY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonQuery
|
||||
JSON_SCALAR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonScalar
|
||||
JSON_SERIALIZE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonSerialize
|
||||
JSON_STRIP_NULLS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonStripNulls
|
||||
JSON_TYPEOF: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonTypeof
|
||||
JSON_VALUE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonValue
|
||||
TO_JSON: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJson
|
||||
ROW_TO_JSON: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson
|
||||
|
||||
# jsonb specific functions
|
||||
JSONB_ARRAY_ELEMENTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements
|
||||
JSONB_ARRAY_ELEMENTS_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText
|
||||
JSONB_ARRAY_LENGTH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength
|
||||
JSONB_BUILD_OBJECT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject
|
||||
JSONB_EACH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach
|
||||
JSONB_EACH_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText
|
||||
JSONB_EXISTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists
|
||||
JSONB_INSERT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbInsert
|
||||
JSONB_OBJECT_KEYS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectKeys
|
||||
JSONB_PATH_EXISTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathExists
|
||||
JSONB_PATH_MATCH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathMatch
|
||||
JSONB_PATH_QUERY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQuery
|
||||
JSONB_PATH_QUERY_ARRAY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryArray
|
||||
JSONB_PATH_QUERY_FIRST: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPathQueryFirst
|
||||
JSONB_PRETTY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbPretty
|
||||
JSONB_SET: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSet
|
||||
JSONB_SET_LAX: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSetLax
|
||||
JSONB_STRIP_NULLS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbStripNulls
|
||||
TO_JSONB: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToJsonb
|
||||
|
||||
# text search specific
|
||||
TO_TSQUERY: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery
|
||||
TO_TSVECTOR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector
|
||||
TSMATCH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsmatch
|
||||
|
||||
# date specific functions
|
||||
DATE_ADD: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateAdd
|
||||
DATE_BIN: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateBin
|
||||
DATE_EXTRACT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateExtract
|
||||
DATE_OVERLAPS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateOverlaps
|
||||
DATE_SUBTRACT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateSubtract
|
||||
|
||||
# range functions
|
||||
DATERANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Daterange
|
||||
INT4RANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int4range
|
||||
INT8RANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int8range
|
||||
NUMRANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Numrange
|
||||
TSRANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsrange
|
||||
TSTZRANGE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tstzrange
|
||||
|
||||
# Arithmetic functions
|
||||
CBRT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cbrt
|
||||
CEIL: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ceil
|
||||
DEGREES: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Degrees
|
||||
EXP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exp
|
||||
FLOOR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Floor
|
||||
LN: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ln
|
||||
LOG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Log
|
||||
PI: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Pi
|
||||
POWER: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Power
|
||||
RADIANS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Radians
|
||||
RANDOM: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Random
|
||||
ROUND: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Round
|
||||
SIGN: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Sign
|
||||
TRUNC: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Trunc
|
||||
WIDTH_BUCKET: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\WidthBucket
|
||||
|
||||
# other operators
|
||||
CAST: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Cast
|
||||
ILIKE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ilike
|
||||
SIMILAR_TO: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SimilarTo
|
||||
NOT_SIMILAR_TO: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotSimilarTo
|
||||
UNACCENT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Unaccent
|
||||
REGEXP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Regexp
|
||||
IREGEXP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IRegexp
|
||||
NOT_REGEXP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotRegexp
|
||||
NOT_IREGEXP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\NotIRegexp
|
||||
REGEXP_COUNT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpCount
|
||||
REGEXP_INSTR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpInstr
|
||||
REGEXP_LIKE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike
|
||||
REGEXP_MATCH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch
|
||||
REGEXP_REPLACE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpReplace
|
||||
REGEXP_SUBSTR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpSubstr
|
||||
ROW: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Row
|
||||
STRCONCAT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StrConcat
|
||||
DISTANCE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Distance
|
||||
|
||||
# aggregation functions
|
||||
ARRAY_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAgg
|
||||
JSON_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg
|
||||
JSON_OBJECT_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectAgg
|
||||
JSONB_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbAgg
|
||||
JSONB_OBJECT_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectAgg
|
||||
STRING_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringAgg
|
||||
XML_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\XmlAgg
|
||||
|
||||
# data type formatting functions
|
||||
TO_CHAR: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToChar
|
||||
TO_DATE: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToDate
|
||||
TO_NUMBER: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToNumber
|
||||
TO_TIMESTAMP: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTimestamp
|
||||
mappings:
|
||||
Aggregator:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/Aggregator'
|
||||
prefix: 'Basango\Aggregator\Domain\Model'
|
||||
IdentityAndAccess:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/IdentityAndAccess'
|
||||
prefix: 'Basango\IdentityAndAccess\Domain\Model'
|
||||
FeedManagement:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/FeedManagement'
|
||||
prefix: 'Basango\FeedManagement\Domain\Model'
|
||||
SharedKernel:
|
||||
is_bundle: false
|
||||
type: xml
|
||||
dir: '%kernel.project_dir%/config/doctrine/SharedKernel'
|
||||
prefix: 'Basango\SharedKernel\Domain\Model'
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ final readonly class GetArticlesForExportDbalHandler implements GetArticlesForEx
|
||||
'a.id as article_id',
|
||||
'a.title as article_title',
|
||||
'a.link as article_link',
|
||||
'a.categories as article_categories',
|
||||
"array_to_string(a.categories, ',') as article_categories",
|
||||
'a.body as article_body',
|
||||
's.name as article_source',
|
||||
'a.hash as article_hash',
|
||||
@@ -49,7 +49,7 @@ final readonly class GetArticlesForExportDbalHandler implements GetArticlesForEx
|
||||
}
|
||||
|
||||
if ($query->date instanceof DateRange) {
|
||||
$qb->andWhere('a.published_at BETWEEN :start AND :end')
|
||||
$qb->andWhere('a.published_at BETWEEN to_timestamp(:start) AND to_timestamp(:end)')
|
||||
->setParameter('start', $query->date->start)
|
||||
->setParameter('end', $query->date->end);
|
||||
}
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ final readonly class GetEarliestPublicationDateDBalHandler implements GetEarlies
|
||||
->setParameter('source', $query->source);
|
||||
|
||||
if ($query->category !== null) {
|
||||
$qb->andWhere('a.categories LIKE :category')
|
||||
->setParameter('category', sprintf('%%%s%%', $query->category));
|
||||
$qb->andWhere(':category = ANY(a.categories)')
|
||||
->setParameter('category', $query->category);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ final readonly class GetLatestPublicationDateDBalHandler implements GetLatestPub
|
||||
->setParameter('source', $query->source);
|
||||
|
||||
if ($query->category !== null) {
|
||||
$qb->andWhere('a.categories LIKE :category')
|
||||
->setParameter('category', sprintf('%%%s%%', $query->category));
|
||||
$qb->andWhere(':category = ANY(a.categories)')
|
||||
->setParameter('category', $query->category);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ final readonly class GetSourceStatisticsListDbalHandler implements GetSourceStat
|
||||
)
|
||||
->from('source', 's')
|
||||
->leftJoin('s', 'article', 'a', 'a.source_id = s.id')
|
||||
->groupBy('s.id')
|
||||
->groupBy('s.id, s.name')
|
||||
->orderBy('s.name', 'ASC');
|
||||
|
||||
try {
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ final class OpenGraphType extends Type
|
||||
{
|
||||
return $platform->getJsonTypeDeclarationSQL([
|
||||
'nullable' => true,
|
||||
'jsonb' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -9,6 +9,7 @@ use Basango\Aggregator\Domain\Model\Entity\Article;
|
||||
use Basango\Aggregator\Domain\Model\Identity\ArticleId;
|
||||
use Basango\Aggregator\Domain\Model\Repository\ArticleRepository;
|
||||
use Basango\SharedKernel\Domain\Model\ValueObject\DateRange;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
@@ -69,9 +70,9 @@ final class ArticleOrmRepository extends ServiceEntityRepository implements Arti
|
||||
}
|
||||
|
||||
if ($date instanceof DateRange) {
|
||||
$qb->andWhere('a.publishedAt BETWEEN FROM_UNIXTIME(:start) AND FROM_UNIXTIME(:end)')
|
||||
->setParameter('start', $date->start)
|
||||
->setParameter('end', $date->end);
|
||||
$qb->andWhere('a.publishedAt BETWEEN :startDate AND :endDate')
|
||||
->setParameter('startDate', new DateTimeImmutable()->setTimestamp($date->start))
|
||||
->setParameter('endDate', new DateTimeImmutable()->setTimestamp($date->end));
|
||||
}
|
||||
|
||||
$limit = 1000;
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetArticleCommentListDbalHandler.
|
||||
@@ -41,7 +40,7 @@ final readonly class GetArticleCommentListDbalHandler implements GetArticleComme
|
||||
->innerJoin('c', 'user', 'u', 'c.user_id = u.id')
|
||||
->where('c.article_id = :articleId')
|
||||
->orderBy('c.created_at', 'DESC')
|
||||
->setParameter('articleId', $query->articleId->toBinary(), ParameterType::BINARY);
|
||||
->setParameter('articleId', $query->articleId->toRfc4122());
|
||||
|
||||
$qb = $this->applyCursorPagination($qb, $query->page, new PaginatorKeyset('c.id', 'c.created_at'));
|
||||
|
||||
|
||||
+2
-3
@@ -13,7 +13,6 @@ use Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Queries\Book
|
||||
use Basango\FeedManagement\Infrastructure\Persistence\Doctrine\DBAL\Queries\SourceQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetArticleDetailsDbalHandler.
|
||||
@@ -42,8 +41,8 @@ final readonly class GetArticleDetailsDbalHandler implements GetArticleDetailsHa
|
||||
$qb->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
->from('article', 'a')
|
||||
->where('a.id = :articleId')
|
||||
->setParameter('articleId', $query->id->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('articleId', $query->id->toRfc4122())
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
;
|
||||
|
||||
try {
|
||||
|
||||
+1
-2
@@ -14,7 +14,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetArticleOverviewListDbalHandler.
|
||||
@@ -44,7 +43,7 @@ final readonly class GetArticleOverviewListDbalHandler implements GetArticleOver
|
||||
$qb->from('article', 'a')
|
||||
->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
//->orderBy('a.published_at', $query->filters->sortDirection->value)
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
;
|
||||
|
||||
$qb = $this->applyArticleFilters($qb, $query->filters);
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetBookmarkListDbalHandler.
|
||||
@@ -39,7 +38,7 @@ final readonly class GetBookmarkListDbalHandler implements GetBookmarkListHandle
|
||||
->where('b.user_id = :userId')
|
||||
->groupBy('b.id')
|
||||
->orderBy('b.id', 'DESC')
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
;
|
||||
|
||||
$qb = $this->applyCursorPagination($qb, $query->page, new PaginatorKeyset('b.id'));
|
||||
|
||||
+2
-3
@@ -13,7 +13,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetBookmarkedArticleListDbalHandler.
|
||||
@@ -44,8 +43,8 @@ final readonly class GetBookmarkedArticleListDbalHandler implements GetBookmarke
|
||||
->innerJoin('ba', 'bookmark', 'b', 'b.id = ba.bookmark_id AND b.user_id = :userId')
|
||||
->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
->where('b.id = :bookmarkId')
|
||||
->setParameter('bookmarkId', $query->bookmarkId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('bookmarkId', $query->bookmarkId->toRfc4122())
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
;
|
||||
|
||||
$qb = $this->applyArticleFilters($qb, $query->filters);
|
||||
|
||||
+2
-3
@@ -14,7 +14,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetArticleOverviewListDbalHandler.
|
||||
@@ -45,8 +44,8 @@ final readonly class GetSourceArticleOverviewListDbalHandler implements GetSourc
|
||||
->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
->where('s.id = :sourceId')
|
||||
->orderBy('a.published_at', $query->filters->sortDirection->value)
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('sourceId', $query->sourceId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
->setParameter('sourceId', $query->sourceId->toRfc4122())
|
||||
;
|
||||
|
||||
$qb = $this->applyArticleFilters($qb, $query->filters);
|
||||
|
||||
+8
-6
@@ -49,8 +49,10 @@ final readonly class GetSourceDetailsDbalHandler implements GetSourceDetailsHand
|
||||
$qb->from('source', 's')
|
||||
->leftJoin('s', 'article', 'a', 'a.source_id = s.id')
|
||||
->where('s.id = :sourceId')
|
||||
->setParameter('sourceId', $query->sourceId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY);
|
||||
->setParameter('sourceId', $query->sourceId->toRfc4122())
|
||||
->setParameter('userId', $query->userId->toRfc4122());
|
||||
// Aggregate columns are selected; include non-aggregated columns in GROUP BY for PostgreSQL
|
||||
$qb->groupBy('s.id, s.name, s.description, s.url, s.updated_at, s.display_name, s.bias, s.reliability, s.transparency');
|
||||
|
||||
try {
|
||||
$data = $qb->executeQuery()->fetchAssociative();
|
||||
@@ -79,10 +81,10 @@ final readonly class GetSourceDetailsDbalHandler implements GetSourceDetailsHand
|
||||
->from('article', 'a')
|
||||
->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
->where(' s.id = :sourceId')
|
||||
->andWhere('a.published_at BETWEEN FROM_UNIXTIME(:start) AND FROM_UNIXTIME(:end)')
|
||||
->andWhere('a.published_at BETWEEN to_timestamp(:start) AND to_timestamp(:end)')
|
||||
->groupBy('day')
|
||||
->orderBy('day', 'ASC')
|
||||
->setParameter('sourceId', $query->sourceId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('sourceId', $query->sourceId->toRfc4122())
|
||||
->setParameter('start', $dateRange->start, ParameterType::INTEGER)
|
||||
->setParameter('end', $dateRange->end, ParameterType::INTEGER)
|
||||
->enableResultCache(new QueryCacheProfile(SourceCacheAttributes::CACHE_TTL, $cacheKey));
|
||||
@@ -120,11 +122,11 @@ final readonly class GetSourceDetailsDbalHandler implements GetSourceDetailsHand
|
||||
{
|
||||
$cacheKey = SourceCacheAttributes::CATEGORIES->withId($query->sourceId->toString());
|
||||
$qb = $this->connection->createQueryBuilder()
|
||||
->select('a.categories')
|
||||
->select("array_to_string(a.categories, ',') AS categories")
|
||||
->from('article', 'a')
|
||||
->innerJoin('a', 'source', 's', 'a.source_id = s.id')
|
||||
->where('s.id = :sourceId')
|
||||
->setParameter('sourceId', $query->sourceId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('sourceId', $query->sourceId->toRfc4122())
|
||||
->enableResultCache(new QueryCacheProfile(SourceCacheAttributes::CACHE_TTL, $cacheKey));
|
||||
|
||||
try {
|
||||
|
||||
+1
-3
@@ -12,7 +12,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\Features\PaginationQuery;
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\DBAL\NoResult;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetSourceOverviewListDbalHandler.
|
||||
@@ -37,8 +36,7 @@ final readonly class GetSourceOverviewListDbalHandler implements GetSourceOvervi
|
||||
$qb = $this->addFollowedSourceExistsQuery($qb);
|
||||
|
||||
$qb->from('source', 's')
|
||||
->groupBy('s.name')
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $query->userId->toRfc4122())
|
||||
;
|
||||
|
||||
$qb = $this->applyCursorPagination($qb, $query->page, new PaginatorKeyset('s.id', 's.created_at'));
|
||||
|
||||
+8
-6
@@ -22,7 +22,7 @@ trait ArticleQuery
|
||||
'a.id as article_id',
|
||||
'a.title as article_title',
|
||||
'a.link as article_link',
|
||||
'a.categories as article_categories',
|
||||
"array_to_string(a.categories, ',') as article_categories",
|
||||
'a.excerpt as article_excerpt',
|
||||
'a.published_at as article_published_at',
|
||||
'a.image as article_image',
|
||||
@@ -36,7 +36,7 @@ trait ArticleQuery
|
||||
'a.id as article_id',
|
||||
'a.title as article_title',
|
||||
'a.link as article_link',
|
||||
'a.categories as article_categories',
|
||||
"array_to_string(a.categories, ',') as article_categories",
|
||||
'a.body as article_body',
|
||||
'a.hash as article_hash',
|
||||
'a.published_at as article_published_at',
|
||||
@@ -62,17 +62,19 @@ trait ArticleQuery
|
||||
private function applyArticleFilters(QueryBuilder $qb, ArticleFilters $filters): QueryBuilder
|
||||
{
|
||||
if ($filters->category !== null) {
|
||||
$qb->andWhere('a.categories LIKE :category')
|
||||
->setParameter('category', sprintf('%%%s%%', $filters->category));
|
||||
// PostgreSQL array containment for single value
|
||||
$qb->andWhere(':category = ANY(a.categories)')
|
||||
->setParameter('category', $filters->category);
|
||||
}
|
||||
|
||||
if ($filters->search !== null) {
|
||||
$qb->andWhere('a.title LIKE :search')
|
||||
// Case-insensitive search in PostgreSQL
|
||||
$qb->andWhere('a.title ILIKE :search')
|
||||
->setParameter('search', sprintf('%%%s%%', $filters->search));
|
||||
}
|
||||
|
||||
if ($filters->dateRange instanceof DateRange) {
|
||||
$qb->andWhere('a.published_at BETWEEN FROM_UNIXTIME(:start) AND FROM_UNIXTIME(:end)')
|
||||
$qb->andWhere('a.published_at BETWEEN to_timestamp(:start) AND to_timestamp(:end)')
|
||||
->setParameter('start', $filters->dateRange->start, ParameterType::INTEGER)
|
||||
->setParameter('end', $filters->dateRange->end, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ trait SourceQuery
|
||||
"CONCAT('https://devscast.org/images/sources/', s.name, '.png') as source_image",
|
||||
'COUNT(a.hash) AS articles_count',
|
||||
'MAX(a.crawled_at) AS source_crawled_at',
|
||||
'COUNT(CASE WHEN a.metadata IS NOT NULL THEN 1 ELSE NULL END) AS articles_metadata_available',
|
||||
'COUNT(*) FILTER (WHERE a.metadata IS NOT NULL) AS articles_metadata_available',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -9,7 +9,6 @@ use Basango\FeedManagement\Domain\Model\Entity\FollowedSource;
|
||||
use Basango\FeedManagement\Domain\Model\Repository\FollowedSourceRepository;
|
||||
use Basango\IdentityAndAccess\Domain\Model\Identity\UserId;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
@@ -41,10 +40,10 @@ final class FollowedSourceOrmRepository extends ServiceEntityRepository implemen
|
||||
public function getByUserId(UserId $userId, SourceId $sourceId): ?FollowedSource
|
||||
{
|
||||
return $this->createQueryBuilder('fs')
|
||||
->andWhere('fs.follower = :userId')
|
||||
->andWhere('fs.source = :sourceId')
|
||||
->setParameter('sourceId', $sourceId->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('userId', $userId->toBinary(), ParameterType::BINARY)
|
||||
->andWhere('IDENTITY(fs.follower) = :userId')
|
||||
->andWhere('IDENTITY(fs.source) = :sourceId')
|
||||
->setParameter('sourceId', $sourceId->toRfc4122())
|
||||
->setParameter('userId', $userId->toRfc4122())
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class User
|
||||
|
||||
public readonly UserId $id;
|
||||
|
||||
private function __construct(
|
||||
public function __construct(
|
||||
private(set) string $name,
|
||||
private(set) EmailAddress $email,
|
||||
private(set) Roles $roles,
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ use Basango\IdentityAndAccess\Application\UseCase\Query\GetUserProfile;
|
||||
use Basango\IdentityAndAccess\Application\UseCase\QueryHandler\GetUserProfileHandler;
|
||||
use Basango\IdentityAndAccess\Domain\Exception\UserNotFound;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/**
|
||||
* Class GetUserProfileDbalHandler.
|
||||
@@ -35,7 +34,7 @@ final readonly class GetUserProfileDbalHandler implements GetUserProfileHandler
|
||||
)
|
||||
->from('user', 'u')
|
||||
->where('u.id = :userId')
|
||||
->setParameter('userId', $query->userId->toBinary(), ParameterType::BINARY);
|
||||
->setParameter('userId', $query->userId->toRfc4122());
|
||||
|
||||
/** @var array<string, mixed>|false $data */
|
||||
$data = $qb->executeQuery()->fetchAssociative();
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ final class LoginAttemptOrmRepository extends ServiceEntityRepository implements
|
||||
$this->createQueryBuilder('la')
|
||||
->delete(LoginAttempt::class, 'la')
|
||||
->where('la.user = :user')
|
||||
->setParameter('user', $user->id->toBinary())
|
||||
->setParameter('user', $user)
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
|
||||
+3
-4
@@ -8,7 +8,6 @@ use Basango\SharedKernel\Domain\Model\Pagination\Page;
|
||||
use Basango\SharedKernel\Domain\Model\Pagination\PaginationCursor;
|
||||
use Basango\SharedKernel\Domain\Model\Pagination\PaginationInfo;
|
||||
use Basango\SharedKernel\Domain\Model\Pagination\PaginatorKeyset;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
|
||||
/**
|
||||
@@ -41,12 +40,12 @@ trait PaginationQuery
|
||||
if ($keyset->date === null) {
|
||||
$qb
|
||||
->andWhere(sprintf('%s <= :cursorLastId', $keyset->id))
|
||||
->setParameter('cursorLastId', $cursor->id->toString(), ParameterType::BINARY);
|
||||
->setParameter('cursorLastId', $cursor->id->toRfc4122());
|
||||
} else {
|
||||
$qb
|
||||
->andWhere(sprintf('(%s, %s) <= (:cursorLastDate, :cursorLastId)', $keyset->date, $keyset->id))
|
||||
->setParameter('cursorLastDate', $cursor->id->toBinary(), ParameterType::BINARY)
|
||||
->setParameter('cursorLastId', $cursor->date->format('Y-m-d H:i:s'));
|
||||
->setParameter('cursorLastDate', $cursor->date->format('Y-m-d H:i:s'))
|
||||
->setParameter('cursorLastId', $cursor->id->toRfc4122());
|
||||
}
|
||||
|
||||
return $qb->setMaxResults($page->limit + 1);
|
||||
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Basango\SharedKernel\Infrastructure\Persistence\Doctrine\Importer;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Statement;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Generator;
|
||||
use PDO;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* ImportEngine: unified, naming-accurate API for migrating data
|
||||
* from a source database (old MariaDB over PDO) to a target database
|
||||
* (new PostgreSQL via Doctrine DBAL/ORM).
|
||||
*
|
||||
* - Source: MariaDB/MySQL via PDO (unbuffered)
|
||||
* - Target: PostgreSQL via Doctrine DBAL/ORM
|
||||
*
|
||||
* Memory tactics:
|
||||
* - Reuse a fixed-size params array for inserts (no per-row allocations)
|
||||
* - Stream source rows unbuffered; close cursor in finally
|
||||
* - Batch transactions; commit regularly
|
||||
* - Disable DBAL middlewares/loggers; disable PDO emulate prepares
|
||||
* - Periodic gc_collect_cycles() on long runs
|
||||
*/
|
||||
final readonly class ImportEngine
|
||||
{
|
||||
/**
|
||||
* Columns to ignore per target table.
|
||||
* Key = normalized table name (lowercase, unquoted),
|
||||
* Value = list of column names to exclude from insert.
|
||||
*/
|
||||
private const array IGNORE_COLUMNS = [
|
||||
'article' => ['tsv', 'image', 'excerpt'],
|
||||
];
|
||||
|
||||
private Connection $targetConnection;
|
||||
|
||||
private PDO $sourceConnection;
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
#[Autowire(env: 'SOURCE_DATABASE_HOST')] private string $host,
|
||||
#[Autowire(env: 'SOURCE_DATABASE_USER')] private string $user,
|
||||
#[Autowire(env: 'SOURCE_DATABASE_PASS')] private string $pass,
|
||||
#[Autowire(env: 'SOURCE_DATABASE_PORT')] private int $port = 3306,
|
||||
#[Autowire(env: 'SOURCE_DATABASE_NAME')] private string $name = 'app',
|
||||
) {
|
||||
// Target (PostgreSQL via Doctrine DBAL)
|
||||
$this->targetConnection = $this->em->getConnection();
|
||||
$this->targetConnection->getConfiguration()->setMiddlewares([]);
|
||||
|
||||
// If DBAL exposes a native PDO, harden it for low memory
|
||||
try {
|
||||
$native = $this->targetConnection->getNativeConnection();
|
||||
if ($native instanceof PDO) {
|
||||
// Use server-side prepares; avoids driver-side buffering
|
||||
$native->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
$native->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
|
||||
$native->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// If the platform/driver doesn’t expose a PDO, ignore safely
|
||||
}
|
||||
|
||||
// Source (MariaDB/MySQL via PDO), unbuffered
|
||||
$this->sourceConnection = new PDO(
|
||||
dsn: sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $this->host, $this->port, $this->name),
|
||||
username: $this->user,
|
||||
password: $this->pass,
|
||||
options: [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
// Unbuffered cursor (critical for memory)
|
||||
if (defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) {
|
||||
$this->sourceConnection->setAttribute(constant('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), false);
|
||||
}
|
||||
}
|
||||
|
||||
public function import(string $table, int $batchSize = 1000): int
|
||||
{
|
||||
$this->reset($table);
|
||||
$rows = $this->copy($table);
|
||||
return $this->paste($table, $rows, $batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate target table safely with replication role toggling.
|
||||
*/
|
||||
private function reset(string $tableName): void
|
||||
{
|
||||
$platform = $this->targetConnection->getDatabasePlatform();
|
||||
$this->targetConnection->beginTransaction();
|
||||
|
||||
try {
|
||||
$this->targetConnection->executeStatement("SET session_replication_role = 'replica'");
|
||||
$sql = $platform->getTruncateTableSQL($tableName, true);
|
||||
$this->targetConnection->executeStatement($sql);
|
||||
$this->targetConnection->executeStatement("SET session_replication_role = 'origin'");
|
||||
$this->targetConnection->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($this->targetConnection->isTransactionActive()) {
|
||||
$this->targetConnection->rollBack();
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream rows from MySQL unbuffered; ensure cursor is always closed.
|
||||
*/
|
||||
private function copy(string $table): iterable
|
||||
{
|
||||
$sql = sprintf('SELECT * FROM `%s`', str_replace('`', '', $table));
|
||||
$stmt = $this->sourceConnection->query($sql);
|
||||
|
||||
if ($stmt === false) {
|
||||
// Return an empty iterable on failure
|
||||
return [];
|
||||
}
|
||||
|
||||
return (function () use ($stmt): Generator {
|
||||
try {
|
||||
while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
|
||||
yield $row;
|
||||
}
|
||||
} finally {
|
||||
// Free server resources ASAP
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert rows into PostgreSQL with minimal allocations.
|
||||
* - Fixed-size $params array reused per row
|
||||
* - Batch transactions to limit peak memory
|
||||
* - Periodic GC for long streams
|
||||
*/
|
||||
private function paste(string $table, iterable $rows, int $batchSize = 1000): int
|
||||
{
|
||||
if ($batchSize <= 0) {
|
||||
$batchSize = 1000;
|
||||
}
|
||||
|
||||
$platform = $this->targetConnection->getDatabasePlatform();
|
||||
$quote = static fn (string|int $id) => $platform->quoteIdentifier((string) $id);
|
||||
|
||||
$ignored = $this->ignoredColumnsFor($table);
|
||||
$ignoredFlip = $ignored !== [] ? array_flip($ignored) : [];
|
||||
|
||||
$columns = null;
|
||||
$statement = null;
|
||||
$params = null; // fixed-size, reused
|
||||
$total = 0;
|
||||
$inBatch = 0;
|
||||
|
||||
try {
|
||||
foreach ($rows as $row) {
|
||||
// Build statement on first row (after ignoring columns)
|
||||
if ($columns === null) {
|
||||
if ($ignoredFlip !== []) {
|
||||
$row = array_diff_key($row, $ignoredFlip);
|
||||
}
|
||||
|
||||
/** @var list<string> $columns */
|
||||
$columns = array_map(static fn (int|string $k): string => (string) $k, array_keys($row));
|
||||
$columnList = implode(', ', array_map($quote, $columns));
|
||||
$placeholders = implode(', ', array_fill(0, count($columns), '?'));
|
||||
$sql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $quote($table), $columnList, $placeholders);
|
||||
$statement = $this->targetConnection->prepare($sql);
|
||||
|
||||
// Allocate params array once, with fixed size
|
||||
$params = array_fill(0, count($columns), null);
|
||||
|
||||
// Begin first batch transaction
|
||||
$this->targetConnection->beginTransaction();
|
||||
}
|
||||
|
||||
// Fill params by index (avoid per-row array allocs)
|
||||
$i = 0;
|
||||
foreach ($columns as $col) {
|
||||
$val = $row[$col] ?? null;
|
||||
|
||||
if ($val !== null) {
|
||||
// Convert BINARY(16) UUIDs to canonical RFC4122
|
||||
if ($col === 'id' || str_ends_with((string) $col, '_id')) {
|
||||
$params[$i++] = Uuid::fromBinary($val)->toRfc4122();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert invalid date to now()
|
||||
if (str_ends_with((string) $col, '_at') && $val === '0000-00-00 00:00:00') {
|
||||
$val = new \DateTimeImmutable('now')->format('Y-m-d H:i:s');
|
||||
$params[$i++] = $val;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert categories to PG text[] literal cheaply
|
||||
if ($col === 'categories') {
|
||||
if (is_string($val)) {
|
||||
$val = $this->ensureUtf8String($val);
|
||||
}
|
||||
|
||||
$params[$i++] = sprintf('{%s}', $val);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_string($val)) {
|
||||
$params[$i++] = $this->ensureUtf8String($val);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$params[$i++] = $val;
|
||||
}
|
||||
|
||||
if (! $statement instanceof Statement) {
|
||||
throw new \LogicException('Insert statement not initialized.');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$statement->executeStatement($params);
|
||||
$total++;
|
||||
$inBatch++;
|
||||
|
||||
if ($inBatch >= $batchSize) {
|
||||
$this->targetConnection->commit();
|
||||
$inBatch = 0;
|
||||
|
||||
// Start next batch transaction
|
||||
$this->targetConnection->beginTransaction();
|
||||
|
||||
// Help GC on very long imports
|
||||
if (($total % ($batchSize * 5)) === 0) {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit trailing rows if any
|
||||
if ($inBatch > 0 && $this->targetConnection->isTransactionActive()) {
|
||||
$this->targetConnection->commit();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($this->targetConnection->isTransactionActive()) {
|
||||
$this->targetConnection->rollBack();
|
||||
}
|
||||
|
||||
// Keep failure payloads small to avoid memory spikes
|
||||
throw $e;
|
||||
} finally {
|
||||
// Release large references promptly
|
||||
$statement = null;
|
||||
$columns = null;
|
||||
$params = null;
|
||||
gc_collect_cycles();
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function ignoredColumnsFor(string $table): array
|
||||
{
|
||||
$normalized = strtolower(trim($table, '`"'));
|
||||
return self::IGNORE_COLUMNS[$normalized] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep it cheap: fast-path valid UTF-8; otherwise minimal conversions.
|
||||
*/
|
||||
private function ensureUtf8String(string $value): string
|
||||
{
|
||||
// Fast path: valid UTF-8
|
||||
if (@preg_match('//u', $value) === 1) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Try common legacy encodings with transliteration
|
||||
$converted = @iconv('CP1252', 'UTF-8//TRANSLIT', $value);
|
||||
if ($converted === false) {
|
||||
$converted = @iconv('ISO-8859-1', 'UTF-8//TRANSLIT', $value);
|
||||
}
|
||||
|
||||
if ($converted === false) {
|
||||
// Last resort: drop invalid sequences
|
||||
$converted = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
|
||||
}
|
||||
|
||||
return $converted !== false ? $converted : $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Basango\SharedKernel\Presentation\Console;
|
||||
|
||||
use Basango\SharedKernel\Infrastructure\Persistence\Doctrine\Importer\ImportEngine;
|
||||
use Override;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:sync-import',
|
||||
description: 'from mariadb to postgres'
|
||||
)]
|
||||
class SyncImport extends Command
|
||||
{
|
||||
use AskArgumentFeature;
|
||||
|
||||
private SymfonyStyle $io;
|
||||
|
||||
public function __construct(
|
||||
private readonly ImportEngine $importEngine
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (! $this->io->confirm('Do you want to continue?', false)) {
|
||||
$this->io->warning('Process aborted');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$tables = ['user', 'source', 'article'];
|
||||
foreach ($tables as $table) {
|
||||
$count = $this->importEngine->import($table);
|
||||
$this->io->text(sprintf('Imported %d records into %s table.', $count, $table));
|
||||
}
|
||||
|
||||
$this->io->success('Source add successfully');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user