From 743c664e0ca8ef910b2d02f2ef6223555b0445c1 Mon Sep 17 00:00:00 2001
From: mariaeloi <mariaeduardaeloi@outlook.com>
Date: Sat, 23 Apr 2022 20:32:47 -0300
Subject: [PATCH] add authenticator

---
 .../main/java/ufrn/imd/BankingClientApp.java  |  20 +++-----
 .../java/ufrn/imd/utils/MachineState.java     |  44 +++++++++++++---
 .../main/java/ufrn/imd/utils/NameSpace.java   |   2 +
 .../main/java/ufrn/imd/BankingServerApp.java  |  12 ++---
 .../impl/AuthenticatorControllerImpl.java     |  47 ++++++++++++++++++
 .../impl/BalanceControllerImpl.java           |   2 +-
 .../imd/repository/impl/ClientRepository.java |   4 +-
 .../impl/AuthenticatorServiceImpl.java        |  29 +++++++++++
 .../main/java/ufrn/imd/utils/NameSpace.java   |   4 ++
 .../classes/ufrn/imd/BankingServerApp.class   | Bin 1943 -> 2271 bytes
 .../impl/BalanceControllerImpl.class          | Bin 2232 -> 2232 bytes
 .../repository/impl/ClientRepository.class    | Bin 3080 -> 3080 bytes
 .../classes/ufrn/imd/utils/NameSpace.class    | Bin 1139 -> 1296 bytes
 13 files changed, 133 insertions(+), 31 deletions(-)
 create mode 100644 banking-server/src/main/java/ufrn/imd/controller/impl/AuthenticatorControllerImpl.java
 create mode 100644 banking-server/src/main/java/ufrn/imd/service/impl/AuthenticatorServiceImpl.java

diff --git a/banking-client/src/main/java/ufrn/imd/BankingClientApp.java b/banking-client/src/main/java/ufrn/imd/BankingClientApp.java
index bf72420..ec24073 100644
--- a/banking-client/src/main/java/ufrn/imd/BankingClientApp.java
+++ b/banking-client/src/main/java/ufrn/imd/BankingClientApp.java
@@ -1,30 +1,26 @@
 package ufrn.imd;
 
 import lombok.extern.java.Log;
-import ufrn.imd.controller.Controller;
-import ufrn.imd.domain.Account;
-import ufrn.imd.domain.Client;
 import ufrn.imd.utils.MachineState;
 
 import java.net.MalformedURLException;
-import java.rmi.Naming;
 import java.rmi.NotBoundException;
 import java.rmi.RemoteException;
 
-import static ufrn.imd.utils.MachineState.*;
-import static ufrn.imd.utils.NameSpace.HOST_DEPOSIT_SERVICE;
-
 @Log
 public class BankingClientApp {
     public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException {
 
         MachineState mS = new MachineState();
 
-        while(mS.quit()) {
-            mS.render();
-            mS.processEvents();
-            mS.update();
-        }
+        for(;;) {
+            while(!mS.hasUser()) mS.login();
 
+            while(mS.quit() || mS.hasUser()) {
+                mS.render();
+                mS.processEvents();
+                mS.update();
+            }
+        }
     }
 }
