BloomFeed Bash Script Upgrade v0.2

This commit is contained in:
Esteban
2026-07-03 22:23:25 +02:00
parent 2f86a6c1e3
commit d73c25cbbb
10 changed files with 68 additions and 179 deletions
-8
View File
@@ -30,14 +30,6 @@ DB_PASSWORD=
# Utilisé par docker-compose pour initialiser MariaDB (doit correspondre à DB_PASSWORD ci-dessus)
DB_ROOT_PASSWORD=
# Catalogue central BloomFluxDB (lecture seule)
BLOOMFLUX_DB_HOST=163.5.159.95
BLOOMFLUX_DB_PORT=3306
BLOOMFLUX_DB_DATABASE=bloomfluxdb
BLOOMFLUX_DB_USERNAME=bloomflux_reader
BLOOMFLUX_DB_PASSWORD=BloomFlux_Public_Read_2026
BLOOMFLUX_DB_TIMEOUT=4
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
+10 -14
View File
@@ -21,8 +21,8 @@ n'a qu'un seul compte propriétaire, créé une seule fois au premier lancement.
- Lecture approfondie intégrée : extraction du contenu principal (via
[Readability](https://github.com/fivefilters/readability.php)) converti en Markdown, sans
quitter BloomFeed.
- Catalogue de flux façon "market" alimenté par **BloomFluxDB** (base centrale en lecture seule),
avec cache local de secours si le serveur catalogue est injoignable.
- Catalogue de flux façon "market" : une sélection de flux embarquée avec l'application
([resources/data/bloomflux.json](resources/data/bloomflux.json)), à ajouter en un clic.
- Récupération périodique des flux via une tâche planifiée.
- Thème clair/sombre.
- Inscription fermée après la création du compte propriétaire — pas de SaaS ouvert.
@@ -80,23 +80,19 @@ Pour désinstaller proprement (avec confirmation avant toute suppression de donn
bash uninstall.sh
```
## BloomFluxDB (catalogue central)
## Catalogue de flux (BloomFlux)
Les instances BloomFeed consultent en lecture seule une base MariaDB centrale contenant une
liste de flux pré-sélectionnés, affichée dans l'onglet **Catalogue**. La liste est mise en cache
localement : si le serveur catalogue est injoignable, la dernière copie connue est servie, et
elle se rafraîchit automatiquement à la prochaine consultation réussie.
L'onglet **Catalogue** propose une sélection de flux pré-faits (actualités, tech, développement,
IT/sysadmin, sécurité, sciences…) à ajouter en un clic. La liste vit dans
[resources/data/bloomflux.json](resources/data/bloomflux.json), versionnée avec le code : pour
l'enrichir, éditez le fichier et déployez — aucun service externe n'est nécessaire.
Le script de création (base, table, utilisateur `bloomflux_reader` en `SELECT` uniquement, et
liste de flux initiale) est dans [docs/bloomfluxdb-setup.sql](docs/bloomfluxdb-setup.sql) — à
exécuter sur le serveur catalogue :
Format d'une entrée :
```bash
mysql -u root -p < docs/bloomfluxdb-setup.sql
```json
{ "title": "Nom du flux", "url": "https://exemple.com/rss.xml", "category": "Tech", "lang": "fr", "description": "Courte description." }
```
La connexion se configure dans le `.env` de chaque instance via les variables `BLOOMFLUX_DB_*`.
## Licence
MIT.
+1 -5
View File
@@ -10,14 +10,10 @@ class CatalogController extends Controller
{
public function index(Request $request, FluxCatalogService $catalog): View
{
$result = $catalog->getCatalog();
$subscribedUrls = $request->user()->feeds()->pluck('url')->all();
return view('catalog', [
'feeds' => collect($result['feeds'])->groupBy('category'),
'fromCache' => $result['fromCache'],
'updatedAt' => $result['updatedAt'],
'feeds' => collect($catalog->getCatalog())->groupBy('category'),
'subscribedUrls' => $subscribedUrls,
]);
}
+8 -33
View File
@@ -2,53 +2,28 @@
namespace App\Services;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Throwable;
class FluxCatalogService
{
private const CACHE_KEY = 'bloomflux.catalog';
private const CACHE_TIME_KEY = 'bloomflux.catalog_updated_at';
/**
* Retourne le catalogue BloomFluxDB, en le rafraîchissant depuis le serveur
* distant quand il est joignable, sinon en servant la dernière copie en cache.
* Retourne le catalogue de flux embarqué (resources/data/bloomflux.json).
* La liste est versionnée avec le code : la mettre à jour = éditer le fichier et commit.
*
* @return array{feeds: list<array{title: string, url: string, category: string, lang: string, description: ?string}>, fromCache: bool, updatedAt: ?Carbon}
* @return list<array{title: string, url: string, category: string, lang: string, description: ?string}>
*/
public function getCatalog(): array
{
try {
$rows = DB::connection('bloomflux')
->table('feeds')
->where('is_active', 1)
->orderBy('category')
->orderBy('title')
->get(['title', 'url', 'category', 'lang', 'description'])
->map(fn ($row) => (array) $row)
->all();
$raw = file_get_contents(resource_path('data/bloomflux.json'));
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
Cache::forever(self::CACHE_KEY, $rows);
Cache::forever(self::CACHE_TIME_KEY, now()->toIso8601String());
return ['feeds' => $rows, 'fromCache' => false, 'updatedAt' => now()];
return $data['feeds'] ?? [];
} catch (Throwable $e) {
Log::warning('BloomFluxDB unreachable, serving cached catalog', [
'error' => $e->getMessage(),
]);
Log::warning('Catalogue bloomflux.json illisible', ['error' => $e->getMessage()]);
$cached = Cache::get(self::CACHE_KEY, []);
$updatedAt = Cache::get(self::CACHE_TIME_KEY);
return [
'feeds' => $cached,
'fromCache' => true,
'updatedAt' => $updatedAt ? Carbon::parse($updatedAt) : null,
];
return [];
}
}
}
-18
View File
@@ -64,24 +64,6 @@ return [
]) : [],
],
// Catalogue central BloomFluxDB (lecture seule, distant) — timeout court
// pour ne jamais bloquer la page si le serveur catalogue est injoignable.
'bloomflux' => [
'driver' => 'mysql',
'host' => env('BLOOMFLUX_DB_HOST', '163.5.159.95'),
'port' => env('BLOOMFLUX_DB_PORT', '3306'),
'database' => env('BLOOMFLUX_DB_DATABASE', 'bloomfluxdb'),
'username' => env('BLOOMFLUX_DB_USERNAME', 'bloomflux_reader'),
'password' => env('BLOOMFLUX_DB_PASSWORD', 'BloomFlux_Public_Read_2026'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'options' => extension_loaded('pdo_mysql') ? [
\PDO::ATTR_TIMEOUT => (int) env('BLOOMFLUX_DB_TIMEOUT', 4),
] : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
-77
View File
@@ -1,77 +0,0 @@
/* ============================================================ */
/* BloomFluxDB — catalogue central de flux RSS pour les instances BloomFeed */
/* À exécuter sur le serveur catalogue (163.5.159.95), en root MariaDB/MySQL : */
/* mysql -u root -p < bloomfluxdb-setup.sql */
/* Prérequis réseau : MariaDB doit écouter sur l'extérieur */
/* (bind-address = 0.0.0.0 dans /etc/mysql/mariadb.conf.d/50-server.cnf, */
/* puis systemctl restart mariadb) et le port 3306 doit être ouvert. */
/* ============================================================ */
CREATE DATABASE IF NOT EXISTS bloomfluxdb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE bloomfluxdb;
CREATE TABLE IF NOT EXISTS feeds (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
url VARCHAR(2048) NOT NULL,
category VARCHAR(100) NOT NULL DEFAULT 'Divers',
lang CHAR(2) NOT NULL DEFAULT 'fr',
description VARCHAR(500) NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_feeds_url (url(500)),
KEY idx_feeds_category (category),
KEY idx_feeds_active (is_active)
) ENGINE = InnoDB;
/* ------------------------------------------------------------ */
/* Utilisateur en LECTURE SEULE pour les instances BloomFeed ('%' = accessible */
/* depuis n'importe quelle IP). Changez le mot de passe si vous voulez restreindre, */
/* et reportez-le dans le .env des instances (BLOOMFLUX_DB_PASSWORD). */
/* ------------------------------------------------------------ */
CREATE USER IF NOT EXISTS 'bloomflux_reader'@'%' IDENTIFIED BY 'BloomFlux_Public_Read_2026';
GRANT SELECT ON bloomfluxdb.feeds TO 'bloomflux_reader'@'%';
FLUSH PRIVILEGES;
/* ------------------------------------------------------------ */
/* Liste de flux pré-faite (extensible à volonté : INSERT depuis root/localhost) */
/* ------------------------------------------------------------ */
INSERT IGNORE INTO feeds (title, url, category, lang, description) VALUES
/* Actualités générales */
('Le Monde', 'https://www.lemonde.fr/rss/une.xml', 'Actualités', 'fr', 'La une du journal Le Monde.'),
('France Info', 'https://www.franceinfo.fr/titres.rss', 'Actualités', 'fr', 'Les titres de France Info en continu.'),
('Courrier International', 'https://www.courrierinternational.com/feed/all/rss.xml', 'Actualités', 'fr', 'L''actualité internationale vue par la presse étrangère.'),
('BBC News', 'https://feeds.bbci.co.uk/news/rss.xml', 'Actualités', 'en', 'Fil d''actualité mondial de la BBC.'),
/* Tech & informatique */
('Korben', 'https://korben.info/feed', 'Tech', 'fr', 'Actu tech, astuces et bidouilles.'),
('Numerama', 'https://www.numerama.com/feed/', 'Tech', 'fr', 'Société numérique, tech et sciences.'),
('Next', 'https://next.ink/feed/', 'Tech', 'fr', 'Actualité informatique et numérique de fond.'),
('Journal du hacker', 'https://www.journalduhacker.net/rss', 'Tech', 'fr', 'Liens tech partagés par la communauté francophone.'),
('Ars Technica', 'https://feeds.arstechnica.com/arstechnica/index', 'Tech', 'en', 'Analyses tech approfondies.'),
('Hacker News', 'https://news.ycombinator.com/rss', 'Tech', 'en', 'Les liens les plus discutés de la communauté HN.'),
('The Verge', 'https://www.theverge.com/rss/index.xml', 'Tech', 'en', 'Tech, science et culture numérique.'),
/* Linux & open source */
('Phoronix', 'https://www.phoronix.com/rss.php', 'Open source', 'en', 'Linux, matériel et open source.'),
('LinuxFr', 'https://linuxfr.org/news.atom', 'Open source', 'fr', 'L''actualité du logiciel libre en français.'),
('It''s FOSS', 'https://itsfoss.com/rss/', 'Open source', 'en', 'Tutoriels et actus Linux/open source.'),
/* Sécurité */
('CERT-FR', 'https://www.cert.ssi.gouv.fr/feed/', 'Sécurité', 'fr', 'Alertes et avis de sécurité de l''ANSSI.'),
('Krebs on Security', 'https://krebsonsecurity.com/feed/', 'Sécurité', 'en', 'Investigations cybersécurité de Brian Krebs.'),
('Zataz', 'https://www.zataz.com/feed/', 'Sécurité', 'fr', 'Actualité cybersécurité et fuites de données.'),
/* Développement */
('Dev.to', 'https://dev.to/feed', 'Développement', 'en', 'Articles de la communauté dev.to.'),
('GitHub Blog', 'https://github.blog/feed/', 'Développement', 'en', 'Nouveautés et ingénierie GitHub.'),
('Laravel News', 'https://feed.laravel-news.com/', 'Développement', 'en', 'L''écosystème Laravel au quotidien.'),
/* Sciences */
('Futura Sciences', 'https://www.futura-sciences.com/rss/actualites.xml', 'Sciences', 'fr', 'Actualités scientifiques tous domaines.'),
('CNRS Le Journal', 'https://lejournal.cnrs.fr/rss', 'Sciences', 'fr', 'Le journal de la recherche française.'),
/* Jeux vidéo */
('Gamekult', 'https://www.gamekult.com/feed.xml', 'Jeux vidéo', 'fr', 'Actualité et critiques jeux vidéo.'),
('Rock Paper Shotgun', 'https://www.rockpapershotgun.com/feed', 'Jeux vidéo', 'en', 'Jeux PC, tests et actus.');
/* Vérification rapide */
SELECT category, COUNT(*) AS nb FROM feeds GROUP BY category ORDER BY category;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 330 KiB

-11
View File
@@ -1008,17 +1008,6 @@ h1, h2, h3 {
flex-wrap: wrap;
}
.cache-notice {
background: var(--accent-soft);
border: 1px solid var(--accent-soft-strong);
border-radius: var(--radius-sm);
padding: 12px 16px;
font-size: var(--font-sm);
font-weight: 600;
color: var(--accent-strong);
animation: fade-up 350ms var(--ease-out) both;
}
.market-search {
font-size: var(--font-base);
padding: 13px 18px;
+48
View File
@@ -0,0 +1,48 @@
{
"version": 1,
"updated_at": "2026-07-03",
"feeds": [
{ "title": "Le Monde", "url": "https://www.lemonde.fr/rss/une.xml", "category": "Actualités", "lang": "fr", "description": "La une du journal Le Monde." },
{ "title": "France Info", "url": "https://www.franceinfo.fr/titres.rss", "category": "Actualités", "lang": "fr", "description": "Les titres de France Info en continu." },
{ "title": "Courrier International", "url": "https://www.courrierinternational.com/feed/all/rss.xml", "category": "Actualités", "lang": "fr", "description": "L'actualité internationale vue par la presse étrangère." },
{ "title": "BBC News", "url": "https://feeds.bbci.co.uk/news/rss.xml", "category": "Actualités", "lang": "en", "description": "Fil d'actualité mondial de la BBC." },
{ "title": "Korben", "url": "https://korben.info/feed", "category": "Tech", "lang": "fr", "description": "Actu tech, astuces et bidouilles." },
{ "title": "Numerama", "url": "https://www.numerama.com/feed/", "category": "Tech", "lang": "fr", "description": "Société numérique, tech et sciences." },
{ "title": "Next", "url": "https://next.ink/feed/", "category": "Tech", "lang": "fr", "description": "Actualité informatique et numérique de fond." },
{ "title": "Journal du hacker", "url": "https://www.journalduhacker.net/rss", "category": "Tech", "lang": "fr", "description": "Liens tech partagés par la communauté francophone." },
{ "title": "Ars Technica", "url": "https://feeds.arstechnica.com/arstechnica/index", "category": "Tech", "lang": "en", "description": "Analyses tech approfondies." },
{ "title": "Hacker News", "url": "https://news.ycombinator.com/rss", "category": "Tech", "lang": "en", "description": "Les liens les plus discutés de la communauté HN." },
{ "title": "The Verge", "url": "https://www.theverge.com/rss/index.xml", "category": "Tech", "lang": "en", "description": "Tech, science et culture numérique." },
{ "title": "Dev.to", "url": "https://dev.to/feed", "category": "Développement", "lang": "en", "description": "Articles de la communauté dev.to." },
{ "title": "GitHub Blog", "url": "https://github.blog/feed/", "category": "Développement", "lang": "en", "description": "Nouveautés et ingénierie GitHub." },
{ "title": "Laravel News", "url": "https://feed.laravel-news.com/", "category": "Développement", "lang": "en", "description": "L'écosystème Laravel au quotidien." },
{ "title": "Stack Overflow Blog", "url": "https://stackoverflow.blog/feed/", "category": "Développement", "lang": "en", "description": "Le blog officiel de Stack Overflow." },
{ "title": "freeCodeCamp", "url": "https://www.freecodecamp.org/news/rss/", "category": "Développement", "lang": "en", "description": "Tutoriels et articles pour apprendre à coder." },
{ "title": "CSS-Tricks", "url": "https://css-tricks.com/feed/", "category": "Développement", "lang": "en", "description": "Tout sur le CSS et le front-end." },
{ "title": "Smashing Magazine", "url": "https://www.smashingmagazine.com/feed/", "category": "Développement", "lang": "en", "description": "Design et développement web." },
{ "title": "Développez.com", "url": "https://www.developpez.com/index/rss", "category": "Développement", "lang": "fr", "description": "L'actualité des développeurs francophones." },
{ "title": "Human Coders News", "url": "https://news.humancoders.com/items/feed", "category": "Développement", "lang": "fr", "description": "Veille dev partagée par la communauté francophone." },
{ "title": "IT-Connect", "url": "https://www.it-connect.fr/feed/", "category": "IT / Sysadmin", "lang": "fr", "description": "Tutoriels et actus sysadmin, Windows et Linux." },
{ "title": "ZDNet France", "url": "https://www.zdnet.fr/feeds/rss/actualites/", "category": "IT / Sysadmin", "lang": "fr", "description": "L'actualité IT pour les professionnels." },
{ "title": "Silicon", "url": "https://www.silicon.fr/feed", "category": "IT / Sysadmin", "lang": "fr", "description": "L'actualité IT et cloud pour les DSI." },
{ "title": "The Register", "url": "https://www.theregister.com/headlines.atom", "category": "IT / Sysadmin", "lang": "en", "description": "L'IT sans langue de bois." },
{ "title": "ServeTheHome", "url": "https://www.servethehome.com/feed/", "category": "IT / Sysadmin", "lang": "en", "description": "Serveurs, stockage, réseau et homelab." },
{ "title": "Phoronix", "url": "https://www.phoronix.com/rss.php", "category": "Open source", "lang": "en", "description": "Linux, matériel et open source." },
{ "title": "LinuxFr", "url": "https://linuxfr.org/news.atom", "category": "Open source", "lang": "fr", "description": "L'actualité du logiciel libre en français." },
{ "title": "It's FOSS", "url": "https://itsfoss.com/rss/", "category": "Open source", "lang": "en", "description": "Tutoriels et actus Linux/open source." },
{ "title": "CERT-FR", "url": "https://www.cert.ssi.gouv.fr/feed/", "category": "Sécurité", "lang": "fr", "description": "Alertes et avis de sécurité de l'ANSSI." },
{ "title": "Krebs on Security", "url": "https://krebsonsecurity.com/feed/", "category": "Sécurité", "lang": "en", "description": "Investigations cybersécurité de Brian Krebs." },
{ "title": "Zataz", "url": "https://www.zataz.com/feed/", "category": "Sécurité", "lang": "fr", "description": "Actualité cybersécurité et fuites de données." },
{ "title": "Futura Sciences", "url": "https://www.futura-sciences.com/rss/actualites.xml", "category": "Sciences", "lang": "fr", "description": "Actualités scientifiques tous domaines." },
{ "title": "CNRS Le Journal", "url": "https://lejournal.cnrs.fr/rss", "category": "Sciences", "lang": "fr", "description": "Le journal de la recherche française." },
{ "title": "Gamekult", "url": "https://www.gamekult.com/feed.xml", "category": "Jeux vidéo", "lang": "fr", "description": "Actualité et critiques jeux vidéo." },
{ "title": "Rock Paper Shotgun", "url": "https://www.rockpapershotgun.com/feed", "category": "Jeux vidéo", "lang": "en", "description": "Jeux PC, tests et actus." }
]
}
+1 -13
View File
@@ -10,26 +10,14 @@
<div>
<h2 style="margin:0;">Catalogue de flux</h2>
<p class="text-muted text-sm" style="margin:4px 0 0;">
Une sélection de flux RSS maintenue par BloomFluxDB ajoutez-les à votre collection en un clic.
Une sélection de flux RSS livrée avec BloomFeed ajoutez-les à votre collection en un clic.
</p>
</div>
</div>
@if ($fromCache)
<div class="cache-notice">
Serveur catalogue injoignable liste servie depuis le cache local
@if ($updatedAt)
(mise à jour {{ $updatedAt->diffForHumans() }})
@endif
</div>
@endif
@if ($feeds->isEmpty())
<div class="card empty-state">
Le catalogue est vide pour le moment.
@if ($fromCache)
Il sera récupéré automatiquement dès que le serveur catalogue sera joignable.
@endif
</div>
@else
<input type="search" id="market-search" class="form-input market-search" placeholder="Rechercher un flux… (nom, description, catégorie)">