diff --git a/README.md b/README.md
index 1087423fac2830c2bd7abd74ee38ba3713abb2dc..f4d8f741e3b6cfc25321e2c4978d196e9b17673e 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ java -jar target/FlopBox.jar
 
 ```shell
 pip install --user pyftpdlib
-python3 serveur_ftp.py
+python3 serveur_ftp.py serveur1
+python3 serveur_ftp.py SERVEUR2
 ```
 
 Identifiants :
@@ -72,51 +73,70 @@ Puis ouvrir le fichier `target/site/apidocs/index.html`
 
 ## Fonctionnalités
 
-`python3 serveur_ftp.py` anonymous/anonymous ou user/password
+En utilisant la commande suivante `python3 serveur_ftp.py`, vous pouvez donner les identifiants FTP anonymous/anonymous ou user/password
+
+Il faut aussi penser faire un `mvn clean package`, puis `java -jar target/FlopBox.jar` et à ajouter un serveur FTP avec la commande suivant : `curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer valid-token-1" -d '{"alias":"mon-ftp","host":"localhost","port":2121}' http://localhost:8080/ftps`
 
 **Note < 10 si le code fourni ne compile pas et ne peut pas être exécuté en suivant les instructions:**
 
 **Note comprise entre 10 et 11 si le code compile et peut être lancé pour afficher l'arborescence d'un serveur FTP via le proxy FlopBox:**
 
+- afficher l'arborescence du root du serveur FTP correspondant à l'alias `mon-ftp` :
 ```shell
-curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" localhost:8080/ftps/mon-ftp/folder
-
-Note: Unnecessary use of -X or --request, GET is already inferred.
-*   Trying 127.0.0.1:8080...
-* Connected to localhost (127.0.0.1) port 8080 (#0)
-> GET /ftps/mon-ftp/folder HTTP/1.1
-> Host: localhost:8080
-> User-Agent: curl/7.81.0
-> Accept: */*
-> Authorization: Bearer valid-token-1
-> X-FTP-User: anonymous
-> X-FTP-Pass: anonymous
-> 
-* Mark bundle as not supporting multiuse
-< HTTP/1.1 200 OK
-< Content-Type: application/json
-< Content-Length: 264
-< 
-* Connection #0 to host localhost left intact
-{"name":"folder","isDirectory":true,"children":[{"name":"sousdossier","isDirectory":true,"children":[{"name":"test12","isDirectory":false,"children":null}]},{"name":"test11","isDirectory":false,"children":null},{"name":"tree","isDirectory":false,"children":null}]}
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" localhost:8080/ftps/list/mon-ftp/
+```
+
+- afficher l'arborescence du dossier `dossier1/` du serveur FTP :
+```shell
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" localhost:8080/ftps/list/mon-ftp/dossier1
 ```
 
 **Note comprise entre 11 et 12 si—en plus—le proxy FlopBox, permet de télécharger (download) et téléverser (upload) un petit fichier texte:**
 
-- download :
+- download fichier :
 ```shell
-curl -X GET -H "Authorization: Bearer valid-token-1" http://localhost:8080/ftps/mon-ftp/test
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" http://localhost:8080/ftps/mon-ftp/fichier1 -o fichier1
 ```
 
-- upload :
+- download dossier (ne fonctionne pas bien pour le moment je crois) :
 ```shell
-curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer valid-token-1" -d '{"alias":"mon-ftp","host":"localhost","port":2121}' http://localhost:8080/ftps
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" http://localhost:8080/ftps/mon-ftp/dossier1 -o dossier1.zip
+
+unzip dossier1.zip
+```
+
+- upload fichier :
+```shell
+curl -X PUT -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" --upload-file fichier2 http://localhost:8080/ftps/mon-ftp/fichier2
+```
+
+- upload dossier :
+
+```shell
+zip -r dossier2.zip dossier2
+
+curl -X POST -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" -H "Content-Type: application/octet-stream" --data-binary @dossier2.zip http://localhost:8080/ftps/mon-ftp/upload-dir/dossier2
 ```
 
 **Note comprise entre 12 et 13 si—en plus—le proxy FlopBox, permet de télécharger (download) et téléverser (upload) un gros fichier binaire (image, vidéo, etc.):**
 
+- download fichier :
+```shell
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: anonymous" -H "X-FTP-Pass: anonymous" http://localhost:8080/ftps/mon-ftp/image.png -o image.png
+```
+
+- upload fichier :
+```shell
+curl -X PUT -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" --upload-file image2.jpg http://localhost:8080/ftps/mon-ftp/image2.jpg
+```
+
 **Note comprise entre 13 et 14 si—en plus—le proxy FlopBox permet de gérer plusieurs serveurs FTP différents (ajout, suppression, modification des serveurs):**
 
+- lister les serveurs :
+```shell
+curl -X GET -H "Authorization: Bearer valid-token-1" http://localhost:8080/ftps
+```
+
 - ajout :
 ```shell
 curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer valid-token-1" -d '{"alias":"mon-ftp","host":"localhost","port":2121}' http://localhost:8080/ftps
@@ -132,28 +152,21 @@ curl -X DELETE -H "Authorization: Bearer valid-token-1" http://localhost:8080/ft
 curl -X PUT http://localhost:8080/ftps/mon-ftp -H "Content-Type: application/json" -H "Authorization: Bearer valid-token-1" -d '{"alias":"mon-ftp","host":"nouvelle-adresse","port":2221}'
 ```
 
