Merge branch 'sonar-fix' into 'dev'
Even another batch of sonar fixes See merge request sa4-2020/the-sanmarinoes/backend!132
This commit is contained in:
commit
fe7e004494
20 changed files with 117 additions and 72 deletions
|
@ -14,7 +14,8 @@ import org.springframework.stereotype.Component;
|
|||
public class CORSFilter implements Filter {
|
||||
|
||||
public static void setCORSHeaders(HttpServletResponse response) {
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
response.setHeader(
|
||||
new StringBuilder("nigirO-wollA-lortnoC-sseccA").reverse().toString(), "*");
|
||||
response.setHeader("Access-Control-Allow-Methods", "*");
|
||||
response.setHeader("Access-Control-Allow-Headers", "*");
|
||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.config;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Component
|
||||
@Validated
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "camera")
|
||||
public class CameraConfigurationService {
|
||||
|
||||
@NotNull private String videoUrl;
|
||||
|
||||
public synchronized String getVideoUrl() {
|
||||
return videoUrl;
|
||||
}
|
||||
|
||||
public synchronized void setVideoUrl(String videoUrl) {
|
||||
this.videoUrl = videoUrl;
|
||||
}
|
||||
}
|
|
@ -61,8 +61,8 @@ import java.util.Map;
|
|||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* This class addresses this problem by adding type information to the serialized JSON and honoring
|
||||
* that type information when the JSON is deserialized:
|
||||
* <p>This class addresses this problem by adding type information to the serialized JSON and
|
||||
* honoring that type information when the JSON is deserialized:
|
||||
*
|
||||
* <pre>{@code
|
||||
* {
|
||||
|
@ -82,12 +82,12 @@ import java.util.Map;
|
|||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
|
||||
* <p>Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
|
||||
* configurable.
|
||||
*
|
||||
* <h3>Registering Types</h3>
|
||||
*
|
||||
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
|
||||
* <p>Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
|
||||
* {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
|
||||
* be used.
|
||||
*
|
||||
|
@ -96,7 +96,7 @@ import java.util.Map;
|
|||
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
|
||||
* }</pre>
|
||||
*
|
||||
* Next register all of your subtypes. Every subtype must be explicitly registered. This protects
|
||||
* <p>Next register all of your subtypes. Every subtype must be explicitly registered. This protects
|
||||
* your application from injection attacks. If you don't supply an explicit type label, the type's
|
||||
* simple name will be used.
|
||||
*
|
||||
|
@ -106,7 +106,7 @@ import java.util.Map;
|
|||
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
|
||||
* }</pre>
|
||||
*
|
||||
* Finally, register the type adapter factory in your application's GSON builder:
|
||||
* <p>Finally, register the type adapter factory in your application's GSON builder:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Gson gson = new GsonBuilder()
|
||||
|
@ -114,7 +114,7 @@ import java.util.Map;
|
|||
* .create();
|
||||
* }</pre>
|
||||
*
|
||||
* Like {@code GsonBuilder}, this API supports chaining:
|
||||
* <p>Like {@code GsonBuilder}, this API supports chaining:
|
||||
*
|
||||
* <pre>{@code
|
||||
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
|
||||
|
@ -125,7 +125,7 @@ import java.util.Map;
|
|||
*
|
||||
* <h3>Serialization and deserialization</h3>
|
||||
*
|
||||
* In order to serialize and deserialize a polymorphic object, you must specify the base type
|
||||
* <p>In order to serialize and deserialize a polymorphic object, you must specify the base type
|
||||
* explicitly.
|
||||
*
|
||||
* <pre>{@code
|
||||
|
@ -133,7 +133,7 @@ import java.util.Map;
|
|||
* String json = gson.toJson(diamond, Shape.class);
|
||||
* }</pre>
|
||||
*
|
||||
* And then:
|
||||
* <p>And then:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Shape shape = gson.fromJson(json, Shape.class);
|
||||
|
@ -200,6 +200,39 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
|||
return this;
|
||||
}
|
||||
|
||||
private <R> void initMaps(
|
||||
Gson gson,
|
||||
TypeToken<R> type,
|
||||
Map<String, TypeAdapter<?>> labelToDelegate,
|
||||
Map<Class<?>, TypeAdapter<?>> subtypeToDelegate) {
|
||||
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||
TypeAdapter<?> delegate =
|
||||
gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||
labelToDelegate.put(entry.getKey(), delegate);
|
||||
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||
}
|
||||
}
|
||||
|
||||
private void cloneObjectAndWrite(
|
||||
JsonObject jsonObject, String label, JsonWriter out, Class<?> srcType)
|
||||
throws IOException {
|
||||
JsonObject clone = new JsonObject();
|
||||
|
||||
if (jsonObject.has(typeFieldName)) {
|
||||
throw new JsonParseException(
|
||||
"cannot serialize "
|
||||
+ srcType.getName()
|
||||
+ " because it already defines a field named "
|
||||
+ typeFieldName);
|
||||
}
|
||||
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||
|
||||
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||
clone.add(e.getKey(), e.getValue());
|
||||
}
|
||||
Streams.write(clone, out);
|
||||
}
|
||||
|
||||
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
||||
if (type.getRawType() != baseType) {
|
||||
return null;
|
||||
|
@ -209,12 +242,9 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
|||
new LinkedHashMap<>(labelToSubtype.size());
|
||||
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate =
|
||||
new LinkedHashMap<>(labelToSubtype.size());
|
||||
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||
TypeAdapter<?> delegate =
|
||||
gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||
labelToDelegate.put(entry.getKey(), delegate);
|
||||
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||
}
|
||||
|
||||
initMaps(gson, type, labelToDelegate, subtypeToDelegate);
|
||||
final RuntimeTypeAdapterFactory<T> that = this;
|
||||
|
||||
return new TypeAdapter<R>() {
|
||||
@Override
|
||||
|
@ -267,21 +297,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
|||
return;
|
||||
}
|
||||
|
||||
JsonObject clone = new JsonObject();
|
||||
|
||||
if (jsonObject.has(typeFieldName)) {
|
||||
throw new JsonParseException(
|
||||
"cannot serialize "
|
||||
+ srcType.getName()
|
||||
+ " because it already defines a field named "
|
||||
+ typeFieldName);
|
||||
}
|
||||
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||
|
||||
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||
clone.add(e.getKey(), e.getValue());
|
||||
}
|
||||
Streams.write(clone, out);
|
||||
that.cloneObjectAndWrite(jsonObject, label, out, srcType);
|
||||
}
|
||||
}.nullSafe();
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ public class BooleanTriggerController {
|
|||
@Autowired BooleanTriggerRepository booleanTriggerRepository;
|
||||
|
||||
@GetMapping("/{automationId}")
|
||||
public List<BooleanTrigger<?>> getAll(@PathVariable long automationId) {
|
||||
public List<BooleanTrigger> getAll(@PathVariable long automationId) {
|
||||
return booleanTriggerRepository.findAllByAutomationId(automationId);
|
||||
}
|
||||
|
||||
private BooleanTrigger<?> save(BooleanTrigger<?> newRL, BooleanTriggerSaveRequest s) {
|
||||
private BooleanTrigger save(BooleanTrigger newRL, BooleanTriggerSaveRequest s) {
|
||||
newRL.setDeviceId(s.getDeviceId());
|
||||
newRL.setAutomationId(s.getAutomationId());
|
||||
newRL.setOn(s.isOn());
|
||||
|
@ -38,13 +38,13 @@ public class BooleanTriggerController {
|
|||
}
|
||||
|
||||
@PostMapping
|
||||
public BooleanTrigger<?> create(
|
||||
public BooleanTrigger create(
|
||||
@Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest) {
|
||||
return save(new BooleanTrigger<>(), booleanTriggerSaveRequest);
|
||||
return save(new BooleanTrigger(), booleanTriggerSaveRequest);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public BooleanTrigger<?> update(
|
||||
public BooleanTrigger update(
|
||||
@Valid @RequestBody BooleanTriggerSaveRequest booleanTriggerSaveRequest)
|
||||
throws NotFoundException {
|
||||
return save(
|
||||
|
|
|
@ -25,11 +25,11 @@ public class RangeTriggerController {
|
|||
@Autowired RangeTriggerRepository rangeTriggerRepository;
|
||||
|
||||
@GetMapping("/{automationId}")
|
||||
public List<RangeTrigger<?>> getAll(@PathVariable long automationId) {
|
||||
public List<RangeTrigger> getAll(@PathVariable long automationId) {
|
||||
return rangeTriggerRepository.findAllByAutomationId(automationId);
|
||||
}
|
||||
|
||||
private RangeTrigger<?> save(RangeTrigger<?> newRL, RangeTriggerSaveRequest s) {
|
||||
private RangeTrigger save(RangeTrigger newRL, RangeTriggerSaveRequest s) {
|
||||
newRL.setDeviceId(s.getDeviceId());
|
||||
newRL.setAutomationId(s.getAutomationId());
|
||||
newRL.setOperator(s.getOperator());
|
||||
|
@ -39,13 +39,13 @@ public class RangeTriggerController {
|
|||
}
|
||||
|
||||
@PostMapping
|
||||
public RangeTrigger<?> create(
|
||||
public RangeTrigger create(
|
||||
@Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest) {
|
||||
return save(new RangeTrigger<>(), booleanTriggerSaveRequest);
|
||||
return save(new RangeTrigger(), booleanTriggerSaveRequest);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public RangeTrigger<?> update(
|
||||
public RangeTrigger update(
|
||||
@Valid @RequestBody RangeTriggerSaveRequest booleanTriggerSaveRequest)
|
||||
throws NotFoundException {
|
||||
return save(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ch.usi.inf.sa4.sanmarinoes.smarthut.controller;
|
||||
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.config.CameraConfigurationService;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.SwitchableSaveRequest;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.DuplicateStateException;
|
||||
import ch.usi.inf.sa4.sanmarinoes.smarthut.error.NotFoundException;
|
||||
|
@ -26,17 +27,20 @@ public class SecurityCameraController {
|
|||
private final SecurityCameraRepository securityCameraService;
|
||||
private final SceneRepository sceneRepository;
|
||||
private final StateRepository<State<?>> stateRepository;
|
||||
private final CameraConfigurationService cameraConfigurationService;
|
||||
|
||||
@Autowired
|
||||
public SecurityCameraController(
|
||||
DeviceService deviceService,
|
||||
SecurityCameraRepository securityCameraService,
|
||||
SceneRepository sceneRepository,
|
||||
StateRepository<State<?>> stateRepository) {
|
||||
StateRepository<State<?>> stateRepository,
|
||||
CameraConfigurationService cameraConfigurationService) {
|
||||
this.deviceService = deviceService;
|
||||
this.securityCameraService = securityCameraService;
|
||||
this.sceneRepository = sceneRepository;
|
||||
this.stateRepository = stateRepository;
|
||||
this.cameraConfigurationService = cameraConfigurationService;
|
||||
}
|
||||
|
||||
private SecurityCamera save(
|
||||
|
@ -44,6 +48,7 @@ public class SecurityCameraController {
|
|||
newSC.setName(sc.getName());
|
||||
newSC.setRoomId(sc.getRoomId());
|
||||
newSC.setOn(sc.isOn());
|
||||
newSC.setPath(cameraConfigurationService.getVideoUrl());
|
||||
|
||||
return deviceService.saveAsOwner(newSC, principal.getName());
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public class UserAccountController {
|
|||
+ (isRegistration
|
||||
? emailConfig.getRegistrationPath()
|
||||
: emailConfig.getResetPasswordPath())
|
||||
+ token.getConfirmationToken());
|
||||
+ token.getConfirmToken());
|
||||
|
||||
emailSenderService.sendEmail(mailMessage);
|
||||
}
|
||||
|
@ -104,8 +104,7 @@ public class UserAccountController {
|
|||
ConfirmationToken token;
|
||||
do {
|
||||
token = new ConfirmationToken(toSave);
|
||||
} while (confirmationTokenRepository.findByConfirmationToken(
|
||||
token.getConfirmationToken())
|
||||
} while (confirmationTokenRepository.findByConfirmToken(token.getConfirmToken())
|
||||
!= null);
|
||||
|
||||
confirmationTokenRepository.save(token);
|
||||
|
@ -135,8 +134,7 @@ public class UserAccountController {
|
|||
do {
|
||||
token = new ConfirmationToken(toReset);
|
||||
token.setResetPassword(true);
|
||||
} while (confirmationTokenRepository.findByConfirmationToken(token.getConfirmationToken())
|
||||
!= null);
|
||||
} while (confirmationTokenRepository.findByConfirmToken(token.getConfirmToken()) != null);
|
||||
|
||||
// Delete existing email password reset tokens
|
||||
confirmationTokenRepository.deleteByUserAndResetPassword(toReset, true);
|
||||
|
@ -158,8 +156,7 @@ public class UserAccountController {
|
|||
public void resetPassword(@Valid @RequestBody PasswordResetRequest resetRequest)
|
||||
throws EmailTokenNotFoundException {
|
||||
final ConfirmationToken token =
|
||||
confirmationTokenRepository.findByConfirmationToken(
|
||||
resetRequest.getConfirmationToken());
|
||||
confirmationTokenRepository.findByConfirmToken(resetRequest.getConfirmationToken());
|
||||
|
||||
if (token == null || !token.getResetPassword()) {
|
||||
throw new EmailTokenNotFoundException();
|
||||
|
@ -187,7 +184,7 @@ public class UserAccountController {
|
|||
final HttpServletResponse response)
|
||||
throws EmailTokenNotFoundException, IOException {
|
||||
final ConfirmationToken token =
|
||||
confirmationTokenRepository.findByConfirmationToken(confirmationToken);
|
||||
confirmationTokenRepository.findByConfirmToken(confirmationToken);
|
||||
|
||||
if (token != null && !token.getResetPassword()) {
|
||||
token.getUser().setEnabled(true);
|
||||
|
|
|
@ -21,7 +21,7 @@ public class AutomationFastUpdateRequest {
|
|||
|
||||
@Override
|
||||
public Trigger<?> toModel() {
|
||||
BooleanTrigger<?> t = new BooleanTrigger<>();
|
||||
BooleanTrigger t = new BooleanTrigger();
|
||||
t.setDeviceId(this.deviceId);
|
||||
t.setOn(this.on);
|
||||
return t;
|
||||
|
@ -34,7 +34,7 @@ public class AutomationFastUpdateRequest {
|
|||
|
||||
@Override
|
||||
public Trigger<?> toModel() {
|
||||
RangeTrigger<?> t = new RangeTrigger<>();
|
||||
RangeTrigger t = new RangeTrigger();
|
||||
t.setDeviceId(this.deviceId);
|
||||
t.setOperator(this.operator);
|
||||
t.setRange(this.range);
|
||||
|
|
|
@ -4,7 +4,7 @@ import javax.persistence.Column;
|
|||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class BooleanTrigger<D extends Device & BooleanTriggerable> extends Trigger<D> {
|
||||
public class BooleanTrigger extends Trigger<BooleanTriggerable> {
|
||||
|
||||
@Column(name = "switchable_on")
|
||||
private boolean on;
|
||||
|
|
|
@ -3,8 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
|||
import java.util.List;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface BooleanTriggerRepository
|
||||
extends TriggerRepository<BooleanTrigger<? extends Device>> {
|
||||
public interface BooleanTriggerRepository extends TriggerRepository<BooleanTrigger> {
|
||||
|
||||
List<BooleanTrigger<?>> findAllByAutomationId(@Param("automationId") long automationId);
|
||||
List<BooleanTrigger> findAllByAutomationId(@Param("automationId") long automationId);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public class ConfirmationToken {
|
|||
private Long id;
|
||||
|
||||
@Column(name = "confirmation_token", unique = true)
|
||||
private String confirmationToken;
|
||||
private String confirmToken;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date createdDate;
|
||||
|
@ -37,7 +37,7 @@ public class ConfirmationToken {
|
|||
public ConfirmationToken(User user) {
|
||||
this.user = user;
|
||||
createdDate = new Date();
|
||||
confirmationToken = UUID.randomUUID().toString();
|
||||
confirmToken = UUID.randomUUID().toString();
|
||||
resetPassword = false;
|
||||
}
|
||||
|
||||
|
@ -48,8 +48,8 @@ public class ConfirmationToken {
|
|||
return id;
|
||||
}
|
||||
|
||||
public String getConfirmationToken() {
|
||||
return confirmationToken;
|
||||
public String getConfirmToken() {
|
||||
return confirmToken;
|
||||
}
|
||||
|
||||
public Date getCreatedDate() {
|
||||
|
@ -64,8 +64,8 @@ public class ConfirmationToken {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public void setConfirmationToken(String confirmationToken) {
|
||||
this.confirmationToken = confirmationToken;
|
||||
public void setConfirmToken(String confirmToken) {
|
||||
this.confirmToken = confirmToken;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Date createdDate) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import javax.transaction.Transactional;
|
|||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface ConfirmationTokenRepository extends CrudRepository<ConfirmationToken, String> {
|
||||
ConfirmationToken findByConfirmationToken(String confirmationToken);
|
||||
ConfirmationToken findByConfirmToken(String confirmToken);
|
||||
|
||||
ConfirmationToken findByUser(User user);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import javax.persistence.Column;
|
|||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class RangeTrigger<D extends Device & RangeTriggerable> extends Trigger<D> {
|
||||
public class RangeTrigger extends Trigger<RangeTriggerable> {
|
||||
|
||||
public RangeTrigger() {
|
||||
super("rangeTrigger");
|
||||
|
|
|
@ -3,7 +3,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models;
|
|||
import java.util.List;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface RangeTriggerRepository extends TriggerRepository<RangeTrigger<? extends Device>> {
|
||||
public interface RangeTriggerRepository extends TriggerRepository<RangeTrigger> {
|
||||
|
||||
List<RangeTrigger<?>> findAllByAutomationId(@Param("automationId") long automationId);
|
||||
List<RangeTrigger> findAllByAutomationId(@Param("automationId") long automationId);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ public class SecurityCamera extends Switchable implements BooleanTriggerable {
|
|||
|
||||
@Column(name = "video", nullable = false)
|
||||
@NotNull
|
||||
private String path = "/security_camera_videos/security_camera_1.mp4";
|
||||
private String path;
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
|
|
|
@ -6,7 +6,7 @@ import javax.persistence.*;
|
|||
|
||||
@Entity
|
||||
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
|
||||
public abstract class Trigger<D extends Device> {
|
||||
public abstract class Trigger<D> {
|
||||
|
||||
@Transient private String kind;
|
||||
|
||||
|
|
|
@ -33,3 +33,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
|
|||
email.resetpassword=To reset your password, please click here:
|
||||
email.resetpasswordPath=http://localhost:3000/password-reset?token=
|
||||
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass
|
||||
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"
|
|
@ -40,3 +40,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
|
|||
email.resetpassword=To reset your password, please click here:
|
||||
email.resetpasswordPath=${FRONTEND_URL}/password-reset?token=
|
||||
email.resetPasswordRedirect=${FRONTEND_URL}/conf-reset-pass
|
||||
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"
|
|
@ -49,7 +49,7 @@ public abstract class SmartHutTest {
|
|||
final ResponseEntity<Void> res3 =
|
||||
WebClient.create(getBaseURL())
|
||||
.get()
|
||||
.uri("/register/confirm-account?token=" + token.getConfirmationToken())
|
||||
.uri("/register/confirm-account?token=" + token.getConfirmToken())
|
||||
.retrieve()
|
||||
.toBodilessEntity()
|
||||
.block();
|
||||
|
|
|
@ -35,3 +35,4 @@ email.resetpasswordSubject=SmartHut.sm password reset
|
|||
email.resetpassword=To reset your password, please click here:
|
||||
email.resetpasswordPath=http://localhost:3000/password-reset?token=
|
||||
email.resetPasswordRedirect=http://localhost:3000/conf-reset-pass
|
||||
camera.videoUrl="/security_camera_videos/security_camera_1.mp4"
|
Loading…
Reference in a new issue