initial commit
All checks were successful
Build and Publish / Build Yale Access Backend (push) Successful in 28s
Build and Publish / Build Yale Access Frontend (push) Successful in 47s
Build and Publish / Push Yale Access Backend Docker Image (push) Successful in 9s
Build and Publish / Push Yale Access Frontend Docker Image (push) Successful in 10s
All checks were successful
Build and Publish / Build Yale Access Backend (push) Successful in 28s
Build and Publish / Build Yale Access Frontend (push) Successful in 47s
Build and Publish / Push Yale Access Backend Docker Image (push) Successful in 9s
Build and Publish / Push Yale Access Frontend Docker Image (push) Successful in 10s
This commit is contained in:
11
packages/backend/Services/Interfaces/IYaleAccessor.cs
Normal file
11
packages/backend/Services/Interfaces/IYaleAccessor.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using YaleAccess.Models;
|
||||
|
||||
namespace YaleAccess.Services.Interfaces
|
||||
{
|
||||
public interface IYaleAccessor
|
||||
{
|
||||
public Task<YaleUserCode> GetCodeInformationAsync(int userCodeId);
|
||||
public Task<bool> SetUserCode(int userCodeId, string code);
|
||||
public Task<bool> SetCodeAsAvailable(int userCode);
|
||||
}
|
||||
}
|
30
packages/backend/Services/MockYaleAccessor.cs
Normal file
30
packages/backend/Services/MockYaleAccessor.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using YaleAccess.Models;
|
||||
using YaleAccess.Services.Interfaces;
|
||||
|
||||
namespace YaleAccess.Services
|
||||
{
|
||||
public class MockYaleAccessor : IYaleAccessor
|
||||
{
|
||||
private readonly MockYaleData _data;
|
||||
|
||||
public MockYaleAccessor(MockYaleData data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public Task<YaleUserCode> GetCodeInformationAsync(int userCodeId)
|
||||
{
|
||||
return Task.FromResult(_data.GetUserCode(userCodeId));
|
||||
}
|
||||
|
||||
public Task<bool> SetCodeAsAvailable(int userCode)
|
||||
{
|
||||
return Task.FromResult(_data.SetUserCodeAsAvailable(userCode));
|
||||
}
|
||||
|
||||
public Task<bool> SetUserCode(int userCodeId, string code)
|
||||
{
|
||||
return Task.FromResult(_data.SetUserCode(userCodeId, code));
|
||||
}
|
||||
}
|
||||
}
|
78
packages/backend/Services/MockYaleData.cs
Normal file
78
packages/backend/Services/MockYaleData.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using YaleAccess.Models;
|
||||
|
||||
namespace YaleAccess.Services
|
||||
{
|
||||
public class MockYaleData
|
||||
{
|
||||
public List<YaleUserCode> UserCodes { get; set; } = new();
|
||||
|
||||
public MockYaleData()
|
||||
{
|
||||
// Create the home code
|
||||
YaleUserCode homeCode = new()
|
||||
{
|
||||
Id = 1,
|
||||
Code = "1234",
|
||||
IsHome = true,
|
||||
Status = UserCodeStatus.ENABLED
|
||||
};
|
||||
|
||||
// Create 5 guest codes
|
||||
List<YaleUserCode> guestCodes = new();
|
||||
foreach (int code in Enumerable.Range(2, 5))
|
||||
{
|
||||
guestCodes.Add(new YaleUserCode()
|
||||
{
|
||||
Id = code,
|
||||
Code = "",
|
||||
IsHome = false,
|
||||
Status = UserCodeStatus.AVAILABLE
|
||||
});
|
||||
}
|
||||
|
||||
// Add the home code to the list
|
||||
guestCodes.Add(homeCode);
|
||||
|
||||
// Set the user codes
|
||||
UserCodes = guestCodes;
|
||||
}
|
||||
|
||||
public YaleUserCode GetUserCode(int id)
|
||||
{
|
||||
return UserCodes.First(x => x.Id == id);
|
||||
}
|
||||
|
||||
public bool SetUserCode(int id, string code)
|
||||
{
|
||||
// Get the user code
|
||||
YaleUserCode userCode = GetUserCode(id);
|
||||
|
||||
// Set the code
|
||||
userCode.Code = code;
|
||||
|
||||
// Update code status
|
||||
userCode.Status = UserCodeStatus.ENABLED;
|
||||
|
||||
// Update the user code in the list
|
||||
UserCodes[UserCodes.IndexOf(userCode)] = userCode;
|
||||
|
||||
// Return true to indicate success
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetUserCodeAsAvailable(int id)
|
||||
{
|
||||
// Get the user code
|
||||
YaleUserCode userCode = GetUserCode(id);
|
||||
|
||||
// Set the code
|
||||
userCode.Status = UserCodeStatus.AVAILABLE;
|
||||
|
||||
// Update the user code in the list
|
||||
UserCodes[UserCodes.IndexOf(userCode)] = userCode;
|
||||
|
||||
// Return true to indicate success
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
35
packages/backend/Services/SMSService.cs
Normal file
35
packages/backend/Services/SMSService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Twilio;
|
||||
using Twilio.Rest.Api.V2010.Account;
|
||||
using YaleAccess.Models.Options;
|
||||
|
||||
namespace YaleAccess.Services
|
||||
{
|
||||
public class SMSService
|
||||
{
|
||||
private readonly TwiloOptions _twiloOptions;
|
||||
|
||||
public SMSService(IOptions<TwiloOptions> twiloOptions)
|
||||
{
|
||||
_twiloOptions = twiloOptions.Value;
|
||||
}
|
||||
|
||||
public async Task SendCodeViaSMSAsync(string code, string phoneNumber)
|
||||
{
|
||||
// Create a Twilio client
|
||||
TwilioClient.Init(_twiloOptions.AccountSid, _twiloOptions.AuthToken);
|
||||
|
||||
// Send the message
|
||||
var message = await MessageResource.CreateAsync(
|
||||
body: $"{_twiloOptions.Message} {code}",
|
||||
from: new Twilio.Types.PhoneNumber(_twiloOptions.FromNumber),
|
||||
to: new Twilio.Types.PhoneNumber(phoneNumber)
|
||||
);
|
||||
|
||||
// Log the message
|
||||
Log.Logger.Information("SMS sent to {PhoneNumber} with message SID {MessageSid}.", phoneNumber, message.Sid);
|
||||
Log.Logger.Debug("SMS message: {MessageBody}", message.Body);
|
||||
}
|
||||
}
|
||||
}
|
195
packages/backend/Services/YaleAccessor.cs
Normal file
195
packages/backend/Services/YaleAccessor.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using YaleAccess.Models;
|
||||
using YaleAccess.Models.Options;
|
||||
using YaleAccess.Services.Interfaces;
|
||||
using ZWaveJS.NET;
|
||||
using ZWaveOptions = YaleAccess.Models.Options.ZWaveOptions;
|
||||
|
||||
namespace YaleAccess.Services
|
||||
{
|
||||
public class YaleAccessor : IYaleAccessor, IDisposable
|
||||
{
|
||||
#region Dispose Logic
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
driver?.Destroy();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
driver = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion Dispose Logic
|
||||
|
||||
private Driver? driver = null;
|
||||
private readonly ZWaveNode lockNode = null!;
|
||||
|
||||
public YaleAccessor(IOptions<ZWaveOptions> zwave, IOptions<DevicesOptions> device)
|
||||
{
|
||||
// Retrive options from configuration
|
||||
ZWaveOptions zwaveOptions = zwave.Value;
|
||||
DevicesOptions devicesOptions = device.Value;
|
||||
|
||||
// Create a new driver instance
|
||||
driver = new Driver(new Uri(zwaveOptions.Url), zwaveOptions.SchemaVersion);
|
||||
|
||||
// Start the driver
|
||||
driver.Start();
|
||||
|
||||
// Flag to indicate if the driver is ready to use
|
||||
bool isReady = false;
|
||||
|
||||
// Subscribe to the driver ready event
|
||||
driver.DriverReady += () =>
|
||||
{
|
||||
isReady = true;
|
||||
};
|
||||
|
||||
driver.StartUpError += (message) =>
|
||||
{
|
||||
throw new Exception(message);
|
||||
};
|
||||
|
||||
// Wait for the driver to be ready
|
||||
while (!isReady)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// Get the lock node from the driver
|
||||
lockNode = driver.Controller.Nodes.Get(devicesOptions.YaleLockNodeId);
|
||||
}
|
||||
|
||||
public async Task<YaleUserCode> GetCodeInformationAsync(int userCodeId)
|
||||
{
|
||||
// Setup the two tasks to get the values we need
|
||||
CMDResult status = await lockNode.GetValue(GetUserStatusValue(userCodeId));
|
||||
CMDResult code = await lockNode.GetValue(GetUserCodeValue(userCodeId));
|
||||
|
||||
// Covert the result to a YaleUserCode object
|
||||
return new YaleUserCode()
|
||||
{
|
||||
Id = userCodeId,
|
||||
Code = GetUserCodeValue(code),
|
||||
Status = GetUserStatusValue(status),
|
||||
IsHome = false
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> SetUserCode(int userCodeId, string code)
|
||||
{
|
||||
// Setup the set value task
|
||||
CMDResult result = await lockNode.SetValue(GetUserCodeValue(userCodeId), code);
|
||||
|
||||
// If the result is not successful log the message
|
||||
if (!result.Success)
|
||||
{
|
||||
Log.Logger.Error("Failed to set user code {@userCodeId} to {@code}. Error message: {message}", userCodeId, code, result.Message);
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return result.Success;
|
||||
}
|
||||
|
||||
public async Task<bool> SetCodeAsAvailable(int userCode)
|
||||
{
|
||||
// Setup the set value task
|
||||
CMDResult result = await lockNode.SetValue(GetUserStatusValue(userCode), (int)UserCodeStatus.AVAILABLE);
|
||||
|
||||
// If the result is not successful log the message
|
||||
if (!result.Success)
|
||||
{
|
||||
Log.Logger.Error("Failed to set user code {@userCode} to available status. Error message: {message}", userCode, result.Message);
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return result.Success;
|
||||
}
|
||||
|
||||
private static UserCodeStatus GetUserStatusValue(CMDResult result)
|
||||
{
|
||||
// Parse the payload as a JSON object
|
||||
JObject payloadObject = (JObject)result.ResultPayload;
|
||||
|
||||
// Return the value property
|
||||
return (UserCodeStatus)payloadObject.GetValue("value")!.ToObject<int>();
|
||||
}
|
||||
|
||||
private static string GetUserCodeValue(CMDResult result)
|
||||
{
|
||||
// Parse the payload as a JSON object
|
||||
JObject payloadObject = (JObject)result.ResultPayload;
|
||||
|
||||
// Return the value property
|
||||
return payloadObject.GetValue("value")!.ToString();
|
||||
}
|
||||
|
||||
private static ValueID GetUserStatusValue(int userCodeId)
|
||||
{
|
||||
return new ValueID()
|
||||
{
|
||||
commandClass = 99,
|
||||
endpoint = 0,
|
||||
property = "userIdStatus",
|
||||
propertyKey = userCodeId
|
||||
};
|
||||
}
|
||||
|
||||
private static ValueID GetUserCodeValue(int userCodeId)
|
||||
{
|
||||
return new ValueID()
|
||||
{
|
||||
commandClass = 99,
|
||||
endpoint = 0,
|
||||
property = "userCode",
|
||||
propertyKey = userCodeId
|
||||
};
|
||||
}
|
||||
|
||||
public static string ValidateCode(string newCode)
|
||||
{
|
||||
// The code must be between 4 and 6 digits
|
||||
if (newCode.Length < 4 || newCode.Length > 6)
|
||||
{
|
||||
return "The code must be between 4 and 6 digits.";
|
||||
}
|
||||
|
||||
// The code must be numeric
|
||||
if (!int.TryParse(newCode, out int _))
|
||||
{
|
||||
return "The code must be numeric.";
|
||||
}
|
||||
|
||||
// Otherwise, the code is valid, return an empty string
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string ValidateClearCode(int codeId, int homeCodeId)
|
||||
{
|
||||
// If the code is the home code, return an error message
|
||||
if (codeId == homeCodeId)
|
||||
{
|
||||
return "The home code cannot be cleared.";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user