Utiliser PHPMailer pour envoyer des emails avec un SMTP

Introduction à PHPMailer en PHP : Un Guide pour Envoyer des Emails en Toute Simplicité

Dans le développement web, l’envoi d’emails est une fonctionnalité souvent indispensable : confirmations d’inscription, réinitialisation de mot de passe, notifications, etc. PHP possède sa propre fonction native mail(), mais celle-ci reste assez limitée et complexe à configurer pour des envois avancés, en particulier avec des serveurs SMTP sécurisés. mail() est à proscrire aujourd’hui.

C’est là que PHPMailer entre en jeu. PHPMailer est une bibliothèque PHP populaire qui simplifie l’envoi d’emails et permet de se connecter facilement à des serveurs SMTP. Elle offre des fonctionnalités supplémentaires telles que l’envoi sécurisé via SSL/TLS, la gestion des pièces jointes, le formatage HTML des emails, et bien plus encore. Dans ce guide, nous vous montrerons comment intégrer PHPMailer dans vos projets PHP et vous guiderons pas à pas pour envoyer des emails en utilisant cette bibliothèque.

Pourquoi utiliser PHPMailer ?

PHPMailer présente plusieurs avantages par rapport à la fonction mail() :

  1. Simplicité d’utilisation : PHPMailer simplifie la configuration et l’envoi d’emails.
  2. Support SMTP avec Authentification : PHPMailer supporte les protocoles SMTP avec authentification, ce qui est plus sûr et essentiel pour éviter que les emails soient marqués comme spam.
  3. Support SSL et TLS : PHPMailer prend en charge les protocoles de sécurité SSL et TLS.
  4. Support HTML et Pièces Jointes : Ajoutez facilement des pièces jointes et formatez le contenu en HTML pour des emails professionnels.

C’est simple cela a toujours marché avec moi en SMTP, et quand il y a un problème d’envois, vous pouvez activer le mode DEBUG pour avoir des messages clairs.

Étape 1 : Installer PHPMailer avec Composer

La méthode la plus simple pour installer PHPMailer est d’utiliser Composer. Si vous n’avez pas encore installé Composer, vous pouvez le faire en suivant les instructions officielles sur le site de Composer.

Une fois Composer installé, ouvrez un terminal ou une invite de commande, placez-vous dans le répertoire de votre projet et exécutez la commande suivante :

composer require phpmailer/phpmailer

Composer téléchargera PHPMailer et l’ajoutera aux dépendances de votre projet.

Étape 2 : Envoyer un Email avec PHPMailer

Créons un script PHP simple pour envoyer un email en utilisant PHPMailer.

Configuration de base de PHPMailer

  1. Créez un fichier email.php dans votre projet.
  2. Ajoutez le code suivant pour configurer et envoyer un email :
<?php
// Charger automatiquement les dépendances de Composer
require 'vendor/autoload.php'; // ne pas oublier cette ligne très importante !

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

// Initialiser PHPMailer
$mail = new PHPMailer(true);

try {
    // Paramètres du serveur SMTP
    $mail->isSMTP();                                        // Utiliser SMTP
    $mail->Host = 'smtp.example.com';                       // Remplacez par le serveur SMTP
    $mail->SMTPAuth = true;                                 // Activer l'authentification SMTP
    $mail->Username = 'votre-email@example.com';            // Votre adresse email
    $mail->Password = 'votre-mot-de-passe';                 // Votre mot de passe
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;     // Activer TLS (ou 'ssl' pour SSL)
    $mail->Port = 587;                                      // Port SMTP, 587 pour TLS ou 465 pour SSL

    // Expéditeur et destinataire
    $mail->setFrom('votre-email@example.com', 'Nom de l\'Expéditeur');
    $mail->addAddress('destinataire@example.com', 'Nom du Destinataire');

    // Contenu de l'email
    $mail->isHTML(true);                                    // Activer HTML
    $mail->Subject = 'Sujet de l\'email';
    $mail->Body    = '<h1>Bonjour!</h1><p>Ceci est un email envoyé depuis PHPMailer.</p>';
    $mail->AltBody = 'Ceci est un email envoyé depuis PHPMailer.'; // Version texte brut

    // Envoyer l'email
    $mail->send();
    echo 'Email envoyé avec succès';
} catch (Exception $e) {
    echo "L'email n'a pas pu être envoyé. Erreur : {$mail->ErrorInfo}";
}
?>

Délivrabilité des emails

Il existe des services qui offrent des SMTP gratuits, mais la délivrabilité est aléatoire. Par exemple le SMTP de Mailjet n’envoit pas toujours des email, alors qu’un SMTP comme Gmail est beaucoup plus fiables. Mais vous ne pouvez pas modifier l’email de l’expéditeur, alors que Mailjet le permet.

Comment exposer votre site local au réseau local avec Laragon ou Apache

Quand vous développez un site en responsive, vous aurez envie de le consulter sur votre terminal mobile (smartphone). Sous certaines conditions, vous n’aurez pas besoin de le déployer sur Github pages ou sur la préproduction pour pouvoir le voir. En fait si vous avez le wifi et un réseau local (une box internet) c’est possible.

Quelle est l’adresse de mon site sur mon réseau local?

Sur l’ordinateur de développement, l’hôte local a pour adresse IP 127.0.0.1, depuis le mobile en accédant à cette adresse, vous allez sur l’hôte local de votre smartphone.

Vous accéderez à une adresse du type 192.168.0.150, qui est l’adresse que pourrait avoir votre ordinateur dans le réseau local.

Comment faire en sorte que mon ordinateur soit visible dans le réseau local?

C’est où les choses intéressantes commencent. Pour exposer votre ordinateur, il faut binder à l’adresse 0.0.0.0, dans dans le fichier httpd.conf, il y a une ligne à modifier.

Mais si vous n’avez pas envie de modifier cette ligne, il y a un autre moyen, c’est d’utiliser ViteJS. En effet en lançant la commande :

npm run dev -- --host

Vous aurez un affichage du style :

  VITE v4.5.5  ready in 802 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: http://192.168.1.151:5173/  <<< adresse à entrer dans votre smartphone
  ➜  Network: http://172.19.16.1:5173/
  ➜  Network: http://172.18.96.1:5173/
  ➜  press h to show help

Mais voici les options que vous pouvez avoir, si vous avez Laragon par exemple, et que vous voulez exposer manuellement sans l’aide de viteJS votre machine au réseau local.

Pour exposer votre site local au réseau local (LAN) avec Laragon ou Apache, le processus est similaire à celui d’autres serveurs locaux comme Node.js. Cependant, il y a quelques ajustements spécifiques à faire selon que vous utilisez le serveur intégré de Laragon ou une installation Apache autonome.

1. Exposer Laragon au LAN

Laragon est un environnement de développement populaire sous Windows, incluant Apache, MySQL, PHP, etc. Voici comment vous pouvez exposer un site Laragon à votre réseau local.

Étape 1 : Modifier la configuration Apache de Laragon

Par défaut, Laragon est configuré pour n’écouter que sur localhost. Vous devez modifier la configuration d’Apache pour qu’il écoute sur toutes les interfaces (0.0.0.0), permettant ainsi l’accès depuis le réseau local.

  • Ouvrez le fichier de configuration Apache dans Laragon :
    • Allez dans Menu > Apache > httpd.conf.
    • Recherchez la ligne suivante (généralement définie sur localhost) :apacheCopy codeListen 127.0.0.1:80
  • Changez 127.0.0.1 en 0.0.0.0 pour qu’Apache écoute sur toutes les interfaces :apacheCopy codeListen 0.0.0.0:80

Étape 2 : Permettre aux hôtes virtuels de fonctionner sur le LAN

Si vous utilisez des hôtes virtuels avec Laragon (par exemple, monsite.test), vous devez également modifier la configuration des hôtes virtuels :

  • Ouvrez votre fichier de configuration des hôtes virtuels (Menu > Apache > sites-enabled > 00-default.conf ou httpd-vhosts.conf).
  • Trouvez la directive VirtualHost, qui ressemble à ceci :apacheCopy code<VirtualHost 127.0.0.1:80>
  • Changez 127.0.0.1 en 0.0.0.0 :apacheCopy code<VirtualHost 0.0.0.0:80>

Étape 3 : Trouver votre adresse IP locale

Vous devez maintenant trouver l’adresse IP locale de votre machine. Sous Windows, exécutez la commande ipconfig. Sous Linux ou Mac, utilisez ifconfig ou ip a.

Une fois que vous avez votre adresse IP (par exemple, 192.168.1.100), les autres appareils du réseau local peuvent accéder à votre site hébergé sur Laragon en allant à l’adresse suivante :

arduinoCopy codehttp://192.168.1.100

Si vous utilisez un hôte virtuel comme monsite.test, vous y accéderez avec cette adresse :

arduinoCopy codehttp://192.168.1.100

Étape 4 : Configurer le pare-feu (si nécessaire)

Assurez-vous que votre pare-feu ne bloque pas l’accès au port (généralement 80 pour HTTP) :

  • Sous Windows :
    • Allez dans Panneau de configuration > Système et sécurité > Pare-feu Windows Defender > Autoriser une application ou une fonctionnalité via le pare-feu Windows Defender.
    • Ajoutez une règle pour Apache HTTP Server ou autorisez les connexions entrantes sur le port 80.

Étape 5 : Accéder depuis des appareils sur le LAN