diff --git a/banking-client/src/main/java/ufrn/imd/utils/MachineState.java b/banking-client/src/main/java/ufrn/imd/utils/MachineState.java
index 4f1834f..87a951f 100644
--- a/banking-client/src/main/java/ufrn/imd/utils/MachineState.java
+++ b/banking-client/src/main/java/ufrn/imd/utils/MachineState.java
@@ -34,12 +34,36 @@ public class MachineState {
     private Scanner sc;
     private String inputValue;
 
+    private Client client;
+
     public MachineState() {
         state = INITIAL;
         inputValue = new String();
         sc = new Scanner(System.in);
     }
 
+    public void login() throws MalformedURLException, NotBoundException, RemoteException {
+        System.out.println(">>> Please enter your bank account number");
+        String accountNumber = sc.nextLine();
+
+        Controller server = (Controller) Naming.lookup(HOST_AUTHENTICATOR_SERVICE);
+        ServerResponse response = server.getResponse(accountNumber);
+        if(response.getStatus() == 200) {
+            state = INITIAL;
+            this.client = new Client((Integer) response.getBody());
+        } else System.out.println(">>> Bad credentials");
+
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public Boolean hasUser() {
+        return client != null;
+    }
+
     public Boolean quit() {
         return !state.equals(QUIT);
     }
@@ -77,8 +101,7 @@ public class MachineState {
             }
         } else if(state.equals(DEPOSIT)) {
             String v = sc.nextLine();
-            Client client = new Client(1);
-            DepositMessage depositMessage = new DepositMessage(client, Double.parseDouble(v));
+            DepositMessage depositMessage = new DepositMessage(this.client, Double.parseDouble(v));
             Controller server = (Controller) Naming.lookup(HOST_DEPOSIT_SERVICE);
             ServerResponse response = server.getResponse(depositMessage);
             if(response.getStatus() == 200) {
@@ -93,9 +116,8 @@ public class MachineState {
             }
 
         } else if(state.equals(BALANCE)) {
-            Client client = new Client(1);
             Controller server = (Controller) Naming.lookup(HOST_BALANCE_SERVICE);
-            ServerResponse response = server.getResponse(client);
+            ServerResponse response = server.getResponse(this.client);
             if(response.getStatus() == 200) {
                 System.out.println(
                         String.format(">>> Your balance is: %.2f", ((Account)response.getBody()).getBalance())
@@ -109,8 +131,7 @@ public class MachineState {
 
         } else if(state.equals(WITHDRAWAL)) {
             String v = sc.nextLine();
-            Client client = new Client(1);
-            WithdrawalMessage withdrawalMessage = new WithdrawalMessage(client, Double.parseDouble(v));
+            WithdrawalMessage withdrawalMessage = new WithdrawalMessage(this.client, Double.parseDouble(v));
             Controller server = (Controller) Naming.lookup(HOST_WITHDRAWAL_SERVICE);
             ServerResponse response = server.getResponse(withdrawalMessage);
             if(response.getStatus() == 200) {
@@ -132,9 +153,8 @@ public class MachineState {
             System.out.println(">>> Please enter with the value that you want transfer");
             String v = sc.nextLine();
 
-            Client client = new Client(1);
             Account account = new Account(null, accountNumber, null);
-            TransferMessage transferMessage = new TransferMessage(client, account, Double.parseDouble(v));
+            TransferMessage transferMessage = new TransferMessage(this.client, account, Double.parseDouble(v));
             Controller server = (Controller) Naming.lookup(HOST_TRANSFER_SERVICE);
             ServerResponse response = server.getResponse(transferMessage);
             if(response.getStatus() == 200) {
@@ -151,6 +171,14 @@ public class MachineState {
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
+        } else if(state.equals(QUIT)) {
+            this.client = null;
+            System.out.println(">>> You have logged out");
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
         }
     }
 
diff --git a/banking-client/src/main/java/ufrn/imd/utils/NameSpace.java b/banking-client/src/main/java/ufrn/imd/utils/NameSpace.java
index 3cc1c46..c6b2156 100644
--- a/banking-client/src/main/java/ufrn/imd/utils/NameSpace.java
+++ b/banking-client/src/main/java/ufrn/imd/utils/NameSpace.java
@@ -1,10 +1,12 @@
 package ufrn.imd.utils;
 
 public class NameSpace {
+    public final static Integer PORT_AUTHENTICATOR_SERVICE = 1905;
     public final static Integer PORT_WITHDRAWAL_SERVICE = 1906;
     public static final Integer PORT_DEPOSIT_SERVICE = 1907;
     public static final Integer PORT_BALANCE_SERVICE = 1908;
     public static final Integer PORT_TRANSFER_SERVICE = 1909;
+    public final static String HOST_AUTHENTICATOR_SERVICE = String.format("rmi://127.0.0.1:%d/%s", PORT_AUTHENTICATOR_SERVICE, "authenticator");
     public final static String HOST_WITHDRAWAL_SERVICE = String.format("rmi://127.0.0.1:%d/%s", PORT_WITHDRAWAL_SERVICE, "withdrawal");
     public static final String HOST_DEPOSIT_SERVICE = String.format("rmi://127.0.0.1:%d/%s", PORT_DEPOSIT_SERVICE, "deposit");
     public static final String HOST_BALANCE_SERVICE = String.format("rmi://127.0.0.1:%d/%s", PORT_BALANCE_SERVICE, "balance");
diff --git a/banking-server/src/main/java/ufrn/imd/BankingServerApp.java b/banking-server/src/main/java/ufrn/imd/BankingServerApp.java
index 24f0ed8..0b06471 100644
--- a/banking-server/src/main/java/ufrn/imd/BankingServerApp.java
+++ b/banking-server/src/main/java/ufrn/imd/BankingServerApp.java
@@ -1,15 +1,9 @@
 package ufrn.imd;
 
 import ufrn.imd.controller.Controller;
-import ufrn.imd.controller.impl.BalanceControllerImpl;
-import ufrn.imd.controller.impl.DepositControllerImpl;
-import ufrn.imd.controller.impl.TransferControllerImpl;
-import ufrn.imd.controller.impl.WithdrawalControllerImpl;
+import ufrn.imd.controller.impl.*;
 import ufrn.imd.repository.impl.ClientRepository;
-import ufrn.imd.service.impl.BalanceServiceImpl;
-import ufrn.imd.service.impl.DepositServiceImpl;
-import ufrn.imd.service.impl.TransferServiceImpl;
-import ufrn.imd.service.impl.WithdrawalServiceImpl;
+import ufrn.imd.service.impl.*;
 
 import java.net.MalformedURLException;
 import java.rmi.AlreadyBoundException;
@@ -22,10 +16,12 @@ public class BankingServerApp {
         BalanceServiceImpl balanceService = new BalanceServiceImpl(clientRepository);
         WithdrawalServiceImpl withdrawalService = new WithdrawalServiceImpl(clientRepository);
         TransferServiceImpl transferService = new TransferServiceImpl(clientRepository);
+        AuthenticatorServiceImpl authenticatorService = new AuthenticatorServiceImpl(clientRepository);
 
         Controller depositController = new DepositControllerImpl(depositService);
         Controller balanceController = new BalanceControllerImpl(balanceService);
         Controller withdrawalController = new WithdrawalControllerImpl(withdrawalService);
         Controller transferController = new TransferControllerImpl(transferService);
+        Controller authenticatorController = new AuthenticatorControllerImpl(authenticatorService);
     }
 }
diff --git a/banking-server/src/main/java/ufrn/imd/controller/impl/AuthenticatorControllerImpl.java b/banking-server/src/main/java/ufrn/imd/controller/impl/AuthenticatorControllerImpl.java
new file mode 100644
index 0000000..0c3770f
--- /dev/null
+++ b/banking-server/src/main/java/ufrn/imd/controller/impl/AuthenticatorControllerImpl.java
@@ -0,0 +1,47 @@
+package ufrn.imd.controller.impl;
+
+import lombok.extern.java.Log;
+import ufrn.imd.controller.Controller;
+import ufrn.imd.service.impl.AuthenticatorServiceImpl;
+import ufrn.imd.utils.ServerResponse;
+
+import java.net.MalformedURLException;
+import java.rmi.AlreadyBoundException;
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.server.UnicastRemoteObject;
+
+import static ufrn.imd.utils.NameSpace.HOST_AUTHENTICATOR_SERVICE;
+import static ufrn.imd.utils.NameSpace.PORT_AUTHENTICATOR_SERVICE;
+
+@Log
+public class AuthenticatorControllerImpl extends UnicastRemoteObject implements Controller {
+    private final AuthenticatorServiceImpl service;
+
+    public AuthenticatorControllerImpl(AuthenticatorServiceImpl service) throws RemoteException, MalformedURLException, AlreadyBoundException {
+        super();
+        log.info("Starting Authenticator service!");
+        this.service = service;
+        log.info(String.format("Initializing server in %s", HOST_AUTHENTICATOR_SERVICE));
+        LocateRegistry.createRegistry(PORT_AUTHENTICATOR_SERVICE);
+        Naming.bind(HOST_AUTHENTICATOR_SERVICE, this);
+    }
+
+    @Override
+    public ServerResponse getResponse(Object client) throws RemoteException {
+        String accountNumber = (String) client;
+        ServerResponse response;
+        try {
+            response = this.service.authenticate(accountNumber);
+        } catch (RuntimeException e) {
+            e.printStackTrace();
+            response = ServerResponse.builder()
+                    .status(500)
+                    .message(e.getMessage())
+                    .body(accountNumber)
+                    .build();
+        }
+        return response;
+    }
+}
diff --git a/banking-server/src/main/java/ufrn/imd/controller/impl/BalanceControllerImpl.java b/banking-server/src/main/java/ufrn/imd/controller/impl/BalanceControllerImpl.java
index 50d9cb8..aaab876 100644
--- a/banking-server/src/main/java/ufrn/imd/controller/impl/BalanceControllerImpl.java
+++ b/banking-server/src/main/java/ufrn/imd/controller/impl/BalanceControllerImpl.java
@@ -27,7 +27,7 @@ public class BalanceControllerImpl extends UnicastRemoteObject implements Contro
 
         LocateRegistry.createRegistry(PORT_BALANCE_SERVICE);
         Naming.bind(HOST_BALANCE_SERVICE, this);
-        log.info("Starting Deposit service!");
+        log.info("Starting Balance service!");
         this.service = service;
         log.info(String.format("Initializing server in %s", HOST_BALANCE_SERVICE));
 
diff --git a/banking-server/src/main/java/ufrn/imd/repository/impl/ClientRepository.java b/banking-server/src/main/java/ufrn/imd/repository/impl/ClientRepository.java
index 22520da..a893a7b 100644
--- a/banking-server/src/main/java/ufrn/imd/repository/impl/ClientRepository.java
+++ b/banking-server/src/main/java/ufrn/imd/repository/impl/ClientRepository.java
@@ -9,8 +9,8 @@ import java.util.Optional;
 
 public class ClientRepository implements Repository<Client> {
     List<Client> clients = List.of(
-            new Client(1, "Fernando Feirreira", new Account(1, "321456789", 200.00)),
-            new Client(2, "Maria Eduarda", new Account(2, "987654321", 100.00))
+            new Client(0, "Fernando Feirreira", new Account(1, "321456789", 200.00)),
+            new Client(1, "Maria Eduarda", new Account(2, "987654321", 100.00))
             );
 
     @Override
diff --git a/banking-server/src/main/java/ufrn/imd/service/impl/AuthenticatorServiceImpl.java b/banking-server/src/main/java/ufrn/imd/service/impl/AuthenticatorServiceImpl.java
new file mode 100644
index 0000000..f7ef113
--- /dev/null
+++ b/banking-server/src/main/java/ufrn/imd/service/impl/AuthenticatorServiceImpl.java
@@ -0,0 +1,29 @@
+package ufrn.imd.service.impl;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.java.Log;
+import ufrn.imd.domain.Client;
+import ufrn.imd.repository.Repository;
+import ufrn.imd.repository.impl.ClientRepository;
+import ufrn.imd.service.Service;
+import ufrn.imd.utils.ServerResponse;
+
+@Log
+@AllArgsConstructor
+public class AuthenticatorServiceImpl implements Service {
+
+    private final Repository<Client> clientRepository;
+
+    public ServerResponse authenticate(String accountNumber) {
+        Client client = ((ClientRepository) clientRepository).findByAccountNumber(accountNumber)
+                .orElseThrow(() -> new RuntimeException(
+                        String.format("Customer not found with account number %s", accountNumber)));
+
+        log.info(client.getName() + " logged in (account number: " + client.getAccount().getNumber() + ")");
+        return ServerResponse.builder()
+                .status(200)
+                .message("Operation sucessed!")
+                .body(client.getId())
+                .build();
+    }
+}
diff --git a/banking-server/src/main/java/ufrn/imd/utils/NameSpace.java b/banking-server/src/main/java/ufrn/imd/utils/NameSpace.java
index fe82fc8..b3b8007 100644
--- a/banking-server/src/main/java/ufrn/imd/utils/NameSpace.java
+++ b/banking-server/src/main/java/ufrn/imd/utils/NameSpace.java
@@ -5,6 +5,7 @@ public class NameSpace {
     public final static Integer PORT_BALANCE_SERVICE = 1908;
     public final static Integer PORT_DEPOSIT_SERVICE = 1907;
     public final static Integer PORT_WITHDRAWAL_SERVICE = 1906;
+    public final static Integer PORT_AUTHENTICATOR_SERVICE = 1905;
 
     public final static String HOST_DEPOSIT_SERVICE =
             String.format("rmi://127.0.0.1:%d/%s", PORT_DEPOSIT_SERVICE, "deposit");
@@ -17,4 +18,7 @@ public class NameSpace {
 
     public final static String HOST_TRANSFER_SERVICE =
             String.format("rmi://127.0.0.1:%d/%s", PORT_TRANSFER_SERVICE, "transfer");
+
+    public final static String HOST_AUTHENTICATOR_SERVICE =
+            String.format("rmi://127.0.0.1:%d/%s", PORT_AUTHENTICATOR_SERVICE, "authenticator");
 }
diff --git a/banking-server/target/classes/ufrn/imd/BankingServerApp.class b/banking-server/target/classes/ufrn/imd/BankingServerApp.class
index 8d5ef782c69ac0f87e6afb4dd4b59b1497d69553..d2be3c2c2aa3de8f81a018455bbf467e4d6bfe88 100644
GIT binary patch
delta 766
zcmY+COHUI~6vuyeI?RPjX(_ML;sX)o{jeb7fILM7Q9%?O6BkHoYyvH*l^8eOxp8GC
z(a%9#OkxxFvU2T)FX7sFrkVhEasKDr^ZK8A?w9y+qI~q{+jrmw&kZSVX=V&oh_eQp
zInBJm330(t8{(qDgm~K!4ROg3<BsO8p^<x<WkVAyn)`+pRy7X{Z9LRG(yS?*>0CZn
zno&f0`!*H!Vd1B0Z`poZdY#Reay#3lLUAKoJjm^26{G&TN=D~T`TSAq!*KFU^kN}j
zDi(Hkvqgnjdbg9^FXamPH=1?LW6g$tWc5VSbojq5Pw<Jq7Ww4=QVnP8{)o(q^Qm8N
zj|p!2^Y(<3W=k>R$Ly5<*=`r}nPNONJ5N(8o)B2x+ux0n#`}6`A1GTf)*DVxJ`?_b
zx-fRCZ<9;3%d>~aLP>6i$a7+i2r9vv{s47Yb)OgMR4=1k5Iy=YfQxjBRVSsoq=+P|
z3=#1{zJlci{zqr1JP^>}Iu&BxU_ji}6&k&vfP`CHp~*|x0nKh*g%&SW6VU3K3T<A>
z2}rv2C*3pEsxkQoaT2oaw8(DR_At#%(#nEtdX*$wGSo{MYfmCVrtc|N>2O)Xk@Qzw
z;i}M2@PZzCg|=*lKKg}tH!BP<D4%bbIfg{mNbw1Ikx?0cK|I&Upruh4W1`hcyCmbH
h)k)JR*F`hZT1ifZdimMXu}KO~auZjsr{uz7`Y!;Rg`xle

delta 522
zcmYL`%}x|S6otR)znZBGFf+j5j6VpDg5v=4C)g_BD2hKw+|di8E8@nju8fahVkH_E
zzJtU?Cywy}d<mm3;2RK54-0!y=iJ-3&N;XHckR>2vtLKwe*$alImWqWS#tyht~(;G
zTT(|-;0;Hqz?%+N;4Mdm4a=rum@Ug~N0pxCj-$?9%eLj-;j6#Ke-7UGPB>)mTXrlD
z{6;tvHkj})!z^j=&@k)&n1+9!)cwEk!2gJ<{$n(ukw^Y(w5*ZGhDHB2YWg5<slDqb
z;}zkPZy(~<<~dW0DS7b(7^JN8l&S@x5yqVyb2xb2)R_#Lbz0(;lL1b1Mpc=!oRf$y
z8$+lyA@g7C(z|ZXP@41^&gODPQroARHKUwbTIy5JnsLr(TK@0R8QdMy2OFoRNG*kG
zOLtyuU2Z-3?aFOmenpOh6E7w?ud2jTEj6udHFmf_`-MWa*yJLY6syfDGtBDm%yWgy
qbciK7CEnF?Jz33(Ss7F`(Usi3q!z@=Tph@VEV9J1x+|&!R*wPVSUwy8

diff --git a/banking-server/target/classes/ufrn/imd/controller/impl/BalanceControllerImpl.class b/banking-server/target/classes/ufrn/imd/controller/impl/BalanceControllerImpl.class
index e990c821bbca67e3753e9d7b7726bf02f8827d69..d21c3306c0a4f767e4fda868fd072ae710e59058 100644
GIT binary patch
delta 20
ccmdlXxI=Km2PSr>#GJ&u<kZdInGUi709T#}Q~&?~

delta 20
ccmdlXxI=Km2PSrx)Pnrt%#zLDnGUi709iB$h5!Hn

diff --git a/banking-server/target/classes/ufrn/imd/repository/impl/ClientRepository.class b/banking-server/target/classes/ufrn/imd/repository/impl/ClientRepository.class
index c6d086468a406fbde73aefe262c8388575fe861e..9274fcdcb68e9bfc2d8548d156fd2a0d114e1228 100644
GIT binary patch
delta 20
bcmeB>=#bd(kCTyk@_$ZwMwZR;TrU{`Nzev?

delta 20
bcmeB>=#bd(kCTyQ@_$ZwM%K;pTrU{`N#X{B

diff --git a/banking-server/target/classes/ufrn/imd/utils/NameSpace.class b/banking-server/target/classes/ufrn/imd/utils/NameSpace.class
index 42a6dfd302b25b75da9780c5ae89421c50754d76..bca364926d480d38728b84a028f4af2299447065 100644
GIT binary patch
delta 653
zcmY+B%TE(g6vn^lbeP-W7U`6S6j9OoMDT$MzR>c}vaw1B6XK#7OARDViFFL_Sh#X0
znYeMKJ2x(b4KdLbEB+RD#x3zXcNUGC?>pz0JNG;H%pdi^s~;Tx_yu4Jiykt#<Y62W
z8T8|_$Q6;RBG*JF6|TFup>WgV2h)OXDclynqi|RJp2CdyeT4_&B^R^X&)((>Pby16
zt+X7J7aj+TbETlNw7tLO7s{3D|2UquJ{C{mw2L{7c`RrQU{K?z$dE`uWEg|-Z)@u5
zLybo$YaGLf1@}eR3|sYv#&K!A&Gl$)wH<DTO^p*mRMZYzuU|IW8YiXChHs*^Mk`vc
zhtUS%C`RI1>RY{^n*coKF`Lzp#2e=X#yNy>&S9MS#u;p!amE>AoHx@`u)wELi*d~!
zm^?$)#SXA>ma0wd;2h4gQ@D_vW&IZp75EZ=wToLh_thR!yKsHw{6uEO-hoqHN&8-P
z58b=a9h?@NO>j>KcL_d{;NA|l1?Lj%cd#ospWwdlK)^9&^jY*$(_EGVpAQ*^iz3G!
n=jvwA&A*q$GJ5cw=bFf&jXd6=5AV6wPh8w*x`m6pBZaYlMs;4^

delta 515
zcmYL_xlY4S5Jiu(;1~1QOjyjm1PJ?1*q5G)NJuG70D**31Q3V@Xj6gw9TY%=kU+Fh
z@h>z~h<Q$t!uQS`&6_*&o%5r1zy4m|fec#?iXn$I!wFnQjEou?GcvB2u$fd$IZQKS
z(5zz4_`G7lct){ke930{K7V=tdmfamQg<g^HY=J{)-;VIHBCmEjkJ&q79*L}b<GBw
znpQjud(S`d3;6?0n`xbk<FljvqJQC^Xxa@?XGOnodU#OObl?Rak%z1+w<Dit5~T7>
zNa&Iix<rL85uwZO&}C!jvPnFGg&Zu}9ehWdXM2QINe5ASMRi!m>7!pdMXEe2^(S#r
zxDC8m>e{vAZ>U`KiFh^_a}(JYoM);lIA*Xe<C+StGPt&k>nb>EaD5p!JW7g1kLUt5
rqA?PZ?aH0)*m6;V`7{nIa_qJY>`+IZdX8n}N`B9cxW#}xj4=2QQa?dd

-- 
GitLab