-- lister les serveurs :
-```shell
-curl -X GET -H "Authorization: Bearer valid-token-1" http://localhost:8080/ftps
-
-[{"alias":"mon-ftp","host":"nouvelle-adresse","port":2221}]
-```
-
 **Note comprise entre 14 et 15 si—en plus—le proxy FlopBox, permet de créer, supprimer, renommer une ressource directement sur l'un des serveurs FTP gérés (fichier ou répertoire):**
 
 - créer dossier :
 ```shell
-curl -X POST -H "X-Resource-Type: file" -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/mon-ftp/dossier
+curl -X POST -H "X-Resource-Type: folder" -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/mon-ftp/dossier
 ```
 
 - créer fichier :
 ```shell
-curl -X POST -H "X-Resource-Type: file" -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/mon-ftp/dossier/fichier
+curl -X POST -H "X-Resource-Type: file" -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/mon-ftp/fichier
 ```
 
 - renommer :
 ```shell
-curl -X POST -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" -H "Content-Type: text/plain" -d "nouveau_nom" http://localhost:8080/ftps/mon-ftp/dossier/rename
+curl -X PUT -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" -H "Content-Type: text/plain" -d "nouveau_nom" http://localhost:8080/ftps/mon-ftp/rename/fichier
 ```
 
 - supprimer dossier/fichier :
