Upgrade of BloomFeed v0.1

This commit is contained in:
Esteban
2026-07-03 21:46:06 +02:00
parent 7a7440daab
commit c549e9cfc0
24 changed files with 591 additions and 6 deletions
Vendored
BIN
View File
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
+74
View File
@@ -0,0 +1,74 @@
APP_NAME=BloomFeed
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost:7081
APP_LOCALE=fr
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=fr_FR
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=bloomfeed
DB_USERNAME=bloomfeed
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
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
+8
View File
@@ -0,0 +1,8 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
+30
View File
@@ -0,0 +1,30 @@
*.log
.DS_Store
**/.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.claude
/.codex
/.cursor/
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/fonts-manifest.dev.json
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
_ide_helper.php
composer.phar
Homestead.json
Homestead.yaml
Thumbs.db
+22 -3
View File
@@ -21,6 +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 - Lecture approfondie intégrée : extraction du contenu principal (via
[Readability](https://github.com/fivefilters/readability.php)) converti en Markdown, sans [Readability](https://github.com/fivefilters/readability.php)) converti en Markdown, sans
quitter BloomFeed. 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.
- Récupération périodique des flux via une tâche planifiée. - Récupération périodique des flux via une tâche planifiée.
- Thème clair/sombre. - Thème clair/sombre.
- Inscription fermée après la création du compte propriétaire — pas de SaaS ouvert. - Inscription fermée après la création du compte propriétaire — pas de SaaS ouvert.
@@ -31,9 +33,9 @@ n'a qu'un seul compte propriétaire, créé une seule fois au premier lancement.
|---|---| |---|---|
| ![Tableau de bord](docs/screenshots/dashboard-light.png) | ![Lecteur intégré](docs/screenshots/reader-light.png) | | ![Tableau de bord](docs/screenshots/dashboard-light.png) | ![Lecteur intégré](docs/screenshots/reader-light.png) |
| Mode sombre | Connexion | | Catalogue de flux | Mode sombre |
|---|---| |---|---|
| ![Mode sombre](docs/screenshots/dashboard-dark.png) | ![Connexion](docs/screenshots/login-light.png) | | ![Catalogue de flux](docs/screenshots/catalog-light.png) | ![Mode sombre](docs/screenshots/dashboard-dark.png) |
## Stack technique ## Stack technique
@@ -63,7 +65,7 @@ planificateur, MariaDB). Voir la page **[/self-hosting](/self-hosting)** une foi
l'application lancée, ou directement : l'application lancée, ou directement :
```bash ```bash
git clone https://git.eldorianet.work/esteban/RSS-Eldoria-Network bloomfeed git clone https://git.eldorianet.work/esteban/BloomFeed-RSS bloomfeed
cd bloomfeed cd bloomfeed
bash scriptsite.sh bash scriptsite.sh
``` ```
@@ -78,6 +80,23 @@ Pour désinstaller proprement (avec confirmation avant toute suppression de donn
bash uninstall.sh bash uninstall.sh
``` ```
## BloomFluxDB (catalogue central)
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.
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 :
```bash
mysql -u root -p < docs/bloomfluxdb-setup.sql
```
La connexion se configure dans le `.env` de chaque instance via les variables `BLOOMFLUX_DB_*`.
## Licence ## Licence
MIT. MIT.
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Services\FluxCatalogService;
use Illuminate\Http\Request;
use Illuminate\View\View;
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'],
'subscribedUrls' => $subscribedUrls,
]);
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
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.
*
* @return array{feeds: list<array{title: string, url: string, category: string, lang: string, description: ?string}>, fromCache: bool, updatedAt: ?Carbon}
*/
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();
Cache::forever(self::CACHE_KEY, $rows);
Cache::forever(self::CACHE_TIME_KEY, now()->toIso8601String());
return ['feeds' => $rows, 'fromCache' => false, 'updatedAt' => now()];
} catch (Throwable $e) {
Log::warning('BloomFluxDB unreachable, serving cached catalog', [
'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,
];
}
}
}
+18
View File
@@ -64,6 +64,24 @@ 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' => [ 'mariadb' => [
'driver' => 'mariadb', 'driver' => 'mariadb',
'url' => env('DB_URL'), 'url' => env('DB_URL'),
+78
View File
@@ -0,0 +1,78 @@
-- ============================================================
-- 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.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

After

Width:  |  Height:  |  Size: 751 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 322 KiB

