Comment uploader fichiers publics avec Meteor.js

Comment uploader fichiers publics avec Meteor.js

Alexandre P. dans Dev - Le 04-07-2022

MeteorJS est une plateforme fullstack qui de part sa conception et l'utilisation du Distributed Data Protocol (DDP) change l'approche en matière de communication front/back. Comment faire pour gérer l'upload de fichiers dans ces conditions ?

L'upload de fichier avec le protocole DDP

Dernièrement je partageais avec vous ma façon d'organiser mon code lorsque j'utilise MeteorJS, vous pouvez retrouver cet article ici .

Mais comme je vous disais dans cet article, l'utilisation protocole DDP risque de casser vos habitudes en matière de communication serveur. Il ne s'agira pas de REST API ni de GraphQL ! Nous allons devoir nous servir des RPC (Remote Procedure Call).

Ainsi, je vais vous expliquer comment je m'y prends pour uploader des ressources publiques. Notez que le mot public a son importance étant donné que pour une ressource publique, il suffit de le positionner dans un dossier public/ à la racine de votre projet Meteor.

Ce fichier sera accessible via un appel HTTP standard. Nous verrons ultérieurement comment déposer les fichiers dans un dossiers inaccessible, nous forçant ainsi à gérer les accès aux fichiers (plus facile à faire une fois que nous avons mis en place des sessions !).

Important à savoir pour les utilisateurs de Meteor Typescript ⚠️

Lorsque vous travaillez en Typescript, votre code est compilé. Donc au runtime, votre interpreter se positionne dans le dossier .meteor qui contient le build du projet. Ce qui signifie que parcourir les dossiers et fichiers depuis cet endroit peut s'avérer fastidieux !

C'est pourquoi il y a des variables d'environnement pour nous aider à nous repérer plus facilement au niveau des paths. Nous utiliserons : process.env.PWD

Preparons notre API pour l'upload

Commençons par créer un dossier /public à la racine de notre projet et dans ce même dossier, ajoutons un fichier .keep pour pouvoir ensuite faire les règles .gitignore nécessaires :

# gitignore
public/
!public/.keep

Maintenant tout ce que vous uploadez ne sera pas versionné (évitons d'envoyer des fichiers binaires sur git... 😅).

// /imports/api/file.ts
import { Meteor } from 'meteor/meteor'
import * as fs from 'fs'
import path from 'path'
import { v4 as uuid } from 'uuid'

const FILES_PATH = path.resolve(process.env.PWD ?? '', 'public/files')

type UploadedFile = {
  mimeType: string
  name: string
}

Meteor.methods({
  'file.upload'(
    file: UploadedFile, // Changement de type car le File Object ne passe pas via RPC
    fileData: any // Ici nous passons les données binaires en raw
  ) {
    if (Meteor.isServer) {
      // On donne un nom unique à notre fichier uploadé
      const fileName = uuid()
      const filePath = path.resolve(FILES_PATH, `${fileName}.${file.mimeType?.split('/')?.[1]}`)

      // On écrit notre fichier dans notre répertoire
      fs.writeFileSync(filePath, fileData, 'binary')
      
      // ... Ici je fais les écritures DB nécessaires (à personnaliser) 
      
      // On retourne le nom du fichier uploadé
      return `${fileName}.${file.mimeType?.split('/')?.[1]}`
    }
  },
})

Mise à jour du fichier api/index.ts :

// imports/api/index.ts

// ...
import './file'
// ...

Côté formulaire client

Sur notre page, nous allons ajouter un input file pour gérer l'envoi du fichier :

// pages/page.tsx

//...

type MaPageProps = {
  uploadFile: (f: File) => void
}

const MaPage = ({ uploadFile }: MaPageProps) => {
  return (
    /* ... form inputs */
    <div>
      <label htmlFor="name">
        Photo
      </label>
      <input type="file" onChange={e => uploadFile(e.currentTarget.files?.[0])} />
    </div>
  )
}

export default withTracker(() => {
  return {
    uploadFile: (file: File) => {
      if (file) {
        const reader = new FileReader()
        
        reader.onload = function () {
          Meteor.call(
            'file.upload',
            // File to Object pour le RPC
            {
              mimeType: file.type,
              name: file.name,
            },
            // Raw binary data
            reader.result,
            (err: any, fileName: string) => {
              if (err) {
                console.log(err)
                return error()
              }
              alert('Fichier uploadé')
              console.log(fileName)
            }
          )
        }

        reader.readAsBinaryString(file)
      }
    }
  }
})(MaPage)

Affichage du fichier uploadé sur la page

Une fois le fichier uploadé, puisqu'il s'agit d'un fichier public. Je pars du principe que vous avez fait les stockage DB nécessaires. Une fois que vous récupérez votre collection via des requêtes, il ne vous reste plus qu'à l'afficher. Vous pouvez tester avec une URL en dur juste pour vérifier que votre fichier est accessible.

Si votre dossier public est organisé de la sorte :

/public
|_/img
   |_picture.jpg

Il ne vous reste plus qu'à faire un essai :

// /pages/index.tsx

const pictureUrl = "/img/picture.jpg";

// ...
<img src={pictureUrl} />

Dans un prochain article on parlera de la pagination de données sur les requêtes de collection. D'ici là, portez vous bien, bon code ! 😉

#meteor#react#typescript#programming#upload#file

user picture
Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.