Gérer l'upload de fichiers avec Parse Server et Digital Ocean Spaces

Cet article a pour but de vous expliquer comment configurer votre serveur Parse pour que les fichiers uploadés via une application soit directement posés dans un Spaces de Digital Ocean.

Parmi les fonctionnalités proposées par Parse Server il y a l'upload de fichiers cette fonctionnalité peut être utilisée pour la publication de photos dans une application de partage de photos par exemple.

Grâce au sdk javascript fourni cela s'avère relativement simple :

var base64 = 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE='
// création du fichier
var parseFile = new Parse.File('picture001.jpg', { base64: base64 })
// sauvegarde du fichier
parseFile.save().then(
  function() {
    //création d'un post
    var Post = new Parse.Object('Post')
    Post.set('user', 'Joe Smith')
    //définition du ficher attaché au Post
    Post.set('picture', parseFile)
    // sauvegarde de l'objet Post
    Post.save()
  },
  function(error) {
    console.log(error.message)
  }
)

Cependant où sont stockées ces images ? Précédemment lorsque Parse.com existait encore ces fichiers étaient stockés sur Amazon S3.

Maintenant avec Parse Server, la configutation par défaut stocke ces fichiers dans votre base MongoDB cependant ce n'est pas la configuration recommandée pour un usage optimale.

Heurseusement Parse Server vous permet de modifier ce comportement grâce aux FileAdpater. Les FileAdapter vous permettent de définir ou seront stockés vos fichiers sans que vous ayez à vous préoccuper du reste. Vous avez juste à configurer le FileAdapter de votre choix et ensuite c'est lui qui s'occupe de votre fichier lorsqu'il est envoyé sur votre serveur Parse.

Voici une liste de quelqu'un des FileAdapter :

  • GridStoreAdapter : Par défaut qui stocke les fichiers directement dans la base.
  • S3Adapter : Permet de stocker les fichiers via le service S3 d'amazon.
  • GCSAdapter : Permet de stocker les fichiers via Google Cloud Storage.

Comme nous l'avons vu dans l'article précédent Spaces fonctionnent de la même manière que S3. Nous allons donc pouvoir utiliser le S3Adapter pour que nos fichiers soient stockés chez Digital Ocean.

Pour commencer nous allons partir de la configuration basique de Parse Server de cet article et que vous pouvez télécharger ici.

La première chose à faire est de rajouter la dépendance @parse/s3-files-adapter": "^1.2.1" :

{
  "name": "parse-server-sample",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.16.4",
    "parse-dashboard": "^1.2.0",
    "parse-server": "^3.1.3",
    "@parse/s3-files-adapter": "^1.2.1"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0"
  },
  "scripts": {
    "clean": "rm -rf build && mkdir build",
    "build-server": "babel -d ./build ./server -s",
    "build": "npm run clean && npm run build-server",
    "start": "npm run build && node ./build/index.js",
    "dev": "babel-node ./server/index.js"
  }
}

Ensuite nous allons modifier le fichier docker-compose.yml pour y ajouter les variables d'environnement nécéssaires à la configuration du FileAdapter.

Ajoutez les variables suivantes dans le service api :

SPACE_ACCESS_KEY: 'access_key'
SPACE_ACCESS_SECRET: 'secret_key'
SPACE_REGION: 'region'
SPACE_BUCKET_PREFIX: 'prefix'
SPACE_BUCKET_NAME: 'name'
SPACE_BASE_URL: 'base_url'
SPACE_ENDPOINT: 'end_point'
  • SPACE_ACCESS_KEY : Votre access_key.
  • SPACE_ACCESS_SECRET : Votre access_secret.
  • SPACE_REGION : La région de votre Space ams3 pour Amsterdam.
  • SPACE_BUCKET_PREFIX : Le nom du dossier dans lequel seront mis vos fichiers appdata/.
  • SPACE_BUCKET_NAME : Le nom de votre Spaces testparsearticle.
  • SPACE_BASE_URL : L'adresse complète de votre Space https://testparsearticle.ams3.digitaloceanspaces.com.
  • SPACE_ENDPOINT : L'endPoint de votre Space ams3.digitaloceanspaces.com.

