mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 15:33:22 +00:00
feat: Utilisation d'une clef API Docuseal par établissement
This commit is contained in:
@ -223,7 +223,7 @@ def makeToken(user):
|
||||
"""
|
||||
try:
|
||||
# Récupérer tous les rôles de l'utilisateur actifs
|
||||
roles = ProfileRole.objects.filter(profile=user, is_active=True).values('role_type', 'establishment__id', 'establishment__name', 'establishment__evaluation_frequency', 'establishment__total_capacity')
|
||||
roles = ProfileRole.objects.filter(profile=user, is_active=True).values('role_type', 'establishment__id', 'establishment__name', 'establishment__evaluation_frequency', 'establishment__total_capacity', 'establishment__api_docuseal')
|
||||
|
||||
# Générer le JWT avec la bonne syntaxe datetime
|
||||
access_payload = {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
@ -7,49 +6,67 @@ from rest_framework import status
|
||||
import jwt
|
||||
import datetime
|
||||
import requests
|
||||
from Establishment.models import Establishment
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(['POST'])
|
||||
def generate_jwt_token(request):
|
||||
# Vérifier la clé API
|
||||
# Récupérer l'établissement concerné (par ID ou autre info transmise)
|
||||
establishment_id = request.data.get('establishment_id')
|
||||
if not establishment_id:
|
||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
establishment = Establishment.objects.get(id=establishment_id)
|
||||
except Establishment.DoesNotExist:
|
||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Vérifier la clé API reçue dans le header
|
||||
api_key = request.headers.get('X-Auth-Token')
|
||||
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
|
||||
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
# Récupérer les données de la requête
|
||||
user_email = request.data.get('user_email')
|
||||
documents_urls = request.data.get('documents_urls', [])
|
||||
id = request.data.get('id') # Récupérer le id
|
||||
template_id = request.data.get('id')
|
||||
|
||||
# Vérifier les données requises
|
||||
if not user_email:
|
||||
return Response({'error': 'User email is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Utiliser la configuration JWT de DocuSeal depuis les settings
|
||||
jwt_secret = settings.DOCUSEAL_JWT['API_KEY']
|
||||
# Utiliser la clé API de l'établissement comme secret JWT
|
||||
jwt_secret = establishment.api_docuseal
|
||||
jwt_algorithm = settings.DOCUSEAL_JWT['ALGORITHM']
|
||||
expiration_delta = settings.DOCUSEAL_JWT['EXPIRATION_DELTA']
|
||||
|
||||
# Définir le payload
|
||||
payload = {
|
||||
'user_email': user_email,
|
||||
'documents_urls': documents_urls,
|
||||
'template_id': id, # Ajouter le id au payload
|
||||
'exp': datetime.datetime.utcnow() + expiration_delta # Temps d'expiration du token
|
||||
'template_id': template_id,
|
||||
'exp': datetime.datetime.utcnow() + expiration_delta
|
||||
}
|
||||
|
||||
# Générer le token JWT
|
||||
token = jwt.encode(payload, jwt_secret, algorithm=jwt_algorithm)
|
||||
|
||||
return Response({'token': token}, status=status.HTTP_200_OK)
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(['POST'])
|
||||
def clone_template(request):
|
||||
# Vérifier la clé API
|
||||
# Récupérer l'établissement concerné
|
||||
establishment_id = request.data.get('establishment_id')
|
||||
print(f"establishment_id : {establishment_id}")
|
||||
if not establishment_id:
|
||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
establishment = Establishment.objects.get(id=establishment_id)
|
||||
except Establishment.DoesNotExist:
|
||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Vérifier la clé API reçue dans le header
|
||||
api_key = request.headers.get('X-Auth-Token')
|
||||
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
|
||||
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
# Récupérer les données de la requête
|
||||
document_id = request.data.get('templateId')
|
||||
@ -57,7 +74,7 @@ def clone_template(request):
|
||||
is_required = request.data.get('is_required')
|
||||
|
||||
# Vérifier les données requises
|
||||
if not document_id :
|
||||
if not document_id:
|
||||
return Response({'error': 'template ID is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# URL de l'API de DocuSeal pour cloner le template
|
||||
@ -67,7 +84,7 @@ def clone_template(request):
|
||||
try:
|
||||
response = requests.post(clone_url, headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY']
|
||||
'X-Auth-Token': establishment.api_docuseal
|
||||
})
|
||||
|
||||
if response.status_code != status.HTTP_200_OK:
|
||||
@ -79,12 +96,15 @@ def clone_template(request):
|
||||
# URL de l'API de DocuSeal pour créer une submission
|
||||
submission_url = f'https://docuseal.com/api/submissions'
|
||||
|
||||
# Faire la requête pour cloner le template
|
||||
try:
|
||||
clone_id = data['id']
|
||||
response = requests.post(submission_url, json={'template_id':clone_id, 'send_email': False, 'submitters': [{'email': email}]}, headers={
|
||||
response = requests.post(submission_url, json={
|
||||
'template_id': clone_id,
|
||||
'send_email': False,
|
||||
'submitters': [{'email': email}]
|
||||
}, headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY']
|
||||
'X-Auth-Token': establishment.api_docuseal
|
||||
})
|
||||
|
||||
if response.status_code != status.HTTP_200_OK:
|
||||
@ -93,10 +113,10 @@ def clone_template(request):
|
||||
data = response.json()
|
||||
data[0]['id'] = clone_id
|
||||
return Response(data[0], status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
except requests.RequestException as e:
|
||||
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
else :
|
||||
else:
|
||||
print(f'NOT REQUIRED -> on ne crée pas de submission')
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@ -106,18 +126,28 @@ def clone_template(request):
|
||||
@csrf_exempt
|
||||
@api_view(['DELETE'])
|
||||
def remove_template(request, id):
|
||||
# Vérifier la clé API
|
||||
api_key = request.headers.get('X-Auth-Token')
|
||||
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
|
||||
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
# Récupérer l'établissement concerné
|
||||
establishment_id = request.GET.get('establishment_id')
|
||||
if not establishment_id:
|
||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# URL de l'API de DocuSeal pour cloner le template
|
||||
try:
|
||||
establishment = Establishment.objects.get(id=establishment_id)
|
||||
except Establishment.DoesNotExist:
|
||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Vérifier la clé API reçue dans le header
|
||||
api_key = request.headers.get('X-Auth-Token')
|
||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
# URL de l'API de DocuSeal pour supprimer le template
|
||||
|
||||
clone_url = f'https://docuseal.com/api/templates/{id}'
|
||||
|
||||
# Faire la requête pour cloner le template
|
||||
try:
|
||||
response = requests.delete(clone_url, headers={
|
||||
'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY']
|
||||
'X-Auth-Token': establishment.api_docuseal
|
||||
})
|
||||
|
||||
if response.status_code != status.HTTP_200_OK:
|
||||
@ -132,23 +162,32 @@ def remove_template(request, id):
|
||||
@csrf_exempt
|
||||
@api_view(['GET'])
|
||||
def download_template(request, slug):
|
||||
# Vérifier la clé API
|
||||
# Récupérer l'établissement concerné
|
||||
establishment_id = request.GET.get('establishment_id')
|
||||
if not establishment_id:
|
||||
return Response({'error': 'establishment_id requis'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
establishment = Establishment.objects.get(id=establishment_id)
|
||||
except Establishment.DoesNotExist:
|
||||
return Response({'error': "Établissement introuvable"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Vérifier la clé API reçue dans le header
|
||||
api_key = request.headers.get('X-Auth-Token')
|
||||
if not api_key or api_key != settings.DOCUSEAL_JWT["API_KEY"]:
|
||||
return Response({'error': 'Invalid API key'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
if not api_key or not establishment.api_docuseal or api_key != establishment.api_docuseal:
|
||||
return Response({'error': 'Clé API invalide'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
# Vérifier les données requises
|
||||
if not slug :
|
||||
if not slug:
|
||||
return Response({'error': 'slug is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# URL de l'API de DocuSeal pour cloner le template
|
||||
# URL de l'API de DocuSeal pour télécharger le template
|
||||
download_url = f'https://docuseal.com/submitters/{slug}/download'
|
||||
|
||||
# Faire la requête pour cloner le template
|
||||
try:
|
||||
response = requests.get(download_url, headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY']
|
||||
'X-Auth-Token': establishment.api_docuseal
|
||||
})
|
||||
|
||||
if response.status_code != status.HTTP_200_OK:
|
||||
|
||||
@ -21,6 +21,7 @@ class Establishment(models.Model):
|
||||
licence_code = models.CharField(max_length=100, blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
api_docuseal = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -363,12 +363,10 @@ SIMPLE_JWT = {
|
||||
}
|
||||
|
||||
# Configuration for DocuSeal JWT
|
||||
DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"
|
||||
DOCUSEAL_JWT = {
|
||||
'ALGORITHM': 'HS256',
|
||||
'SIGNING_KEY': SECRET_KEY,
|
||||
'EXPIRATION_DELTA': timedelta(hours=1),
|
||||
'API_KEY': DOCUSEAL_API_KEY
|
||||
'EXPIRATION_DELTA': timedelta(hours=1)
|
||||
}
|
||||
|
||||
# Django Channels Configuration
|
||||
|
||||
@ -14,7 +14,7 @@ test_mode = os.getenv('TEST_MODE', 'False') == 'True'
|
||||
|
||||
commands = [
|
||||
["python", "manage.py", "collectstatic", "--noinput"],
|
||||
#["python", "manage.py", "flush", "--noinput"],
|
||||
["python", "manage.py", "flush", "--noinput"],
|
||||
["python", "manage.py", "makemigrations", "Common", "--noinput"],
|
||||
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
|
||||
["python", "manage.py", "makemigrations", "Settings", "--noinput"],
|
||||
|
||||
@ -2,5 +2,4 @@ NEXT_PUBLIC_API_URL=_NEXT_PUBLIC_API_URL_
|
||||
NEXT_PUBLIC_WSAPI_URL=_NEXT_PUBLIC_WSAPI_URL_
|
||||
NEXT_PUBLIC_USE_FAKE_DATA=_NEXT_PUBLIC_USE_FAKE_DATA_
|
||||
AUTH_SECRET=_AUTH_SECRET_
|
||||
NEXTAUTH_URL=_NEXTAUTH_URL_
|
||||
DOCUSEAL_API_KEY=_DOCUSEAL_API_KEY_
|
||||
NEXTAUTH_URL=_NEXTAUTH_URL_
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Users, Clock, CalendarCheck, School } from 'lucide-react';
|
||||
import { Users, Clock, CalendarCheck, School, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
||||
import Loader from '@/components/Loader';
|
||||
import StatCard from '@/components/StatCard';
|
||||
import logger from '@/utils/logger';
|
||||
@ -39,7 +39,7 @@ export default function DashboardPage() {
|
||||
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||
|
||||
const [absencesToday, setAbsencesToday] = useState([]);
|
||||
const { selectedEstablishmentId, selectedEstablishmentTotalCapacity } =
|
||||
const { selectedEstablishmentId, selectedEstablishmentTotalCapacity, apiDocuseal } =
|
||||
useEstablishment();
|
||||
|
||||
const [statusDistribution, setStatusDistribution] = useState([
|
||||
@ -168,7 +168,24 @@ export default function DashboardPage() {
|
||||
|
||||
return (
|
||||
<div key={selectedEstablishmentId} className="p-6">
|
||||
<h1 className="text-2xl font-bold mb-6">{t('dashboard')}</h1>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<span
|
||||
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold ${
|
||||
apiDocuseal
|
||||
? 'bg-green-100 text-green-700 border border-green-300'
|
||||
: 'bg-red-100 text-red-700 border border-red-300'
|
||||
}`}
|
||||
>
|
||||
{apiDocuseal ? (
|
||||
<CheckCircle2 className="w-4 h-4 mr-2 text-green-500" />
|
||||
) : (
|
||||
<AlertTriangle className="w-4 h-4 mr-2 text-red-500" />
|
||||
)}
|
||||
{apiDocuseal
|
||||
? 'Clé API Docuseal renseignée'
|
||||
: 'Clé API Docuseal manquante'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Statistiques principales */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
|
||||
@ -52,7 +52,7 @@ export default function Page() {
|
||||
);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEstablishmentId) {
|
||||
@ -353,6 +353,7 @@ export default function Page() {
|
||||
<FilesGroupsManagement
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
apiDocuseal={apiDocuseal}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
@ -96,7 +96,7 @@ export default function CreateSubscriptionPage() {
|
||||
const { getNiveauLabel } = useClasses();
|
||||
|
||||
const formDataRef = useRef(formData);
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const router = useRouter();
|
||||
@ -473,8 +473,6 @@ export default function CreateSubscriptionPage() {
|
||||
}
|
||||
})();
|
||||
|
||||
logger.debug('test : ', guardians);
|
||||
|
||||
const data = {
|
||||
student: {
|
||||
last_name: formDataRef.current.studentLastName,
|
||||
@ -532,12 +530,14 @@ export default function CreateSubscriptionPage() {
|
||||
const clonePromises = masters.map((templateMaster) =>
|
||||
cloneTemplate(
|
||||
templateMaster.id,
|
||||
formData.guardianEmail,
|
||||
templateMaster.is_required
|
||||
formDataRef.current.guardianEmail,
|
||||
templateMaster.is_required,
|
||||
selectedEstablishmentId,
|
||||
apiDocuseal
|
||||
)
|
||||
.then((clonedDocument) => {
|
||||
const cloneData = {
|
||||
name: `${templateMaster.name}_${formData.studentFirstName}_${formData.studentLastName}`,
|
||||
name: `${templateMaster.name}_${formDataRef.current.studentFirstName}_${formDataRef.current.studentLastName}`,
|
||||
slug: clonedDocument.slug,
|
||||
id: clonedDocument.id,
|
||||
master: templateMaster.id,
|
||||
@ -655,6 +655,7 @@ export default function CreateSubscriptionPage() {
|
||||
return {
|
||||
...prevData,
|
||||
selectedGuardians: updatedSelectedGuardians,
|
||||
guardianEmail: guardian.associated_profile_email,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ export default function Page() {
|
||||
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
@ -47,6 +47,7 @@ export default function Page() {
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
apiDocuseal = {apiDocuseal}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_ADMIN_SUBSCRIPTIONS_URL}
|
||||
errors={formErrors}
|
||||
|
||||
@ -14,7 +14,7 @@ export default function Page() {
|
||||
const enable = searchParams.get('enabled') === 'true';
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
try {
|
||||
@ -31,6 +31,7 @@ export default function Page() {
|
||||
studentId={studentId}
|
||||
csrfToken={csrfToken}
|
||||
selectedEstablishmentId={selectedEstablishmentId}
|
||||
apiDocuseal = {apiDocuseal}
|
||||
onSubmit={handleSubmit}
|
||||
cancelUrl={FE_PARENTS_HOME_URL}
|
||||
enable={enable}
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
FE_API_DOCUSEAL_CLONE_URL,
|
||||
FE_API_DOCUSEAL_DOWNLOAD_URL,
|
||||
FE_API_DOCUSEAL_GENERATE_TOKEN,
|
||||
FE_API_DOCUSEAL_DELETE_URL
|
||||
} from '@/utils/Url';
|
||||
import { errorHandler, requestResponseHandler } from './actionsHandlers';
|
||||
|
||||
@ -337,8 +338,23 @@ export const deleteRegistrationParentFileTemplate = (id, csrfToken) => {
|
||||
};
|
||||
|
||||
// API requests
|
||||
export const removeTemplate = (templateId, selectedEstablishmentId, apiDocuseal) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_DELETE_URL}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
establishment_id :selectedEstablishmentId,
|
||||
apiDocuseal
|
||||
}),
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
.catch(errorHandler);
|
||||
};
|
||||
|
||||
export const cloneTemplate = (templateId, email, is_required) => {
|
||||
export const cloneTemplate = (templateId, email, is_required, selectedEstablishmentId, apiDocuseal) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -348,14 +364,17 @@ export const cloneTemplate = (templateId, email, is_required) => {
|
||||
templateId,
|
||||
email,
|
||||
is_required,
|
||||
establishment_id :selectedEstablishmentId,
|
||||
apiDocuseal
|
||||
}),
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
.catch(errorHandler);
|
||||
};
|
||||
|
||||
export const downloadTemplate = (slug) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}`, {
|
||||
export const downloadTemplate = (slug, selectedEstablishmentId, apiDocuseal) => {
|
||||
const url = `${FE_API_DOCUSEAL_DOWNLOAD_URL}/${slug}?establishment_id=${selectedEstablishmentId}&apiDocuseal=${apiDocuseal}`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -365,13 +384,13 @@ export const downloadTemplate = (slug) => {
|
||||
.catch(errorHandler);
|
||||
};
|
||||
|
||||
export const generateToken = (email, id = null) => {
|
||||
export const generateToken = (email, id = null, selectedEstablishmentId, apiDocuseal) => {
|
||||
return fetch(`${FE_API_DOCUSEAL_GENERATE_TOKEN}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user_email: email, id }),
|
||||
body: JSON.stringify({ user_email: email, id, establishment_id :selectedEstablishmentId, apiDocuseal }),
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
.catch(errorHandler);
|
||||
|
||||
@ -30,7 +30,7 @@ export default function FileUploadDocuSeal({
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
const { selectedEstablishmentId, user, apiDocuseal } = useEstablishment();
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
|
||||
@ -44,10 +44,12 @@ export default function FileUploadDocuSeal({
|
||||
}, [fileToEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
const email = 'n3wt.school@gmail.com';
|
||||
if (!user && !user?.email) {
|
||||
return;
|
||||
}
|
||||
const id = fileToEdit ? fileToEdit.id : null;
|
||||
|
||||
generateToken(email, id)
|
||||
generateToken(user?.email, id, selectedEstablishmentId, apiDocuseal)
|
||||
.then((data) => {
|
||||
setToken(data.token);
|
||||
})
|
||||
@ -119,7 +121,7 @@ export default function FileUploadDocuSeal({
|
||||
|
||||
guardianDetails.forEach((guardian, index) => {
|
||||
logger.debug('creation du clone avec required : ', is_required);
|
||||
cloneTemplate(templateMaster?.id, guardian.email, is_required)
|
||||
cloneTemplate(templateMaster?.id, guardian.email, is_required, selectedEstablishmentId, apiDocuseal)
|
||||
.then((clonedDocument) => {
|
||||
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
||||
const data = {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Download, Edit3, Trash2, FolderPlus, Signature } from 'lucide-react';
|
||||
import { Download, Edit3, Trash2, FolderPlus, Signature, AlertTriangle } from 'lucide-react';
|
||||
import Modal from '@/components/Modal';
|
||||
import Table from '@/components/Table';
|
||||
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
|
||||
@ -22,6 +22,8 @@ import {
|
||||
deleteRegistrationFileGroup,
|
||||
deleteRegistrationSchoolFileMaster,
|
||||
deleteRegistrationParentFileMaster,
|
||||
|
||||
removeTemplate
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||||
import logger from '@/utils/logger';
|
||||
@ -35,6 +37,7 @@ import AlertMessage from '@/components/AlertMessage';
|
||||
export default function FilesGroupsManagement({
|
||||
csrfToken,
|
||||
selectedEstablishmentId,
|
||||
apiDocuseal
|
||||
}) {
|
||||
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
||||
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
||||
@ -114,17 +117,19 @@ export default function FilesGroupsManagement({
|
||||
setRemovePopupOnConfirm(() => () => {
|
||||
setIsLoading(true);
|
||||
// Supprimer les clones associés via l'API DocuSeal
|
||||
const removeClonesPromises = schoolFileTemplates
|
||||
.filter((template) => template.master === templateMaster.id)
|
||||
.map((template) => removeTemplate(template.id));
|
||||
|
||||
// Ajouter la suppression du master à la liste des promesses
|
||||
removeClonesPromises.push(removeTemplate(templateMaster.id));
|
||||
const removeClonesPromises = [
|
||||
...schoolFileTemplates
|
||||
.filter((template) => template.master === templateMaster.id)
|
||||
.map((template) =>
|
||||
removeTemplate(template.id, selectedEstablishmentId, apiDocuseal)
|
||||
),
|
||||
removeTemplate(templateMaster.id, selectedEstablishmentId, apiDocuseal),
|
||||
];
|
||||
|
||||
// Attendre que toutes les suppressions dans DocuSeal soient terminées
|
||||
Promise.all(removeClonesPromises)
|
||||
.then((responses) => {
|
||||
const allSuccessful = responses.every((response) => response.ok);
|
||||
const allSuccessful = responses.every((response) => response && response.id);
|
||||
if (allSuccessful) {
|
||||
logger.debug('Master et clones supprimés avec succès de DocuSeal.');
|
||||
|
||||
@ -188,31 +193,6 @@ export default function FilesGroupsManagement({
|
||||
});
|
||||
};
|
||||
|
||||
const removeTemplate = (templateId) => {
|
||||
return fetch('/api/docuseal/removeTemplate/', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((err) => {
|
||||
throw new Error(err.message);
|
||||
});
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error removing template:', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const editTemplateMaster = (file) => {
|
||||
setIsEditing(true);
|
||||
setFileToEdit(file);
|
||||
@ -562,13 +542,25 @@ export default function FilesGroupsManagement({
|
||||
icon={Signature}
|
||||
title="Formulaires à remplir"
|
||||
description="Gérez les formulaires nécessitant une signature électronique."
|
||||
button={true}
|
||||
button={apiDocuseal}
|
||||
buttonOpeningModal={true}
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
/>
|
||||
<div className="mb-4">
|
||||
<span
|
||||
className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold ${
|
||||
!apiDocuseal && 'bg-red-100 text-red-700 border border-red-300'
|
||||
}`}
|
||||
>
|
||||
{!apiDocuseal && (
|
||||
<AlertTriangle className="w-4 h-4 mr-2 text-red-500" />
|
||||
)}
|
||||
{!apiDocuseal && 'Clé API Docuseal manquante'}
|
||||
</span>
|
||||
</div>
|
||||
<Table
|
||||
data={filteredFiles}
|
||||
columns={columnsFiles}
|
||||
|
||||
@ -46,6 +46,10 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
const storedUser = sessionStorage.getItem('user');
|
||||
return storedUser ? JSON.parse(storedUser) : null;
|
||||
});
|
||||
const [apiDocuseal, setApiDocusealState] = useState(() => {
|
||||
const storedApiDocuseal = sessionStorage.getItem('apiDocuseal');
|
||||
return storedApiDocuseal ? JSON.parse(storedApiDocuseal) : null;
|
||||
});
|
||||
|
||||
// Sauvegarder dans sessionStorage à chaque mise à jour
|
||||
const setSelectedEstablishmentId = (id) => {
|
||||
@ -86,6 +90,11 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
sessionStorage.setItem('user', JSON.stringify(user));
|
||||
};
|
||||
|
||||
const setApiDocuseal = (api) => {
|
||||
setApiDocusealState(api);
|
||||
sessionStorage.setItem('apiDocuseal', JSON.stringify(api));
|
||||
};
|
||||
|
||||
/**
|
||||
* Fonction d'initialisation du contexte avec la session (appelée lors du login)
|
||||
* @param {*} session
|
||||
@ -104,6 +113,7 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
name: role.establishment__name,
|
||||
evaluation_frequency: role.establishment__evaluation_frequency,
|
||||
total_capacity: role.establishment__total_capacity,
|
||||
api_docuseal: role.establishment__api_docuseal,
|
||||
role_id: i,
|
||||
role_type: role.role_type,
|
||||
}));
|
||||
@ -123,6 +133,9 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
setSelectedEstablishmentTotalCapacity(
|
||||
userEstablishments[roleIndexDefault].total_capacity
|
||||
);
|
||||
setApiDocuseal(
|
||||
userEstablishments[roleIndexDefault].api_docuseal
|
||||
);
|
||||
setProfileRole(userEstablishments[roleIndexDefault].role_type);
|
||||
}
|
||||
if (endInitFunctionHandler) {
|
||||
@ -140,6 +153,9 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
setProfileRoleState(null);
|
||||
setEstablishmentsState([]);
|
||||
setUserState(null);
|
||||
setSelectedEstablishmentEvaluationFrequencyState(null);
|
||||
setSelectedEstablishmentTotalCapacityState(null);
|
||||
setApiDocusealState(null);
|
||||
sessionStorage.clear();
|
||||
};
|
||||
|
||||
@ -154,6 +170,8 @@ export const EstablishmentProvider = ({ children }) => {
|
||||
setSelectedEstablishmentEvaluationFrequency,
|
||||
selectedEstablishmentTotalCapacity,
|
||||
setSelectedEstablishmentTotalCapacity,
|
||||
apiDocuseal,
|
||||
setApiDocuseal,
|
||||
selectedRoleId,
|
||||
setSelectedRoleId,
|
||||
profileRole,
|
||||
|
||||
@ -3,18 +3,19 @@ import { BE_DOCUSEAL_CLONE_TEMPLATE } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'POST') {
|
||||
const { templateId, email, is_required } = req.body;
|
||||
const { templateId, email, is_required, establishment_id, apiDocuseal } = req.body;
|
||||
|
||||
fetch(BE_DOCUSEAL_CLONE_TEMPLATE, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY,
|
||||
'X-Auth-Token': apiDocuseal,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
templateId,
|
||||
email,
|
||||
is_required,
|
||||
establishment_id,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
|
||||
@ -3,13 +3,13 @@ import { BE_DOCUSEAL_DOWNLOAD_TEMPLATE } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'GET') {
|
||||
const { slug } = req.query;
|
||||
const { slug, establishment_id, apiDocuseal } = req.query;
|
||||
logger.debug('slug : ', slug);
|
||||
|
||||
fetch(`${BE_DOCUSEAL_DOWNLOAD_TEMPLATE}/${slug}`, {
|
||||
fetch(`${BE_DOCUSEAL_DOWNLOAD_TEMPLATE}/${slug}?establishment_id=${establishment_id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY,
|
||||
'X-Auth-Token': apiDocuseal,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
|
||||
@ -3,13 +3,15 @@ import { BE_DOCUSEAL_GET_JWT } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'POST') {
|
||||
const { apiDocuseal, ...rest } = req.body;
|
||||
|
||||
fetch(BE_DOCUSEAL_GET_JWT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY,
|
||||
'X-Auth-Token': apiDocuseal,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
body: JSON.stringify(rest),
|
||||
})
|
||||
.then((response) => {
|
||||
logger.debug('Response status:', response.status);
|
||||
|
||||
@ -3,12 +3,12 @@ import { BE_DOCUSEAL_REMOVE_TEMPLATE } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'DELETE') {
|
||||
const { templateId } = req.body;
|
||||
const { templateId, establishment_id, apiDocuseal } = req.body;
|
||||
|
||||
fetch(`${BE_DOCUSEAL_REMOVE_TEMPLATE}/${templateId}`, {
|
||||
fetch(`${BE_DOCUSEAL_REMOVE_TEMPLATE}/${templateId}?establishment_id=${establishment_id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY,
|
||||
'X-Auth-Token': apiDocuseal,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
|
||||
@ -136,6 +136,7 @@ export const FE_PARENTS_EDIT_SUBSCRIPTION_URL = '/parents/editSubscription';
|
||||
export const FE_API_DOCUSEAL_GENERATE_TOKEN = '/api/docuseal/generateToken';
|
||||
export const FE_API_DOCUSEAL_CLONE_URL = '/api/docuseal/cloneTemplate';
|
||||
export const FE_API_DOCUSEAL_DOWNLOAD_URL = '/api/docuseal/downloadTemplate';
|
||||
export const FE_API_DOCUSEAL_DELETE_URL = '/api/docuseal/removeTemplate';
|
||||
|
||||
/**
|
||||
* Fonction pour obtenir l'URL de redirection en fonction du rôle
|
||||
|
||||
@ -16,15 +16,40 @@ services:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: school
|
||||
TZ: Europe/Paris
|
||||
# docuseal_db:
|
||||
# image: postgres:latest
|
||||
# environment:
|
||||
# POSTGRES_USER: postgres
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# DOCUSEAL_DB_HOST: docuseal_db
|
||||
# POSTGRES_DB: docuseal
|
||||
# ports:
|
||||
# - 5433:5432 # port différent si besoin d'accès direct depuis l'hôte
|
||||
|
||||
docuseal:
|
||||
image: docuseal/docuseal:latest
|
||||
depends_on:
|
||||
- database
|
||||
ports:
|
||||
- 3001:3000
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:postgres@database:5432/docuseal
|
||||
# docuseal:
|
||||
# image: docuseal/docuseal:latest
|
||||
# container_name: docuseal_app
|
||||
# depends_on:
|
||||
# - docuseal_db
|
||||
# ports:
|
||||
# - "3001:3000"
|
||||
# environment:
|
||||
# DATABASE_URL: postgresql://postgres:postgres@docuseal_db:5432/docuseal
|
||||
# volumes:
|
||||
# - ./docuseal:/data/docuseal
|
||||
|
||||
# caddy:
|
||||
# image: caddy:2
|
||||
# container_name: caddy
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "4000:4443"
|
||||
# volumes:
|
||||
# - ./Caddyfile:/etc/caddy/Caddyfile
|
||||
# - caddy_data:/data
|
||||
# - caddy_config:/config
|
||||
# depends_on:
|
||||
# - docuseal
|
||||
|
||||
backend:
|
||||
build:
|
||||
@ -44,25 +69,26 @@ services:
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
- docuseal
|
||||
#- docuseal
|
||||
command: python start.py
|
||||
|
||||
init_docuseal_users:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- docuseal
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
USER_FIRST_NAME: n3wt
|
||||
USER_LAST_NAME: school
|
||||
USER_COMPANY: n3wt.innov
|
||||
USER_EMAIL: n3wt.school@gmail.com
|
||||
USER_PASSWORD: n3wt1234
|
||||
volumes:
|
||||
- ./initDocusealUsers.sh:/docker-entrypoint-initdb.d/initDocusealUsers.sh
|
||||
# init_docuseal_users:
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: Dockerfile
|
||||
# depends_on:
|
||||
# - docuseal
|
||||
# environment:
|
||||
# DOCUSEAL_DB_HOST: docuseal_db
|
||||
# POSTGRES_USER: postgres
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# USER_FIRST_NAME: n3wt
|
||||
# USER_LAST_NAME: school
|
||||
# USER_COMPANY: n3wt.innov
|
||||
# USER_EMAIL: n3wt.school@gmail.com
|
||||
# USER_PASSWORD: n3wt1234
|
||||
# volumes:
|
||||
# - ./initDocusealUsers.sh:/docker-entrypoint-initdb.d/initDocusealUsers.sh
|
||||
|
||||
# frontend:
|
||||
# build:
|
||||
@ -79,3 +105,6 @@ services:
|
||||
# - TZ=Europe/Paris
|
||||
# depends_on:
|
||||
# - backend
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
|
||||
Reference in New Issue
Block a user