+142
View File
@@ -998,6 +998,148 @@ h1, h2, h3 {
background: var(--accent-soft); background: var(--accent-soft);
} }
/* ---------- Catalogue (market) ---------- */
.market-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: var(--space-3);
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;
border-radius: var(--radius);
}
.market-chips {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
}
.chip {
background: var(--surface);
border: 1.5px solid var(--border);
border-radius: var(--radius-full);
padding: 6px 15px;
font-size: var(--font-xs);
font-weight: 700;
color: var(--text-muted);
cursor: pointer;
font-family: inherit;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.chip:hover {
border-color: var(--accent);
color: var(--accent-strong);
transform: translateY(-1px);
}
.chip.active {
background: var(--gradient-accent);
border-color: transparent;
color: var(--accent-contrast);
box-shadow: var(--shadow-sm);
}
.chip-static {
cursor: default;
background: var(--accent-soft);
border-color: transparent;
color: var(--accent-strong);
font-size: 10px;
padding: 3px 10px;
}
.chip-static:hover {
transform: none;
color: var(--accent-strong);
border-color: transparent;
}
.market-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
gap: var(--space-4);
}
.market-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-sm);
padding: var(--space-4) var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-2);
animation: fade-up 450ms var(--ease-out) both;
transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
}
.market-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--accent-soft-strong);
}
.market-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
}
.market-card-title {
font-weight: 700;
font-size: var(--font-base);
}
.market-card-desc {
font-size: var(--font-sm);
color: var(--text-muted);
margin: 0;
flex: 1;
overflow-wrap: anywhere;
}
.market-card-foot {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: var(--space-1);
}
.market-lang {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.08em;
color: var(--text-faint);
border: 1px solid var(--border);
border-radius: 6px;
padding: 2px 6px;
}
.market-added {
color: var(--green);
font-size: var(--font-sm);
font-weight: 700;
}
/* ---------- Responsive ---------- */ /* ---------- Responsive ---------- */
@media (max-width: 720px) { @media (max-width: 720px) {
+45
View File
@@ -31,4 +31,49 @@
event.preventDefault(); event.preventDefault();
} }
}); });
// Catalogue : recherche + filtre par catégorie côté client
document.addEventListener('DOMContentLoaded', function () {
var grid = document.getElementById('market-grid');
if (!grid) {
return;
}
var search = document.getElementById('market-search');
var chips = document.getElementById('market-chips');
var emptyNotice = document.getElementById('market-empty');
var activeCategory = '';
function applyFilters() {
var query = (search.value || '').toLowerCase().trim();
var visible = 0;
grid.querySelectorAll('.market-card').forEach(function (card) {
var matchesCategory = !activeCategory || card.getAttribute('data-category') === activeCategory;
var matchesQuery = !query || card.getAttribute('data-search').indexOf(query) !== -1;
var show = matchesCategory && matchesQuery;
card.hidden = !show;
if (show) {
visible++;
}
});
emptyNotice.hidden = visible > 0;
}
search.addEventListener('input', applyFilters);
chips.addEventListener('click', function (event) {
var chip = event.target.closest('.chip');
if (!chip) {
return;
}
chips.querySelectorAll('.chip').forEach(function (c) {
c.classList.remove('active');
});
chip.classList.add('active');
activeCategory = chip.getAttribute('data-category');
applyFilters();
});
});
})(); })();
+74
View File
@@ -0,0 +1,74 @@
@extends('layouts.app', ['title' => 'Catalogue — BloomFeed'])
@section('content')
<div class="main-column" style="width:100%;">
@if (session('status'))
<div class="form-status">{{ session('status') }}</div>
@endif
<div class="market-header">
<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.
</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)">
<div class="market-chips" id="market-chips">
<button type="button" class="chip active" data-category="">Tout</button>
@foreach ($feeds->keys() as $category)
<button type="button" class="chip" data-category="{{ $category }}">{{ $category }}</button>
@endforeach
</div>
<div class="market-grid" id="market-grid">
@foreach ($feeds as $category => $items)
@foreach ($items as $item)
<div class="market-card" data-category="{{ $category }}" data-search="{{ Str::lower($item['title'].' '.$item['description'].' '.$category) }}">
<div class="market-card-head">
<span class="market-card-title">{{ $item['title'] }}</span>
<span class="chip chip-static">{{ $category }}</span>
</div>
<p class="market-card-desc">{{ $item['description'] ?: $item['url'] }}</p>
<div class="market-card-foot">
<span class="market-lang">{{ strtoupper($item['lang']) }}</span>
@if (in_array($item['url'], $subscribedUrls))
<span class="market-added"> Ajouté</span>
@else
<form method="POST" action="{{ route('feeds.store') }}">
@csrf
<input type="hidden" name="url" value="{{ $item['url'] }}">
<input type="hidden" name="title" value="{{ $item['title'] }}">
<button type="submit" class="btn btn-primary btn-sm">+ Ajouter</button>
</form>
@endif
</div>
</div>
@endforeach
@endforeach
</div>
<div class="card empty-state" id="market-empty" hidden>Aucun flux ne correspond à votre recherche.</div>
@endif
</div>
@endsection
+1 -1
View File
@@ -43,7 +43,7 @@
<footer class="site-footer"> <footer class="site-footer">
<span>BloomFeed projet open-source, auto-hébergé.</span> <span>BloomFeed projet open-source, auto-hébergé.</span>
<a href="https://git.eldorianet.work/esteban/RSS-Eldoria-Network" target="_blank" rel="noopener">Code source</a> <a href="https://git.eldorianet.work/esteban/BloomFeed-RSS" target="_blank" rel="noopener">Code source</a>
</footer> </footer>
<script src="{{ asset('js/theme-toggle.js') }}" defer></script> <script src="{{ asset('js/theme-toggle.js') }}" defer></script>
@@ -5,6 +5,7 @@
</a> </a>
<div class="navbar-links"> <div class="navbar-links">
<a href="{{ route('dashboard') }}" class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">Articles</a> <a href="{{ route('dashboard') }}" class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">Articles</a>
<a href="{{ route('catalog') }}" class="{{ request()->routeIs('catalog') ? 'active' : '' }}">Catalogue</a>
<a href="{{ route('feeds.index') }}" class="{{ request()->routeIs('feeds.*') ? 'active' : '' }}">Mes flux</a> <a href="{{ route('feeds.index') }}" class="{{ request()->routeIs('feeds.*') ? 'active' : '' }}">Mes flux</a>
<button type="button" class="theme-toggle" data-theme-toggle title="Changer de thème"></button> <button type="button" class="theme-toggle" data-theme-toggle title="Changer de thème"></button>
<form method="POST" action="{{ route('logout') }}"> <form method="POST" action="{{ route('logout') }}">
+1 -1
View File
@@ -51,7 +51,7 @@
<div class="card mt-2"> <div class="card mt-2">
<h2>Code source</h2> <h2>Code source</h2>
<p> <p>
<a href="https://git.eldorianet.work/esteban/RSS-Eldoria-Network" target="_blank" rel="noopener">git.eldorianet.work/esteban/RSS-Eldoria-Network</a> <a href="https://git.eldorianet.work/esteban/BloomFeed-RSS" target="_blank" rel="noopener">git.eldorianet.work/esteban/BloomFeed-RSS</a>
</p> </p>
</div> </div>
</section> </section>
+3
View File
@@ -2,6 +2,7 @@
use App\Http\Controllers\ArticleController; use App\Http\Controllers\ArticleController;
use App\Http\Controllers\AuthController; use App\Http\Controllers\AuthController;
use App\Http\Controllers\CatalogController;
use App\Http\Controllers\DashboardController; use App\Http\Controllers\DashboardController;
use App\Http\Controllers\FeedController; use App\Http\Controllers\FeedController;
use App\Http\Controllers\PageController; use App\Http\Controllers\PageController;
@@ -22,6 +23,8 @@ Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/catalog', [CatalogController::class, 'index'])->name('catalog');
Route::get('/feeds', [FeedController::class, 'index'])->name('feeds.index'); Route::get('/feeds', [FeedController::class, 'index'])->name('feeds.index');
Route::post('/feeds', [FeedController::class, 'store'])->name('feeds.store'); Route::post('/feeds', [FeedController::class, 'store'])->name('feeds.store');
Route::delete('/feeds/{feed}', [FeedController::class, 'destroy'])->name('feeds.destroy'); Route::delete('/feeds/{feed}', [FeedController::class, 'destroy'])->name('feeds.destroy');
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
REPO_URL="https://git.eldorianet.work/esteban/RSS-Eldoria-Network" REPO_URL="https://git.eldorianet.work/esteban/BloomFeed-RSS"
APP_DIR="/opt/bloomfeed" APP_DIR="/opt/bloomfeed"
HOST_PORT="7081" HOST_PORT="7081"