Merge pull request 'Ajout de l'administration par Directus' (#1) from drone into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone Build is passing Details

Reviewed-on: #1
This commit is contained in:
Simon 2021-11-22 14:22:56 +01:00
commit 4275fed4f3
19 changed files with 5017 additions and 47 deletions

107
.drone.yml Normal file
View File

@ -0,0 +1,107 @@
---
# drone encrypt weko/lestoitsduval $REGISTRY_PASSWORD
kind: secret
name: REGISTRY_PASSWORD
data: RrJX1Ir1Fqp83P43po3iQh4RE+SULN060LDe/PkEYw+mxPLrdDREEoQNKfh8RvAMjvocNojY9OuxXaZSpC72GOpi8qzslnHAbHhQfCX/
---
# drone encrypt weko/lestoitsduval $REGISTRY_USER
kind: secret
name: REGISTRY_USER
data: fyPAM/EECUhDI5XGhj3RbQWgzbGija2DAwepuAWmnEx1+A==
---
# drone encrypt weko/lestoitsduval "{\"auths\":{\"https://registry.weko.io\":{\"auth\":\"$(echo -n "$REGISTRY_USER:$REGISTRY_PASSWORD" | base64)\",\"email\":\"$REGISTRY_USER\"}}}"
kind: secret
name: REGISTRY_CONFIG
data: NsJ77R/8TEmyCM7h6OXkOeGhKAdctofukKezjfjIMGaBv5aGYN4CKYCIBlLsBWfFvMb+0SIDqrExyLw4i5dFWfDepwdNacJXNRmjIEkxuaJmvGxV8BZpcof28LjhB4psKrKjmTFpGioS3kG1MRfnljD/AlwckUd6S4KYNfGgJW0hQccxbamW9M0tqagPddayGEDghwxUfzqQk937rmOV7ngI+mon9f4DCWVA
---
# drone encrypt weko/lestoitsduval $DIRECTUS_URL
kind: secret
name: DIRECTUS_URL
data: pPaOgtpSRFhEZ6zq61OoN+gYxZ6nzg+FlmFYEhUq/GLoFXTwA3i+Yci7ZrwJUmhOpLtG
---
# drone encrypt weko/lestoitsduval $DIRECTUS_EMAIL
kind: secret
name: DIRECTUS_EMAIL
data: Egj2gQqz40mbxx9tFc7bfTOiUc36Dch+2Y4RjVEt9qpoL1y5d5KEpSE=
---
# drone encrypt weko/lestoitsduval $DIRECTUS_PASSWORD
kind: secret
name: DIRECTUS_PASSWORD
data: TC0fUdQ7NvL7Qri7SuqTK6JC9zbX0xVSyndHl/zbFlecARlc7Yzru65OO1fvf1VQxSejIVlPcyorZRiHepUeTnt0nEXTgDmO
---
kind: pipeline
type: docker
name: default
platform:
os: linux
arch: arm64
steps:
- name: install submodule
image: drone/git
commands:
- git config --global http.sslverify false
- git submodule update --init
- name: install npm
image: node:current-alpine
volumes:
- name: hugo-theme-directus-import_node_modules
path: /drone/src/themes/hugo-theme-directus-import/node_modules
- name: hugo-theme-lowtech_node_modules
path: /drone/src/themes/hugo-theme-lowtech/node_modules
environment:
DIRECTUS_URL:
from_secret: DIRECTUS_URL
DIRECTUS_EMAIL:
from_secret: DIRECTUS_EMAIL
DIRECTUS_PASSWORD:
from_secret: DIRECTUS_PASSWORD
commands:
- (cd themes/hugo-theme-lowtech && npm i)
- (cd themes/hugo-theme-directus-import && npm i)
- node themes/hugo-theme-directus-import/import.js
- name: build website
image: jakejarvis/hugo-extended
commands:
- hugo --minify --environment production
- name: typo
image: node:current-alpine
volumes:
- name: hugo-theme-lowtech_node_modules
path: /drone/src/themes/hugo-theme-lowtech/node_modules
commands:
- node themes/hugo-theme-lowtech/scripts/typo
- name: push docker image on registry
image: plugins/docker
settings:
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASSWORD
repo: registry.weko.io/lestoitsduval
registry: registry.weko.io
tags:
- latest
image_pull_secrets:
- REGISTRY_CONFIG
volumes:
- name: hugo-theme-directus-import_node_modules
host:
path: /tmp/drone/cache/weko/lestoitsduval/themes/hugo-theme-directus-import
- name: hugo-theme-lowtech_node_modules
host:
path: /tmp/drone/cache/weko/lestoitsduval/themes/hugo-theme-lowtech