Depuis n’importe quel appareil sur le même réseau local, accédez à votre site Laragon local en saisissant l’adresse IP de votre machine dans le navigateur, comme décrit ci-dessus.

2. Exposer un Apache autonome au LAN

Si vous utilisez une installation Apache autonome, le processus est similaire, mais implique la modification directe des fichiers de configuration Apache.

Étape 1 : Modifier la configuration Apache

  1. Ouvrez votre fichier de configuration Apache. Il est généralement situé à :
    • Sous Windows (XAMPP/WAMP) : C:\xampp\apache\conf\httpd.conf ou C:\wamp\bin\apache\apache2.x.x\conf\httpd.conf
    • Sous Linux/Mac : /etc/httpd/httpd.conf ou /etc/apache2/apache2.conf
  2. Recherchez la directive Listen, qui ressemble à ceci :apacheCopy codeListen 127.0.0.1:80
  3. Modifiez-la pour écouter sur toutes les interfaces (0.0.0.0) :apacheCopy codeListen 0.0.0.0:80

Étape 2 : Mettre à jour les hôtes virtuels (si applicable)

Si vous utilisez des hôtes virtuels, ouvrez votre fichier d’hôtes virtuels (par exemple, httpd-vhosts.conf) et changez l’hôte virtuel de 127.0.0.1 à 0.0.0.0 :

apacheCopy code<VirtualHost 0.0.0.0:80>
  DocumentRoot "C:/xampp/htdocs/monsite"
  ServerName monsite.local
</VirtualHost>

Cela garantit que les hôtes virtuels sont accessibles sur le réseau local.

Étape 3 : Configuration du pare-feu

Assurez-vous que votre pare-feu permet les connexions entrantes sur le port 80 :

  • Sous Linux (avec ufw) :bashCopy codesudo ufw allow 80/tcp
  • Sous Windows : Suivez les étapes mentionnées plus haut pour autoriser le trafic via le port 80.

Étape 4 : Accéder depuis des appareils sur le LAN

Une fois votre serveur Apache configuré pour écouter sur 0.0.0.0, vous pouvez y accéder depuis d’autres appareils sur le LAN en utilisant l’adresse IP locale de votre machine :

arduinoCopy codehttp://192.168.1.x

Si vous avez configuré un hôte virtuel, cela fonctionnera de manière similaire.


Résumé

  • Pour Laragon : Modifiez Listen 127.0.0.1:80 en Listen 0.0.0.0:80 dans le fichier httpd.conf ainsi que dans les fichiers des hôtes virtuels si nécessaire.
  • Pour Apache autonome : Suivez un processus similaire en modifiant la configuration pour qu’Apache écoute sur 0.0.0.0 et mettez à jour les hôtes virtuels.
  • Après avoir configuré votre serveur, accédez au site via l’adresse IP locale de votre machine (192.168.x.x) depuis d’autres appareils sur le réseau local.

De cette manière, vous pouvez exposer votre environnement de développement local, que ce soit avec Laragon ou Apache, à votre réseau local (LAN) !

Comment connaitre l’IP exposé sous Windows?

Dans le terminal DOS, faites la commande ipconfig, et vous aurez une sortie de ce genre:

C:\Users\admin>ipconfig

Configuration IP de Windows


Carte Ethernet Ethernet 2 :

   Suffixe DNS propre à la connexion. . . : lan
   Adresse IPv4. . . . . . . . . . . . . .: 192.168.1.151
   Masque de sous-réseau. . . . . . . . . : 255.255.255.0
   Passerelle par défaut. . . . . . . . . : 192.168.1.254

Carte inconnue Connexion au réseau local :

   Statut du média. . . . . . . . . . . . : Média déconnecté
   Suffixe DNS propre à la connexion. . . :

Carte Ethernet vEthernet (Default Switch) :

   Suffixe DNS propre à la connexion. . . :
   Adresse IPv6 de liaison locale. . . . .: fe80::e9be:7f6:cc88:dfa2%34
   Adresse IPv4. . . . . . . . . . . . . .: 172.19.16.1
   Masque de sous-réseau. . . . . . . . . : 255.255.240.0
   Passerelle par défaut. . . . . . . . . :

Carte Ethernet vEthernet (WSL) :

   Suffixe DNS propre à la connexion. . . :
   Adresse IPv6 de liaison locale. . . . .: fe80::aa45:c533:e7ff:dde4%49
   Adresse IPv4. . . . . . . . . . . . . .: 172.18.96.1
   Masque de sous-réseau. . . . . . . . . : 255.255.240.0
   Passerelle par défaut. . . . . . . . . :

C:\Users\admin>

Installer une application NodeJs sur O2Switch

Mettre en place le projet NodeJS dans l’insterface O2Switch (CPanel)

Aller depuis l apage d’accueil de Cpanel, vers la section « Logiciels », choisir Setup Node.js App

Dans la page de configuration de votre application NodeJS, cliquer sur « Create Application »

Remplir le champs version de NodeJS, le mode de déploiement (Productionà, le chemin absolu vers la racine, le startup file corresond au fichier d’entrée de votre application, celui auquel vous faite node nom_fichier.js.

Une fois la configuration validée, vous verrez cet écran.

Comment organiser votre projet Node/Expresse/React Typescript?

La difficulté est que dans mon cas j’avais deux répertoires, frontend et backend. Imaginons que notre nom de domaine soit cloneflickr.jedeploiemonappli.com, la page d’accueil doit correspondre à la route ‘/’ dans Express. Cependant on a aussi une application ReactJS

fff

Connectez vous à Mysql pour un projet NodeJS Typescript

Si vous avez lu le premier article concernant le bootstrapping d’un projet Node Express Typescript, je n’ai pas encore parlé de la connection de la dernière pièce qui est la base de données. Dans cet article je vais parler de la connexion à une base MySQL.

Installation des composants mysql2

npm install mysql2

npm install typeorm reflect-metadata

Ici on installe un ORM, mais on ne va pas forcément l’utiliser.

Connexion à la base de données

Démarrer un projet ReactJS typescript avec ViteJS

Installation de viteJS avec npm

D’abord installons ViteJS

npm create vite@4.1.0

// alternative pour installer la dernière version

npm create vite@latest

// répondez aux questions
? Project name: » nom_projet

>   Vanilla
    Vue
    React
    Preact
    Lit
    Svelte
    Others


// on sélectionne React puis on a l'écran suivant
>   JavaScript
    TypeScript
    JavaScript + SWC
    TypeScript + SWC

Sélectionnons Typescript simple


√ Project name: ... cloneflicker
√ Select a framework: » React
√ Select a variant: » TypeScript

Scaffolding project in E:\projet\frontend\cloneflicker...

Done. Now run:

  cd cloneflicker
  npm install    
  npm run dev 

A la fin de la configuration de votre projet, un petit rappel de ses caractéristiques et une suggestion de commandes.

Au lancement on a l’écran suivant :

  VITE v4.5.5  ready in 13692 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

Vou spouvez CTRL + Click sur le lien pour lancer le site

Une des choses que vous allez remarquer le plus est la vitesse de lancement. C’est plus rapide qu’avec Webpack, la raison est que Vite optimise le build et ne construit pas tout, comme Webpack.

Structure du projet React Typescript

Le dossier public contient la page index.html qui va servir d’hôte à toute votre application React. Tandis que le dossier src contient tous les fichier de votre application React.

Le fichier tsconfig.json sert à configurer Typescript, le fichier vite.config.ts sert à introduire Vite,et n’oublions pas le fichier package.json.

Création de notre premier composant React en typescript !

Créons un fichier Message.tsx

function Message() {
    return <h1>Hello World</h1>;
}

export default Message;

Maintenant incorporons le dans App.tsx, effaçons le contenu par défaut de App.tsx pour simplifier notre construction.

import Message from './Message';

function App() {
  return <div>
    <Message />
  </div>
}

export default App

Au fur et à mesure que vous mettez à jour vos fichier, regardez la console, il y a le message

22:53:30 [vite] hmr update /src/App.tsx (x2)

Votre page s’est rafraichie.

Installation de Tailwind CSS

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

ça va générer deux fichiers de configuration tailwind.config.js et postcss.config.js. Remarquez que tailwindcss est une devDependency !

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Rajouter dans index.css ces lignes :

@tailwind base;
@tailwind components;
@tailwind utilities;

Modifier la class pour h1

function Message() {
    const name = 'Yvon';
    return <h1 className="text-4xl font-bold underline">Hello {name}</h1>;
}

export default Message;

Lancer l’application accessible via votre mobile

> npm run dev -- --host



  VITE v4.5.5  ready in 830 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: http://172.22.80.1:5173/
  ➜  Network: http://172.18.96.1:5173/
  ➜  Network: http://192.168.1.151:5173/
  ➜  press h to show help

Pour pouvoir utilise vite --host, il faut l’installer globalement.

Changer le répertoire de build pour vite

Par défaut vite build dans le répertoire dist. Ce répertoire sert pour la production, en phase de développement, il n’est pas généré. Pour avoir le build dans le répertoire build, il faut modifier vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'build', // Spécifie ici le répertoire de sortie
  },
})

L’attribut build n’apparait dans le fichier originel.

Requêter Sqlite en ligne de commande

Pour utiliser Sqlite avec une base de données, vous avez 2 possibilités :

Vous êtes déjà dans Sqlite, dans ce cas ouvrez le fichier de base de donnée, par exemple le fichier s’appelle bdd.db, pou ouvrir un fichier il faut indiquer le chemin complet. Dans l’exemple ci-dessous, on a déjà ouvert Sqlite dans le répertoire du fichier bdd.db.

