Compare commits
10 Commits
428f2d7d9a
...
fe8b3baf1f
| Author | SHA1 | Date | |
|---|---|---|---|
|
fe8b3baf1f
|
|||
|
6a9717008b
|
|||
|
5595587c6f
|
|||
| cb8e918279 | |||
| a06e208bfa | |||
| 5ac4b99e32 | |||
| bc0aafa8b3 | |||
| 90f07c68db | |||
| 67f8c565cc | |||
| 56aef7751a |
+1
-1
@@ -61,7 +61,7 @@ PUBLIC_URL="http://localhost:8055"
|
||||
# you need to pass to the database instance.
|
||||
|
||||
DB_CLIENT="sqlite3"
|
||||
DB_FILENAME="data.db"
|
||||
DB_FILENAME="./database/data.db"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,169 @@
|
||||
# api.konstitisyon.la
|
||||
# api.konstitisyon.nu
|
||||
|
||||
Backend Directus pour konstitisyon.nu, une plateforme de rédaction constitutionnelle collaborative. Ce dépôt contient la configuration Directus et le schéma de la base de données.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Technologies
|
||||
- **Backend**: Directus 11.x
|
||||
- **Base de données**: SQLite
|
||||
- **Authentification**: JWT via Directus
|
||||
|
||||
### Structure du projet
|
||||
```
|
||||
api.konstitisyon.nu/
|
||||
├── extensions/ # Extensions Directus personnalisées
|
||||
├── uploads/ # Fichiers uploadés
|
||||
├── data.db # Base de données SQLite
|
||||
└── .env # Configuration de l'environnement
|
||||
```
|
||||
|
||||
## Modèle de données
|
||||
|
||||
### Schéma de la base de données
|
||||
```mermaid
|
||||
erDiagram
|
||||
TITRES ||--o{ ARTICLES : contient
|
||||
TITRES ||--o{ COMMENTAIRES : discute
|
||||
TITRES {
|
||||
uuid id PK
|
||||
string status
|
||||
uuid user_created FK
|
||||
datetime date_created
|
||||
text contenu
|
||||
integer numero
|
||||
}
|
||||
ARTICLES {
|
||||
uuid id PK
|
||||
string status
|
||||
uuid user_created FK
|
||||
datetime date_created
|
||||
text contenu
|
||||
integer numero
|
||||
uuid titre FK
|
||||
}
|
||||
COMMENTAIRES {
|
||||
uuid id PK
|
||||
string status
|
||||
uuid user_created FK
|
||||
datetime date_created
|
||||
text contenu
|
||||
uuid titre FK
|
||||
}
|
||||
VOTES {
|
||||
uuid id PK
|
||||
uuid user_created FK
|
||||
datetime date_created
|
||||
datetime date_updated
|
||||
uuid content_version_id FK
|
||||
integer vote
|
||||
}
|
||||
DIRECTUS_VERSIONS ||--o{ VOTES : evalue
|
||||
DIRECTUS_USERS ||--o{ VOTES : cree
|
||||
DIRECTUS_USERS ||--o{ COMMENTAIRES : ecrit
|
||||
DIRECTUS_USERS ||--o{ ARTICLES : redige
|
||||
DIRECTUS_USERS ||--o{ TITRES : redige
|
||||
```
|
||||
|
||||
### Collections Directus
|
||||
|
||||
#### Collections principales
|
||||
- **Titres**
|
||||
- Sections principales de la constitution
|
||||
- Numérotés et versionnés
|
||||
- Champs : numero, contenu, status
|
||||
|
||||
- **Articles**
|
||||
- Contenus détaillés sous chaque titre
|
||||
- Liés à un titre parent
|
||||
- Champs : numero, contenu, titre (FK), status
|
||||
|
||||
#### Collections participatives
|
||||
- **Commentaires**
|
||||
- Discussions sur les titres
|
||||
- Liés à un titre spécifique
|
||||
- Champs : contenu, titre (FK), user_created
|
||||
|
||||
- **Votes**
|
||||
- Évaluation des versions de contenu
|
||||
- Valeurs : +1 ou -1
|
||||
- Champs : content_version_id (FK), vote, user_created
|
||||
|
||||
## Installation
|
||||
|
||||
### Prérequis
|
||||
- Node.js 16+
|
||||
- npm ou yarn
|
||||
- SQLite 3
|
||||
|
||||
### Configuration
|
||||
Copier le fichier `.env.sample` vers un fichier `.env` :
|
||||
|
||||
```bash
|
||||
cp .env.sample .env
|
||||
```
|
||||
|
||||
### Démarrage
|
||||
|
||||
```bash
|
||||
# Installation des dépendances
|
||||
npm install
|
||||
|
||||
# Démarrage du serveur
|
||||
npx directus start
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Points d'entrée principaux
|
||||
|
||||
#### Titres
|
||||
- `GET /items/titres` : Liste des titres
|
||||
- `GET /items/titres/:id` : Détails d'un titre
|
||||
- `POST /items/titres` : Créer un titre
|
||||
- `PATCH /items/titres/:id` : Modifier un titre
|
||||
|
||||
#### Articles
|
||||
- `GET /items/articles` : Liste des articles
|
||||
- `GET /items/articles/:id` : Détails d'un article
|
||||
- `POST /items/articles` : Créer un article
|
||||
- `PATCH /items/articles/:id` : Modifier un article
|
||||
|
||||
#### Commentaires
|
||||
- `GET /items/commentaires` : Liste des commentaires
|
||||
- `GET /items/commentaires/:id` : Détails d'un commentaire
|
||||
- `POST /items/commentaires` : Créer un commentaire
|
||||
|
||||
#### Votes
|
||||
- `GET /items/votes` : Liste des votes
|
||||
- `GET /items/votes/:id` : Détails d'un vote
|
||||
- `POST /items/votes` : Créer un vote
|
||||
- `PATCH /items/votes/:id` : Modifier un vote
|
||||
|
||||
### Authentification
|
||||
|
||||
- `POST /auth/login` : Connexion utilisateur
|
||||
- `POST /auth/refresh` : Rafraîchissement du token
|
||||
- `POST /auth/logout` : Déconnexion
|
||||
|
||||
### Base de données
|
||||
|
||||
Le projet utilise une base de données SQLite. Un fichier `data.sample.db` est fourni avec :
|
||||
- La structure complète de la base de données
|
||||
- Un compte administrateur par défaut :
|
||||
- Email : admin@example.com
|
||||
- Mot de passe : admin
|
||||
|
||||
Pour démarrer un nouveau projet :
|
||||
1. Copier `data.sample.db` vers `data.db`
|
||||
- `cp data.sample.db data.db`
|
||||
2. Mettre à jour le fichier `.env` pour pointer vers `data.db`
|
||||
3. Démarrer le serveur
|
||||
|
||||
## License
|
||||
|
||||
Ce projet est sous licence AGPL-3. Cette licence garantit que le code reste libre et que toute modification doit être partagée avec la communauté. Voir le fichier `LICENSE` pour plus de détails.
|
||||
|
||||
## Contact
|
||||
|
||||
Pour toute question ou suggestion concernant le projet, n'hésitez pas à ouvrir une issue sur Codeberg.
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
directus:
|
||||
image: directus/directus:11.14.0
|
||||
ports:
|
||||
- 8055:8055
|
||||
volumes:
|
||||
- ./database:/directus/database
|
||||
- ./uploads:/directus/uploads
|
||||
- ./extensions:/directus/extensions
|
||||
env_file: ".env"
|
||||
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "disallow-votes",
|
||||
"description": "Interdit les votes sur des versions ayant plus de 3 jours",
|
||||
"icon": "extension",
|
||||
"version": "1.0.0",
|
||||
"keywords": [
|
||||
"directus",
|
||||
"directus-extension",
|
||||
"directus-extension-hook"
|
||||
],
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"directus:extension": {
|
||||
"type": "hook",
|
||||
"path": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"host": "^10.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "directus-extension build",
|
||||
"dev": "directus-extension build -w --no-minify",
|
||||
"link": "directus-extension link"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@directus/extensions-sdk": "12.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
export default ({filter}, {services}) => {
|
||||
const checkVersionValidity = async (versionId, database, schema) => {
|
||||
if (!versionId) {
|
||||
throw new Error('L’identifiant de la version est manquant.')
|
||||
}
|
||||
|
||||
const version = await database('directus_versions')
|
||||
.select('date_created')
|
||||
.where({id: versionId })
|
||||
.first()
|
||||
|
||||
if (!version) {
|
||||
throw new Error('Version non trouvée.')
|
||||
}
|
||||
|
||||
// Check if version is older than 3 days
|
||||
const createdAt = new Date(version.date_created)
|
||||
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
|
||||
|
||||
if (createdAt < threeDaysAgo) {
|
||||
throw new Error(
|
||||
'Le vote n’est plus possible après 3 jours de la création de la version.'
|
||||
)
|
||||
}
|
||||
|
||||
// Check if version is outdated (has been superseded by a promoted version)
|
||||
try {
|
||||
const {VersionsService} = services
|
||||
const versionsService = new VersionsService({schema, knex: database})
|
||||
const comparison = await versionsService.compare(versionId)
|
||||
|
||||
if (comparison && comparison.outdated === true) {
|
||||
throw new Error(
|
||||
'Le vote n’est plus possible sur une version obsolète.'
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
// If it's our custom error, rethrow it
|
||||
if (error.message === 'Le vote n’est plus possible sur une version obsolète.') {
|
||||
throw error
|
||||
}
|
||||
// Otherwise, log and continue (don't block vote if comparison fails)
|
||||
console.warn('Could not check version outdated status:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
filter('items.create', async (input, {collection}, {database, schema}) => {
|
||||
if (collection === 'votes') {
|
||||
await checkVersionValidity(input.content_version_id, database, schema)
|
||||
}
|
||||
return input
|
||||
})
|
||||
|
||||
filter('items.update', async (input, {collection}, {database, schema}) => {
|
||||
if (collection === 'votes') {
|
||||
await checkVersionValidity(input.content_version_id, database, schema)
|
||||
}
|
||||
return input
|
||||
})
|
||||
|
||||
filter('items.delete', async (input, {collection}, {database, schema}) => {
|
||||
if (collection === 'votes') {
|
||||
const voteId = input[0]
|
||||
|
||||
if (!voteId) {
|
||||
throw new Error('L’identifiant du vote est manquant.')
|
||||
}
|
||||
|
||||
const vote = await database('votes')
|
||||
.select('content_version_id')
|
||||
.where({id: voteId })
|
||||
.first()
|
||||
|
||||
if (!vote) {
|
||||
throw new Error('Vote non trouvé.')
|
||||
}
|
||||
|
||||
await checkVersionValidity(vote.content_version_id, database, schema)
|
||||
}
|
||||
return input
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
EMAIL_NEW_USER=
|
||||
DIRECTUS_URL="http://0.0.0.0:8055"
|
||||
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
+5014
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "directus-extension-new-user",
|
||||
"description": "Envoie un e-mail lors de l’inscription d’un utilisateur",
|
||||
"icon": "extension",
|
||||
"version": "1.0.0",
|
||||
"keywords": [
|
||||
"directus",
|
||||
"directus-extension",
|
||||
"directus-extension-hook"
|
||||
],
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"directus:extension": {
|
||||
"type": "hook",
|
||||
"path": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"host": "^10.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "directus-extension build",
|
||||
"dev": "directus-extension build -w --no-minify",
|
||||
"link": "directus-extension link"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@directus/extensions-sdk": "12.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export default ({filter}, {services, env}) => {
|
||||
const checkEmailExists = async (email, database) => {
|
||||
const user = await database('directus_users')
|
||||
.select('id')
|
||||
.where({email})
|
||||
.first()
|
||||
|
||||
return user !== undefined
|
||||
}
|
||||
|
||||
filter('users.create', async (input, {schema}, {database}) => {
|
||||
if (!services.MailService) {
|
||||
console.error('Le service MailService est manquant.')
|
||||
return input
|
||||
}
|
||||
|
||||
const adminEmail = env.EMAIL_NEW_USER
|
||||
const directusURL = env.DIRECTUS_URL || 'http://0.0.0.0:8055'
|
||||
|
||||
if (!adminEmail) {
|
||||
console.error('La variable EMAIL_NEW_USER est manquante.')
|
||||
return input
|
||||
}
|
||||
|
||||
const emailExists = await checkEmailExists(input.email, database)
|
||||
|
||||
if (emailExists) {
|
||||
console.error('L’adresse e-mail est déjà utilisée.')
|
||||
return input
|
||||
}
|
||||
|
||||
try {
|
||||
const mailService = new services.MailService({schema})
|
||||
mailService.send({
|
||||
to: adminEmail,
|
||||
subject: `Nouvel utilisateur : ${input.email}`,
|
||||
text: `Un nouvel utilisateur a été créé :\n Nom: ${input.first_name || 'N/A'} Email: ${input.email || 'N/A'}\n Pour valider => ${directusURL}/admin/users`,
|
||||
html: `
|
||||
<p>Un nouvel utilisateur a été créé :</p>
|
||||
<ul>
|
||||
<li><strong>Nom:</strong> ${input.first_name || 'N/A'} </li>
|
||||
<li><strong>Email:</strong> ${input.email || 'N/A'}</li>
|
||||
</ul>
|
||||
<p>Pour valider, <a href="${directusURL}/admin/users" target="_blank"><strong>cliquez ici</strong></a> ou sur ce lien => <a href="${directusURL}/admin/users" target="_blank">${directusURL}/admin/users</a></p>
|
||||
`,
|
||||
})
|
||||
|
||||
console.log('Email envoyé avec succès à', adminEmail)
|
||||
} catch (err) {
|
||||
console.error("Erreur lors de l’envoi de l’e-mail via MailService:", err)
|
||||
}
|
||||
|
||||
return input
|
||||
})
|
||||
}
|
||||
Generated
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "api.konstitisyon.la",
|
||||
"name": "api.konstitisyon.nu",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "api.konstitisyon.la",
|
||||
"name": "api.konstitisyon.nu",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "api.konstitisyon.la",
|
||||
"name": "api.konstitisyon.nu",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user