rubis/k3s/app/landing.yml
ordinarthur 6dcae6956c
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 1m32s
Build & Deploy API / build-and-deploy (push) Successful in 2m20s
Build & Deploy Landing / build-and-deploy (push) Successful in 1m20s
feat(blog): admin CRUD + image upload + sidebar link
L'éditeur du blog (jusqu'ici limité au seeder) a maintenant une vraie
interface au-dessus de l'API.

Backend (apps/api) :
* Migration users.is_admin (boolean default false).
* Middleware admin (404 si user.is_admin=false après auth).
* Commande ace promote:admin --email=… [--revoke].
* AdminPostsController CRUD complet : list/show/store/update/publish/
  unpublish/destroy + suggest-slug. Au save, contentHtml + wordCount +
  readingTime sont re-calculés via blog_renderer. Au publish, durcit la
  validation SEO (titre ≤60, excerpt 120-160, hero+alt requis, ≥600 mots),
  flippe status='published' + publishedAt, ping Google+Bing pour le sitemap.
* BlogUploadsController :
  - POST /api/v1/admin/uploads (multipart, JPEG/PNG/WebP, max 4MB)
    → MinIO clé uploads/blog/{uuid}.{ext}
    → renvoie URL relative /api/v1/uploads/blog/{filename}
  - GET /api/v1/uploads/blog/:filename (public, cache immutable 1 an)
    → stream depuis MinIO, regex anti-traversal sur le nom.
* UserTransformer expose isAdmin (cf. shared/types/user).
* k3s/app/landing.yml : NodePort 30111 explicite (pour Traefik repo proxmox).

Frontend (apps/web) :
* Lib typée admin-blog (calls API, queryKeys, helpers URL).
* Route /admin/blog : liste filtrable avec status badge, ouverture
  publique, dépublier, supprimer, "+ Nouveau brouillon".
* Route /admin/blog/:id : éditeur 2-colonnes
  - Gauche : @uiw/react-md-editor (lazy import) avec preview live.
  - Droite : hero image (drag&drop + alt), excerpt avec compteur
    120-160, tags, aperçu Google snippet, validations bloquantes.
  - Autosave debounce 2s + bouton Publier qui sauve d'abord.
  - Hero image upload via MinIO (HeroImageUpload component).
* Sidebar : lien "Blog (admin)" si user.isAdmin.
* Gate côté client (beforeLoad redirect si non admin) + côté serveur
  (middleware admin) — defense in depth.

Note : les requirements de publish miroir backend ↔ frontend (cf.
PUBLISH_REQUIREMENTS dans validators/post.ts et VALIDATION_RULES dans
admin.blog_.\$id.tsx). À synchroniser si un seuil bouge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 17:25:34 +02:00

94 lines
2.4 KiB
YAML

# Rubis Landing — Astro 6 SSR (Node 22).
# Sert rubis.pro/* (pages statiques prerenderées) + rubis.pro/blog/* (SSR
# qui fetch apps/api JSON via le DNS K3s rubis-api.rubis.svc.cluster.local).
#
# Routing Traefik : Host(`rubis.pro`) → service rubis-landing:4321
# (les pages légales et le blog sont des routes Astro internes).
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rubis-landing
namespace: rubis
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: rubis-landing
template:
metadata:
labels:
app: rubis-landing
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- name: landing
image: git.arthurbarre.fr/ordinarthur/rubis-landing:latest
imagePullPolicy: Always
ports:
- containerPort: 4321
name: http
envFrom:
- configMapRef: { name: rubis-landing-config }
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 384Mi
startupProbe:
httpGet: { path: /, port: http }
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
livenessProbe:
httpGet: { path: /, port: http }
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet: { path: /, port: http }
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
---
# NodePort — Traefik (sur la VM Proxmox) tape sur 10.10.10.5:30111.
# Le routing dynamique Traefik vit dans le repo proxmox :
# ansible/roles/traefik/templates/rubis.yml.j2
apiVersion: v1
kind: Service
metadata:
name: rubis-landing
namespace: rubis
spec:
type: NodePort
selector:
app: rubis-landing
ports:
- port: 4321
targetPort: http
nodePort: 30111
name: http
---
apiVersion: v1
kind: ConfigMap
metadata:
name: rubis-landing-config
namespace: rubis
data:
TZ: 'Europe/Paris'
HOST: '0.0.0.0'
PORT: '4321'
NODE_ENV: 'production'
LOG_LEVEL: 'info'
# URL interne pour fetcher l'API JSON depuis le SSR Astro. Pas besoin de
# passer par l'extérieur (app.rubis.pro) — DNS K3s suffit.
API_URL: 'http://rubis-api.rubis.svc.cluster.local:3333'