diff --git a/.env.example b/.env.example index 5d70b7b..74ad02d 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index d858f07..8e11448 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app/Http/Controllers/CatalogController.php b/app/Http/Controllers/CatalogController.php index 32ea888..61b635f 100644 --- a/app/Http/Controllers/CatalogController.php +++ b/app/Http/Controllers/CatalogController.php @@ -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, ]); } diff --git a/app/Services/FluxCatalogService.php b/app/Services/FluxCatalogService.php index 3bd420d..0225ded 100644 --- a/app/Services/FluxCatalogService.php +++ b/app/Services/FluxCatalogService.php @@ -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, fromCache: bool, updatedAt: ?Carbon} + * @return list */ 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 []; } } } diff --git a/config/database.php b/config/database.php index 6ba6aa4..abbb88e 100644 --- a/config/database.php +++ b/config/database.php @@ -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'), diff --git a/docs/bloomfluxdb-setup.sql b/docs/bloomfluxdb-setup.sql deleted file mode 100644 index 1257ecd..0000000 --- a/docs/bloomfluxdb-setup.sql +++ /dev/null @@ -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; diff --git a/docs/screenshots/catalog-light.png b/docs/screenshots/catalog-light.png index f1d4d9d..51a6f48 100644 Binary files a/docs/screenshots/catalog-light.png and b/docs/screenshots/catalog-light.png differ diff --git a/public/css/app.css b/public/css/app.css index ca484fa..d01fde8 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -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; diff --git a/resources/data/bloomflux.json b/resources/data/bloomflux.json new file mode 100644 index 0000000..c4b5edc --- /dev/null +++ b/resources/data/bloomflux.json @@ -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." } + ] +} diff --git a/resources/views/catalog.blade.php b/resources/views/catalog.blade.php index f855993..43a984d 100644 --- a/resources/views/catalog.blade.php +++ b/resources/views/catalog.blade.php @@ -10,26 +10,14 @@

Catalogue de flux

- 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.

- @if ($fromCache) -
- ⚠ Serveur catalogue injoignable — liste servie depuis le cache local - @if ($updatedAt) - (mise à jour {{ $updatedAt->diffForHumans() }}) - @endif -
- @endif - @if ($feeds->isEmpty())
Le catalogue est vide pour le moment. - @if ($fromCache) - Il sera récupéré automatiquement dès que le serveur catalogue sera joignable. - @endif
@else