Renommer l'application en app
This commit is contained in:
parent
7fc75eacde
commit
ddb391c673
33 changed files with 166 additions and 83 deletions
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
tasks:
|
||||
- name: Lancer le script de sauvegarde
|
||||
ansible.builtin.command: /etc/cron.daily/groupe-meditation-backup
|
||||
ansible.builtin.command: /etc/cron.daily/app-backup
|
||||
changed_when: true
|
||||
|
||||
- name: Lister les dernieres sauvegardes
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
groupe_meditation_domain: app.87-16.org
|
||||
|
||||
# Chemins de déploiement.
|
||||
groupe_meditation_app_dir: /opt/groupe-meditation
|
||||
groupe_meditation_app_dir: /opt/app
|
||||
groupe_meditation_backend_dir: "{{ groupe_meditation_app_dir }}/backend"
|
||||
groupe_meditation_deploy_dir: "{{ groupe_meditation_app_dir }}/deploy"
|
||||
groupe_meditation_data_dir: "{{ groupe_meditation_app_dir }}/data"
|
||||
groupe_meditation_frontend_dir: "{{ groupe_meditation_app_dir }}/frontend"
|
||||
groupe_meditation_web_root: /var/www/groupe-meditation
|
||||
groupe_meditation_web_root: /var/www/app
|
||||
groupe_meditation_upload_dir: "{{ groupe_meditation_app_dir }}/uploads/preuves"
|
||||
|
||||
# Service applicatif.
|
||||
groupe_meditation_service_name: groupe-meditation
|
||||
groupe_meditation_service_name: app
|
||||
groupe_meditation_service_user: www-data
|
||||
groupe_meditation_service_group: www-data
|
||||
groupe_meditation_api_host: 127.0.0.1
|
||||
|
|
@ -54,5 +54,5 @@ groupe_meditation_enable_tls: false
|
|||
groupe_meditation_certbot_email: admin@example.com
|
||||
|
||||
# Sauvegardes.
|
||||
groupe_meditation_backup_dir: /var/backups/groupe-meditation
|
||||
groupe_meditation_backup_dir: /var/backups/app
|
||||
groupe_meditation_backup_retention_days: 30
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ all:
|
|||
children:
|
||||
groupe_meditation:
|
||||
hosts:
|
||||
groupe-meditation-prod:
|
||||
app-prod:
|
||||
ansible_host: 192.168.12.46
|
||||
ansible_user: ansible
|
||||
ansible_ssh_private_key_file: ~/.ssh/id_ed25519_ansible_chezlepro
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Redemarrer groupe-meditation
|
||||
- name: Redemarrer app
|
||||
ansible.builtin.service:
|
||||
name: "{{ groupe_meditation_service_name }}"
|
||||
state: restarted
|
||||
|
|
|
|||
|
|
@ -39,21 +39,21 @@
|
|||
- "--exclude=.env"
|
||||
- "--exclude=__pycache__/"
|
||||
- "--exclude=*.pyc"
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Synchroniser les fichiers de deploiement
|
||||
ansible.posix.synchronize:
|
||||
src: "{{ playbook_dir }}/../deploy/"
|
||||
dest: "{{ groupe_meditation_deploy_dir }}/"
|
||||
delete: true
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Synchroniser les donnees d'import
|
||||
ansible.posix.synchronize:
|
||||
src: "{{ playbook_dir }}/../data/"
|
||||
dest: "{{ groupe_meditation_data_dir }}/"
|
||||
delete: true
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Synchroniser le frontend
|
||||
ansible.posix.synchronize:
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
ansible.builtin.pip:
|
||||
requirements: "{{ groupe_meditation_backend_dir }}/requirements.txt"
|
||||
virtualenv: "{{ groupe_meditation_backend_dir }}/venv"
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Deployer le fichier d'environnement backend
|
||||
ansible.builtin.template:
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
group: "{{ groupe_meditation_service_group }}"
|
||||
mode: "0640"
|
||||
no_log: true
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Installer les dependances frontend sur le poste de controle avec npm ci
|
||||
ansible.builtin.command:
|
||||
|
|
@ -130,16 +130,31 @@
|
|||
- "{{ groupe_meditation_app_dir }}"
|
||||
- "{{ groupe_meditation_web_root }}"
|
||||
|
||||
- name: Arreter l'ancien service applicatif si present
|
||||
ansible.builtin.service:
|
||||
name: groupe-meditation
|
||||
state: stopped
|
||||
enabled: false
|
||||
failed_when: false
|
||||
when: groupe_meditation_service_name != "groupe-meditation"
|
||||
|
||||
- name: Retirer l'ancienne unite systemd
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/groupe-meditation.service
|
||||
state: absent
|
||||
when: groupe_meditation_service_name != "groupe-meditation"
|
||||
notify: Recharger systemd
|
||||
|
||||
- name: Installer l'unite systemd
|
||||
ansible.builtin.template:
|
||||
src: groupe-meditation.service.j2
|
||||
src: app.service.j2
|
||||
dest: "/etc/systemd/system/{{ groupe_meditation_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify:
|
||||
- Recharger systemd
|
||||
- Redemarrer groupe-meditation
|
||||
- Redemarrer app
|
||||
|
||||
- name: Compter les groupes existants
|
||||
community.postgresql.postgresql_query:
|
||||
|
|
@ -185,7 +200,7 @@
|
|||
- groupe_meditation_run_seed | bool
|
||||
- groupes_table.query_result[0].count | int == 0 or (groupes_count.query_result[0].count | default(0) | int == 0)
|
||||
no_log: true
|
||||
notify: Redemarrer groupe-meditation
|
||||
notify: Redemarrer app
|
||||
|
||||
- name: Appliquer les handlers apres le seed
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[Unit]
|
||||
Description=Groupe Meditation API
|
||||
Description=App API
|
||||
After=network.target postgresql.service
|
||||
Requires=postgresql.service
|
||||
|
||||
|
|
@ -15,4 +15,3 @@ RestartSec=5
|
|||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
|
|
@ -9,9 +9,14 @@
|
|||
|
||||
- name: Installer la sauvegarde quotidienne
|
||||
ansible.builtin.template:
|
||||
src: groupe-meditation-backup.j2
|
||||
dest: /etc/cron.daily/groupe-meditation-backup
|
||||
src: app-backup.j2
|
||||
dest: /etc/cron.daily/app-backup
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
no_log: true
|
||||
|
||||
- name: Retirer l'ancienne sauvegarde quotidienne
|
||||
ansible.builtin.file:
|
||||
path: /etc/cron.daily/groupe-meditation-backup
|
||||
state: absent
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@ cd "{{ groupe_meditation_backend_dir }}"
|
|||
"{{ groupe_meditation_backend_dir }}/venv/bin/python" "{{ groupe_meditation_deploy_dir }}/tenant_schemas.py"
|
||||
"{{ groupe_meditation_backend_dir }}/venv/bin/python" "{{ groupe_meditation_deploy_dir }}/group_backup.py" all
|
||||
|
||||
pg_dump -U "$DB_USER" -h "$DB_HOST" -Fc "$DB_NAME" > "$BACKUP_DIR/grpmed_${DATE}.dump"
|
||||
pg_dump -U "$DB_USER" -h "$DB_HOST" -Fc "$DB_NAME" > "$BACKUP_DIR/app_${DATE}.dump"
|
||||
for schema in $(PGPASSWORD="$PGPASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -d "$DB_NAME" -Atc "SELECT tenant_schema FROM groupes WHERE tenant_schema IS NOT NULL ORDER BY tenant_schema"); do
|
||||
pg_dump -U "$DB_USER" -h "$DB_HOST" -n "$schema" -Fc "$DB_NAME" > "$BACKUP_DIR/${schema}_${DATE}.dump"
|
||||
done
|
||||
find "$BACKUP_DIR" -name "grpmed_*.dump" -mtime +"$RETENTION_DAYS" -delete
|
||||
find "$BACKUP_DIR" -name "app_*.dump" -mtime +"$RETENTION_DAYS" -delete
|
||||
find "$BACKUP_DIR" -name "group_*.json" -mtime +"$RETENTION_DAYS" -delete
|
||||
find "$BACKUP_DIR" -name "grp_*.dump" -mtime +"$RETENTION_DAYS" -delete
|
||||
|
||||
echo "[$(date)] Sauvegarde terminee: grpmed_${DATE}.dump"
|
||||
echo "[$(date)] Sauvegarde terminee: app_${DATE}.dump"
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
- name: Installer le site NGINX
|
||||
ansible.builtin.template:
|
||||
src: groupe-meditation.conf.j2
|
||||
dest: /etc/nginx/sites-available/groupe-meditation
|
||||
src: app.conf.j2
|
||||
dest: /etc/nginx/sites-available/app
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
|
@ -15,12 +15,21 @@
|
|||
|
||||
- name: Activer le site NGINX
|
||||
ansible.builtin.file:
|
||||
src: /etc/nginx/sites-available/groupe-meditation
|
||||
dest: /etc/nginx/sites-enabled/groupe-meditation
|
||||
src: /etc/nginx/sites-available/app
|
||||
dest: /etc/nginx/sites-enabled/app
|
||||
state: link
|
||||
force: true
|
||||
notify: Recharger nginx
|
||||
|
||||
- name: Desactiver l'ancien site NGINX
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /etc/nginx/sites-enabled/groupe-meditation
|
||||
- /etc/nginx/sites-available/groupe-meditation
|
||||
notify: Recharger nginx
|
||||
|
||||
- name: Desactiver le site NGINX par defaut
|
||||
ansible.builtin.file:
|
||||
path: /etc/nginx/sites-enabled/default
|
||||
|
|
@ -36,4 +45,3 @@
|
|||
name: nginx
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
- name: Installer Groupe Meditation
|
||||
- name: Installer App
|
||||
hosts: groupe_meditation
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
- name: Verifier Groupe Meditation
|
||||
- name: Verifier App
|
||||
hosts: groupe_meditation
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
|
@ -39,4 +39,3 @@
|
|||
that:
|
||||
- frontend_index.stat.exists
|
||||
fail_msg: "Le fichier {{ groupe_meditation_web_root }}/index.html est absent."
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class Settings(BaseSettings):
|
|||
VAPID_PUBLIC_KEY: str | None = None
|
||||
VAPID_PRIVATE_KEY_B64: str | None = None
|
||||
VAPID_SUBJECT: str = "mailto:admin@app.87-16.org"
|
||||
GROUP_BACKUP_DIR: str = "/var/backups/groupe-meditation"
|
||||
GROUP_BACKUP_DIR: str = "/var/backups/app"
|
||||
|
||||
class Config:
|
||||
env_file = str(_ENV_FILE)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Groupe Méditation — API Backend
|
||||
App — API Backend
|
||||
District 87-16, Région 87
|
||||
100% logiciel libre · Hébergé au Québec
|
||||
"""
|
||||
|
|
@ -130,8 +130,8 @@ async def lifespan(app: FastAPI):
|
|||
|
||||
|
||||
app = FastAPI(
|
||||
title="Groupe Méditation API",
|
||||
description="API du Groupe Méditation — District 87-16, Région 87",
|
||||
title="App API",
|
||||
description="API du App — District 87-16, Région 87",
|
||||
version="0.1.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
|
@ -178,4 +178,4 @@ app.include_router(journal.router, prefix="/api")
|
|||
|
||||
@app.get("/api/health")
|
||||
async def root_health():
|
||||
return {"status": "ok", "app": "Groupe Méditation", "version": "0.1.0"}
|
||||
return {"status": "ok", "app": "App", "version": "0.1.0"}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from sqlalchemy import func, select
|
|||
from pydantic import BaseModel
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import create_token, get_current_membre, is_sysadmin, verify_pin
|
||||
from app.core.security import create_token, get_current_membre, hash_pin, is_sysadmin, verify_pin
|
||||
from app.services.admin_database import (
|
||||
assurer_sysadmin_instance,
|
||||
creer_groupe,
|
||||
|
|
@ -48,6 +48,11 @@ class RestoreGroupeRequest(BaseModel):
|
|||
fichier: str
|
||||
|
||||
|
||||
class ChangePinRequest(BaseModel):
|
||||
pin_actuel: str
|
||||
nouveau_pin: str
|
||||
|
||||
|
||||
async def require_sysadmin(membre=Depends(get_current_membre)):
|
||||
if not is_sysadmin(membre):
|
||||
raise HTTPException(status_code=403, detail="Réservé au sysadmin")
|
||||
|
|
@ -75,6 +80,21 @@ async def sysadmin_login(req: SysadminLoginRequest, db: AsyncSession = Depends(g
|
|||
raise HTTPException(status_code=401, detail="PIN Sysadmin invalide")
|
||||
|
||||
|
||||
@router.post("/sysadmin-pin")
|
||||
async def changer_pin_sysadmin(
|
||||
req: ChangePinRequest,
|
||||
sysadmin=Depends(require_sysadmin),
|
||||
):
|
||||
if getattr(sysadmin, "groupe_id", None) is not None:
|
||||
raise HTTPException(status_code=403, detail="Réservé au sysadmin d'instance")
|
||||
if not req.nouveau_pin.isdigit() or len(req.nouveau_pin) != 4:
|
||||
raise HTTPException(status_code=400, detail="Le nouveau PIN doit contenir 4 chiffres")
|
||||
if not verify_pin(req.pin_actuel, sysadmin.pin_hash):
|
||||
raise HTTPException(status_code=401, detail="PIN actuel invalide")
|
||||
sysadmin.pin_hash = hash_pin(req.nouveau_pin)
|
||||
return {"message": "PIN sysadmin mis à jour"}
|
||||
|
||||
|
||||
@router.post("/demo")
|
||||
async def demo_database(
|
||||
_sysadmin=Depends(require_sysadmin),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from app.services.historique import enregistrer_historique, snapshot, verifier_v
|
|||
router = APIRouter(prefix="/depenses", tags=["S5 — Dépenses"])
|
||||
|
||||
METHODES_VALIDES = {"VIREMENT", "CHEQUE", "COLLECTE"}
|
||||
UPLOAD_DIR = "/opt/groupe-meditation/uploads/preuves"
|
||||
UPLOAD_DIR = "/opt/app/uploads/preuves"
|
||||
|
||||
|
||||
async def _to_out(d: Depense, db: AsyncSession) -> DepenseOut:
|
||||
|
|
|
|||
17
deploy/app.service
Normal file
17
deploy/app.service
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=App API
|
||||
After=network.target postgresql.service
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/opt/app/backend
|
||||
Environment=PATH=/opt/app/backend/venv/bin
|
||||
ExecStart=/opt/app/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
# Sauvegarde quotidienne PostgreSQL — Groupe Méditation
|
||||
# Sauvegarde quotidienne PostgreSQL — App
|
||||
# À placer dans /etc/cron.daily/ ou via crontab
|
||||
|
||||
BACKUP_DIR="/var/backups/groupe-meditation"
|
||||
BACKUP_DIR="/var/backups/app"
|
||||
RETENTION_DAYS=30
|
||||
DB_NAME="groupe_meditation"
|
||||
DB_USER="grpmed"
|
||||
|
|
@ -11,9 +11,10 @@ DATE=$(date +%Y-%m-%d_%H%M)
|
|||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Dump
|
||||
pg_dump -U "$DB_USER" -h 127.0.0.1 -Fc "$DB_NAME" > "$BACKUP_DIR/grpmed_${DATE}.dump"
|
||||
pg_dump -U "$DB_USER" -h 127.0.0.1 -Fc "$DB_NAME" > "$BACKUP_DIR/app_${DATE}.dump"
|
||||
|
||||
# Nettoyage des anciennes sauvegardes
|
||||
find "$BACKUP_DIR" -name "grpmed_*.dump" -mtime +$RETENTION_DAYS -delete
|
||||
find "$BACKUP_DIR" -name "app_*.dump" -mtime +$RETENTION_DAYS -delete
|
||||
|
||||
echo "[$(date)] Sauvegarde terminée: grpmed_${DATE}.dump"
|
||||
echo "[$(date)] Sauvegarde terminée: app_${DATE}.dump"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"""Sauvegardes applicatives par groupe.
|
||||
|
||||
Usage:
|
||||
cd /opt/groupe-meditation/backend
|
||||
cd /opt/app/backend
|
||||
venv/bin/python ../deploy/group_backup.py all
|
||||
"""
|
||||
import asyncio
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
[Unit]
|
||||
Description=Groupe Méditation API
|
||||
After=network.target postgresql.service
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/opt/groupe-meditation/backend
|
||||
Environment=PATH=/opt/groupe-meditation/backend/venv/bin
|
||||
ExecStart=/opt/groupe-meditation/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# Groupe Méditation — NGINX (seul, sans Apache)
|
||||
# App — NGINX (seul, sans Apache)
|
||||
# Sert les fichiers statiques + reverse proxy vers FastAPI
|
||||
#
|
||||
# Installation :
|
||||
# sudo cp deploy/nginx-groupe-meditation.conf /etc/nginx/sites-available/groupe-meditation
|
||||
# sudo ln -s /etc/nginx/sites-available/groupe-meditation /etc/nginx/sites-enabled/
|
||||
# sudo cp deploy/nginx-app.conf /etc/nginx/sites-available/app
|
||||
# sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
|
||||
# sudo nginx -t && sudo systemctl reload nginx
|
||||
# sudo certbot --nginx -d app.87-16.org
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ server {
|
|||
}
|
||||
|
||||
# ── Frontend — fichiers statiques (React build) ──
|
||||
root /var/www/groupe-meditation;
|
||||
root /var/www/app;
|
||||
index index.html;
|
||||
|
||||
# Cache longue durée sur les assets (JS, CSS, images)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Script d'initialisation.
|
||||
Crée le schéma et le groupe de démonstration initial.
|
||||
Usage: cd /opt/groupe-meditation/backend && venv/bin/python ../deploy/seed.py
|
||||
Usage: cd /opt/app/backend && venv/bin/python ../deploy/seed.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Dernière révision: 2026-06-01
|
|||
Sur la VM:
|
||||
|
||||
```bash
|
||||
systemctl status groupe-meditation
|
||||
systemctl status app
|
||||
systemctl status nginx
|
||||
```
|
||||
|
||||
|
|
@ -28,20 +28,20 @@ curl http://127.0.0.1:8000/api/health
|
|||
Réponse attendue:
|
||||
|
||||
```json
|
||||
{"status":"ok","app":"Groupe Méditation","version":"0.1.0"}
|
||||
{"status":"ok","app":"App","version":"0.1.0"}
|
||||
```
|
||||
|
||||
## Redémarrer
|
||||
|
||||
```bash
|
||||
sudo systemctl restart groupe-meditation
|
||||
sudo systemctl restart app
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## Lire les journaux système
|
||||
|
||||
```bash
|
||||
journalctl -u groupe-meditation -n 200 --no-pager
|
||||
journalctl -u app -n 200 --no-pager
|
||||
journalctl -u nginx -n 100 --no-pager
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -105,13 +105,13 @@ ansible-playbook ansible/verify.yml --ask-vault-pass
|
|||
Vérification directe de l'API:
|
||||
|
||||
```bash
|
||||
ansible groupe-meditation-prod -m uri -a 'url=http://127.0.0.1:8000/api/health return_content=true status_code=200' -o
|
||||
ansible app-prod -m uri -a 'url=http://127.0.0.1:8000/api/health return_content=true status_code=200' -o
|
||||
```
|
||||
|
||||
Vérification des groupes publics:
|
||||
|
||||
```bash
|
||||
ansible groupe-meditation-prod -m uri -a 'url=http://127.0.0.1:8000/api/auth/groupes return_content=true status_code=200' -o
|
||||
ansible app-prod -m uri -a 'url=http://127.0.0.1:8000/api/auth/groupes return_content=true status_code=200' -o
|
||||
```
|
||||
|
||||
## Initialisation de la base
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Le groupe courant est déterminé par le membre authentifié et le `groupe_id` d
|
|||
Chaque groupe reçoit aussi un schéma PostgreSQL dédié, nommé à partir de son identifiant (`grp_<uuid>`). Les tables du groupe peuvent être rafraîchies dans ce schéma avec le script:
|
||||
|
||||
```bash
|
||||
cd /opt/groupe-meditation/backend
|
||||
cd /opt/app/backend
|
||||
venv/bin/python ../deploy/tenant_schemas.py
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Groupe Méditation</title>
|
||||
<title>App</title>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "groupe-meditation",
|
||||
"name": "app",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "groupe-meditation",
|
||||
"name": "app",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "groupe-meditation",
|
||||
"name": "app",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "Application du Groupe Méditation — District 87-16",
|
||||
"description": "App — District 87-16",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ self.addEventListener('push', (event) => {
|
|||
try {
|
||||
data = event.data.json();
|
||||
} catch {
|
||||
data = { title: 'Groupe Méditation', body: event.data.text() };
|
||||
data = { title: 'App', body: event.data.text() };
|
||||
}
|
||||
}
|
||||
const title = data.title || 'Groupe Méditation';
|
||||
const title = data.title || 'App';
|
||||
const options = {
|
||||
body: data.body || 'Nouvelle notification',
|
||||
icon: '/icon-192.png',
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ export default function GestionGroupes() {
|
|||
try {
|
||||
const data = await api.get('/admin/groupes');
|
||||
setGroupes(data);
|
||||
if (!ouvertId && data.length) setOuvertId(data[0].id);
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
|
|
@ -168,6 +167,36 @@ export default function GestionGroupes() {
|
|||
}
|
||||
};
|
||||
|
||||
const changerPinSysadmin = async () => {
|
||||
const pinActuel = window.prompt('PIN actuel');
|
||||
if (!pinActuel) return;
|
||||
const nouveauPin = window.prompt('Nouveau PIN sysadmin (4 chiffres)');
|
||||
if (!nouveauPin) return;
|
||||
if (!/^[0-9]{4}$/.test(nouveauPin)) {
|
||||
setError('Le nouveau PIN doit contenir 4 chiffres');
|
||||
return;
|
||||
}
|
||||
const confirmation = window.prompt('Confirmer le nouveau PIN');
|
||||
if (confirmation !== nouveauPin) {
|
||||
setError('La confirmation ne correspond pas');
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setError('');
|
||||
setMsg('');
|
||||
try {
|
||||
const res = await api.post('/admin/sysadmin-pin', {
|
||||
pin_actuel: pinActuel,
|
||||
nouveau_pin: nouveauPin,
|
||||
});
|
||||
setMsg(res.message || 'PIN sysadmin mis à jour');
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<PageHeader titre="Gestion des groupes" sousTitre="Groupes, démo et opérations sysadmin" />
|
||||
|
|
@ -184,6 +213,13 @@ export default function GestionGroupes() {
|
|||
>
|
||||
Créer un groupe
|
||||
</button>
|
||||
<button
|
||||
onClick={changerPinSysadmin}
|
||||
disabled={busy}
|
||||
className="w-full min-h-[48px] p-3 bg-gray-800 text-white rounded-xl text-sm font-bold disabled:opacity-40"
|
||||
>
|
||||
Changer le PIN sysadmin
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{form && (
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function Login() {
|
|||
const [courriel, setCourriel] = useState(localStorage.getItem('gm_courriel') || '');
|
||||
const [pin, setPin] = useState('');
|
||||
const [groupeId, setGroupeId] = useState(localStorage.getItem('gm_groupe_id') || '');
|
||||
const [groupeOuvert, setGroupeOuvert] = useState(localStorage.getItem('gm_groupe_id') || '');
|
||||
const [groupeOuvert, setGroupeOuvert] = useState('');
|
||||
const [seSouvenir, setSeSouvenir] = useState(!!localStorage.getItem('gm_courriel'));
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -25,7 +25,6 @@ export default function Login() {
|
|||
if (groupeId && groupeExiste) return;
|
||||
const prochainGroupeId = groupes.length === 1 ? groupes[0].id : '';
|
||||
setGroupeId(prochainGroupeId);
|
||||
setGroupeOuvert(prochainGroupeId);
|
||||
if (prochainGroupeId) {
|
||||
localStorage.setItem('gm_groupe_id', prochainGroupeId);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Client API — Groupe Méditation
|
||||
* Client API — App
|
||||
*/
|
||||
const BASE = '/api';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ export default defineConfig({
|
|||
registerType: 'autoUpdate',
|
||||
includeAssets: ['favicon.svg'],
|
||||
manifest: {
|
||||
name: 'Groupe Méditation',
|
||||
name: 'App',
|
||||
short_name: 'Méditation',
|
||||
description: 'Application du Groupe Méditation — District 87-16',
|
||||
description: 'App — District 87-16',
|
||||
theme_color: '#1a5276',
|
||||
background_color: '#f8f9fa',
|
||||
display: 'standalone',
|
||||
|
|
|
|||
Loading…
Reference in a new issue