mirror of
https://github.com/klmp200/PQT_Gestionnaire_vente_stock.git
synced 2024-11-16 21:33:21 +00:00
Module Server : ajout support nouvelles query; MAJ Javadoc; Suppr TODO
This commit is contained in:
parent
0ceb0d72cd
commit
4e14dea618
@ -8,22 +8,18 @@ import com.pqt.core.entities.product.LightweightProduct;
|
|||||||
import com.pqt.core.entities.product.Product;
|
import com.pqt.core.entities.product.Product;
|
||||||
import com.pqt.core.entities.product.ProductUpdate;
|
import com.pqt.core.entities.product.ProductUpdate;
|
||||||
import com.pqt.core.entities.sale.Sale;
|
import com.pqt.core.entities.sale.Sale;
|
||||||
|
import com.pqt.core.entities.server_config.ServerConfig;
|
||||||
|
import com.pqt.core.entities.user_account.Account;
|
||||||
import com.pqt.core.entities.user_account.AccountLevel;
|
import com.pqt.core.entities.user_account.AccountLevel;
|
||||||
import com.pqt.server.exception.ServerQueryException;
|
import com.pqt.server.exception.ServerQueryException;
|
||||||
import com.pqt.server.module.account.AccountService;
|
import com.pqt.server.module.account.AccountService;
|
||||||
import com.pqt.server.module.client.ClientService;
|
|
||||||
import com.pqt.server.module.sale.SaleService;
|
import com.pqt.server.module.sale.SaleService;
|
||||||
import com.pqt.server.module.state.ServerStateService;
|
import com.pqt.server.module.state.ServerStateService;
|
||||||
import com.pqt.server.module.statistics.StatisticsService;
|
import com.pqt.server.module.statistics.StatisticsService;
|
||||||
import com.pqt.server.module.stock.StockService;
|
import com.pqt.server.module.stock.StockService;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
//TODO ajouter des messages d'erreur spécifiques pour les NullPointerException si le param du message vaut null
|
|
||||||
//TODO mettre à jour la liste des query supportées lorsque la version du serveur sera proche de la release
|
|
||||||
//TODO ne pas oublier de préciser le niveau de permission requis pour chaque requête
|
|
||||||
//TODO Paramétrer les supports de query et leurs permissions via un meilleur système (config file, etc ...)
|
//TODO Paramétrer les supports de query et leurs permissions via un meilleur système (config file, etc ...)
|
||||||
/**
|
/**
|
||||||
* Implémentation de l'interface {@link IMessageHandler}. Cette classe définit le gestionnaire de message par défaut du
|
* Implémentation de l'interface {@link IMessageHandler}. Cette classe définit le gestionnaire de message par défaut du
|
||||||
@ -31,12 +27,20 @@ import java.util.Map;
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Liste des requêtes supportées :<br/>
|
* Liste des requêtes supportées :<br/>
|
||||||
* <ul>
|
* <ul>
|
||||||
*
|
* <li>QUERY_STOCK (WAITER)</li>
|
||||||
|
* <li>QUERY_SALE (WAITER)</li>
|
||||||
|
* <li>QUER_STAT (WAITER)</li>
|
||||||
|
* <li>QUERY_UPDATE (MASTER)</li>
|
||||||
|
* <li>QUERY_ACCOUNT_LIST (NONE)</li>
|
||||||
|
* <li>QUERY_CONNECT_ACCOUNT (NONE)</li>
|
||||||
|
* <li>QUERY_PING (NONE)</li>
|
||||||
|
* <li>QUERY_CONFIG_LIST (NONE)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p/>
|
* <p/>
|
||||||
* Liste des requêtes non-supportées :<br/>
|
* Liste des requêtes non-supportées :<br/>
|
||||||
* <ul>
|
* <ul>
|
||||||
*
|
* <li>QUERY_REVERT_SALE</li>
|
||||||
|
* <li>QUERY_LAST_SALES_LIST</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* @see IMessageHandler
|
* @see IMessageHandler
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
@ -72,24 +76,26 @@ public class SimpleMessageHandler implements IMessageHandler {
|
|||||||
|
|
||||||
manager = new MessageManager();
|
manager = new MessageManager();
|
||||||
|
|
||||||
//TODO ajouter support des query de connexion de compte utilisateur
|
/*
|
||||||
manager.support(MessageType.QUERY_STOCK, (message)->{
|
WAITER-restricted queries
|
||||||
|
*/
|
||||||
|
manager.supportForConnectedAccounts(MessageType.QUERY_STOCK, (message)->{
|
||||||
Map<String, String> fields = new HashMap<>();
|
Map<String, String> fields = new HashMap<>();
|
||||||
fields.put("stock", messageToolFactory.getListFormatter(Product.class).format(stockService.getProductList()));
|
fields.put("stock", messageToolFactory.getListFormatter(Product.class).format(stockService.getProductList()));
|
||||||
return new Message(MessageType.MSG_STOCK, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
return new Message(MessageType.MSG_STOCK, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
}, AccountLevel.WAITER);
|
}, AccountLevel.WAITER);
|
||||||
manager.support(MessageType.QUERY_SALE, (message)->{
|
manager.supportForConnectedAccounts(MessageType.QUERY_SALE, (message)->{
|
||||||
Map<String, String> fields = new HashMap<>();
|
Map<String, String> fields = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
long saleId = saleService.submitSale(messageToolFactory.getObjectParser(Sale.class).parse(message.getField("sale")));
|
long saleId = saleService.submitSale(messageToolFactory.getObjectParser(Sale.class).parse(message.getField("sale")));
|
||||||
fields.put("saleId", Long.toString(saleId));
|
fields.put("saleId", Long.toString(saleId));
|
||||||
return new Message(MessageType.ACK_SALE, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
return new Message(MessageType.ACK_SALE, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
}catch(ServerQueryException | NullPointerException e){
|
}catch(NullPointerException e){
|
||||||
fields.put(header_ref_query, e.toString());
|
fields.put(header_ref_query, e.toString());
|
||||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
}
|
}
|
||||||
}, AccountLevel.WAITER);
|
}, AccountLevel.WAITER);
|
||||||
manager.support(MessageType.QUERY_STAT, (message)->{
|
manager.supportForConnectedAccounts(MessageType.QUERY_STAT, (message)->{
|
||||||
Map<String, String> fields = new HashMap<>();
|
Map<String, String> fields = new HashMap<>();
|
||||||
fields.put("total_sale_worth", Double.toString(statisticsService.getTotalSaleWorth()));
|
fields.put("total_sale_worth", Double.toString(statisticsService.getTotalSaleWorth()));
|
||||||
fields.put("total_sale_amount", Integer.toString(statisticsService.getTotalAmountSale()));
|
fields.put("total_sale_amount", Integer.toString(statisticsService.getTotalAmountSale()));
|
||||||
@ -102,7 +108,11 @@ public class SimpleMessageHandler implements IMessageHandler {
|
|||||||
|
|
||||||
return new Message(MessageType.MSG_STAT, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
return new Message(MessageType.MSG_STAT, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
}, AccountLevel.WAITER);
|
}, AccountLevel.WAITER);
|
||||||
manager.support(MessageType.QUERY_UPDATE, (message)->{
|
|
||||||
|
/*
|
||||||
|
MASTER-restricted queries
|
||||||
|
*/
|
||||||
|
manager.supportForConnectedAccounts(MessageType.QUERY_UPDATE, (message)->{
|
||||||
try{
|
try{
|
||||||
List<ProductUpdate> updates = messageToolFactory.getListParser(ProductUpdate.class).parse(message.getField("updates"));
|
List<ProductUpdate> updates = messageToolFactory.getListParser(ProductUpdate.class).parse(message.getField("updates"));
|
||||||
stockService.applyUpdateList(updates);
|
stockService.applyUpdateList(updates);
|
||||||
@ -113,19 +123,82 @@ public class SimpleMessageHandler implements IMessageHandler {
|
|||||||
return new Message(MessageType.ERROR_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
return new Message(MessageType.ERROR_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
}
|
}
|
||||||
}, AccountLevel.MASTER);
|
}, AccountLevel.MASTER);
|
||||||
/*
|
|
||||||
manager.support(MessageType.QUERY_REVERT_SALE, (message)->{
|
|
||||||
|
|
||||||
try{
|
/*
|
||||||
saleService.submitSaleRevert(messageToolFactory.getObjectParser(Long.class).parse(message.getField("saleId")));
|
Queries without account connection requirements
|
||||||
return new Message(MessageType.ACK_REVERT_SALE, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null);
|
|
||||||
}catch(ServerQueryException | NullPointerException e){
|
|
||||||
Map<String, String> fields = new HashMap<>();
|
|
||||||
fields.put(header_err_query, e.toString());
|
|
||||||
return new Message(MessageType.ERROR_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
|
||||||
}
|
|
||||||
}, AccountLevel.MASTER);
|
|
||||||
*/
|
*/
|
||||||
|
manager.support(MessageType.QUERY_ACCOUNT_LIST, (message)->{
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put("accounts", messageToolFactory.getListFormatter(Account.class).format(accountService.getAccountList()));
|
||||||
|
return new Message(MessageType.MSG_ACCOUNT_LIST, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
|
}, AccountLevel.getLowest(), false);
|
||||||
|
manager.support(MessageType.QUERY_CONNECT_ACCOUNT, (message)->{
|
||||||
|
final String desiredStateFieldHeader = "desired_state",
|
||||||
|
accountCredentialsFieldHeader = "account";
|
||||||
|
|
||||||
|
if(message.getFields().containsKey(desiredStateFieldHeader)){
|
||||||
|
if(message.getFields().containsKey(accountCredentialsFieldHeader)){
|
||||||
|
boolean desiredState = messageToolFactory.getObjectParser(Boolean.class).parse(message.getField(desiredStateFieldHeader));
|
||||||
|
Account accountCredentials = messageToolFactory.getObjectParser(Account.class).parse(message.getField(accountCredentialsFieldHeader));
|
||||||
|
|
||||||
|
if(accountService.submitAccountCredentials(accountCredentials, desiredState)){
|
||||||
|
return new Message(MessageType.ACK_CONNECT_ACCOUNT, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null);
|
||||||
|
}else{
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put(header_ref_query, "Impossible d'effectuer l'action : identifiants invalides ou état désiré déjà atteint");
|
||||||
|
|
||||||
|
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return getMissingArgumentQueryReplyMessage(message, accountCredentialsFieldHeader);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return getMissingArgumentQueryReplyMessage(message, desiredStateFieldHeader);
|
||||||
|
}
|
||||||
|
}, AccountLevel.getLowest(), false);
|
||||||
|
manager.support(MessageType.QUERY_PING, (message)->new Message(MessageType.ACK_PING, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null), AccountLevel.getLowest(), false);
|
||||||
|
manager.support(MessageType.QUERY_CONFIG_LIST, (message)->{
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put("config", messageToolFactory.getObjectFormatter(ServerConfig.class).format(serverStateService.getConfig()));
|
||||||
|
return new Message(MessageType.MSG_CONFIG_LIST, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
|
}, AccountLevel.getLowest(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message getUnsupportedQueryReplyMessage(Message message){
|
||||||
|
final String msg_ref_unsupported_query = "Ce type de requêtes n'est actuellement pas supportée par ce serveur.";
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put(header_ref_query, msg_ref_unsupported_query);
|
||||||
|
|
||||||
|
return new Message(MessageType.REFUSED_QUERY,
|
||||||
|
serverStateService.getServer(),
|
||||||
|
message.getEmitter(),
|
||||||
|
message.getUser(),
|
||||||
|
message,
|
||||||
|
fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message getMissingArgumentQueryReplyMessage(Message message, String missingArgumentHeader){
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put(header_err_query, "The following required header is missing : "+missingArgumentHeader);
|
||||||
|
|
||||||
|
return new Message(MessageType.ERROR_QUERY,
|
||||||
|
serverStateService.getServer(),
|
||||||
|
message.getEmitter(),
|
||||||
|
message.getUser(),
|
||||||
|
message,
|
||||||
|
fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message getExceptionOccuredQueryReplyMessage(Message message, Exception exception){
|
||||||
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
fields.put(header_err_query, exception.getMessage());
|
||||||
|
|
||||||
|
return new Message(MessageType.ERROR_QUERY,
|
||||||
|
serverStateService.getServer(),
|
||||||
|
message.getEmitter(),
|
||||||
|
message.getUser(),
|
||||||
|
message,
|
||||||
|
fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -133,20 +206,25 @@ public class SimpleMessageHandler implements IMessageHandler {
|
|||||||
|
|
||||||
Map<String, String> fields = new HashMap<>();
|
Map<String, String> fields = new HashMap<>();
|
||||||
|
|
||||||
if(!isAccountRegisteredAndConnected(message)){
|
|
||||||
fields.put(header_ref_query, "Compte utilisateur inconnu");
|
|
||||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
|
||||||
}
|
|
||||||
if(!checkAccountPermission(message)){
|
|
||||||
fields.put(header_ref_query, "Compte utilisateur avec permission trop faible");
|
|
||||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
|
||||||
}
|
|
||||||
if(manager.contains(message.getType())){
|
if(manager.contains(message.getType())){
|
||||||
return manager.getProcess(message.getType()).execute(message);
|
if(manager.isQueryRestrictedToConnectedAccount(message.getType())) {
|
||||||
|
if (!isAccountRegisteredAndConnected(message)) {
|
||||||
|
fields.put(header_ref_query, "Compte utilisateur inconnu ou déconnecté.");
|
||||||
|
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
|
}
|
||||||
|
if (!checkAccountPermission(message)) {
|
||||||
|
fields.put(header_ref_query, "Compte utilisateur avec permission trop faible");
|
||||||
|
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
return manager.getProcess(message.getType()).execute(message);
|
||||||
|
}catch(Exception e){
|
||||||
|
return getExceptionOccuredQueryReplyMessage(message, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fields.put(header_err_query, "Type requête non pris en charge par ce serveur");
|
return getUnsupportedQueryReplyMessage(message);
|
||||||
return new Message(MessageType.ERROR_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,30 +234,97 @@ public class SimpleMessageHandler implements IMessageHandler {
|
|||||||
Message execute(Message request);
|
Message execute(Message request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MessageManager{
|
private class MessageTypeEntry{
|
||||||
private Map<MessageType, IMessageProcess> processes;
|
private MessageType type;
|
||||||
private Map<MessageType, AccountLevel> levels;
|
private IMessageProcess process;
|
||||||
|
private AccountLevel level;
|
||||||
|
private boolean connectedAccountRestriction;
|
||||||
|
|
||||||
MessageManager(){
|
MessageTypeEntry(MessageType type, IMessageProcess process, AccountLevel level, boolean connectedAccountRestriction) {
|
||||||
processes = new HashMap<>();
|
this.type = type;
|
||||||
levels = new HashMap<>();
|
this.process = process;
|
||||||
|
this.level = level;
|
||||||
|
this.connectedAccountRestriction = connectedAccountRestriction;
|
||||||
}
|
}
|
||||||
|
|
||||||
void support(MessageType type, IMessageProcess process, AccountLevel permissionLevel){
|
IMessageProcess getProcess() {
|
||||||
processes.put(type, process);
|
return process;
|
||||||
levels.put(type, permissionLevel);
|
}
|
||||||
|
|
||||||
|
AccountLevel getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isConnectedAccountRestriction() {
|
||||||
|
return connectedAccountRestriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matches(MessageType type){
|
||||||
|
return type.equals(this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
MessageTypeEntry that = (MessageTypeEntry) o;
|
||||||
|
|
||||||
|
return connectedAccountRestriction == that.connectedAccountRestriction && type == that.type && process.equals(that.process) && level == that.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = type.hashCode();
|
||||||
|
result = 31 * result + process.hashCode();
|
||||||
|
result = 31 * result + level.hashCode();
|
||||||
|
result = 31 * result + (connectedAccountRestriction ? 1 : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageManager{
|
||||||
|
private Set<MessageTypeEntry> entries;
|
||||||
|
|
||||||
|
MessageManager(){
|
||||||
|
entries = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void supportForConnectedAccounts(MessageType type, IMessageProcess process, AccountLevel permissionLevel){
|
||||||
|
support(type, process, permissionLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void support(MessageType type, IMessageProcess process, AccountLevel permissionLevel, boolean accountConnectionRequired){
|
||||||
|
entries.add(new MessageTypeEntry(type, process, permissionLevel, accountConnectionRequired));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageTypeEntry getFirstMatch(MessageType type){
|
||||||
|
return entries.stream().filter(entry->entry.matches(type)).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
IMessageProcess getProcess(MessageType messageType){
|
IMessageProcess getProcess(MessageType messageType){
|
||||||
return processes.get(messageType);
|
MessageTypeEntry entry = getFirstMatch(messageType);
|
||||||
|
if(entry!=null)
|
||||||
|
return entry.getProcess();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountLevel getLevel(MessageType messageType){
|
AccountLevel getLevel(MessageType messageType){
|
||||||
return levels.get(messageType);
|
MessageTypeEntry entry = getFirstMatch(messageType);
|
||||||
|
if(entry!=null)
|
||||||
|
return entry.getLevel();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean contains(MessageType type) {
|
boolean contains(MessageType type) {
|
||||||
return processes.containsKey(type);
|
return getFirstMatch(type)!=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isQueryRestrictedToConnectedAccount(MessageType type) {
|
||||||
|
MessageTypeEntry entry = getFirstMatch(type);
|
||||||
|
return entry != null && entry.isConnectedAccountRestriction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.pqt.server.module.state;
|
package com.pqt.server.module.state;
|
||||||
|
|
||||||
import com.pqt.core.entities.members.DataServer;
|
import com.pqt.core.entities.members.DataServer;
|
||||||
|
import com.pqt.core.entities.server_config.ConfigFields;
|
||||||
|
import com.pqt.core.entities.server_config.ServerConfig;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@ -16,10 +18,16 @@ public class ServerStateService {
|
|||||||
|
|
||||||
private ServerState serverState;
|
private ServerState serverState;
|
||||||
private DataServer server;
|
private DataServer server;
|
||||||
|
private ServerConfig config;
|
||||||
|
|
||||||
public ServerStateService() {
|
public ServerStateService() {
|
||||||
this.server = new DataServer();
|
this.server = new DataServer();
|
||||||
this.serverState = new ServerState();
|
this.serverState = new ServerState();
|
||||||
|
this.config = new ServerConfig(
|
||||||
|
ConfigFields.ALLOW_ACCOUNT_CONNECT,
|
||||||
|
ConfigFields.ALLOW_SALE_COMMIT,
|
||||||
|
ConfigFields.ALLOW_STOCK_UPDATE,
|
||||||
|
ConfigFields.ALLOW_STOCK_VIEW);
|
||||||
|
|
||||||
//TODO config adresse IP
|
//TODO config adresse IP
|
||||||
//this.com.pqt.server.setAddress(...);
|
//this.com.pqt.server.setAddress(...);
|
||||||
@ -49,4 +57,7 @@ public class ServerStateService {
|
|||||||
return serverState.copy();
|
return serverState.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerConfig getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user