Maintenant nous allons pouvoir modifier le fichier index.js qui contient la configuration de notre serveur Parse :

Premièrement on ajoute les dépendances nécéssaires :

var S3Adapter = require('@parse/s3-files-adapter')
var AWS = require('aws-sdk')

Ensuite nous allons spécifier que nous ne voulons pas utiliser Amazon S3 mais Digital Ocean Space. Pour ce faire nous allons indiquer que le endpoint utilisé sera celui de Space :

const spacesEndpoint = new AWS.Endpoint(process.env.SPACE_ENDPOINT)

Ensuite nous allons définir les options de notre Adpater :

var s3Options = {
  bucket: process.env.SPACE_BUCKET_NAME,
  baseUrl: process.env.SPACE_BASE_URL,
  region: process.env.SPACE_REGION,
  directAccess: true,
  globalCacheControl: 'public, max-age=31536000',
  bucketPrefix: process.env.SPACE_BUCKET_PREFIX,
  s3overrides: {
    accessKeyId: process.env.SPACE_ACCESS_KEY,
    secretAccessKey: process.env.SPACE_ACCESS_SECRET,
    endpoint: spacesEndpoint,
  },
}
  • bucket : Le nom de votre Space.
  • baseUrl: L'url qui permet d'accéder au Space.
  • region : La region ou est situé le Space.
  • directAccess : Permet de définir si les fichier sont lus directement depuis Space ou bien en passant par votre Serveur Parse. La valeur true crée les fichiers et les rend accessibles publiquement.
  • globalCacheControl : Définit la valeur du cache que les fichiers auront.
  • bucketPrefix : Le prefix de votre bucket si vous voulez en ajouter un.
  • s3overrides : Les access à votre Space.

Explications supplémentaires à propos de la baseUrl et du bucketPrefix

La baseUrl sera utilisée pour définir l'url d'accès à votre fichier, reprenons notre exemple javascript :

Post.set('picture', file)

Lorsqu'on sauvegarde notre objet Post, le champs picture qui est un champ de type File contiendra les informations suivantes :

  • nom du fichier
  • url

L'url du fichier sera construit de la manière suivante : baseUrl+fileName

Le bucketPrefix à pour but de "classer" les objets dans votre Space. Imaginons que vous avez créé un Space et que dedans vous stockez les assets de votre application. Par la suite vous voulez stocker aussi les fichiers uploadés par votre application mais vous ne souhaitez pas les mélanger avec vos assets. Vous souhaitez pouvoir créer un dossier appdata et mettre les fichiers uploadés dedans.

C'est la qu'intervient le bucketPrefix en nommant ce dernier appdata vos fichiers irons bien dans un dossier appdata. En réalité c'est pas totalement vrai car la notions de dossier n'existe pas dans Space ou S3 c'est pour ça qu'on apelle ça Prefix car ça ajoute le prefix au nom du fichier mais lorsqu'on se rend dans les interfaces d'administration c'est représenté sous forme de dossier.

Maintenant que les options de notre Adapter sont définies nous avons plus qu'à créer l'Adapter et indiquer à notre serveur d'utiliser cet Adapter comme fileAdapter.

var s3Adapter = new S3Adapter(s3Options)
var api = new ParseServer({
  databaseURI: databaseURI,
  cloud: cloudPath,
  appId: appId,
  masterKey: masterKey,
  serverURL: serverURL,
  logLevel: logLevel,
  filesAdapter: s3Adapter,
})

Voici le fichier index.js complet :

// 1. On importe les dépendances nécessaires
var express = require('express')
var ParseServer = require('parse-server').ParseServer
var ParseDashboard = require('parse-dashboard')
var S3Adapter = require('@parse/s3-files-adapter')
var AWS = require('aws-sdk')