sqlite> .open bdd.db

Vous n’êtes pas encore dans Sqlite:

sqlite bdd.db

Montrer les commandes

sqlite> .help
.archive ...             Manage SQL archives
.auth ON|OFF             Show authorizer callbacks
.backup ?DB? FILE        Backup DB (default "main") to FILE
.bail on|off             Stop after hitting an error.  Default OFF
.binary on|off           Turn binary output on or off.  Default OFF
.cd DIRECTORY            Change the working directory to DIRECTORY
.changes on|off          Show number of rows changed by SQL
.check GLOB              Fail if output since .testcase does not match
.clone NEWDB             Clone data into NEWDB from the existing database
.databases               List names and files of attached databases
.dbconfig ?op? ?val?     List or change sqlite3_db_config() options
.dbinfo ?DB?             Show status information about the database
.dump ?TABLE? ...        Render all database content as SQL
.echo on|off             Turn command echo on or off
.eqp on|off|full         Enable or disable automatic EXPLAIN QUERY PLAN
.excel                   Display the output of next command in a spreadsheet
.exit ?CODE?             Exit this program with return-code CODE
.expert                  EXPERIMENTAL. Suggest indexes for specified queries
.fullschema ?--indent?   Show schema and the content of sqlite_stat tables
.headers on|off          Turn display of headers on or off
.help ?-all? ?PATTERN?   Show help text for PATTERN
.import FILE TABLE       Import data from FILE into TABLE
.imposter INDEX TABLE    Create imposter table TABLE on index INDEX
.indexes ?TABLE?         Show names of indexes
.limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT
.lint OPTIONS            Report potential schema issues.
.load FILE ?ENTRY?       Load an extension library
.log FILE|off            Turn logging on or off.  FILE can be stderr/stdout
.mode MODE ?TABLE?       Set output mode
.nullvalue STRING        Use STRING in place of NULL values
.once (-e|-x|FILE)       Output for the next SQL command only to FILE
.open ?OPTIONS? ?FILE?   Close existing database and reopen FILE
.output ?FILE?           Send output to FILE or stdout if FILE is omitted
.print STRING...         Print literal STRING
.prompt MAIN CONTINUE    Replace the standard prompts
.quit                    Exit this program
.read FILE               Read input from FILE
.restore ?DB? FILE       Restore content of DB (default "main") from FILE
.save FILE               Write in-memory database into FILE
.scanstats on|off        Turn sqlite3_stmt_scanstatus() metrics on or off
.schema ?PATTERN?        Show the CREATE statements matching PATTERN
.selftest ?OPTIONS?      Run tests defined in the SELFTEST table
.separator COL ?ROW?     Change the column and row separators
.sha3sum ...             Compute a SHA3 hash of database content
.shell CMD ARGS...       Run CMD ARGS... in a system shell
.show                    Show the current values for various settings
.stats ?on|off?          Show stats or turn stats on or off
.system CMD ARGS...      Run CMD ARGS... in a system shell
.tables ?TABLE?          List names of tables matching LIKE pattern TABLE
.testcase NAME           Begin redirecting output to 'testcase-out.txt'
.timeout MS              Try opening locked tables for MS milliseconds
.timer on|off            Turn SQL timer on or off
.trace FILE|off          Output each SQL statement as it is run
.vfsinfo ?AUX?           Information about the top-level VFS
.vfslist                 List all available VFSes
.vfsname ?AUX?           Print the name of the VFS stack
.width NUM1 NUM2 ...     Set column widths for "column" mode

Toutes les commandes commencent par un point.

sqlite>.databases
main: /home/scxxccc70/easyupload.jedeploiemonappli.com/bdd.db

sqlite>.tables


sqlite>.schemapiece_jointe
CREATE TABLE IF NOT EXISTS "piece_jointe" (   
        "id"    INTEGER NOT NULL UNIQUE,      
        "email_emmeteur"        TEXT NOT NULL,
        "email_destinataire"    TEXT NOT NULL,
        "date_creation" INTEGER NOT NULL,     
        "chemin"        TEXT NOT NULL,        
        PRIMARY KEY("id" AUTOINCREMENT)       
);

Je me suis intéressé à ça pour comparer les schémas des tables en local et en production avant de déployer EasyUpload, un clone de WeTransfer développé à 95% par mes étudiants.

Démarrer une application Node/Express Typescript

Bootstrapping du projet Node Express

Avec NodeJS, il existe plusieurs façon de faire une application web

#créer le répertoire de projet
mkdir nodeexpressts
# initaliser le projet en répondant oui à tout
npm init -y

npm init -y    // dit oui à tout
{
  "name": "nodeexpressts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  // Ensuite créer un fichier index.js pour commencer
touch index.js
ouvrir VSCode dans ce répertoire projet
code .

On va installer Express

npm install express

On va code le fichier index.js

//index.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`);
});

Passons à Typescript !

Installation de Typescript

npm install typescript

Ensuite renommons le fichier index.js en index.ts !

En survolant les variable maintenant vous allez voir que pas mal de variable sont de type any, ça prouve qu’on est passé nen mode Typescript.

Installaton des fichier de type pour nodeJS et Express

npm install @types/express @types/node

modification des façons d’importer

Dans Typescript on n’utilise pas require mais import

import express, { Express, Request, Response } from 'express';
const app: Express = express();
const port = process.env.PORT || 3000;

