Added deviceType and deviceName update.
Merged sql.enabled and cassandra.enabled into single property - database.type.
This commit is contained in:
parent
912cffc3b1
commit
6e2bbe0241
@ -28,6 +28,7 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
|
|||||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
|
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
|
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
|
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
|
||||||
|
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
|
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
|
||||||
import org.thingsboard.server.extensions.api.plugins.msg.*;
|
import org.thingsboard.server.extensions.api.plugins.msg.*;
|
||||||
|
|
||||||
@ -60,7 +61,9 @@ public class DeviceActor extends ContextAwareActor {
|
|||||||
} else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
|
} else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
|
||||||
processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
|
processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
|
||||||
} else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){
|
} else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){
|
||||||
processor.processCredentialsUpdate(context(), (DeviceCredentialsUpdateNotificationMsg) msg);
|
processor.processCredentialsUpdate();
|
||||||
|
} else if (msg instanceof DeviceNameOrTypeUpdateMsg){
|
||||||
|
processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
|
||||||
}
|
}
|
||||||
} else if (msg instanceof TimeoutMsg) {
|
} else if (msg instanceof TimeoutMsg) {
|
||||||
processor.processTimeout(context(), (TimeoutMsg) msg);
|
processor.processTimeout(context(), (TimeoutMsg) msg);
|
||||||
|
|||||||
@ -37,10 +37,7 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg;
|
|||||||
import org.thingsboard.server.common.msg.session.MsgType;
|
import org.thingsboard.server.common.msg.session.MsgType;
|
||||||
import org.thingsboard.server.common.msg.session.SessionType;
|
import org.thingsboard.server.common.msg.session.SessionType;
|
||||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
|
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
|
import org.thingsboard.server.extensions.api.device.*;
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
|
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
|
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
|
|
||||||
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
|
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
|
||||||
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
|
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
|
||||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
|
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
|
||||||
@ -372,11 +369,16 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processCredentialsUpdate(ActorContext context, DeviceCredentialsUpdateNotificationMsg msg) {
|
public void processCredentialsUpdate() {
|
||||||
sessions.forEach((k, v) -> {
|
sessions.forEach((k, v) -> {
|
||||||
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
|
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
|
||||||
});
|
});
|
||||||
attributeSubscriptions.clear();
|
attributeSubscriptions.clear();
|
||||||
rpcSubscriptions.clear();
|
rpcSubscriptions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
|
||||||
|
this.deviceName = msg.getDeviceName();
|
||||||
|
this.deviceType = msg.getDeviceType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,4 +31,6 @@ public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor
|
|||||||
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
|
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
|
||||||
|
|
||||||
void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
|
void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
|
||||||
|
|
||||||
|
void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,6 @@ import akka.actor.ActorSystem;
|
|||||||
import akka.actor.Props;
|
import akka.actor.Props;
|
||||||
import akka.actor.Terminated;
|
import akka.actor.Terminated;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.thingsboard.server.actors.ActorSystemContext;
|
import org.thingsboard.server.actors.ActorSystemContext;
|
||||||
@ -42,13 +40,13 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
|
|||||||
import org.thingsboard.server.common.msg.cluster.ServerAddress;
|
import org.thingsboard.server.common.msg.cluster.ServerAddress;
|
||||||
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
|
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
|
||||||
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
|
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
|
||||||
|
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
|
||||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
|
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
|
||||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
|
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
|
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
|
||||||
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
|
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
|
||||||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
|
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
|
||||||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
|
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
|
||||||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
|
|
||||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
|
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
|
||||||
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
|
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
|
||||||
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
|
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
|
||||||
@ -238,6 +236,18 @@ public class DefaultActorService implements ActorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType) {
|
||||||
|
log.trace("[{}] Processing onDeviceNameOrTypeUpdate event, deviceName: {}, deviceType: {}", deviceId, deviceName, deviceType);
|
||||||
|
DeviceNameOrTypeUpdateMsg msg = new DeviceNameOrTypeUpdateMsg(tenantId, deviceId, deviceName, deviceType);
|
||||||
|
Optional<ServerAddress> address = actorContext.getRoutingService().resolveById(deviceId);
|
||||||
|
if (address.isPresent()) {
|
||||||
|
rpcService.tell(address.get(), msg);
|
||||||
|
} else {
|
||||||
|
onMsg(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void broadcast(ToAllNodesMsg msg) {
|
public void broadcast(ToAllNodesMsg msg) {
|
||||||
rpcService.broadcast(msg);
|
rpcService.broadcast(msg);
|
||||||
appActor.tell(msg, ActorRef.noSender());
|
appActor.tell(msg, ActorRef.noSender());
|
||||||
|
|||||||
@ -61,7 +61,9 @@ public class DeviceController extends BaseController {
|
|||||||
public Device saveDevice(@RequestBody Device device) throws ThingsboardException {
|
public Device saveDevice(@RequestBody Device device) throws ThingsboardException {
|
||||||
try {
|
try {
|
||||||
device.setTenantId(getCurrentUser().getTenantId());
|
device.setTenantId(getCurrentUser().getTenantId());
|
||||||
return checkNotNull(deviceService.saveDevice(device));
|
Device savedDevice = checkNotNull(deviceService.saveDevice(device));
|
||||||
|
actorService.onDeviceNameOrTypeUpdate(device.getTenantId(), device.getId(), device.getName(), device.getType());
|
||||||
|
return savedDevice;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw handleException(e);
|
throw handleException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,9 +105,11 @@ coap:
|
|||||||
adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
|
adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
|
||||||
timeout: "${COAP_TIMEOUT:10000}"
|
timeout: "${COAP_TIMEOUT:10000}"
|
||||||
|
|
||||||
|
database:
|
||||||
|
type: "${DATABASE_TYPE:cassandra}" # cassandra OR postgres
|
||||||
|
|
||||||
# Cassandra driver configuration parameters
|
# Cassandra driver configuration parameters
|
||||||
cassandra:
|
cassandra:
|
||||||
enabled: "${CASSANDRA_ENABLED:false}"
|
|
||||||
# Thingsboard cluster name
|
# Thingsboard cluster name
|
||||||
cluster_name: "${CASSANDRA_CLUSTER_NAME:Thingsboard Cluster}"
|
cluster_name: "${CASSANDRA_CLUSTER_NAME:Thingsboard Cluster}"
|
||||||
# Thingsboard keyspace name
|
# Thingsboard keyspace name
|
||||||
@ -225,8 +227,6 @@ spring.mvc.cors:
|
|||||||
allow-credentials: "true"
|
allow-credentials: "true"
|
||||||
|
|
||||||
# SQL DAO Configuration
|
# SQL DAO Configuration
|
||||||
sql:
|
|
||||||
enabled: "${SQL_ENABLED:true}"
|
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
data:
|
data:
|
||||||
|
|||||||
@ -34,6 +34,6 @@ public class ControllerTestSuite {
|
|||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
|
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
|
||||||
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
|
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
|
||||||
new ClassPathCQLDataSet("system-test.cql", false, false)),
|
new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
|
||||||
"cassandra-test.yaml", 30000l);
|
"cassandra-test.yaml", 30000l);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,16 +16,14 @@
|
|||||||
package org.thingsboard.server.dao.sql.user;
|
package org.thingsboard.server.dao.sql.user;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.data.repository.CrudRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.thingsboard.server.common.data.User;
|
|
||||||
import org.thingsboard.server.common.data.security.UserCredentials;
|
import org.thingsboard.server.common.data.security.UserCredentials;
|
||||||
import org.thingsboard.server.dao.DaoUtil;
|
import org.thingsboard.server.dao.DaoUtil;
|
||||||
import org.thingsboard.server.dao.model.ModelConstants;
|
|
||||||
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
|
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
|
||||||
import org.thingsboard.server.dao.sql.JpaAbstractDao;
|
import org.thingsboard.server.dao.sql.JpaAbstractDao;
|
||||||
import org.thingsboard.server.dao.user.UserCredentialsDao;
|
import org.thingsboard.server.dao.user.UserCredentialsDao;
|
||||||
|
import org.thingsboard.server.dao.util.SqlDao;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -33,7 +31,7 @@ import java.util.UUID;
|
|||||||
* Created by Valerii Sosliuk on 4/22/2017.
|
* Created by Valerii Sosliuk on 4/22/2017.
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ConditionalOnProperty(prefix="sql", value="enabled",havingValue = "true", matchIfMissing = false)
|
@SqlDao
|
||||||
public class JpaUserCredentialsDao extends JpaAbstractDao<UserCredentialsEntity, UserCredentials> implements UserCredentialsDao {
|
public class JpaUserCredentialsDao extends JpaAbstractDao<UserCredentialsEntity, UserCredentials> implements UserCredentialsDao {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@ -15,16 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.dao.sql.user;
|
package org.thingsboard.server.dao.sql.user;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.data.repository.CrudRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
|
import org.thingsboard.server.dao.model.sql.UserCredentialsEntity;
|
||||||
|
import org.thingsboard.server.dao.util.SqlDao;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Valerii Sosliuk on 4/22/2017.
|
* Created by Valerii Sosliuk on 4/22/2017.
|
||||||
*/
|
*/
|
||||||
@ConditionalOnProperty(prefix="sql", value="enabled",havingValue = "true", matchIfMissing = false)
|
@SqlDao
|
||||||
public interface UserCredentialsRepository extends CrudRepository<UserCredentialsEntity, UUID> {
|
public interface UserCredentialsRepository extends CrudRepository<UserCredentialsEntity, UUID> {
|
||||||
|
|
||||||
UserCredentialsEntity findByUserId(UUID userId);
|
UserCredentialsEntity findByUserId(UUID userId);
|
||||||
|
|||||||
@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
|
||||||
@ConditionalOnProperty(prefix = "cassandra", value = "enabled", havingValue = "true")
|
@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "cassandra")
|
||||||
public @interface NoSqlDao {
|
public @interface NoSqlDao {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,6 @@ package org.thingsboard.server.dao.util;
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
|
||||||
@ConditionalOnProperty(prefix = "sql", value = "enabled", havingValue = "true")
|
@ConditionalOnProperty(prefix = "database", value = "type", havingValue = "postgres")
|
||||||
public @interface SqlDao {
|
public @interface SqlDao {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public class NoSqlDaoServiceTestSuite {
|
|||||||
new CustomCassandraCQLUnit(
|
new CustomCassandraCQLUnit(
|
||||||
Arrays.asList(new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
|
Arrays.asList(new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
|
||||||
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
|
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false),
|
||||||
new ClassPathCQLDataSet("system-test.cql", false, false)),
|
new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)),
|
||||||
"cassandra-test.yaml", 30000L);
|
"cassandra-test.yaml", 30000L);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite {
|
|||||||
|
|
||||||
@ClassRule
|
@ClassRule
|
||||||
public static CustomPostgresUnit postgresUnit = new CustomPostgresUnit(
|
public static CustomPostgresUnit postgresUnit = new CustomPostgresUnit(
|
||||||
Arrays.asList("postgres/schema.sql", "postgres/system-data.sql", "system-test.sql"),
|
Arrays.asList("postgres/schema.sql", "postgres/system-data.sql", "postgres/system-test.sql"),
|
||||||
"postgres-embedded-test.properties");
|
"postgres-embedded-test.properties");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
sql.enabled=false
|
database.type=cassandra
|
||||||
cassandra.enabled=true
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
cassandra.enabled=false
|
database.type=postgres
|
||||||
sql.enabled=true
|
|
||||||
|
|
||||||
spring.jpa.show-sql=false
|
spring.jpa.show-sql=false
|
||||||
spring.jpa.hibernate.ddl-auto=validate
|
spring.jpa.hibernate.ddl-auto=validate
|
||||||
@ -7,7 +6,3 @@ spring.jpa.hibernate.ddl-auto=validate
|
|||||||
spring.datasource.url=jdbc:postgresql://localhost:5433/thingsboard-test
|
spring.datasource.url=jdbc:postgresql://localhost:5433/thingsboard-test
|
||||||
spring.datasource.username=postgres
|
spring.datasource.username=postgres
|
||||||
spring.datasource.password=postgres
|
spring.datasource.password=postgres
|
||||||
|
|
||||||
#spring.datasource.url=jdbc:h2:mem:test;MODE=PostgreSQL
|
|
||||||
#spring.datasource.schema=classpath:postgres/schema.sql
|
|
||||||
#spring.datasource.data=classpath:postgres/system-data.sql;classpath:system-test.sql
|
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2017 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.extensions.api.device;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.thingsboard.server.common.data.id.DeviceId;
|
||||||
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andrew Shvayka
|
||||||
|
*/
|
||||||
|
@ToString
|
||||||
|
public class DeviceNameOrTypeUpdateMsg implements ToDeviceActorNotificationMsg {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final TenantId tenantId;
|
||||||
|
@Getter
|
||||||
|
private final DeviceId deviceId;
|
||||||
|
@Getter
|
||||||
|
private final String deviceName;
|
||||||
|
@Getter
|
||||||
|
private final String deviceType;
|
||||||
|
|
||||||
|
public DeviceNameOrTypeUpdateMsg(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
this.deviceType = deviceType;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user