// 2 On définit les options de notre serveur à partir des variables d'environnements
const mountPath = process.env.PARSE_MOUNT || '/parse'
const port = process.env.PORT || 1337
const databaseURI = process.env.DATABASE_URI || 'mongodb://localhost:27017/dev'
const cloudPath = __dirname + '/cloud/main.js'
const appId = process.env.APP_ID || 'myAppId'
const masterKey = process.env.MASTER_KEY || ''
const serverURL = process.env.SERVER_URL || 'http://localhost:1337/parse'
const logLevel = process.env.LOG_LEVEL || 'info'
const allowInsecureHTTP = process.env.ALLOW_INSECURE_HTTP_DASHBOARD
const appName = process.env.APP_NAME
const dashboard_user = process.env.DASHBOARD_USER
const dashboard_password = process.env.DASHBOARD_PASSWORD

// 3. On définit les options du FileAdapter
const spacesEndpoint = new AWS.Endpoint(process.env.SPACE_ENDPOINT)
var s3Options = {
  bucket: process.env.SPACE_BUCKET_NAME,
  baseUrl: process.env.SPACE_BASE_URL,
  region: process.env.SPACE_REGION,
  directAccess: true,
  globalCacheControl: 'public, max-age=31536000',
  bucketPrefix: process.env.SPACE_BUCKET_PREFIX,
  s3overrides: {
    accessKeyId: process.env.SPACE_ACCESS_KEY,
    secretAccessKey: process.env.SPACE_ACCESS_SECRET,
    endpoint: spacesEndpoint,
  },
}
var s3Adapter = new S3Adapter(s3Options)

// 4. On crée une nouvelle instance Parse avec la configuration de base
var api = new ParseServer({
  databaseURI: databaseURI,
  cloud: cloudPath,
  appId: appId,
  masterKey: masterKey,
  serverURL: serverURL,
  logLevel: logLevel,
  filesAdapter: s3Adapter,
})

// 5. On crée le dashboard
var dashboard = new ParseDashboard(
  {
    apps: [
      {
        serverURL: serverURL,
        appId: appId,
        masterKey: masterKey,
        appName: appName,
      },
    ],
    users: [
      {
        user: dashboard_user,
        pass: dashboard_password,
        apps: [{ appId: appId }],
      },
    ],
  },
  //options
  { allowInsecureHTTP: allowInsecureHTTP }
)

// 6. On lance le serveur
var app = express()
app.use(mountPath, api)
app.use('/dashboard', dashboard)

var httpServer = require('http').createServer(app)
httpServer.listen(port, function() {
  console.log('parse-server running on port ' + port + '.')
})

Pour tester rapidement le fonctionnement de votre configuration créer un fichier index.html avec le code ci-dessous et ouvrez le dans votre navigateur :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Test Parse Server</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
    <script src="main.js"></script>
  </head>
  <body>
    <script src="https://npmcdn.com/parse/dist/parse.min.js"></script>
    <script>
      Parse.initialize('APP_ID')
      Parse.serverURL = 'http://localhost:1337/parse'
      var base64 = 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE='
      // création du fichier
      var parseFile = new Parse.File('picture001.jpg', { base64: base64 })
      // sauvegarde du fichier
      parseFile.save().then(
        function() {
          //création d'un post
          var Post = new Parse.Object('Post')
          Post.set('user', 'Joe Smith')
          //définition du ficher attaché au Post
          Post.set('picture', parseFile)
          // sauvegarde de l'objet Post
          Post.save()
        },
        function(error) {
          console.log(error.message)
        }
      )
    </script>
  </body>
</html>

Vous devriez voir apparaitre un nouveau fichier dans votre Spaces à chaque fois que vous rafraichissez la page index.html.

Partagez cet article si vous le souhaitez et si vous avez des questions vous pouvez me contacter par le chat.