View File

@ -1,10 +1,12 @@
--- ---
id: 1
title: Stand à la Marche du Cidre title: Stand à la Marche du Cidre
date: 2019-11-11 date: '2019-11-11'
image: stand.jpg image: stand.jpg
image_credit: Françoise sur le stand image_credit: Françoise sur le stand
--- slug: 2019-11-11-marche-du-cidre
draft: 'false'
---
## Stand à la Marche du Cidre ## Stand à la Marche du Cidre
Nous étions présents lors de la marche du Château dAix (Marche du cidre) où Nous étions présents lors de la marche du Château dAix (Marche du cidre) où

View File

@ -1,10 +1,12 @@
--- ---
id: 4
title: Rencontre avec la ComCom de Saint Just-en-Chevalet title: Rencontre avec la ComCom de Saint Just-en-Chevalet
date: 2020-01-16 date: '2020-01-16'
image: mairie.jpg image: mairie.jpg
image_credit: Mairie de Saint Just en Chevalet (Loire 42) image_credit: Mairie de Saint Just en Chevalet (Loire 42)
--- slug: 2020-01-16-rencontre-comcom-st-just
draft: 'false'
---
## Rencontre avec le Conseil Communautaire du Pays d'Urfé ## Rencontre avec le Conseil Communautaire du Pays d'Urfé
Nous nous sommes rendus à la communauté de communes de St Just-en-Chevalet. Les maires étaient curieux de comprendre cette démarche citoyenne soutenant la transition énergétique. Nous avons évoqué le fait que nous nétions pas les seuls à œuvrer dans ce sens ; dautres projets similaires existent dans des territoires voisins : [« Com toit »](https://comtoit.org/) dans le bassin de Vichy et la montagne bourbonnaise , [« Toi et Toits »](https://www.toi-toits.fr/) dans le Livradois Forez. Nous nous sommes rendus à la communauté de communes de St Just-en-Chevalet. Les maires étaient curieux de comprendre cette démarche citoyenne soutenant la transition énergétique. Nous avons évoqué le fait que nous nétions pas les seuls à œuvrer dans ce sens ; dautres projets similaires existent dans des territoires voisins : [« Com toit »](https://comtoit.org/) dans le bassin de Vichy et la montagne bourbonnaise , [« Toi et Toits »](https://www.toi-toits.fr/) dans le Livradois Forez.

View File

@ -1,10 +1,12 @@
--- ---
id: 5
title: Réunion publique à Dancé title: Réunion publique à Dancé
date: 2020-01-20 date: '2020-01-20'
image: equipe.jpg image: equipe.jpg
image_credit: L'équipe pour répondre aux questions image_credit: L'équipe pour répondre aux questions
--- slug: 2020-01-20-reunion-publique-dance
draft: 'false'
---
## Réunion autour du photovoltaïque ## Réunion autour du photovoltaïque
Nous étions invités à Dancé par lAssociation des Jeunes qui avaient étendu linformation à la municipalité élargie de Vézelin sur Loire. Plus d'une vingtaine de personnes étaient présentes, en plus des 8 de notre groupe. Nous étions invités à Dancé par lAssociation des Jeunes qui avaient étendu linformation à la municipalité élargie de Vézelin sur Loire. Plus d'une vingtaine de personnes étaient présentes, en plus des 8 de notre groupe.

View File

@ -1,10 +1,12 @@
--- ---
id: 6
title: Rencontre avec la ComCom de Saint-Germain title: Rencontre avec la ComCom de Saint-Germain
date: 2020-02-13 date: '2020-02-13'
image: comcom.jpg image: comcom.jpg
image_credit: Communauté de Communes des Vals d'Aix et Isable image_credit: Communauté de Communes des Vals d'Aix et Isable
--- slug: 2020-02-13-rencontre-comcom-st-germain
draft: 'false'
---
## Rencontre avec le Conseil Communautaire des Vals d'Aix et Isable ## Rencontre avec le Conseil Communautaire des Vals d'Aix et Isable
Le jeudi 13 février, à l'occasion de la dernière réunion du conseil communautaire des Vals d'Aix et Isable avant les élections municipales, nous sommes venus présenter l'avancée du projet : Le jeudi 13 février, à l'occasion de la dernière réunion du conseil communautaire des Vals d'Aix et Isable avant les élections municipales, nous sommes venus présenter l'avancée du projet :

View File

@ -1,10 +1,12 @@
--- ---
id: 7
title: La Subvention acceptée !!! title: La Subvention acceptée !!!
date: 2020-06-09 date: '2020-06-09'
image: love.jpg image: love.jpg
image_credit: Un coeur en lettre image_credit: Un coeur en lettre
--- slug: 2020-06-09-starter-enr
draft: 'false'
---
## Subvention de l'étude de faisabilité acceptée !!! ## Subvention de l'étude de faisabilité acceptée !!!
{{< figure src="/actualites/2020-06-09-starter-enr/STARTER_EnR.png" class="center" alt="Logo STARTER EnR" >}} {{< figure src="/actualites/2020-06-09-starter-enr/STARTER_EnR.png" class="center" alt="Logo STARTER EnR" >}}

View File

@ -1,10 +1,12 @@
--- ---
id: 8
title: Réunion au Cheval Blanc title: Réunion au Cheval Blanc
date: 2020-09-23 date: '2020-09-23'
image: affiche.jpg image: affiche.jpg
image_credit: Logo des Toits du Val et du Cheval Blanc image_credit: Logo des Toits du Val et du Cheval Blanc
--- slug: 2020-09-23-reunion-au-cheval-blanc
draft: 'false'
---
# Energie renouvelable, locale et citoyenne à Saint Germain Laval ? # Energie renouvelable, locale et citoyenne à Saint Germain Laval ?
L'association « Les Toits du Val » vous invite le **vendredi 9 octobre** à 20h pour une **réunion d'information** au Cheval Blanc. L'association « Les Toits du Val » vous invite le **vendredi 9 octobre** à 20h pour une **réunion d'information** au Cheval Blanc.

View File

@ -1,10 +1,12 @@
--- ---
id: 9
title: Bilan de l'étude de faisabilité title: Bilan de l'étude de faisabilité
date: 2021-01-13 date: '2021-01-13'
image: jon-flobrant-_r19nfvS3wY.jpg image: jon-flobrant-_r19nfvS3wY.jpg
image_credit: Photo de Jon Flobrant sur Unsplash image_credit: Photo de Jon Flobrant sur Unsplash
--- slug: 2021-01-13-bilan-etude-faisabilite
draft: 'false'
---
## Des nouvelles de létude de faisabilité des toitures proposées ## Des nouvelles de létude de faisabilité des toitures proposées
Pour rappel, nous avons bénéficié de la subvention [STARTER EnR](/actualites/2020-06-09-starter-enr/) nous permettant de faire intervenir un bureau d'étude pour examiner la solidité des charpentes de 4 bâtiments : Pour rappel, nous avons bénéficié de la subvention [STARTER EnR](/actualites/2020-06-09-starter-enr/) nous permettant de faire intervenir un bureau d'étude pour examiner la solidité des charpentes de 4 bâtiments :

View File

@ -1,10 +1,12 @@
--- ---
id: 10
title: AG de l'association des Centrales Villageoises title: AG de l'association des Centrales Villageoises
date: 2021-03-27 date: '2021-03-27'
image: sincerely-media-dGxOgeXAXm8-unsplash.jpg image: sincerely-media-dGxOgeXAXm8-unsplash.jpg
image_credit: Photo de Sincerely Media sur Unsplash image_credit: Photo de Sincerely Media sur Unsplash
--- slug: null
draft: 'false'
---
## Assemblée Générale de l'association des Centrales Villageoises ## Assemblée Générale de l'association des Centrales Villageoises
_Nous avons participé à l'assemblée générale de l'association des Centrales Villageoises._ _Nous avons participé à l'assemblée générale de l'association des Centrales Villageoises._

View File

@ -1,9 +1,13 @@
--- ---
id: 3
title: Le printemps des Toits du Val title: Le printemps des Toits du Val
date: 2021-04-27 date: '2021-04-27'
image: lucie-morel-K4vmb0VT8tU-unsplash.jpg image: lucie-morel-K4vmb0VT8tU-unsplash.jpg
image_credit: Photo de Lucie Morel sur Unsplash image_credit: Photo de Lucie Morel sur Unsplash
--- slug: null
draft: 'false'
---
## Le printemps des Toits du Val
_Suite à létude de faisabilité des 4 toitures, [une seule a été jugée suffisamment solide pour accueillir des panneaux solaires](/actualites/2021-01-13-bilan-etude-faisabilite/), à notre grande déception..._ _Suite à létude de faisabilité des 4 toitures, [une seule a été jugée suffisamment solide pour accueillir des panneaux solaires](/actualites/2021-01-13-bilan-etude-faisabilite/), à notre grande déception..._

View File

@ -1,10 +1,12 @@
--- ---
id: 2
title: Consultation des artisans title: Consultation des artisans
date: 2021-05-04 date: '2021-05-04'
image: science-in-hd-67tIEcN2mOA-unsplash.jpg image: science-in-hd-67tIEcN2mOA-unsplash.jpg
image_credit: Photo de Science in HD sur Unsplash image_credit: Photo de Science in HD sur Unsplash
--- slug: null
draft: 'false'
---
## Consultation des artisans ## Consultation des artisans
Léquipe technique a rédigé [le cahier des charges](https://cloud.weko.io/s/BgW4SZxNC2XefNS) afin de consulter 3 artisans locaux pour la « Réalisation dune installation dun générateur photovoltaïque ». Léquipe technique a rédigé [le cahier des charges](https://cloud.weko.io/s/BgW4SZxNC2XefNS) afin de consulter 3 artisans locaux pour la « Réalisation dune installation dun générateur photovoltaïque ».

View File

@ -10,11 +10,13 @@ services:
ltdv-prod: ltdv-prod:
container_name: ltdv-prod container_name: ltdv-prod
build: . build: .
image: registry.weko.io/lestoitsduval
restart: always restart: always
labels: labels:
traefik.enable: "true" - "traefik.enable=true"
traefik.http.routers.ltdv-prod.rule: "Host(`${URL}`)" - "traefik.http.routers.ltdv-prod.rule=Host(`${URL}`)"
traefik.http.routers.ltdv-prod.entrypoints: "web" - "traefik.http.routers.ltdv-prod.entrypoints=web"
- "com.centurylinklabs.watchtower.enable=true"
volumes: volumes:
- ltdv-log:/var/log/nginx - ltdv-log:/var/log/nginx
- ltdv-stats:/usr/share/nginx/html/stats - ltdv-stats:/usr/share/nginx/html/stats
@ -35,5 +37,4 @@ services:
networks: networks:
default: default:
external: name: traefik
name: traefik

View File

@ -2,8 +2,7 @@ version: "3.8"
networks: networks:
default: default:
external: name: traefik
name: traefik
services: services:
ltdv-staging: ltdv-staging:

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,23 @@
const DirectusHugoDriver = require('.');
const driverOptions = {
url: process.env.DIRECTUS_URL,
email: process.env.DIRECTUS_EMAIL,
password: process.env.DIRECTUS_PASSWORD,
buildDrafts: false,
collections: {
'lestoitsduval_actualites': {
pathBuilder: (path, article, urlslug) => {
if (article.slug) {
return `${path}/actualites/${article.slug}`
}
return `${path}/actualites/${article.date}-${urlslug(article.title, { remove: /\./g })}`;
}
}
},
content: {
path: './content'
}
}
const driver = new DirectusHugoDriver(driverOptions);
driver.import()

View File

@ -0,0 +1,244 @@
const util = require('util');
const DirectusSDK = require('@directus/sdk-js');
const yaml = require('js-yaml');
const urlslug = require('url-slug');
const fs = require('fs');
const mime = require('mime-types');
const express = require('express')
const bodyParser = require('body-parser');
const { exec } = require("child_process");
const uuidregex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
class Driver {
constructor(config = {}, directusConfig = {}){
this.url = config.url || 'http://localhost:8055';
this.email = config.email || '';
this.password = config.password || '';
this.frontMatter = (config.frontMatter && config.frontMatter.toUpperCase()) || 'YAML'; // TODO: Add TOML and JSON frontmatter support
this.collection = config.collection
this.collections = config.collections
this.content = {}
this.content.path = (config.content && config.content.path) ? config.content.path : 'content';
this.content.home = (config.content && config.content.home) ? config.content.home.toLowerCase() : 'home';
this.content.index = (config.content && config.content.index) ? config.content.index.toLowerCase() : 'index';
this.content.map = (config.content && config.content.map) ? config.content.map : [];
this.directus = new DirectusSDK(this.url, directusConfig);
this.pathMethod = config.pathBuilder || this._pathBuilder;
this._auth = null;
this.buildDrafts = config.buildDrafts || false;
// Auto-rebuild server related
this.buildPort = config.buildPort || 8060;
this.buildHost = config.buildHost || 'http://localhost';
this.autoWebhook = config.autoWebhook || true;
}
_checkAuth(){
return new Promise(resolve => {
if (this.email && this.password && this._auth === null){
this._auth = this.directus.auth.login({ email: this.email, password: this.password })
.then(() => {
console.info('Login with user', this.email);
resolve()
})
} else {
resolve()
}
})
}
static emptyPromise(){
return new Promise(resolve => resolve())
}
getCollections(){
return this.directus.collections.read()
// Let not following .then() confuse you. It is an arrow function running an arrow filter.
// The Directus internal system fields have "meta.system == true" and those we discard here.
// .then(data => {
// console.log(util.inspect(data, false, null, true /* enable colors */))
// return data
// })
.then(data => data.data.filter(collection => !collection.meta.system))
}
/**
* Build path string where the articles will be stored exlcluding individual article slug
* @param {*} article
* @param {*} collection
* @returns {string}
*/
_pathBuilder(article, collection){
console.log('article', article);
// console.log('collection', collection);
if (this.collections && this.collections[collection.collection] && this.collections[collection.collection].pathBuilder) {
return this.collections[collection.collection].pathBuilder(this.content.path, article, urlslug)
}
if (this._isHome(article, collection)) {
return `${this.content.path}`;
}
if (this._isBranch(article, collection) || this._isPage(article, collection)){
return `${this.content.path}/${collection.collection}`;
}
// console.log(article.date)
const datePrefix = article.date_created ? `${article.date_created.split('T')[0]}_` : '';
const [year, mouth, day] = article.date.split('-')
// console.log(year)
// return `${this.content.path}/${collection.collection}/${article.id}_${datePrefix}${urlslug(article.title, { remove: /\./g })}`;
return `${this.content.path}/actualites/${year}/${mouth}/${article.date.replaceAll('-', '_')}-${urlslug(article.title, { remove: /\./g })}`;
}
_isHome(article, collection){
return (collection.meta.singleton === true && collection.collection === this.content.home)
}
_isBranch(article, collection){
return (collection.meta.singleton === false && article.title.toLowerCase() === this.content.index)
}
// eslint-disable-next-line class-methods-use-this
_isPage(article, collection){
return (collection.meta.singleton === true)
}
_formatFrontMatter(article, collection = {}){
let front = { ...article };
// Manipulate some variable property names
if (front.date_created){
front.date = front.date_created
delete front.date_created
}
if (front.date_updated){
front.lastmod = front.date_updated
delete front.date_updated
}
delete front.body
// Finally, transform from object to string
if (this.frontMatter === 'YAML'){
front = `---\r\n${yaml.safeDump(front).trim()}\r\n---\r\n`
}
return front
}
static _writeFileStream(path, readstream, passtrough = {}){
return new Promise((resolve, reject) => {
const fileWriter = fs.createWriteStream(path);
fileWriter.on('finish', () => {
resolve(passtrough)
});
fileWriter.on('error', error => {
console.log(error)
fileWriter.close();
reject(error);
});
readstream.pipe(fileWriter);
});
}
/**
*
* @param {object} article
* @param {object} collection
* @returns {promise}
*/
_importItem(originArticle, collection){
const article = { ...originArticle }
const itemPath = this.pathMethod(article, collection);
const indexName = this._isHome(article, collection) || this._isBranch(article, collection) || this._isPage(article, collection) ? '_index' : 'index'
// Only continue if this is a published article or we have explicitly set to import Drafts
// Archived items would be always discarded
if ((article.status === 'archived') || (article.draft === 'true' && this.buildDrafts === false)){
return Driver.emptyPromise();
}
// if ((article.status !== 'published') && (article.status === 'draft' && this.buildDrafts === false)){
// return Driver.emptyPromise();
// }
if (!fs.existsSync(itemPath)){
fs.mkdirSync(itemPath, { recursive: true });
}
const writePromises = []
for (const [key, value] of Object.entries(article)){
// TODO: instead of trying to pull asset and hope for the best,
// crosscheck things with the /fields and play by the rules
if (typeof value === 'string' && value.match(uuidregex)) {
// console.log([key, value]);
const downloadPromise = this.directus.axios.get(`assets/${value}?download`, { responseType: 'stream' })
.then(response => {
const disposition = response.headers['content-disposition'].match(/filename="(.*)"/);
const downloadName = disposition ? disposition[1] : `${value}.${mime.extension(response.headers['content-type'])}`;
const savePath = `${itemPath}/${downloadName}`;
return Driver._writeFileStream(savePath, response.data, { key, downloadName })
})
.then(passtrough => {
article[passtrough.key] = passtrough.downloadName
})
.catch(error => {
if (error.response && error.response.status && error.response.status !== 403) {
console.error(error.response.status, error.response.statusText)
}
});
writePromises.push(downloadPromise);
}
}
return Promise.all(writePromises).then(() => {
const frontMatter = this._formatFrontMatter(article, collection);
const itemContent = `${frontMatter}${article.body ? article.body.toString() : ''}`
fs.writeFileSync(`${itemPath}/${indexName}.md`, itemContent)
})
}
_importCollection(collection){
const col = this.directus.items(collection.collection).read() // TODO: IMPORTANT handle pagination when a lot of content in CMS
return col
.then(data => {
// normalize to always have an array of content.
const content = (collection.meta.singleton === true) ? [data.data] : data.data;
return content.map(article => this._importItem(article, collection))
})
}
import() {
console.info('Let\'s import from', this.url);
this._checkAuth()
.then(() => this.getCollections())
.then(collections => {
if (this.collections) {
return collections.filter(collection => Object.keys(this.collections).includes(collection.collection))
}
return collections
})
.then(collections => {
return collections.map(collection => this._importCollection(collection))
})
.then(collectionPromises => {
return Promise.all(collectionPromises)
})
.catch(error => {
console.log(error)
})
}
}
module.exports = Driver

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
{
"name": "directus-hugo-driver",
"version": "0.0.0",
"description": "Translates content from Directus.io to gohugo.io",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pilskalns/directus-hugo-driver.git"
},
"keywords": [
"directus",
"hugo",
"npm",
"cms",
"headless",
"headless-cms",
"automation"
],
"author": "Andžs Pilskalns",
"license": "ISC",
"bugs": {
"url": "https://github.com/pilskalns/directus-hugo-driver/issues"
},
"homepage": "https://github.com/pilskalns/directus-hugo-driver#readme",
"dependencies": {
"@directus/sdk-js": "^9.0.0-rc.32",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"js-yaml": "^3.14.1",
"mime-types": "^2.1.28",
"url-slug": "^3.0.1"
},
"devDependencies": {
"eslint": "^7.18.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1"
}
}

@ -1 +1 @@
Subproject commit 4675c3a10fc35c23c01186faa7f7ccc6dd7d2e72 Subproject commit 437ab76c51a94e646333f77162934b147b5163ac