- PHP 51%
- CSS 17.5%
- HTML 16.7%
- JavaScript 12.7%
- Shell 1.5%
- Other 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| assets | ||
| data | ||
| deploy | ||
| docker | ||
| includes | ||
| 404.html | ||
| about.html | ||
| admin.php | ||
| aggregated-feed.php | ||
| api.php | ||
| apple-touch-icon.png | ||
| backup-data.php | ||
| badge.svg | ||
| blog.php | ||
| Caddyfile | ||
| cron-scan.php | ||
| DEPLOY-DOCKER.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| favicon.ico | ||
| feed.atom.php | ||
| feed.json.php | ||
| feed.xml.php | ||
| index.html | ||
| latest.php | ||
| LICENSE | ||
| og-image.png | ||
| opml.php | ||
| privacy.html | ||
| random.php | ||
| README.md | ||
| refresh-articles.php | ||
| robots.txt | ||
| send-queued-mail.php | ||
| set-password.php | ||
| sitemap.php | ||
| submit.html | ||
Blogărie
Directorul comunității de bloguri românești. Oricine are un blog scris în limba română îl poate trimite - sistemul verifică automat dacă blogul pare a fi în română și îl aprobă instant; dacă nu e sigur, blogul intră într-o coadă de aprobare manuală.
Construit cu PHP, JavaScript vanilla și HTML/CSS. Fără framework-uri, fără baze de date - doar fișiere JSON simple, pregătit să susțină 1000+ bloguri.
Live: blogarie.thinkroot.xyz
Funcționalități
- Aprobare exclusiv manuală - toate trimiterile publice intră într-o coadă de verificare, analizată de un administrator. Nicio combinație de semnale tehnice (limbă, flux RSS etc.) nu poate garanta că un site e chiar un blog, nu doar un site oarecare în limba română.
- Semnale orientative - serverul calculează automat un scor de limbă română și detectează fluxul RSS/Atom, afișate adminului ca ajutor la triaj rapid - nu decid singure aprobarea.
- Categorii - fiecare blog poate avea până la 3 categorii din cele 19 disponibile, alese la trimitere - esențial pentru navigare ușoară cu mii de bloguri.
- Descrieri de bloguri - preluate automat din
<meta name="description">la trimitere. - Detecție flux - detectează automat fluxul RSS/Atom al blogului și afișează o iconiță pe fiecare card.
- Favicon-uri - afișează favicon-ul fiecărui blog prin Google Favicon Service.
- Ordine aleatorie - lista de bloguri e amestecată la fiecare încărcare a paginii (plus sortare A-Z și „cele mai noi”).
- Căutare - căutare pe partea de client, după nume sau adresă.
- Filtrare pe categorii - chip-uri de categorie cu numărul de bloguri din fiecare.
- Paginare - 21 bloguri pe pagină.
- RSS, Atom și JSON Feed - abonează-te la director ca să urmărești blogurile noi.
- Export OPML - importă toate fluxurile blogurilor dintr-o dată în orice cititor RSS.
- Prompt de distribuire - după aprobare, distribuie anunțul pe Mastodon sau Bluesky.
- Panou de administrare - protejat prin parolă, pentru aprobarea cozii de așteptare, adăugare, editare și eliminare bloguri.
- Scanare săptămânală - job cron automat verifică toate blogurile și semnalează cele care nu mai sunt accesibile.
- Limitare de rată - maxim 3 trimiteri pe IP pe oră (ocolită pentru admin).
- Pregătit pentru SEO - meta tag-uri, Open Graph, Twitter Card, date structurate JSON-LD, sitemap dinamic (inclusiv paginile de profil ale blogurilor).
- Securizat - protecție CSRF, prevenire SSRF, honeypot, headere de securitate prin
.htaccess. - Open source - licență MIT.
Structura fișierelor
blogarie/
├── index.html # Pagina principală (director)
├── submit.html # Formular de trimitere blog
├── about.html # Pagina „Despre”
├── privacy.html # Politica de confidențialitate
├── api.php # API REST - toate endpoint-urile
├── admin.php # Panou admin protejat prin parolă
├── sitemap.php # Sitemap XML dinamic
├── feed.xml.php # Flux RSS 2.0 al directorului
├── feed.atom.php # Flux Atom al directorului
├── feed.json.php # JSON Feed al directorului
├── aggregated-feed.php # Flux RSS agregat cu articole recente
├── opml.php # Export OPML al tuturor fluxurilor
├── latest.php # Pagina „Articole recente”
├── blog.php # Pagina de profil a unui blog
├── random.php # Redirect către un articol aleatoriu
├── cron-scan.php # Script cron săptămânal de scanare
├── refresh-articles.php # Script cron - reîmprospătare cache articole (la 10 min)
├── backup-data.php # Script cron - backup zilnic data/*.json (retenție 30 zile)
├── send-queued-mail.php # Script cron - trimite notificările puse în coadă (la 60s)
├── set-password.php # Utilitar CLI pentru hash-ul parolei admin
├── includes/
│ ├── categories.php # Sursă unică pentru lista de categorii
│ ├── helpers.php # esc(), relativeTime(), addRef(), faviconUrl()
│ └── mailer.php # Client SMTP + notificări prin email
├── robots.txt # Directive pentru motoarele de căutare
├── .htaccess # Config Apache - securitate, cache, gzip
├── favicon.ico # Favicon multi-mărime (16/32/48px)
├── apple-touch-icon.png # Iconiță ecran acasă iOS
├── og-image.png # Imagine Open Graph
├── badge.svg # Insignă „Listat pe Blogărie”
├── Dockerfile # Imaginea PHP + Apache
├── docker-compose.yml # Orchestrare app + caddy + cron
├── Caddyfile # Reverse proxy, HTTPS automat
├── .env.example # Șablon variabile de mediu Docker
├── docker/
│ ├── entrypoint.sh # Înlocuiește domeniul la pornirea containerului
│ └── cron-loop.sh # Bucla de scanare/reîmprospătare/backup (container cron)
├── DEPLOY-DOCKER.md # Ghid de publicare cu Docker (recomandat)
├── DEPLOY-VPS.md # Ghid de publicare directă pe VPS, fără Docker
├── LICENSE
├── README.md
├── assets/
│ ├── css/
│ │ ├── main.css # Stiluri site public
│ │ ├── blog.css # Stiluri pagină profil blog
│ │ ├── latest.css # Stiluri pagină articole recente
│ │ └── admin.css # Stiluri panou admin
│ ├── js/
│ │ ├── main.js # JavaScript site public
│ │ └── admin.js # JavaScript panou admin
│ └── images/
│ ├── favicon.svg
│ ├── favicon-16.png / favicon-32.png / favicon-48.png / favicon-180.png
│ └── og-image.svg / og-image.png
└── data/
├── .htaccess # Interzice accesul web la acest director
├── blogs.json # Baza de date a blogurilor
├── articles.json # Cache articole agregate din fluxuri
├── rate.json # Urmărire limitare de rată (gestionat automat)
├── mail-settings.json # Setări SMTP (create din panou, tab Setări)
├── mail-queue.json # Coadă notificări de trimis (gestionată automat)
└── backups/AAAA-LL-ZZ/ # Backup-uri zilnice (create automat, retenție 30 zile)
Cerințe
Cu Docker (recomandat): doar Docker și Docker Compose pe server - restul (PHP, Apache, extensii, certificat HTTPS) e gestionat automat de containere. Vezi DEPLOY-DOCKER.md.
Fără Docker:
- PHP 7.4 sau mai nou
- Apache cu
mod_rewrite,mod_headers,mod_deflate,mod_expires - Extensia cURL activată în PHP
- Permisiuni de scriere pe
data/blogs.jsonșidata/rate.json
Publicare
Metoda recomandată - Docker (VPS sau bare metal): vezi DEPLOY-DOCKER.md. Totul rulează în containere (Apache+PHP, Caddy pentru HTTPS automat, scanare săptămânală) - nu se instalează nimic direct pe server.
Alternativă - instalare directă pe hosting sau VPS, fără Docker: vezi mai jos pentru hosting partajat (cPanel) sau DEPLOY-VPS.md pentru un VPS Ubuntu/Debian cu Apache instalat manual.
Publicare pe hosting partajat
-
Încarcă toate fișierele în
public_html/(sau un subdirector). -
Setează permisiunile fișierelor:
data/blogs.json → 644 data/rate.json → 644 data/ → 755 -
Setează parola de admin - rulează această comandă prin SSH sau un terminal PHP:
php set-password.phpCopiază hash-ul generat și înlocuiește-l în
admin.php, ca valoare aADMIN_PASSWORD_HASH. Parola implicită din fișier esteschimba-parola-acum- schimb-o obligatoriu înainte de a publica site-ul. -
Setează o cheie secretă pentru endpoint-ul de scanare - deschide
api.phpși modifică:define('SCAN_SECRET', 'CHANGE_THIS_TO_A_RANDOM_SECRET_KEY'); -
Dacă domeniul tău nu este
blogarie.thinkroot.xyz, înlocuiește toate aparițiilehttps://blogarie.thinkroot.xyzcu domeniul tău în:index.html,submit.html,about.html,privacy.html,blog.php,latest.php- URL canonic, tag-uri OGsitemap.php,feed.xml.php,feed.atom.php,feed.json.php,aggregated-feed.php,opml.php- constantaBASE_URLrobots.txt- directivaSitemapassets/images/og-image.svg- domeniul afișat în imagine (apoi regenereazăog-image.png)- adresele de contact
root@thinkroot.xyz
-
Configurează job-ul cron săptămânal în cPanel → Cron Jobs:
Minut: 0 | Oră: 8 | Zi: * | Lună: * | Zi săptămână: 1 Comandă: /usr/local/bin/php /home/UTILIZATORUL_TAU/public_html/blogarie/cron-scan.php -
Verifică funcționarea accesând:
https://DOMENIUL_TAU/api.php?action=pingToate valorile trebuie să fie
true.
Endpoint-uri API
Toate endpoint-urile sunt servite de api.php.
| Metodă | Endpoint | Autentificare | Descriere |
|---|---|---|---|
GET |
?action=list |
- | Returnează toate blogurile aprobate, în format JSON |
GET |
?action=categories |
- | Returnează lista de categorii disponibile |
GET |
?action=token |
- | Returnează un token CSRF pentru formularul de trimitere |
GET |
?action=ping |
- | Endpoint de diagnosticare - verifică PHP, cURL, permisiuni fișiere |
GET |
?action=scan |
Sesiune admin sau cheie secretă | Scanează toate blogurile și semnalează cele inaccesibile |
POST |
?action=submit |
Token CSRF | Trimitere publică de blog, cu limitare de rată |
POST |
?action=admin-submit |
Sesiune admin | Adaugă un blog, aprobat direct (necesită acces la site, fără limită de rată) |
POST |
?action=admin-force-add |
Sesiune admin | Adaugă un blog fără să necesite acces la site, aprobat direct |
POST |
?action=admin-approve |
Sesiune admin | Aprobă un blog aflat în așteptare sau inaccesibil |
POST |
?action=admin-edit |
Sesiune admin | Editează numele, adresa, categoria sau descrierea unui blog |
POST |
?action=admin-delete |
Sesiune admin | Elimină un blog după ID |
Aprobarea blogurilor
Toate trimiterile publice merg la verificare manuală (status pending) - nu există aprobare automată. Motivul: nicio combinație de semnale tehnice nu poate garanta că un site e chiar un blog personal - site-uri de firmă, unelte sau publicații de știri pot avea la fel de bine conținut în limba română și chiar un flux RSS.
La trimitere, serverul calculează totuși un scor orientativ de limbă română (funcția detectRomanian() din api.php) și detectează fluxul RSS/Atom - ambele sunt afișate în panoul de admin, în coloana „Semnale”, ca ajutor la triaj rapid al cozii de așteptare. Nu decid singure nimic.
Foarte multe bloguri românești sunt scrise fără diacritice (ă, â, î, ș, ț) - din obiceiuri vechi, teme netraduse sau taste. Din acest motiv, scorul de limbă nu se bazează pe diacritice: textul paginii este normalizat (diacriticele sunt eliminate) înainte de a căuta sintagme și cuvinte specifice limbii, deci funcționează la fel de bine cu sau fără ele.
| Semnal | Puncte | Sursă |
|---|---|---|
Atribut lang="ro" pe <html> |
12 | HTML-ul paginii principale |
<meta http-equiv="content-language" content="ro"> |
8 | HTML-ul paginii principale |
<meta property="og:locale" content="ro_RO"> |
5 | HTML-ul paginii principale (setat automat de multe pluginuri SEO) |
Domeniu .ro |
2 | Adresa blogului |
| Sintagme distinctive (ex: „toate drepturile rezervate”, „citește mai mult”, max 4 găsite) | 3 fiecare | Text vizibil, normalizat fără diacritice |
| Cuvinte distinctive limbii române (max 10 găsite) | 1 fiecare | Text vizibil, normalizat fără diacritice |
| Prezența efectivă a diacriticelor - bonus, nu obligatoriu | 2-4 | Text vizibil de pe pagină |
Blogurile adăugate de admin din panou (?action=admin-submit) sunt aprobate direct - se presupune că adminul deja le-a verificat înainte să le adauge. ?action=admin-force-add rămâne util pentru site-uri care blochează accesul automat al serverului (fetch eșuează), unde nici verificarea minimă nu se poate face.
Categorii
Fiecare blog poate avea până la 3 categorii, alese la trimitere:
Tehnologie · Lifestyle · Călătorii · Mâncare & Rețete · Sănătate & Fitness · Familie & Părinți · Educație · Cultură & Arte · Modă & Frumusețe · Business & Antreprenoriat · Finanțe personale · Auto & Moto · Sport · Gaming · Fotografie · Cărți & Literatură · Muzică · Jurnal personal · Altele
Lista de categorii este definită în api.php (funcția categories()) și este duplicată în submit.html (elementul <select>) și în admin.php/admin.js. Dacă modifici lista, actualizeaz-o în toate aceste locuri.
Panoul de administrare
Accesează panoul de admin la /admin, organizat în cinci pagini (taburi): Panou (statistici + coada de aprobat), Bloguri aprobate, Bloguri inaccesibile, Coș de gunoi și Setări. După autentificare poți:
- Aproba blogurile aflate în coada de așteptare (toate trimiterile publice) sau marcate inaccesibile de scanare - coloana „Semnale” arată scorul de limbă și dacă are flux RSS, ca ajutor la decizie
- Adăuga un blog, aprobat direct (fără limită de rată)
- Adăuga forțat un blog fără să necesite acces la site (pentru cele care blochează accesul automat al serverului)
- Edita numele, adresa, categoria sau descrierea unui blog, direct din tabel
- Verifica acum un singur blog (fără să aștepți scanarea completă a tuturor)
- Elimina un blog - nu se șterge imediat, ci ajunge în coșul de gunoi (recuperabil 30 de zile)
- Restaura sau șterge definitiv un blog din coșul de gunoi
- Căuta și pagina în orice listă de bloguri, după nume, adresă sau descriere
- Scana manual toate blogurile prin butonul de scanare - marchează blogurile inaccesibile
- Configura notificări prin email (tab Setări) și descărca manual un backup al datelor
Scanare automată
Job-ul cron săptămânal (cron-scan.php) verifică fiecare blog aprobat din director și:
- Marchează blogurile inaccesibile ca
needs_review - Restaurează blogurile la
approveddacă redevin accesibile - Nu modifică statusul blogurilor aflate în așteptare de aprobare manuală (
pending) - Trimite un email de alertă dacă au fost marcate ca inaccesibile bloguri noi (dacă notificările sunt activate)
- Șterge definitiv blogurile din coșul de gunoi mai vechi de 30 de zile
Blogurile inaccesibile apar într-o listă separată în panoul de admin („Bloguri inaccesibile”), cu data la care au fost marcate astfel. Nu mai apar în directorul public cât timp au acest status și nu sunt șterse automat - un admin le poate marca manual drept funcționale sau le poate elimina dacă sunt sigur abandonate.
Separat, refresh-articles.php rulează la fiecare 10 minute și reîmprospătează cache-ul de articole (data/articles.json) folosit de pagina „Articole recente” - latest.php doar citește acest cache, nu-l reîmprospătează el însuși la cerere (ar bloca vizitatorul ghinionist până se preiau toate fluxurile RSS, secvențial). Fiecare blog contribuie cu cel mult 10 articole (MAX_PER_BLOG) - un plafon doar pe total, fără plafon per blog, lăsa bloguri întregi fără nicio reprezentare în listă și în filtrele pe categorie. Dacă majoritatea fluxurilor eșuează la un ciclu (posibilă problemă de rețea a serverului), trimite un email de alertă (cel mult unul la 6 ore).
backup-data.php rulează zilnic la 03:00 și copiază data/*.json în data/backups/AAAA-LL-ZZ/, cu retenție de 30 de zile - singura plasă de siguranță reală pe un VPS fără bază de date cu backup gestionat. Poți descărca și manual o copie curentă din tab-ul Setări.
Notificări prin email
Din tab-ul Setări, poți configura un server SMTP (gazdă, port, criptare, utilizator/parolă opționale, adresă expeditor și destinatar) pentru a primi un email când:
- cineva trimite un blog nou prin formularul public (nu și când adaugi tu un blog direct din panou - deja l-ai verificat)
- scanarea săptămânală semnalează bloguri noi ca inaccesibile
- reîmprospătarea articolelor eșuează pe scară largă (probabil o problemă de rețea, nu bloguri individuale picate)
Setările sunt salvate în data/mail-settings.json (protejat de .htaccess, la fel ca restul directorului data/), nu în .env - se pot schimba fără redeploy. Clientul SMTP e scris de mână (fără dependențe externe), suportă STARTTLS, SSL implicit și AUTH LOGIN.
Notificarea de la trimiterea publică e pusă într-o coadă (data/mail-queue.json), nu trimisă direct din cererea vizitatorului - un handshake SMTP poate dura secunde bune sau poate eșua complet, iar asta n-are nicio legătură cu vizitatorul care doar își trimite blogul. Coada e golită efectiv în fundal, de send-queued-mail.php, rulat la fiecare tick (60s) de docker/cron-loop.sh; elementele care eșuează (ex: SMTP temporar indisponibil) rămân pentru reîncercare, iar cele mai vechi de 24h sunt abandonate.
Fluxuri
Directorul oferă trei formate de flux, actualizate automat pe măsură ce blogurile sunt adăugate:
| Format | Adresă |
|---|---|
| RSS 2.0 | /feed.xml.php |
| Atom | /feed.atom.php |
| JSON Feed | /feed.json.php |
| Articole recente (RSS agregat) | /aggregated-feed.php |
| Articole recente, filtrat pe categorie | /aggregated-feed.php?cat=tehnologie (slug-ul categoriei) |
| Export OPML | /opml.php |
Securitate
- Protecție CSRF - toate trimiterile publice de formulare necesită un token legat de sesiune
- Prevenire SSRF - adresele trimise sunt verificate față de intervale de IP private înainte ca serverul să le acceseze
- Honeypot - un câmp invizibil prinde trimiterile automate de tip bot
- Limitare de rată - trimiterile publice sunt limitate la 3 pe IP pe oră
- Headere de securitate -
X-Frame-Options,X-Content-Type-Options,Content-Security-Policy,Referrer-Policy,Permissions-Policy - Hashing IP - adresele IP ale celor care trimit bloguri sunt stocate ca hash-uri SHA-256, niciodată în clar
- Scrieri atomice -
blogs.jsoneste scris prin fișier temporar + redenumire, pentru a preveni corupția datelor - Director de date protejat -
data/are propriul.htaccesscare interzice orice acces web
Scalare la 1000+ bloguri
Proiectul folosește fișiere JSON plate, nu o bază de date - alegere deliberată, pentru simplitate. La 1000-5000 bloguri, blogs.json rămâne de ordinul sutelor de KB, iar ?action=list este cache-uit automat pe server (invalidat quando blogs.json se modifică), deci performanța rămâne bună. Filtrarea pe categorii, căutarea și sortarea au loc pe partea de client, după o singură încărcare a listei complete.
Dacă directorul depășește semnificativ 5000-10000 bloguri, ia în calcul paginarea la nivel de server pentru endpoint-ul ?action=list - arhitectura curentă e suficientă pentru obiectivul de 1000+ bloguri, dar nu e nelimitată.
Cum poți contribui
Proiectul este open source, sub licența MIT. Pentru a contribui:
- Fă un fork al proiectului
- Creează o ramură:
git checkout -b feature/functia-ta - Fă commit la modificări:
git commit -m 'Adaugă funcția ta' - Trimite ramura:
git push origin feature/functia-ta - Deschide un pull request
Păstrează spiritul proiectului: fără framework-uri, fără baze de date, dependențe minime.
Licență
Licență MIT - vezi LICENSE pentru textul complet.
Construit cu
Acest proiect a fost construit cu Claude AI de la Anthropic, pornind de la structura proiectului Pure Blog Discover.