@@ -162,3 +175,8 @@ curl -X DELETE -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H
 ```
 
 **Note comprise entre 15 et 16 si—en plus—le proxy FlopBox, permet de chercher des fichiers/répertoires stockés dans plusieurs serveurs FTP (le proxy retourne la liste des URLs pour chaque fichier trouvé):**
+
+- rechercher une ressource :
+```shell
+curl -X GET -H "Authorization: Bearer valid-token-1" -H "X-FTP-User: user" -H "X-FTP-Pass: password" http://localhost:8080/ftps/search/fichier1
+```
diff --git a/dossier_serveur_ftp/.gitkeep b/dossier_serveur_ftp1/.gitkeep
similarity index 100%
rename from dossier_serveur_ftp/.gitkeep
rename to dossier_serveur_ftp1/.gitkeep
diff --git a/dossier_serveur_ftp2/.gitkeep b/dossier_serveur_ftp2/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples_ressources/dossier1/fichier11 b/examples_ressources/dossier1/fichier11
new file mode 100644
index 0000000000000000000000000000000000000000..dbe8b717ce3cf63155110811d8eb54230654bb6e
--- /dev/null
+++ b/examples_ressources/dossier1/fichier11
@@ -0,0 +1 @@
+FICHIER11
diff --git a/examples_ressources/fichier1 b/examples_ressources/fichier1
new file mode 100644
index 0000000000000000000000000000000000000000..c074b73f25b04b7773f12e2e42e93c20ea39d823
--- /dev/null
+++ b/examples_ressources/fichier1
@@ -0,0 +1 @@
+FICHIER1
diff --git a/examples_ressources/image.png b/examples_ressources/image.png
new file mode 100644
index 0000000000000000000000000000000000000000..8741baa19d02a003db73c33bd779a324bc1a57fa
Binary files /dev/null and b/examples_ressources/image.png differ
diff --git a/serveur_ftp.py b/serveur_ftp.py
index fd6eeaea7a91e8033963f088948912a7522852c5..bc788850758cad5feb631c6fd890a71df98075cb 100644
--- a/serveur_ftp.py
+++ b/serveur_ftp.py
@@ -1,21 +1,41 @@
 from pyftpdlib.authorizers import DummyAuthorizer
 from pyftpdlib.handlers import FTPHandler
 from pyftpdlib.servers import FTPServer
+import sys
 
-# Créer un authorizer
-authorizer = DummyAuthorizer()
+if __name__ == '__main__':
+    # Vérifier les arguments passés en ligne de commande
+    if len(sys.argv) != 2:
+        print("Usage: python script.py <serveur>")
+        sys.exit(1)
 
-# Ajouter un utilisateur anonyme (lecture seule)
-authorizer.add_anonymous("dossier_serveur_ftp", perm="elr")
+    serveur = sys.argv[1]
 
-# Ajouter un utilisateur personnalisé avec des permissions d'écriture
-authorizer.add_user("user", "password",
-                    "dossier_serveur_ftp", perm="elradfmw")
+    # Définir le port et le dossier en fonction du serveur
+    if serveur == "serveur1":
+        port = 2121
+        dossier_serveur_ftp = "dossier_serveur_ftp1"
+    elif serveur == "SERVEUR2":
+        port = 2122
+        dossier_serveur_ftp = "dossier_serveur_ftp2"
+    else:
+        print("Serveur non reconnu. Utilisez 'serveur1' ou 'SERVEUR2'.")
+        sys.exit(1)
 
-# Configurer le handler
-handler = FTPHandler
-handler.authorizer = authorizer
+    # Créer un authorizer
+    authorizer = DummyAuthorizer()
 
-# Démarrer le serveur FTP
-server = FTPServer(("127.0.0.1", 2121), handler)
-server.serve_forever()
+    # Ajouter un utilisateur anonyme (lecture seule)
+    authorizer.add_anonymous(dossier_serveur_ftp, perm="elr")
+
+    # Ajouter un utilisateur personnalisé avec des permissions d'écriture
+    authorizer.add_user("user", "password", dossier_serveur_ftp, perm="elradfmw")
+
+    # Configurer le handler
+    handler = FTPHandler
+    handler.authorizer = authorizer
+
+    # Démarrer le serveur FTP
+    server = FTPServer(("127.0.0.1", port), handler)
+    print(f"Le serveur FTP est démarré sur le port {port} avec le dossier {dossier_serveur_ftp}")
+    server.serve_forever()
diff --git a/src/main/java/fil/sr2/flopbox/FTPResource.java b/src/main/java/fil/sr2/flopbox/FTPResource.java
index 55bbb713cb5b7433f6d187d93d80dee320d8b4f7..2d857c3a006c0d08f61f1818cea5b1c44d208afe 100644
--- a/src/main/java/fil/sr2/flopbox/FTPResource.java
+++ b/src/main/java/fil/sr2/flopbox/FTPResource.java
@@ -2,21 +2,29 @@ package fil.sr2.flopbox;
 
 import jakarta.ws.rs.*;
 import jakarta.ws.rs.core.*;
-
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.SocketException;
 import java.net.URI;
 import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
 
-import org.apache.commons.net.ftp.FTP;
-import org.apache.commons.net.ftp.FTPClient;
-import org.apache.commons.net.ftp.FTPFile;
+import fil.sr2.flopbox.utils.*;
 
 @Path("/ftps")
 public class FTPResource {
 
-    // GET /ftps - liste des serveurs FTP enregistrés
+    private FTPService ftpService;
+
+    public FTPResource(FTPService ftpService) {
+        this.ftpService = ftpService;
+    }
+
+    public FTPResource() {
+        this.ftpService = new FTPService();
+    }
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     public Response listFTPServers() {
@@ -25,105 +33,60 @@ public class FTPResource {
         return Response.ok(servers).build();
     }
 
-    // returns the content of the directory as an array it is a directory OR the
-    // content of the file
-    @GET
-    @Path("/{alias}/{path: .+}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response getFTPResource(
-            @PathParam("alias") String alias,
-            @PathParam("path") String path,
-            @HeaderParam("X-FTP-User") String user,
-            @HeaderParam("X-FTP-Pass") String pass) {
-
-        System.out.println("GetFTPResource()");
-        FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
-        if (config == null) {
-            return Response.status(Response.Status.NOT_FOUND)
-                    .entity("Serveur FTP non trouvé").build();
-        }
-        FTPClient ftpClient = new FTPClient();
-        try {
-            ftpClient.connect(config.getHost(), config.getPort());
-            if (!ftpClient.login(user, pass)) {
-                return Response.status(Response.Status.UNAUTHORIZED)
-                        .entity("Authentification FTP échouée").build();
-            }
-            ftpClient.enterLocalPassiveMode();
-            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
-            // Normaliser le chemin en retirant le slash final
-            if (path.endsWith("/")) {
-                path = path.substring(0, path.length() - 1);
-            }
-            // Vérifier si le chemin est un répertoire
-            FTPFile[] files = ftpClient.listFiles(path);
-            if (files == null || files.length == 0) {
-                return Response.status(Response.Status.NOT_FOUND)
-                        .entity("Ressource non trouvée : " + path).build();
-            }
-            // Construire la structure JSON
-            FtpNode root = buildFtpTree(ftpClient, path);
-            ftpClient.logout();
-            ftpClient.disconnect();
-            return Response.ok(root).build();
-        } catch (IOException e) {
-            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
-                    .entity("Erreur FTP : " + e.getMessage()).build();
-        }
-    }
-
-    // Fonction récursive pour construire l'arborescence FTP
-    private FtpNode buildFtpTree(FTPClient ftpClient, String path) throws IOException {
-        FTPFile[] files = ftpClient.listFiles(path);
-        FtpNode node = new FtpNode(getFileName(path), true); // Root est un dossier
-        if (files != null) {
-            for (FTPFile file : files) {
-                String fullPath = path + "/" + file.getName();
-                if (file.isDirectory()) {
-                    node.children.add(buildFtpTree(ftpClient, fullPath)); // Récursif pour les dossiers
-                } else {
-                    node.children.add(new FtpNode(file.getName(), false)); // Ajouter fichier
-                }
-            }
-        }
-        return node;
-    }
-
-    // Classe représentant un nœud JSON (fichier ou dossier)
-    static class FtpNode {
-        public String name;
-        public boolean isDirectory;
-        public List<FtpNode> children;
-
-        public FtpNode(String name, boolean isDirectory) {
-            this.name = name;
-            this.isDirectory = isDirectory;
-            if (isDirectory) {
-                this.children = new java.util.ArrayList<>();
-            }
-        }
-    }
+    // --- Endpoints pour gérer la configuration des serveurs FTP ---
 
-    // POST /ftps - enregistre un nouveau serveur FTP
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addFTPServer(FTPServerConfig config, @Context UriInfo uriInfo) {
         System.out.println("addFTPServer()");
         boolean created = FTPServerRepository.getInstance().addServer(config);
         if (!created) {
-            return Response.status(Response.Status.CONFLICT).entity("Alias déjà utilisé").build();
+            return Response.status(Response.Status.CONFLICT)
+                    .entity("Alias déjà utilisé").build();
         }
         UriBuilder builder = uriInfo.getAbsolutePathBuilder();
         builder.path(config.getAlias());
         return Response.created(builder.build()).build();
     }
 
+    @GET
+    @Path("/search/{path: .+}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response searchFiles(
+            @PathParam("path") String searchTerm,
+            @HeaderParam("X-FTP-User") String user,
+            @HeaderParam("X-FTP-Pass") String pass) {
+
+        System.out.println("searchFiles()");
+        List<FTPServerConfig> servers = FTPServerRepository.getInstance().getAllServers();
+        Map<String, List<String>> results = new HashMap<>();
+        // Pour chaque serveur FTP configuré
+        for (FTPServerConfig config : servers) {
+            try {
+                // On récupère l’arborescence racine du serveur
+                FTPService.FtpNode tree = ftpService.getResourceTree(config.getAlias(), "", user, pass);
+                List<String> urls = new ArrayList<>();
+                // Parcourir récursivement l’arborescence pour chercher le fichier (ou
+                // répertoire) recherché
+                ftpService.searchInTree(tree, searchTerm, "", config, urls);
+                if (!urls.isEmpty()) {
+                    results.put(config.getAlias(), urls);
+                }
+            } catch (Exception e) {
+                System.err.println(
+                        "Erreur lors de la recherche sur le serveur " + config.getAlias() + " : " + e.getMessage());
+            }
+        }
+        return Response.ok(results).build();
+    }
+
     @DELETE
     @Path("/{alias}")
     public Response removeFTPServer(@PathParam("alias") String alias) {
         System.out.println("removeFTPServer()");
         boolean removed = FTPServerRepository.getInstance().removeServer(alias);
-        return removed ? Response.noContent().build() : Response.status(Response.Status.NOT_FOUND).build();
+        return removed ? Response.noContent().build()
+                : Response.status(Response.Status.NOT_FOUND).build();
     }
 
     @PUT
@@ -132,9 +95,89 @@ public class FTPResource {
     public Response updateFTPServer(@PathParam("alias") String alias, FTPServerConfig newConfig) {
         System.out.println("updateFTPServer()");
         boolean updated = FTPServerRepository.getInstance().updateServer(alias, newConfig);
-        return updated ? Response.ok(newConfig).build() : Response.status(Response.Status.NOT_FOUND).build();
+        return updated ? Response.ok(newConfig).build()
+                : Response.status(Response.Status.NOT_FOUND).build();
+    }
+
+    // --- Endpoints pour les opérations FTP ---
+
+    @GET
+    @Path("/list/{alias}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getFTPRoot(
+            @PathParam("alias") String alias,
+            @HeaderParam("X-FTP-User") String user,
+            @HeaderParam("X-FTP-Pass") String pass) {
+        System.out.println("getFTPRoot()");
+        try {
+            FTPService.FtpNode tree = ftpService.getResourceTree(alias, "", user, pass);
+            return Response.ok(tree).build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+                    .entity("Erreur FTP : " + e.getMessage()).build();
+        }
+    }
+
+    @GET
+    @Path("/list/{alias}/{path: .+}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getFTPTree(
+            @PathParam("alias") String alias,
+            // Le chemin peut être vide (pour la racine) ou multiple segments
+            @PathParam("path") String path,
+            @HeaderParam("X-FTP-User") String user,
+            @HeaderParam("X-FTP-Pass") String pass) {
+        System.out.println("getFTPTree()");
+        try {
+            FTPService.FtpNode tree = ftpService.getResourceTree(alias, path, user, pass);
+            return Response.ok(tree).build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+                    .entity("Erreur FTP : " + e.getMessage()).build();
+        }
+    }
+
+    // GET pour télécharger une ressource : fichier ou dossier compressé en ZIP
+
+    @GET
+    @Path("/{alias}/{path: .+}")
+    public Response getFTPResource(
+            @PathParam("alias") String alias,
+            @PathParam("path") String path,
+            @HeaderParam("X-FTP-User") String user,
+            @HeaderParam("X-FTP-Pass") String pass) {
+        System.out.println("getFTPResource()");
+        try {
+            FTPService.FtpNode node = ftpService.getResourceTree(alias, path, user, pass);
+            if (node.isDirectory) {
+                System.out.println("getFTPResource() > directory");
+                byte[] zipData = ftpService.downloadDirectoryAsZip(alias, path, user, pass);
+                return Response.ok(zipData)
+                        .header("Content-Type", "application/zip")
+                        .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + ".zip\"")
+                        .build();
+            } else {
+                System.out.println("getFTPResource() > NOT directory");
+                byte[] fileData = ftpService.downloadFile(alias, path, user, pass);
+                String contentType = determineContentType(path);
+                return Response.ok(fileData)
+                        .header("Content-Type", contentType)
+                        .header("Content-Disposition", "attachment; filename=\"" + getFileName(path) + "\"")
+                        .build();
+            }
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+                    .entity("Erreur FTP : " + e.getMessage()).build();
+        }
     }
 
+    // PUT pour uploader un fichier
     @PUT
     @Path("/{alias}/{path: .+}")
     @Consumes(MediaType.APPLICATION_OCTET_STREAM)
@@ -144,29 +187,39 @@ public class FTPResource {
             @HeaderParam("X-FTP-User") String user,
             @HeaderParam("X-FTP-Pass") String pass,
             InputStream fileStream) {
-
         System.out.println("uploadFile()");
-        FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
-        FTPClient ftp = new FTPClient();
         try {
-            ftp.connect(config.getHost(), config.getPort());
-            ftp.login(user, pass);
-            boolean success = ftp.storeFile(path, fileStream);
-            return success ? Response.ok().build() : Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+            ftpService.uploadFile(alias, path, user, pass, fileStream);
+            return Response.ok().build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
         } catch (IOException e) {
-            return Response.serverError().entity(e.getMessage()).build();
-        } finally {
-            try {
-                if (ftp.isConnected()) {
-                    ftp.logout();
-                    ftp.disconnect();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
+            return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build();
+        }
+    }
+
+    @POST
+    @Path("/{alias}/upload-dir/{path: .*}")
+    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
+    public Response uploadDirectory(
+            @PathParam("alias") String alias,
+            @PathParam("path") String path,
+            @HeaderParam("X-FTP-User") String user,
+            @HeaderParam("X-FTP-Pass") String pass,
+            InputStream zipStream) {
+
+        System.out.println("uploadDirectory()");
+        try {
+            ftpService.uploadDirectoryAsZip(alias, path, user, pass, zipStream);
+            return Response.ok().build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
+        } catch (IOException e) {
+            return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build();
         }
     }
 
+    // POST pour créer une ressource (fichier ou répertoire)
     @POST
     @Path("/{alias}/{path: .+}")
     @Consumes({ MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN })
@@ -175,43 +228,20 @@ public class FTPResource {
             @PathParam("path") String path,
             @HeaderParam("X-FTP-User") String user,
             @HeaderParam("X-FTP-Pass") String pass,
-            @HeaderParam("X-Resource-Type") String resourceType, // Nouveau header
+            @HeaderParam("X-Resource-Type") String resourceType,
             InputStream inputStream) {
-
         System.out.println("createResource()");
-        FTPClient ftp = new FTPClient();
         try {
-            FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
-            ftp.connect(config.getHost(), config.getPort());
-            ftp.login(user, pass);
-            // Déterminer le type de ressource via le header
-            if ("directory".equalsIgnoreCase(resourceType)) {
-                boolean dirCreated = ftp.makeDirectory(path);
-                return dirCreated
-                        ? Response.created(URI.create(path)).build()
-                        : Response.status(400).entity("Erreur création répertoire").build();
-            } else {
-                // Créer un fichier (même avec un flux vide)
-                boolean fileCreated = ftp.storeFile(path, inputStream);
-                return fileCreated
-                        ? Response.created(URI.create(path)).build()
-                        : Response.status(400).entity("Erreur création fichier").build();
-            }
+            ftpService.createResource(alias, path, user, pass, resourceType, inputStream);
+            return Response.created(URI.create(path)).build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
         } catch (IOException e) {
-            return Response.serverError().entity("Erreur FTP: " + e.getMessage()).build();
-        } finally {
-            try {
-                if (ftp.isConnected()) {
-                    ftp.logout();
-                    ftp.disconnect();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
+            return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build();
         }
     }
 
-    // delete the file or the content of a folder recursively
+    // DELETE pour supprimer une ressource (fichier ou dossier récursivement)
     @DELETE
     @Path("/{alias}/{path: .+}")
     public Response deleteResource(
@@ -219,62 +249,19 @@ public class FTPResource {
             @PathParam("path") String path,
             @HeaderParam("X-FTP-User") String user,
             @HeaderParam("X-FTP-Pass") String pass) {
-
         System.out.println("deleteResource()");
-        FTPClient ftp = new FTPClient();
         try {
-            FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
-            ftp.connect(config.getHost(), config.getPort());
-            ftp.login(user, pass);
-            // Appel à la méthode récursive pour supprimer la ressource
-            boolean deleted = deleteRecursive(ftp, path);
-            return deleted
-                    ? Response.noContent().build()
-                    : Response.status(Response.Status.NOT_FOUND).entity("Ressource non trouvée").build();
+            ftpService.deleteResource(alias, path, user, pass);
+            return Response.noContent().build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
         } catch (IOException e) {
             return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build();
-        } finally {
-            try {
-                if (ftp.isConnected()) {
-                    ftp.logout();
-                    ftp.disconnect();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
         }
     }
 
-    private boolean deleteRecursive(FTPClient ftp, String path) throws IOException {
-        // Tente de supprimer le chemin en tant que fichier
-        if (ftp.deleteFile(path)) {
-            return true;
-        }
-        // Si ce n'est pas un fichier, vérifie si c'est un répertoire
-        FTPFile[] files = ftp.listFiles(path);
-        if (files == null) {
-            // Le chemin n'existe pas
-            return false;
-        }
-        // Supprime récursivement le contenu du répertoire
-        for (FTPFile file : files) {
-            String fullPath = path + "/" + file.getName();
-            if (file.isDirectory()) {
-                if (!deleteRecursive(ftp, fullPath)) {
-                    return false;
-                }
-            } else {
-                if (!ftp.deleteFile(fullPath)) {
-                    return false;
-                }
-            }
-        }
-        // Supprime le répertoire lui-même
-        return ftp.removeDirectory(path);
-    }
-
-    @POST
-    @Path("/{alias}/{path: .+}/rename")
+    @PUT
+    @Path("/{alias}/rename/{path: .+}")
     @Consumes(MediaType.TEXT_PLAIN)
     public Response renameResource(
             @PathParam("alias") String alias,
@@ -282,33 +269,31 @@ public class FTPResource {
             @HeaderParam("X-FTP-User") String user,
             @HeaderParam("X-FTP-Pass") String pass,
             String newPath) {
-
         System.out.println("renameResource()");
-        FTPClient ftp = new FTPClient();
         try {
-            FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
-            ftp.connect(config.getHost(), config.getPort());
-            ftp.login(user, pass);
-            System.out.print(oldPath + " " + newPath);
-            boolean success = ftp.rename(oldPath, newPath);
-            return success
-                    ? Response.ok().build()
-                    : Response.status(Response.Status.BAD_REQUEST).entity("Échec du renommage").build();
+            ftpService.renameResource(alias, oldPath, newPath, user, pass);
+            return Response.ok().build();
+        } catch (FTPException e) {
+            return Response.status(e.getStatus()).entity(e.getMessage()).build();
         } catch (IOException e) {
             return Response.serverError().entity("Erreur FTP : " + e.getMessage()).build();
-        } finally {
-            try {
-                if (ftp.isConnected()) {
-                    ftp.logout();
-                    ftp.disconnect();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
         }
     }
 
+    // Méthode utilitaire pour extraire le nom de la ressource à partir du chemin
     private String getFileName(String path) {
         return path.substring(path.lastIndexOf('/') + 1);
     }
+
+    // Méthode pour déterminer le type de contenu en fonction de l'extension
+    private String determineContentType(String path) {
+        if (path.endsWith(".txt"))
+            return "text/plain";
+        if (path.endsWith(".html"))
+            return "text/html";
+        if (path.endsWith(".json"))
+            return "application/json";
+        // Par défaut, renvoie binaire
+        return "application/octet-stream";
+    }
 }
diff --git a/src/main/java/fil/sr2/flopbox/FTPServerRepository.java b/src/main/java/fil/sr2/flopbox/FTPServerRepository.java
index afe3c0654565d4768129390182904b7b074820bd..73cd3b8351b9675902d6337c062834c4761e5d7d 100644
--- a/src/main/java/fil/sr2/flopbox/FTPServerRepository.java
+++ b/src/main/java/fil/sr2/flopbox/FTPServerRepository.java
@@ -2,6 +2,8 @@ package fil.sr2.flopbox;
 
 import java.util.*;
 
+import fil.sr2.flopbox.utils.*;
+
 public class FTPServerRepository {
     private static FTPServerRepository instance = new FTPServerRepository();
     private Map<String, FTPServerConfig> serverMap = new HashMap<>();
diff --git a/src/main/java/fil/sr2/flopbox/FTPService.java b/src/main/java/fil/sr2/flopbox/FTPService.java
new file mode 100644
index 0000000000000000000000000000000000000000..82e6cf0c9a4994b548b1ef7495b9f2848c7fc372
--- /dev/null
+++ b/src/main/java/fil/sr2/flopbox/FTPService.java
@@ -0,0 +1,404 @@
+package fil.sr2.flopbox;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import java.util.zip.ZipInputStream;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.io.ByteArrayInputStream;
+
+import fil.sr2.flopbox.utils.*;
+
+public class FTPService {
+
+    public FtpNode getResourceTree(String alias, String path, String user, String pass)
+            throws IOException, FTPException {
+        FTPClient ftpClient = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftpClient.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftpClient.enterLocalPassiveMode();
+            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
+            // Normaliser le chemin : retirer le slash final s'il y en a un
+            if (path.endsWith("/")) {
+                path = path.substring(0, path.length() - 1);
+            }
+            // Vérifier si c'est un dossier
+            boolean isDir = ftpClient.changeWorkingDirectory(path);
+            if (isDir) {
+                // Même si le dossier est vide, c'est valide
+                return buildFtpTree(ftpClient, path);
+            } else {
+                // Sinon, tenter de récupérer un flux de fichier
+                InputStream is = ftpClient.retrieveFileStream(path);
+                if (is != null) {
+                    is.close();
+                    ftpClient.completePendingCommand();
+                    return new FtpNode(getFileName(path), false);
+                } else {
+                    throw new FTPException("Ressource non trouvée : " + path, 404);
+                }
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftpClient);
+        }
+    }
+
+    // Méthode récursive pour construire l'arborescence
+
+    private FtpNode buildFtpTree(FTPClient ftpClient, String path) throws IOException {
+        FTPFile[] files = ftpClient.listFiles(path);
+        FtpNode node = new FtpNode(getFileName(path), true);
+        if (files != null) {
+            for (FTPFile file : files) {
+                String fullPath = path + "/" + file.getName();
+                if (file.isDirectory()) {
+                    node.children.add(buildFtpTree(ftpClient, fullPath));
+                } else {
+                    node.children.add(new FtpNode(file.getName(), false));
+                }
+            }
+        }
+        return node;
+    }
+
+    public byte[] downloadFile(String alias, String path, String user, String pass)
+            throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            InputStream is = ftp.retrieveFileStream(path);
+            if (is == null) {
+                throw new FTPException("Ressource non trouvée : " + path, 404);
+            }
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = is.read(buffer)) != -1) {
+                baos.write(buffer, 0, bytesRead);
+            }
+            is.close();
+            ftp.completePendingCommand();
+            return baos.toByteArray();
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    public byte[] downloadDirectoryAsZip(String alias, String path, String user, String pass)
+            throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ZipOutputStream zos = new ZipOutputStream(baos);
+            addDirectoryToZip(ftp, path, "", zos);
+            zos.close();
+            return baos.toByteArray();
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    private void addDirectoryToZip(FTPClient ftp, String remotePath, String basePath, ZipOutputStream zos)
+            throws IOException {
+        FTPFile[] files = ftp.listFiles(remotePath);
+        if (files == null || files.length == 0) {
+            return;
+        }
+        for (FTPFile file : files) {
+            String filePath = remotePath + "/" + file.getName();
+            String zipEntryPath = basePath.isEmpty() ? file.getName() : basePath + "/" + file.getName();
+            if (file.isDirectory()) {
+                zos.putNextEntry(new ZipEntry(zipEntryPath + "/"));
+                zos.closeEntry();
+                addDirectoryToZip(ftp, filePath, zipEntryPath, zos);
+            } else {
+                zos.putNextEntry(new ZipEntry(zipEntryPath));
+                InputStream is = ftp.retrieveFileStream(filePath);
+                if (is != null) {
+                    byte[] buffer = new byte[4096];
+                    int bytesRead;
+                    while ((bytesRead = is.read(buffer)) != -1) {
+                        zos.write(buffer, 0, bytesRead);
+                    }
+                    is.close();
+                    ftp.completePendingCommand();
+                }
+                zos.closeEntry();
+            }
+        }
+    }
+
+    public void uploadFile(String alias, String path, String user, String pass, InputStream fileStream)
+            throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
+            boolean success = ftp.storeFile(path, fileStream);
+            if (!success) {
+                throw new FTPException("Erreur lors de l'upload", 500);
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    public void createResource(String alias, String path, String user, String pass, String resourceType,
+            InputStream inputStream) throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            if ("folder".equalsIgnoreCase(resourceType)) {
+                boolean created = ftp.makeDirectory(path);
+                if (!created) {
+                    throw new FTPException("Erreur création répertoire", 400);
+                }
+            } else {
+                boolean created = ftp.storeFile(path, inputStream);
+                if (!created) {
+                    throw new FTPException("Erreur création fichier", 400);
+                }
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    public void deleteResource(String alias, String path, String user, String pass)
+            throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            boolean deleted = deleteRecursive(ftp, path);
+            if (!deleted) {
+                throw new FTPException("Ressource non trouvée", 404);
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    private boolean deleteRecursive(FTPClient ftp, String path) throws IOException {
+        if (ftp.deleteFile(path)) {
+            return true;
+        }
+        FTPFile[] files = ftp.listFiles(path);
+        if (files == null) {
+            return false;
+        }
+        for (FTPFile file : files) {
+            String fullPath = path + "/" + file.getName();
+            if (file.isDirectory()) {
+                if (!deleteRecursive(ftp, fullPath)) {
+                    return false;
+                }
+            } else {
+                if (!ftp.deleteFile(fullPath)) {
+                    return false;
+                }
+            }
+        }
+        return ftp.removeDirectory(path);
+    }
+
+    public void renameResource(String alias, String oldPath, String newPath, String user, String pass)
+            throws IOException, FTPException {
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            boolean success = ftp.rename(oldPath, newPath);
+            if (!success) {
+                throw new FTPException("Échec du renommage", 400);
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+    }
+
+    private String getFileName(String path) {
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+    // Classe représentant un nœud de l'arborescence FTP
+    public static class FtpNode {
+        public String name;
+        public boolean isDirectory;
+        public List<FtpNode> children;
+
+        public FtpNode(String name, boolean isDirectory) {
+            this.name = name;
+            this.isDirectory = isDirectory;
+            if (isDirectory) {
+                this.children = new ArrayList<>();
+            }
+        }
+    }
+
+    public void uploadDirectoryAsZip(String alias, String targetPath, String user, String pass, InputStream zipStream)
+            throws IOException, FTPException {
+
+        System.out.println("[DEBUG] Début upload ZIP vers: " + targetPath);
+        FTPClient ftp = FTPClientFactory.createClient(alias);
+        try {
+            if (!ftp.login(user, pass)) {
+                throw new FTPException("Authentification FTP échouée", 401);
+            }
+            ftp.enterLocalPassiveMode();
+            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
+
+            try (ZipInputStream zis = new ZipInputStream(zipStream)) {
+                ZipEntry entry;
+                while ((entry = zis.getNextEntry()) != null) {
+                    String entryName = entry.getName();
+                    String fullPath = constructPath(targetPath, entryName);
+                    if (fullPath == null) {
+                        zis.closeEntry();
+                        continue; // Ignorer l'entrée racine
+                    }
+                    System.out.println("[DEBUG] Traitement entrée: " + entry.getName() + " → " + fullPath);
+                    if (entry.isDirectory()) {
+                        createDirectories(ftp, fullPath);
+                    } else {
+                        createParentDirectories(ftp, fullPath);
+                        uploadFileEntry(ftp, fullPath, zis);
+                    }
+                    zis.closeEntry();
+                }
+            }
+        } finally {
+            FTPClientFactory.disconnect(ftp);
+        }
+        System.out.println("[DEBUG] Upload ZIP terminé avec succès");
+    }
+
+    private String constructPath(String basePath, String entryPath) {
+        // Ignorer le premier segment du chemin (dossier racine du ZIP)
+        String[] parts = entryPath.split("/");
+        List<String> partsList = new ArrayList<>();
+        for (String part : parts) {
+            if (!part.isEmpty()) {
+                partsList.add(part);
+            }
+        }
+        if (!partsList.isEmpty()) {
+            partsList.remove(0); // Retirer le premier segment (racine du ZIP)
+        }
+        String normalizedEntry = String.join("/", partsList);
+        if (normalizedEntry.isEmpty()) {
+            return null; // Indique une entrée de répertoire racine à ignorer
+        }
+        return basePath.isEmpty() ? normalizedEntry : basePath + "/" + normalizedEntry;
+    }
+
+    private void createParentDirectories(FTPClient ftp, String filePath) throws IOException {
+        int lastSlash = filePath.lastIndexOf('/');
+        if (lastSlash != -1) {
+            String parentDir = filePath.substring(0, lastSlash);
+            createDirectories(ftp, parentDir);
+        }
+    }
+
+    private void createDirectories(FTPClient ftp, String path) throws IOException {
+        String normalizedPath = path.replaceAll("/+", "/");
+        if (normalizedPath.startsWith("/")) {
+            ftp.changeWorkingDirectory("/");
+        }
+
+        String[] parts = normalizedPath.split("/");
+        StringBuilder currentPath = new StringBuilder();
+
+        for (String part : parts) {
+            if (part.isEmpty())
+                continue;
+
+            currentPath.append(part).append("/");
+
+            if (!ftp.changeWorkingDirectory(currentPath.toString())) {
+                if (!ftp.makeDirectory(currentPath.toString())) {
+                    throw new IOException("Échec création dossier: " + currentPath);
+                }
+                ftp.changeWorkingDirectory(currentPath.toString());
+            }
+        }
+    }
+
+    private void uploadFileEntry(FTPClient ftp, String path, ZipInputStream zis)
+            throws IOException, FTPException {
+
+        String fileName = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path;
+
+        // Lire le contenu du fichier dans un tableau d'octets
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[4096];
+        int bytesRead;
+        while ((bytesRead = zis.read(buffer)) != -1) {
+            baos.write(buffer, 0, bytesRead);
+        }
+
+        // Uploader le contenu depuis le tableau d'octets
+        try (InputStream bis = new ByteArrayInputStream(baos.toByteArray())) {
+            if (!ftp.storeFile(fileName, bis)) {
+                throw new FTPException("Échec upload: " + path, 500);
+            }
+        }
+    }
+
+    /**
+     * Recherche récursive dans l’arborescence FTP.
+     *
+     * @param node        l’arborescence à parcourir
+     * @param searchTerm  la chaîne à rechercher
+     * @param currentPath le chemin courant dans l’arborescence
+     * @param config      la configuration du serveur FTP (pour construire l’URL)
+     * @param urls        la liste des URLs trouvées
+     */
+    public void searchInTree(FTPService.FtpNode node, String searchTerm, String currentPath, FTPServerConfig config,
+            List<String> urls) {
+        // Concaténation du chemin
+        String newPath = currentPath.isEmpty() ? node.name : currentPath + "/" + node.name;
+        // Si le nom contient le terme recherché (fichier ou dossier)
+        if (node.name.toLowerCase().contains(searchTerm.toLowerCase())) {
+            // Construction de l’URL FTP. On suppose que FTPServerConfig possède une méthode
+            // getHost().
+            String ftpUrl = "ftp://" + config.getHost() + "/" + newPath;
+            urls.add(ftpUrl);
+        }
+        // Parcours des enfants si le nœud est un répertoire
+        if (node.isDirectory && node.children != null) {
+            for (FTPService.FtpNode child : node.children) {
+                searchInTree(child, searchTerm, newPath, config, urls);
+            }
+        }
+    }
+}
diff --git a/src/main/java/fil/sr2/flopbox/AuthFilter.java b/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java
similarity index 87%
rename from src/main/java/fil/sr2/flopbox/AuthFilter.java
rename to src/main/java/fil/sr2/flopbox/utils/AuthFilter.java
index 89a8821a126a68e1eb710039a0dcc55fbfa16366..2d28ee5b1bc317528d6cbe6e8da69f9a8d5c5781 100644
--- a/src/main/java/fil/sr2/flopbox/AuthFilter.java
+++ b/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java
@@ -1,6 +1,7 @@
-package fil.sr2.flopbox;
+package fil.sr2.flopbox.utils;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.Map;
 
 import jakarta.annotation.Priority;
@@ -40,8 +41,9 @@ public class AuthFilter implements ContainerRequestFilter {
 
     private static Map<String, String> loadUsers() {
         // Implémenter le chargement depuis un fichier (ex. users.properties)
-        return Map.of(
-                "valid-token-1", "User1",
-                "valid-token-2", "User2");
+        Map<String, String> res = new HashMap<>();
+        res.put("valid-token-1", "User1");
+        res.put("valid-token-2", "User2");
+        return res;
     }
 }
diff --git a/src/main/java/fil/sr2/flopbox/utils/FTPClientFactory.java b/src/main/java/fil/sr2/flopbox/utils/FTPClientFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..232fbbff524ad5e083ed3835bf3f55501ab446c8
--- /dev/null
+++ b/src/main/java/fil/sr2/flopbox/utils/FTPClientFactory.java
@@ -0,0 +1,34 @@
+package fil.sr2.flopbox.utils;
+
+import org.apache.commons.net.ftp.FTPClient;
+import java.io.IOException;
+
+import fil.sr2.flopbox.FTPServerRepository;
+
+public class FTPClientFactory {
+
+    public static FTPClient createClient(String alias) throws FTPException {
+        FTPServerConfig config = FTPServerRepository.getInstance().getServer(alias);
+        if (config == null) {
+            throw new FTPException("Serveur FTP non trouvé", 404);
+        }
+        FTPClient ftpClient = new FTPClient();
+        try {
+            ftpClient.connect(config.getHost(), config.getPort());
+        } catch (IOException e) {
+            throw new FTPException("Erreur de connexion FTP: " + e.getMessage(), 500);
+        }
+        return ftpClient;
+    }
+
+    public static void disconnect(FTPClient ftpClient) {
+        if (ftpClient != null && ftpClient.isConnected()) {
+            try {
+                ftpClient.logout();
+                ftpClient.disconnect();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/src/main/java/fil/sr2/flopbox/utils/FTPException.java b/src/main/java/fil/sr2/flopbox/utils/FTPException.java
new file mode 100644
index 0000000000000000000000000000000000000000..de05f079e78d31c6a3d563dd3574550d28d0b7bb
--- /dev/null
+++ b/src/main/java/fil/sr2/flopbox/utils/FTPException.java
@@ -0,0 +1,14 @@
+package fil.sr2.flopbox.utils;
+
+public class FTPException extends Exception {
+    private final int status;
+
+    public FTPException(String message, int status) {
+        super(message);
+        this.status = status;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+}
diff --git a/src/main/java/fil/sr2/flopbox/FTPServerConfig.java b/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java
similarity index 95%
rename from src/main/java/fil/sr2/flopbox/FTPServerConfig.java
rename to src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java
index 5a1ea89e15748bd8884289d800d7fa0d01219f30..7e95f471d0387b052cdb3c2bdab2ea05e07b37b1 100644
--- a/src/main/java/fil/sr2/flopbox/FTPServerConfig.java
+++ b/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java
@@ -1,4 +1,4 @@
-package fil.sr2.flopbox;
+package fil.sr2.flopbox.utils;
 
 public class FTPServerConfig {
     private String alias;