app.get('/', (req: Request, res: Response) => {
    res.send('Hello World! in Typescript Now'); // modifié
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`);
});

A ce stade en survolant la varaible express, on voit que c’est typé.

Illustration, vous ne pouvez pas mettre n’importe quoi avec la notation pointée, si l’objet ne possède pas la méthode, vous aurez une erreur.

app.toto  << erreur souligné en rouge dans VSCode

Pour aller plus loin dans le typage et aider Typescript à savoir de quel type est une varaible on postfix avec les deux point

app.get('/', (req : Request, res : Response) => {
    res.send('Hello World!');
});

démarrage du serveur sour Typescript

Nous devons transpiler le Typescript en Javascript avant de démarrer le serveur. Invoquons tsc

npx tsc --init    // npx si on n'a pas tsc en global

Created a new tsconfig.json with:                                                                                       
                                                                                                                     TS 
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig

Un fichier tsconfig.json est apparu, pour configurer la manière dont Typescript sera transpilé par exemple. Nous devons indiqué où se trouvent les fichier Js de sortie, ouvrons ce fichier

"outDir": "./dist"

Pour transpiler taper simplement npx tsc ou tsc (si on a installé en global, vérifiez le)

tsc
un répertoire dist est apparu, regarez le contenu ce sont des fichiers JS.
Maintenant pour démarrer nodeJS

node dist/index.js

Faisons comme les pro, commetn avoir npm build par exemple

Ouvron spackage.json et configurons l’objet scripts:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc",
    "start": "node dist/index.js"
  },

Modifions le script

app.get('/', (req: Request, res: Response) => {
    res.send('Hello World! in Typescript Now WITH NPM RUN');
});

Et en ligne de commande faisons

npm run build
> nodeexpressts@1.0.0 build
> tsc
//puis
npm run start

Installons Nodemon ! (en devDependencies)

npm install -D nodemon

Maintenant ça va redémarrer à chaque fois qu’on va modifier un fichier.

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc -w & nodemon dist/index.js"
  },

npm run dev

Le & simple va exécuter en background

Il se peut que ça ne marche pas, en effet le & tout seul n’est pas une commande Windows mais Linux. Remplacer par :

"dev": "tsc -w && nodemon dist/index.js

il est probable que ça ne marche pas non plus, car tsc -w (w comme watch) peut ne pas rendre la main à nodemon. Dans ce cas il faut installer concurrently

npm install concurrently --save-dev
    "dev": "concurrently \"tsc -w\" \"nodemon dist/index.js\""

C’est un peu lourd comme syntaxe mais ça devrait résoudre vos problèmes.

Nettoyer le dossier dist avec rimraf

Si on veut s’assurer que le dossier soit entièrement nettoyer, on va installer rimraf (comme rm -rf !)

npm i -D rimraf


puis on modifie package.json

    "build": "rimraf dist &&tsc",

Mettez un fichier dans dist puis exécutez npm run build, le fichier va disparaître. Modifions le script pour ajouter un prestart et preserve, (et renommer dev en serve), qui vont simplement nettoyer le répertoire dist.

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rimraf dist &&tsc",
    "prestart": "npm run build",
    "start": "node dist/index.js",
    "preserve": "npm run build",
    "serve": "concurrently \"tsc -w\" \"nodemon dist/index.js\""
  },

En lançant npm run serve, c’est preserve qui est lancé en premier.

Ce tuto est inspiré de cette vidéo Youtube

Comment utiliser le SMTP de GMAIL

Pourquoi utiliser le SMTP de Gmail?

Pour quel type d’envoi Gmail est-il adapté?

Comment s’authentifier avec l’email de Gmail

La manipulation aujourd’hui (elle a évolué au cours du temps) est la suivante, il faut avoir une clé d’accès qui remplace le mot de passe de votre compte Gmail.

Créer une clé d’accès

Allez dans les paramètre de votre compte Google

Puis allez dans la sécurité

Pour plus d’information voici le lien vers l’aide de google pour générer le mot de passe d’application

La page de création de mot de passe d’application est sur cette page, personnellement en naviguant dans le rofil de google, je n’ai jamais réussi à retrouver cette page, aussi ce lien est précieux ! Si vous y allez et que vous avez cette page d’erreur :

C’est que vous n’avez pas activé le 2FA. Faites le

Puis revenez sur cette page, vous devriez avoir cet écran

Voilà ! Personnellement j’utilise cette méthode pour me générer de quoi utiliser dans les applications web le SMTP de Gmail, qui a une délivrabilité tès fiable en transactionnel.

Faire des requêtes réseau dans le terminal

Je me suis intéressé récemment à faire des requêtes de al façon la plus basique possible. Loin des librairies qui vous masquent ce qui se passe sous le capot, on va voir les technique dans le shell qui permettent de faire des requêtes réseau, je suis sûr que je n’aurais pas la technique la plus bas niveau mais on va s’en rapprocher.

CURL

Ce programme est mondialement connu, il est présent partout, dans toutes les plateformes. Il est présent dans tous les langages. Par exemple la librairie curl en PHP permet de faire tout type de requête réseau. Voyons comment en shell nous pouvons faire une requête basique

curl https://yvonh.com/ -o yvonh.html
cat yvonh.html  //lire le fichier généré qui est vide

L’argument -o va rediriger l’output dans un fichier texte avec extension HTML.

Dans ce cas précis vous n’allez rien voir, car il y a une redirection vers www.yvonh.com, or par défaut Curl ne suit pas la redirection. Il va retourner un résultat vide. La requête suivante va instruire Curl de suivre les redirections.

curl -L https://yvonh.com -o yvonh.html

On en va pas voir en détail toutes les commandes de Curl, on va voir les autres méthode de requêtage réseau

wget

wget est une commande très souvent utilisée

wget -qO- https://www.example.com

openssl

openssl s_client -connect www.example.com:80

Il y a aussi d’autres commandes mais qui ne sont pas toutes présentes dans Linux

nc, /dec/tcp, telnet, /dev/udp

git versioning

Comment cloner sans mot de passe un Repo Github

Support for password authentication was removed on August 13, 2021

Depuis cette date, il est impossible de cloner un repository privé, car pour cloner un repository privé il faut s’authentifier. Pour remplacer le mot de passe, Github utilise le PAT : Personal Access Token.

Comment créer le PAT avec Github

Pour créer le PAT, allez dans Settings sur la page d’accueil de votre Github, puis sur la barre latérale gauche, cliquer sur Developer Settings, puis sur Personal Access tokens, puis dans la liste choisissez Tokens (Classic)

Cliquez sur Generate new token dans la liste déroulante et choisissez classic.

Pour les opération usuelles, cliquez la checkbox repo, et choisissez une durée de validité, 30 jours par défaut. Une fois généré, copiez la chaine PAT en lieu sûr; car vous ne le verrez plus dans votre Github. (Evitez de le mettre dans votre repository !!!)

Comment utiliser le PAT pour cloner?

Le PAT remplace en lieu et place votre mot de passe, il est plus long et plus secret, et a une durée de vie déterminée, contrairement à votre mot de passe, il est donc plus sûr.

Donc suite à la commande git clone https://…

Username for 'https://github.com': votre_nick
Password for 'https://votre_nick@github.com

Au moment d’entrer le password, vous collez simplement votre PAT (Personal Access Token)

Ou alors si vous renseignez votre PAT ça va plus vite:

git clone https://<PAT>@github.com/<nick>/<repo>.git

Le PAT vous permet aussi d’utiliser le webservice de Github.

Se connecter à un droplet DigitalOcean avec Putty

Vous devez avoir fait la création d’une paire de clé privée-public. Ensuite lors de la création de votre droplet, vous aurez uploadé la clé publique sur digitalOcean.

Nous allons faire la connexion avec Putty

D’abord localisez l’adresse IP de votre Droplet, ouvrez Putty et mettez l’adresse IP. Puis allez dans Auth, puis ajoutez votre clé privée au format .ppk Le soucis c’est qu’avec ssh-keygen, vous ne générez pas de .ppk, il vous faudra l’utilitaire Puttygen.

Création de la clé privée au format .ppk

Dans Puttygen, chargez votre clé privée et sauvez la.

Importez la clé

Sélectionnez votre clé privée (la clé sans extension)

Chargez la clé

Vous pouvez sauver votre clé sans passphrase (c’est plus simple)

Retour dans Putty

Ajout de l’utilisateur, aller dans Connection > data

Sauvegarder les données de la session pour ne pas avoir à les retaper

Dans le cas où cela ne marcherait pas essayez de télécharger la dernière version de Putty, j’étais pour ma part en 0.72 et en mettant à jour vers la 0.81 ça marche.

DigitalOcean recouvrez l’accès à votre console de droplet

Si vous vouslez accéder à votre console et que l’écran tourne en boucle, c’est que sans doute vous avez touché au FireWall. Vous avez touché au FireWall à l’occasion de l’installation de Nginx.

Pour recouvrer l’accès à votre console, il faut utiliser la Recovery Console, pour y accéder, aller dans Access dans la page principale du droplet.

Après le démarrage de la Recovery Console, il vous sera demandé de rentrer le mot de passe du user root, le problème est que vous n’avez pas de mot de passe root si vous avez choisi d’accéder pas clé SSH.

Il vous faudra donc resetter votre mot de passe root, en choisissant l’option juste après la Recovery Console, Reset Root Password.

Vous voila prêt à changer la configuration du Firewall une fois loggé

ufw allow ssh

Installer Nginx sur Ubuntu sur DigitalOcean

Une fois que vous avez créé votre droplet, il est conseillé de créer un utilisateur sudoable, sinon faites les opérations suivantes avec l’utilisateur root.

Nous allons mettre à jour vos paquets et installer Nginx

sudo apt update
sudo apt install nginx

Si vous êtes sous root pas la peine de mettre sudo. Sudo permet d’élever les privilèges d’un utilisateur non root.

Paramétrage du firewall ufw (uncomplicated Fire Wall)

sudo ufw app list   // liste les applications connue par ufw

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

Si on n’a pas encore configuré un SSL pour le serveur, on va utiliser le profil Nginx HTTP.

>ufw allow 'Nginx HTTP'

Afficher le status du firewall

>ufw status

Status: inactive    // si vous avez ça c'est que ufw n'est pas activé

faite la commande suivante:
>ufw enable

>ufw status

Status: active

To                         Action      From
--                         ------      ----
Nginx HTTP                 ALLOW       Anywhere                  
Nginx HTTP (v6)            ALLOW       Anywhere (v6)      

Vérifier que le serveur web tourne

A la fin de l’installation, Unbuntu démarre le serveur Nginx. Nous allons vérifier avec la commande

systemctl status nginx

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Sat 2024-07-27 21:06:47 UTC; 16min ago
       Docs: man:nginx(8)
    Process: 2697 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUC>
    Process: 2699 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 2701 (nginx)
      Tasks: 2 (limit: 509)
     Memory: 2.2M (peak: 2.3M)
        CPU: 16ms
     CGroup: /system.slice/nginx.service
             ├─2701 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             └─2702 "nginx: worker process"

Jul 27 21:06:47 ubuntu-s-1vcpu-512mb-10gb-ams3-01 systemd[1]: Starting nginx.service - A high performance web se>
Jul 27 21:06:47 ubuntu-s-1vcpu-512mb-10gb-ams3-01 systemd[1]: Started nginx.service - A high performance web ser>
lines 1-16/16 (END)

troisième ligne indique active running.

Important ! autorisez le SSH, sinon vous risquez de ne plus avoir accès à la console, si c’est le cas voici une solution

ufw allow ssh

Nous allons visiter dans le navigateur une page pour vérifier que Nginx fonctionne. Faite la commande pour connaitre l’IP qui doit vous afficher une page:

ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

165.232.90.185
10.18.0.5
fe80::28e7:4eff:fee5:b7fe

//commande alternative
curl -4 icanhazip.com

Je passe par l’IP V4, et j’obtiens bien une page web, la page d’accueil de Nginx.

f

Mémento sur les github actions

Concept essentiels de github actions

  • Workflow : un process fait d’un ou plusieur jobs. Le workflow est un fichier YAML
  • job : un ensemble de steps exécuté sur un même runner, tournent en parallèle en général et spécifie l’OS
  • step : une tâche dans un job, peut être une action ou une commande shell., share filesystem
  • runner : le serveur qui exécute le workflow

Qu’est ce qu’un action?

Une action est un ensemble de code au sein d’un workflow. En savoir plus sur les actions (EN)

Dans le contexte de GitHub Actions, une action est une application personnalisée pour la plateforme GitHub Actions qui automatise une tâche spécifique ou un ensemble de tâches au sein d’un workflow. Les actions sont les éléments de base des workflows et peuvent être réutilisées dans plusieurs workflows et dépôts.

Points Clés sur les Actions

  1. Unités de Code Réutilisables : Les actions sont conçues pour être réutilisables. Vous pouvez les partager entre différents dépôts et avec la communauté GitHub.
  2. Préconstruites ou Personnalisées : Vous pouvez utiliser des actions préconstruites disponibles sur le GitHub Marketplace ou créer vos propres actions personnalisées adaptées à vos besoins spécifiques.
  3. Encapsulent des Tâches : Les actions encapsulent des tâches telles que le checkout du code, la configuration des environnements, l’exécution de tests et le déploiement des applications.
  4. Configurables : Les actions acceptent souvent des entrées et produisent des sorties, ce qui permet de les personnaliser et de les composer ensemble pour créer des workflows complexes.

Types d’Actions

  1. Actions JavaScript : Actions construites en utilisant Node.js qui peuvent exécuter du code JavaScript.
  2. Actions de Conteneur Docker : Actions qui s’exécutent dans un conteneur Docker, ce qui vous permet de les empaqueter avec toutes leurs dépendances.
  3. Actions Composites : Actions qui combinent plusieurs étapes en une seule action, définie en utilisant une série de commandes shell et d’autres actions.

Qu’est ce que actions/checkout@v3?

C’est une action prédéfinie de Github, qui clone un repository dans l’environnement du workflow.

Glossaire Github action:

logs

uses : est utlisé pour sélectionner une action déjà définie dans un docker, dans un repository.

name: le nom du workflow

on : écouteur d’événement

artifact

runs-on: spécifie le type de machine (ou « runner ») sur laquelle un job doit s’exécuter. Un runner est un serveur qui exécute les workflows définis dans vos fichiers YAML. Un runner peut être auto-hébergé.

Types de Runners Disponibles

GitHub Actions offre plusieurs types de runners hébergés (préconfigurés par GitHub) que vous pouvez utiliser :

  • ubuntu-latest : Une machine virtuelle exécutant la dernière version stable d’Ubuntu.
  • ubuntu-20.04 : Une machine virtuelle exécutant Ubuntu 20.04.
  • ubuntu-22.04 : Une machine virtuelle exécutant Ubuntu 22.04.
  • windows-latest : Une machine virtuelle exécutant la dernière version stable de Windows.
  • windows-2019 : Une machine virtuelle exécutant Windows Server 2019.
  • macos-latest : Une machine virtuelle exécutant la dernière version stable de macOS.
  • macos-11 : Une machine virtuelle exécutant macOS 11 (Big Sur).

steps:

Liens externes:

https://www.digitalocean.com/community/tech-talks/deploying-to-digitalocean-with-github-actions

Rsync pour synchroniser des répertoire avec une machine distante

Rsync permet de synchroniser des dossiers, où qu’ils soient pourvu que deux machine aient accès via des clés SSH.

Par exemple soient les machines Msource et Mtarget. Msource doit générer une clé SSH et mettre la clé publique dans Mtarget. Ainsi Msource pourra se connecter sans mot de passe à Mtarget.

Bien que su rLinux Rsync soit disponible par défaut, sur Window c’est assez compliqué de l’installer.

Nous partirons du postulat qu’on est sous Linux et que Rsync est présent.

Synchronisation d’un fichier avec Rsync

rsync -a monfichier.txt login_mtarget@mtarget.com:/home/public_html

Synchronisation d’un dossier avec Rsync

rsync -a repertoire1 login_mtarget@mtarget.com:/home/public_html

Tous les fichier du repertoire1 seront copié dans le répertoire /home/public_html de la machine cible Mtarget.

Uploader en ajax un fichier avec barre de progression

Vous savez sans doute uploader via un formulaire une image vers un serveur, et aussi via ajax (c’est un peu plus compliqué). Mais il est possible d’uploader en Ajax progressivement par morceau (chunk en anglais).

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BLOB upload</title>
</head>

<body>
    <form action="#">
        <input type="file" value="" id="file" name="file"><br>
        <input type="submit" id="uploadBtn" value="UPLOAD">
    </form>
</body>
<script>
...
</script>
</html>

Grâce à l’attribut name on peut faire référence au fichier. Dans la balise script on a le code suivant :

<script>
    const uploadBtn = document.querySelector('#uploadBtn')
    const url = 'http://localhost.test/blobupload.php'

    uploadBtn.addEventListener('click', function (e) {
        e.preventDefault()
        const file = document.querySelector('#file').files[0]
        uploadBlob(file)

    })


</script>

La fonction uploadBlob() va prendre le fichier qui est passé en argument, puis le décomposer dans une boucle en plusieurs chunk de taille fixe. Notre boucle se termine quand on a atteint la fin du fichier.

<script>
    const uploadBtn = document.querySelector('#uploadBtn')
    const url = 'http://localhost.test/blobupload.php'

    uploadBtn.addEventListener('click', function (e) {
        e.preventDefault()
        const file = document.querySelector('#file').files[0]
        uploadBlob(file)

    })
  
  function uploadBlob(file) {
        
        const chunkSize = 100_000;
      
        let start = 0

        for (let start = 0; start < file.size; start += chunkSize) {

            const chunk = file.slice(start, start + chunkSize);
            const fd = new FormData();
            fd.set('file', chunk,);


            fetch(url, {
                method: "POST",
                body: chunk
            })
                .then(response => alert('Blob Uploaded'))
                .catch(err => alert(err));
        }
    }
</script>

Note boucle for va saucissonner le fichier en portions égales (à l’exception du dernier tronçon) à chaque itération. Ensuite ce morceau sera uploadé.

Côté backend pour la réception des fichiers en PHP

<?php
//  blobupload.php
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
$result = file_get_contents('php://input');
$written = file_put_contents('cpu.png', $result, FILE_APPEND);
echo $written;

C’est plutôt simple côté backend, le code est synchrone. Il retourne la quantité de bytes écrits ( 1 byte = 1 octet)

Essai d’upload par paquet en javascript

Nous allons prendre un fichier qui fait plus de 100000 octets pour déclencher la découpe. Pour l’exemple j’ai une image de 811 Ko nommée cpu4.png. Cela fait combien de paquets (chunks) à envoyer? Simple division 811000 / 100000 = 9. Le dernier paquet fait moins de 100000 octets ou bytes.

Image que nous allons envoyer.

On ouvre les devtools, et effectivement j’ai 9 envois.

Si vous essyez d’ouvrir l’image cependant, vous ne pourrez pas la visualiser. En fait l’image uploadée est différente de l’image choisie, vous n’avez qu’à comparer leurs checksum sur un outil comme celui-ci.

Mais comment est ce possible? Regardez l’image ci-dessus, le fichier php renvoit la taille écrite en guise de réponse, et l’avant dernier paquet montre 29669 octets, alors que ce devait être le dernier paquet qui doit montrer ce nombre ! Bref les paquets sont envoyés dans le désordre !! Nous allons corriger cela simplement avec async/await, qui va rendre un peu de synchronisme dans la boucle for.

Async/await à la rescousse.

Nous allons modifier le code de la façon suivante : uploadBlob sera async et on « awaitera » fetch.

    async function uploadBlob(file) {
        //reader = new FileReader() // le file reader va lire à partir d'une position donnée
        const chunkSize = 100_000;
        // const filename = file.name;
        let start = 0
        let i = 0

        for (let start = 0; start < file.size; start += chunkSize) {
            console.log("morceau ", i, "uploadé")
            i++
            let end = start + chunkSize
            const chunk = file.slice(start, end);
            const fd = new FormData();
            fd.set('file', chunk,);


            await fetch(url, {
                method: "POST",
                body: chunk
            })
                .then(response => response.text())

        }
    }

Maintenant le dernier paquet est bien le plus petit. Tous les paquets sont envoyés dans l’ordre. Nous pouvons vérifier que l’image s’ouvre bien. Vérifiez les checksum de ces deux fichiers, ils sont pareil.

Il est à noter que dans le cas des envois en désordre, la taille du fichier reçu est variable !

Utiliser le webservice de Github pour extraire des informations

Récemment, pour un projet open source, j’ai voulu afficher un classement des contributeur en fonction du nombre de commits. J’ai tout de suite eu l’idée de passer par le webservice de Github (alors que je n’en ai jamais entendu parler).

Il est normal que Github ait un webservice, cela permet de faire des nitégration un peut partout (plugin git par exemple).

Comment mener sa réflexion du webservice à l’affichage des badges pour récompenser les contributeurs?

L’idée de base est de proposer un classement des contributeurs en fonction du nombre de pull requests. Pour avoir les pull request, il faut avoir les commits sur la branch principale (dans notre cas develop).

Il a fallut d’abord trouver un moyen via la documentation de lister les commits d’une branche. Deplus j’ai pris le parti d’utiliser un token pour avoir moins de restriction sur les appels.

Exemple d’appel sans avoir besoin de clé:

https://api.github.com/user/repos

Appel avec un clé

Il faut d’abord générer une clé, plus exactement un PAT (personal Access Token). Pour ce faire aller à Settings, puis dans la barre de gauche aller dans Developper…

Générer le PAT, sélectionner les droits de ce PAT, je n’ai besoin qu’en lecture, donc cocher comme dans l’image. Je vous propose dans un premier temps d’expérimenter avec Postman, pour ceux qui ne le connaissent pas encore, Postman permet de faire des requêtes HTTP sans navigateur, pratique pour tester des webservices.

Vous aurez les repository du propriétaqire de votre token. Maintenant listons les commits d’une branches, il a fallu explorer la documentation, ne la baclez pas, la documentation est riche.

// lister les branches
https://api.github.com/repos/refschool/Clone-Weetransfert/branches

// lister les commits de la branche main
https://api.github.com/repos/refschool/Clone-Weetransfert/branches/main

//  lister les commits de la branche par défaut qui est main
https://api.github.com/repos/refschool/Clone-Weetransfert/commits

//  lister les commits de la branche develop avec 100 résultats par page.
https://api.github.com/repos/refschool/Clone-Weetransfert/commits?per_page=100&sha=develop

Il faut essayer de se familiariser avec la pagination le jour où l’on a vraiment beaucoup de commit.

Lien vers la documentation:

https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28

Mais ne peut on pas lister les pull request?

A l’évidence si ! voici la documentation :

https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests

vous devez être le propriétaire du repo pour avoir les pull requests du projet (normal)

Installer docker sur DigitalOcean Droplet avec Docker

Dans cet article on va voir comment déployer des container docker d’une application multicontainer dans un VPS à l’aide de Docker-compose. Sur DigitalOcean, la manipulation est plus facile que sur Azure ou AWS (le plus complexe)

Création du Droplet

Un droplet sur DigitalOcean (un prestataire Cloud abordable et pas trop compliqué à utiliser),

1/Créer un compte sur DigitalOcean

2/créer un droplet l’équivalent d’un VPS, choisir Ubuntu comme OS avec Docker préinstallé en vous aidant du Marketplace. Vous créerez par la même occasion un projet qui apparaitra dans la sidebar.

Ensuite vous choisissez le Datacenter le plus proche de chez vous, en France l’on choisira Amsterdam par exemple.

Dans la section « Choose an image« , cliquez sur l’onglet Marketplace, puis sur l’image Docker on Ubuntu, ici l’image représente une machine virtuelle bien sûr et pas une image Docker.

Ensuite choisissez les options les moins chères, dans Droplet Type, choisissez « Basic« , dans CPU options, choisissez « Regular« , et la formule au mois la moins chère.

Dans « Choose Authentication Method« , choisissez Password pour faire simple, et SSH Key pour faire pratique (pas de mémorisation de mot de passe et login nécessaire). Allez sur ce post pour générer et mettre votre clé sur le service. Enfin cliquez sur « Create Droplet ». Au bout d’un certain temps votre droplet sera créée avec Docker installé, cependant Docker-Compose ne sera pas encore installé. Il vous faudra faire via la console.

Si vous voulez installer à la main Docker, les instruction sont sur cette page :

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04

Cloner votre projet depuis Github

Pour vous connecter en console dans votre Droplet dans la sidebar « Projects« , cliquez sur votre projet

Installer docker-compose

Une fois dedans cliquez sur « Console« , cela ouvrira un shell. Installez docker-compose

///sudo apt-get install docker-compose-plugin

ou tapez docker-compose, une erreur avec un message sur comment installer docker-compose s'affichera.
apt install docker-compose

Vous êtes en connecté en root, allez à /home et cloner le projet. Le projet consiste en un front end en ReactJS, un backend en Node/Express et de MongoDB. Ce dernier est directement tiré du hub de Docker. Alors que le front et le back vont être transformé en image via leurs Dockerfiles respectifs.

Clonage du projet depuis Github

/home# git clone git@github.com:refschool/vidly.git

On clone soit en SSH car Github ne supporte plus l’authentification par login et mot de passe. ou alors il faut utiliser un PAT personal access token. L’ennui avec la clé SSH est qu’il faut en créer une sur le droplet et la mettre sur Github, cela fait beaucoup de manipulations. Le mieux est de créer le PAT sur Github. Il s’utilise comme un mot de passe.

git clone https://<PAT>@github.com/refschool/vidly.git

Copie du fichier docker-compose-prod.yml depuis votre poste vers votre droplet

A ce stade il n’y a pas grand d’installé ni de configuré pour votre droplet (pas de FTP etc). Le moyen le plus simple d’envoyer un fichier vers le droplet est d’utiliser l’utilitaire rsync pour envoyer ce fichier. Mais le problème est que ce n’est pas simple d’avoir rsync sur Windows (pas un problème sur Linux). On va utiliser scp depuis le terminal DOS.

scp -i C:\Users\admin\.ssh\id_rsa E:\mosh\vidly\docker-compose-prod.yml root@188.166.87.69:/home/

Update :

Si le fichier docker-compose-prod.yml est dans le git, pas besoin de faire cette manipulation finalement.

Démarrage du docker-compose avec le fichier de prod

Dans la racine du projet où il y a le fichier docker-compose-prod.yml

docker-compose -f docker-compose-prod.yml up -d

Cette unique ligne va créer ou importer les images, démarrer les containers et in fine votre application.

Accès à l’application sur DigitalOcean

Si tout s’est bien passé, vous allez pouvoir accéder à votre application sur le droplet à l’adresse IP que vous pouvez retrouver dans votre interface chez DigitalOcean, sur le port 3000.

Les problèmes rencontrés et leurs solutions

An accédant via l’adresse IP l’application, on rencontre une première erreur :

Problème de CORS

La preière fois que vous accédez à votre application, vous allez rencontrer ce message « Could not fetch movie »

Ceci est vraisemblablement dû à un problème de CORS (cross origin), si on ouvre les devtools, onglet Réseau, en inspectant la requête, vous aurez les indices. La requête est faite à http://localhost:3001/api alors que notre serveur possède une adresse IP (voir image)

En effet en phase de développement on a http://localhost:3001/api (voir le fichier src/services/api.js) , si la variable d’environnement REACT_APP_API_URL est settée, elle surchargera localhost.

const baseUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api";

A la racine du projet, il y a un fichier docker-compose-prod.yml. Dans le Dockerfile du front il vous faudra remplacer dans le fichier Dockerfile après COPY

#Dockerfile
...
COPY . .
ENV REACT_APP_API_URL=http://adresse-ip-du-droplet:3001/api
...

Problème de droits de fichier

Vérifiez aussi que dans le dossier backend, les fichiers docker-entrypoint.sh et wait-for soient exécutables en faisant la commande chmod +x <fichier>.

Une fois que la commande docker-compose est faite, vous avez la main dans le shell, Inspectez les containers

docker ps

//Voir les logs d'un container
docker logs <id-container>

//Par exemple si le container du backend ne fonctionne pas correctement, lisez les logs.

//Pour arrêter
docker-compose down


//Pour relancer avec un build d'images

docker-compose -f docker-compose-prod.yml up -d --build

Utiliser Rollup pour fusionner vos fichiers javascript

Application d’origine sans bundle JS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bundler with Parcel</title>
</head>

<body>
    <form action="#">
        <label for="email">Email</label><br>
        <input type="text" value="" id="email"><br>
        <label for="password">Mot de passe</label><br>
        <input type="text" value="" id="password"><br>
        <input type="submit" id="btn" valu="Soumettre">
    </form>
</body>
<script src="./script1.js"></script>
<script src="./script2.js"></script>
<script src="./script3.js"></script>

</html>

Les différents scripts JS

//script1.js
let email = document.querySelector('#email')
let password = document.querySelector('#password')
let btn = document.querySelector('#btn')


//script2.js
email.addEventListener('keyup', (event) => {
    if (!isEmailValid(email.value)) {
        console.log('Email is invalid')
    } else {
        console.log('Email is valid')
    }
})

password.addEventListener('keyup', (event) => {

    console.log('Password length ', password.value.length)

})

// Vérifie si l'email est correct
const isEmailValid = (email) => {
    return email.toLowerCase()
        .match(
            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
        );
}

//script3.js
btn.addEventListener('click', (event) => {
    event.preventDefault()
    if (isEmailValid(email.value) && password.value.length > 8) {
        alert("Form is valid")
    } else {
        alert("Form is invalid")

    }
})

Si votre page HTML fait appel à l’inclusion de beaucoup de fichier JS, vous aurez envie de fusionner tous ces fichiers en un seul pour le rendre plus compact. je vous propose d’utiliser Rollup, qui est simple comparé à Webpack. On va installer Rollup

npm install rollup @rollup/plugin-node-resolve --save-dev

Ensuite créer un fichier de configuration

import resolve from '@rollup/plugin-node-resolve';

export default {
  input: 'src/main.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [resolve()]
};

Dans le fichier main.js vous importez les fichier Javascript

import './script1.js';
import './script2.js';
import './script3.js';

// etc

Si vous avez un message du genre

Ajoutez une propriété dans package.json

{
    "scripts": {
        "build": "rollup -c"
    },
    "devDependencies": {
        "@rollup/plugin-node-resolve": "^15.2.3",
        "rollup": "^4.18.1"
    },
    "type": "module"
}

Désormais dans le fichier HTML l’inclusion du Js ressemble à ça

//index.html
...
</body>
<script src="./dist/bundle.js"></script>
</html>

A quoi ressemble le bundle.js

(function () {
    'use strict';

    document.querySelector('#email');
    document.querySelector('#password');
    document.querySelector('#btn');

    email.addEventListener('keyup', (event) => {
        if (!isEmailValid$1(email.value)) {
            console.log('Email is invalid');
        } else {
            console.log('Email is valid');
        }
    });

    password.addEventListener('keyup', (event) => {

        console.log('Password length ', password.value.length);

    });

    // Vérifie si l'email est correct
    const isEmailValid$1 = (email) => {
        return email.toLowerCase()
            .match(
                /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
            );
    };

    btn.addEventListener('click', (event) => {
        event.preventDefault();
        if (isEmailValid(email.value) && password.value.length > 8) {
            alert("Form is valid");
        } else {
            alert("Form is invalid");

        }
    });

})();

Configurer un noeud sur Kaleido.io (Hyperledger)

tout d’abord il faut aller créer un Network

2/Ensuite créer un node, ensuite il faut aller dans le node en question, puis aller dans App, et faire Create an App

3/ Créer une nouvelle version pour pouvoir déployer ses chaincode

Un chaincode est l’équivalent d’un smartcontract, écrit en golang, il faut le compiler d’une certaine façon.

Comprendre les sockets avec Socket.io

Qu’est qu’un socket?

Un socket est une interface de communication entre deux noeuds dans un réseau informatique. Plus concrètement, entre deux ordinateurs, et pour les développeurs web entre un serveur et un client, aka votre ordinateur et plus précisément le navigateur.

Côté serveur

On va pour simplifier utiliser Nodejs, avec un script en local on va pouvoir en quelques ligne mettre en oeuvre un socket grâce à la librairie socket.io

Côté client

Grâce à une librairie socket.io-client, on va pouvoir se connecter à un socket.

Quelle est la différence entre un socket et une requête AJAX?

Dans le monde du HTTP, une communication entre client et serveur ne dure qu’un instant, entre deux communications il n’y a rien. Vous faites une requête AJAX, le client initie une demande (Request), le serveur réponse avec un Response, et puis c’est fini.

Dans le cas d’un socket, une fois que le client initie une demande de connexion, la communication s’établie et dure tant que l’on n’a pas arrêté, elle ne s’arrête pas toute seule. Le socket sert à maintenir une connexion persistente, avec un flot continu de données de part et d’autre.

Le socket répond au besoin de recevoir contnuellement des données, une application comme une courbe de bourse en temps réel a besoin d’un socket, cela permet une mise à jour temps réel du cours de bourse. Bien sû on pourrait faire toute les secondes une requête AJAX, mais c’est très consommateur de données, de par la nature du protocole HTTP. Socket ne fait pas partie de la technologie HTTP.

Démarrer un projet avec socket.io

Créez un nouveau répertoire de projet, ouvrez le avec VSCode, ouvrez un terminal sous VSCode.

Créer un fichier package.json comme ci-dessous:

{
  "name": "app-socket",
  "version": "0.1",
  "description": "mon socket.io app",
  "dependencies": {}
}

Installez Express

Express est un framework pour NodeJS en MVC.

npm install express@4

Créez le fichier index.js

const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);

app.get('/', (req, res) => {
  res.send('<h1>Hello world</h1>');
});

server.listen(3000, () => {
  console.log('listening on *:3000');
});

vous pouvez lancer le serveur NodeJS avec la commande:

node index.js

ça va initialiser un serveur qui écoute sur le port 3000 (par défaut pour NodeJS). Si dans votre navigateur, vous ouvrez l’url http://localhost:3000. Gardez un oeil sur le terminal de VSCode également.

Ajout d’un fichier HTML

Nous allons améliorer notre code avec une page HTML à servir

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

      #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
      #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
      #input:focus { outline: none; }
      #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages > li { padding: 0.5rem 1rem; }
      #messages > li:nth-child(odd) { background: #efefef; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form id="form" action="">
      <input id="input" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

Changez le code d’index.js

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

Relancez votre serveur avec node index.js

Note : Pour éviter de redémarrer à la main à chaque fois qu’il y a une mise à jour du code, je vous suggère d’installer nodemon, un démon NodeJS, qui va redémarrer le serveur automatiquement à chaque enregistrement de fichier.

npm install --save-dev nodemon  // installe en tant que dépendance de développement
// redémarrer le serveur
nodemon ./index.js localhost 3000

Intégration de socket.io

Maintenant on va allez voir les choses intéressantes, l’intégration de socket !

Il nous faut agir sur deux fronts, sur le serveur et sur le client. Il y a donc deux partie à la librairie socket.io. (nous sommes toujours en architecture client/serveur)

Socket.io côté serveur

npm install socket.io

et éditons le fichier index.js

const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

// début de code de gestion du socket
io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('disconnect', () => {
    console.log('user disconnected');
  });
});

// fin de code de gestion du socket

server.listen(3000, () => {
  console.log('listening on *:3000');
});

Notez qu’on crée le serveur de socket en lui passant dans le constructeur le server NodeJS. « connection » est l’événement qui se déclenche lorsque la page dans le front end se charge pour la première fois et qu’un message donc est envoyé au serveur.

Socket.io côté client

Ajoutez le snippet dans le fichier HTML avant la fermeture de body

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

ça y est notre application web est équipée!

Maintenant ouvrez la page a port 3000, votre terminal nodeJS va afficher ‘a user connected’. Ce qui se passe c’est que lorsque la page est loadée, la librairie socket.io va envoyer un signal vers le serveur, qui intercepte le message. Si vous bne voyez rien, rafraichissez la page.

Construction de l’application de messagerie instantanée

Maintenant côté client, écrivons le code JS pour émettre les messages entrés au clavier, dans le formulaire:

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();

  var form = document.getElementById('form');
  var input = document.getElementById('input');

  form.addEventListener('submit', function(e) {
    e.preventDefault();
    if (input.value) {
      socket.emit('chat message', input.value);
      input.value = '';
    }
  });
</script>

et côté serveur on va afficher le message, ajoutez le snippet suivant :

io.on('connection', (socket) => {
...
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
  });
});

Tapez les messages dans le formulaire, observez les logs côté serveur.

Le broadcasting côté serveur

Intéressons nous maintenant sur comment le serveur va émettre un message pour tous les client connectés, à la manière d’une station radio, d’où le terme ‘broadcasting’. Il faut utiliser la méthode emit() de l’objet io. Modifiez le code :

io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('disconnect', () => {
        console.log('user disconnected');
    });

    socket.on('chat message', (msg) => {
        console.log('message du client: ' + msg);
        io.emit('retour', { propriete1: 'valeur 1', propriete2: 'valeur2' })
    });
});

Configurer VSCode pour débugger

Il ya plusieurs façons de débugger NodeJS avec VSCode, le plus simple (mais peut être le plus ennuyant) est d’utiliser l’auto Attach, dès qu’un process NodeJS est démarré, VSCode se met en écoute. On va utiliser cette méthode. Pour plus d’information sur les autres méthodes allez sur cette page.

Utiliser les dev tools pour visualiser les communication via le socket

Vous pouvez voir les connexion socket comme vous pouviez le faire avec les requêtes Ajax. Dans les devtools, allez dans l’onglet Réseau, cliquez sur WS dans les filtres (à côté de XHR, Images etc.

Dans l’onglet Réponse, vous pouvez voir en fait les allées et venues des messages.

Programmation orientée événéments en Javascript

Ce que tous les développeurs web connaissent, ce sont les événements du DOM, les clics de souris, les survol, les chargements de page. Soit la page HTML suivante, un formulaire avec une div cachée qui se révèle dès que le formulaire change

<body>
    <style>
        #popup {
            border: black 1px solid;
            width: 50%;
            height: 10vh;
            border-radius: 5px;
            margin: 10px;
            display: none;
        }
    </style>
    <form action="#" id="formulaire">
        <label for="email">Email</label>
        <input type="text" id="email"><br>
        <label for="name">Name</label>
        <input type="text" id="name"><br>
        <input type="submit" value="Send">
    </form>
    <div id="popup">
        <h1>Form filled !</h1>
    </div>
</body>
    let email = document.querySelector('#email')
    let name = document.querySelector('#name')
    let formulaire = document.querySelector('#formulaire')
    let popup = document.querySelector('#popup')

    formulaire.addEventListener('change', (e) => {
        console.log('form has changed')
        popup.style.display = 'block'
    })

Mais il est aussi possible de lancer des événement depuis n’importe quel objet en Javascript, et ceci va nous permettre de coder notre application avec des événements, permettant une grande souplesse et découplage.

Par exemple, imaginez un formulaire avec login et mot de passe, di les deux champs sont validés, nous voulion faire apparaitre une popup.

    let email = document.querySelector('#email')
    let name = document.querySelector('#name')
    let formulaire = document.querySelector('#formulaire')
    let popup = document.querySelector('#popup')
    popup.addEventListener('formEvent', (e) => {
        popup.style.display = 'block'
    })
    //create the CustomEvent
    const formEvent = new CustomEvent('formEvent', { detail: "les détails" })



    formulaire.addEventListener('change', (e) => {
        console.log('form has changed')
        popup.dispatchEvent(formEvent)
        // popup.style.display = 'block'
    })

Il faut mettre bubble à true pour que l’événement se propage

  let email = document.querySelector('#email')
    let name = document.querySelector('#name')
    let formulaire = document.querySelector('#formulaire')
    let popup = document.querySelector('#popup')
    popup.addEventListener('formEvent', (e) => {
        popup.style.display = 'block'
    })
    //create the CustomEvent
    const formEvent = new CustomEvent('formEvent', { detail: "les détails",bubbles:true })



    formulaire.addEventListener('change', (e) => {
        console.log('form has changed')
        popup.dispatchEvent(formEvent)
        // popup.style.display = 'block'
    })

Déjouer les piège du mot clé this en Javascript

Le mot clé this en javascript sert dans 3 contextes différents, et son comportement diffère selon les contexte.

le mot clé this dans une classe

C’est sans doute le plus facile à appréhender, car c’est comme dans les autres langages de programmation comme Java ou PHP.

class Rectangle {
  constructor(hauteur, largeur) {
    this.hauteur = hauteur;
    this.largeur = largeur;
  }
}

Le mot clé this dans une fonction

function test() {
    console.log('this in a function', this);
}

const test2 = () => {
    console.log('this in arrow function', this);

}
test2() // objet window
test() // objet Window

Comme on est dnas l’espace global (Window) le mot clé this représente Window. En effet les deux fonction sont définie dans l’espace de nom global et non dans un objet.

Le mot clé this dans l’espace de nom global

dans une fonction, la valeur de this dépend de comment la fonction est appelée. Considérez this comme un paramètres caché de la fonction. this est l’objet dans lequel la fonction est appelée.





Le cas des fonctions flèches

function test() {
    console.log('this in a function', this);
}

const test2 = () => {
    console.log('this in arrow function', this);

}
// test2() // undefined
// test() // objet Window
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.test = test
obj2.test2 = test2
console.log(obj1.test()) // this est l'objet obj1
console.log('arrow ', obj2.test2()) // this est l'objet Window

Dans les fonctions flèche, le this représente toujours l’objet global Window, alors que dans les fonctions classiques this représente l’objet dans lequel la fonction est définie.

Le cas du strict-mode

"use strict";

function show() {
    console.log(this);
}

show(); // undefined

This dans un event listener

Ici c’est plus courant, vous avez déjà vu avec Jquery le $this, en Javascript pur c’est pareil avec this

<span id="btn">Click Me</span>
<script>
const btn = document.querySelector("#btn")
btn.addEventListener('click',function(event){

console.log(this)

}

</script>

Modifier la valeur de this avec bind,call et apply

const person1 = {
    name: 'Paul',
    surname: 'Auchan',
    sayName: function () {
        return this.name + " " + this.surname;
    }
}

const person2 = {
    name: 'Justin',
    surname: 'News'
}

console.log(person1.sayName.call(person2)); // le this sera le this de person2
//appel avec arguments

const person2 = {
    name: 'Justin',
    surname: 'News'
}

const person3 = {
    name: 'Paul',
    surname: 'Auchan',
    sayName: function (ville) {
        return this.name + " " + this.surname + ', ' + ville;
    }
}
console.log(person3.sayName.call(person2, "Paris")); // le this sera le this de person2

Cas de bind

Autant call et apply se ressemblent, bind n’exécute pas la fonction , il faut l’invoquer manuellement. bind ne fait que binder une variable

//bind
const person4 = {
    name: 'Jules',
    surname: 'Sanchez',
    sayName: function() {
        return this.name + " " + this.surname
    }
}

const person5 = {
    name: 'Rudy',
    surname: 'Hess'
}

const sayPerson2Name = person1.sayName.bind(person2)

console.log(sayPerson2Name())

Tutoriel backtesting avec Tradingview

Dans ce tutoriel nous allons voir comment simplement backtester une strétégie avec Tradingview, cet outil existe en gratuit et fait dans la verison gratuites énormément de chose.

On va écrire le script en Pinescript, et on va exécuter le backtest.

Ceci n’est pas un conseil financier.

strategy("Moving average Cross")

// Pinescript c'est L4G (langage de 4ème génération)
ema20 = ema(close,20)
ema50 = ema(close,50)


// on définit quand est ce qu'on long ou short
long = ema20 > ema50
short = ema20 < ema50

//ouverture du trade
strategy.entry("long", strategy.long,1.0,when=long)
strategy.entry("short", strategy.short,1.0,when=short)

// fermeture du trade
strategy.close("long",when=short)
strategy.close("short",when=long)

Cliquez sur « Update on chart »

Un fois que vous avez fait le script il faut clicker sur « Strategy tester », à côté de « Pine Editor », dans le sous menu, Overview vous pouvez voir la courbe de PNL

lien vers la documentation de Pinescript

définir une date de début et de fin

//@version=4
strategy("Moving average Cross")
ema20 = ema(close,20)
ema50 = ema(close,50)
long = ema20 > ema50
short = ema20 < ema50

start = timestamp(2021,04,01,0,0)
end = timestamp(2024,04,01,0,0)

if time >= start and time <= end
    strategy.entry("long", strategy.long,1.0,when=long)
    strategy.entry("short", strategy.short,1.0,when=short)
strategy.close("long",when=short)
strategy.close("short",when=long)

il faut mettre @version=4 sinon la condition if ne marche pas

Ensuite cliquez sur « Update on chart » pour que ça marche

Affichage des deux moyennes mobiles

//@version=4
strategy("Moving average Cross")
ema20 = ema(close,20)
ema50 = ema(close,50)
long = ema20 > ema50
short = ema20 < ema50

plot(ema50, title="ma50", color=#ffc1cc, linewidth=3)
plot(ema20, title="ma20", color=#00ffaa, linewidth=2)

start = timestamp(2021,04,01,0,0)
end = timestamp(2024,04,01,0,0)

if time >= start and time <= end
    strategy.entry("long", strategy.long,1.0,when=long)
    strategy.entry("short", strategy.short,1.0,when=short)
strategy.close("long",when=short)
strategy.close("short",when=long)

//Version avec Overlay sur la courbe des prix
il suffit de modifier la première ligne de code
strategy("Moving average Cross",overlay=true)

Ensuite cliquez sur « Update on chart » pour que mettre à jour le graphe, les deux moyennes mobiles sont dans le bas de l’écran séparés du graphique des prix. Ajoutez la propriété overlay=true pour superposer les deux graphique, auparavant supprimez le graphique du bas avec les deux moyennes mobiles.

Retarder l’entrée en position à la 10ème bougie en plus de la condition des moyennes mobiles

On fait ceci pour éviter que le croisement des moyennes mobiles ne soit que éphémères.

//@version=4
strategy("Moving average Cross",overlay=true)
ema20 = ema(close,20)
ema50 = ema(close,50)
long = ema20 > ema50
short = ema20 < ema50

//on attend la consolidation pour passer à l'action pour éviter de se faire stopper
longcondition = long and long[10] and not long[11]
shortcondition = short and short[10] and not short[11]

plot(ema50, title="ma50", color=#ffc1cc, linewidth=3)
plot(ema20, title="ma20", color=#00ffaa, linewidth=2)

start = timestamp(2021,04,01,0,0)
end = timestamp(2024,04,01,0,0)

if time >= start and time <= end
    strategy.entry("long", strategy.long,1.0,when=longcondition)
    strategy.entry("short", strategy.short,1.0,when=shortcondition)
strategy.close("long",when=shortcondition)
strategy.close("short",when=longcondition)

On a pris moins de trade mais le PNL est moins intéressant, mais vous voyez ici l’idée, on joue sur les potard afin d’améliorer le PNL. Une fois que vous avez des résultats intéressants, vous pouvez appliquer pour le futur dans votre stratégie de quant.

Après le réglage

Avertissement sur les limites du backtesting

Le backtesting n’est pas trivial, et ce qui est donnée par Tradingview peut ne pas être juste dans la réalité. Regardez cette vidéo

Le quant marche mieux dans le low time frame intraday avec des bougies d’un quart d’heure. je ne suis pas spécialiste et j’ai fait cet article à des fin didactique, je n’ai pas fait de robot de trading.

Simuler le clic d’une souris avec Javascript

Non je ne parle pas de l’événement « click » ou « onclick », mais carrément de créer un événement souris. En javascript nous disposons de MouseEvent, qui ne se limite pas au seul clic gauche ou droit, mais tient compte aussi des déplacements de la souris.

var clickEvent = new MouseEvent('click', {
    bubbles: true,
    cancellable: true,
    clientX: 32,
    clientY: 32,
})

Voici le la documentation officielle de MouseEvent. Cependant je dois aussi vous dire qu’il y a un autre événement appelé PointerEvent. Cet événement est plus générique, car il peut gérer les événement touch, multi contact, alors que MouseEvent ne peut pas. Mais dans cet article nous allons nous concentrer sur MouseEvent.

Description de MouseEvent

Les 3 coordonnées de la souris dans une page web

Le référentiel d’un élément HTML

Le référentiel de la fenêtre

Le référentiel du document

Mise en apllication de MouseEvent

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MouseEvent</title>
    <script src="MouseEvent.js" defer></script>
    <link rel="stylesheet" href="style.css">
</head>

<body>

    <div class="container">
        <div class="header"></div>
        <div class="main">
            <h1>Titre</h1>
            <div class="card">
                <p>Ceci est un exemple pour illustrer MouseEvent</p>
            </div>
        </div>
    </div>
</body>


</html>

function simulateClick() {
    let titre = document.querySelector('h1')

    let clickEvent = new MouseEvent('click', {
        bubbles: true,
        cancellable: true,
        /*    offsetX: 20,
            offsetY: 20,*/
    })


    console.log('dispatch')
    titre.dispatchEvent(clickEvent)
    console.log('clicked')

}

let h2 = document.querySelector('h1').addEventListener('click', function (e) {
    alert('toto')
})

setTimeout(simulateClick, 300)

La fonction simulateClick() va sélectionner l’élément H1 et la mettre dans la variable titre, construit un objet Event (MouseEvent pour être plus précis, MouseEvent héritant de Event), ensuite et c’est là qu’il faut être attentif, c’est titre qui va émettre (dispatch) l’événement clickEvent.

Dans le programme principal, h2 qui représente le même élément que titre reçoit une simulation de click et donc une alert() va faire apparaitre une popup. au bout de 300 millisecondes.

Liens en rapport avec l’article:

/////////////////////// DOM onunload ////////////////////
https://stackoverflow.com/questions/11177233/javascript-event-that-runs-before-page-changes
https://stackoverflow.com/questions/446892/how-to-find-event-listeners-on-a-dom-node-in-javascript-or-in-debugging
https://stackoverflow.com/questions/4386300/javascript-dom-how-to-remove-all-event-listeners-of-a-dom-object/64484951#64484951

Retour en haut