Compare commits

...

4 Commits

Author SHA1 Message Date
Jeas0001
bce48fe8d1 Merge branch 'master' of git.reim.ar:ReiMerc/temperature-alarm 2025-03-26 14:28:30 +01:00
Jeas0001
998cb90acc Changed the name of the publisher 2025-03-26 14:28:20 +01:00
Jeas0001
47797d18a0 Made AMQPPush for pushing the device limits to RabbitMQ and consume after 1 hour 2025-03-26 13:14:12 +01:00
Jeas0001
6f311fabd4 Comments 2025-03-26 12:14:42 +01:00
11 changed files with 200 additions and 11 deletions

View File

@ -0,0 +1,92 @@
using Api.DBAccess;
using Api.Models;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;
namespace Api.AMQP
{
public class AMQPPublisher
{
private readonly IConfiguration _configuration;
private readonly DbAccess _dbAccess;
public AMQPPublisher(IConfiguration configuration, DbAccess dbAccess)
{
_dbAccess = dbAccess;
_configuration = configuration;
}
public async Task Handle_Push_Device_Limits()
{
var factory = new ConnectionFactory();
var queue = "temperature-limits";
factory.UserName = _configuration["AMQP:username"];
factory.Password = _configuration["AMQP:password"];
factory.HostName = _configuration["AMQP:host"];
factory.Port = Convert.ToInt32(_configuration["AMQP:port"]);
// Connecting to our rabbitmq and after that it create's a channel where you can connect to a queue
var conn = await factory.CreateConnectionAsync();
Console.WriteLine("AMQPClient connected");
var channel = await conn.CreateChannelAsync();
// Here we connect to the queue through the channel that got created earlier
await channel.QueueDeclareAsync(queue: queue, durable: false, exclusive: false, autoDelete: false);
Console.WriteLine($"{queue} connected");
while (true)
{
// Publishes all devices limits
var devices = _dbAccess.ReadDevices();
foreach (var device in devices)
{
var deviceLimit = new DeviceLimit();
deviceLimit.ReferenceId = device.ReferenceId;
deviceLimit.TempHigh = device.TempHigh;
deviceLimit.TempLow = device.TempLow;
string message = JsonSerializer.Serialize(deviceLimit);
var body = Encoding.UTF8.GetBytes(message);
await channel.BasicPublishAsync(exchange: string.Empty, routingKey: queue, body: body);
}
// Short delay before disconnecting from rabbitMQ
await Task.Delay(10000);
// Disconnecting from rabbitMQ to save resources
await channel.CloseAsync();
Console.WriteLine($"{queue} disconnected");
await conn.CloseAsync();
Console.WriteLine("AMQPClient disconnected");
await channel.DisposeAsync();
await conn.DisposeAsync();
// 1 hour delay
await Task.Delay(3600000);
// Creating a new connection to rabbitMQ
conn = await factory.CreateConnectionAsync();
Console.WriteLine("AMQPClient connected");
channel = await conn.CreateChannelAsync();
// Here we connect to the queue through the channel that got created earlier
await channel.QueueDeclareAsync(queue: queue, durable: false, exclusive: false, autoDelete: false);
Console.WriteLine($"{queue} connected");
// Here all messages is consumed so the queue is empty
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.ReceivedAsync += (model, ea) =>
{
Console.WriteLine("Emptying queue");
return Task.CompletedTask;
};
// Consumes the data in the queue
await channel.BasicConsumeAsync(queue, true, consumer);
}
}
}
}

View File

@ -30,7 +30,7 @@ namespace Api.AMQPReciever
// Connecting to our rabbitmq and after that it create's a channel where you can connect to a queue
using var conn = await factory.CreateConnectionAsync();
Console.WriteLine("AMQPClien connected");
Console.WriteLine("AMQPClient connected");
using var channel = await conn.CreateChannelAsync();
// Here we connect to the queue through the channel that got created earlier
@ -45,7 +45,7 @@ namespace Api.AMQPReciever
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var messageReceive = JsonSerializer.Deserialize<MQTTMessageReceive>(message);
var messageReceive = JsonSerializer.Deserialize<MessageReceive>(message);
// Checks if the message has the data we need
if (messageReceive == null || messageReceive.device_id == null || messageReceive.timestamp == 0)

View File

@ -16,6 +16,12 @@ namespace Api.BusinessLogic
_configuration = configuration;
}
/// <summary>
/// Gets the user from dbaccess using the userId and checks if the user exists
/// Gets all devices that match the userId and checks if there are any devices connected to the user
/// </summary>
/// <param name="userId">UserId that matches a user that owns the devices</param>
/// <returns>returns the devices in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> GetDevices(int userId)
{
var profile = await _dbAccess.ReadUser(userId);
@ -29,6 +35,13 @@ namespace Api.BusinessLogic
return new OkObjectResult(devices);
}
/// <summary>
/// Checks if the user that the device is trying to be added to exists
/// Then it is send to dbaccess
/// </summary>
/// <param name="device">The new device</param>
/// <param name="userId">The user that owns the device</param>
/// <returns>returns true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> AddDevice(Device device, int userId)
{
var profile = await _dbAccess.ReadUser(userId);
@ -38,6 +51,12 @@ namespace Api.BusinessLogic
return await _dbAccess.CreateDevice(device, userId);
}
/// <summary>
/// Checks if the device exist that is trying to be read from
/// Gets the logs and checks if there are any in the list
/// </summary>
/// <param name="deviceId">The deviceId that you want from the logs</param>
/// <returns>returns the logs in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> GetLogs(int deviceId)
{
var device = await _dbAccess.ReadDevice(deviceId);
@ -51,6 +70,12 @@ namespace Api.BusinessLogic
return new OkObjectResult(logs);
}
/// <summary>
/// Checks if the deviceId matches a device
/// </summary>
/// <param name="device">The updated info</param>
/// <param name="deviceId">The device to be edited</param>
/// <returns>returns the updated device in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> EditDevice(Device device, int deviceId)
{
var device1 = _dbAccess.ReadDevice(deviceId);

View File

@ -22,6 +22,14 @@ namespace Api.BusinessLogic
_configuration = configuration;
}
/// <summary>
/// First checks if the mail is a valid one with regex so if there is something before the @ and after and it has a domain
/// Then it checks if the password is to our security standard
/// Then it makes sure the user has a device list
/// The last thing before it saves the user is creating a salt and then hashing of the password
/// </summary>
/// <param name="user">The new user</param>
/// <returns>returns true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> RegisterUser(User user)
{
if (!new Regex(@".+@.+\..+").IsMatch(user.Email))
@ -48,6 +56,13 @@ namespace Api.BusinessLogic
return await _dbAccess.CreateUser(user);
}
/// <summary>
/// Gets the user that matches the login
/// Hashes the login password with the users salt
/// checks if the hashed password that the login has is the same as the one saved in the database
/// </summary>
/// <param name="login">Has a username or email and a password</param>
/// <returns>Returns a jwt token, username and userid</returns>
public async Task<IActionResult> Login(Login login)
{
User user = await _dbAccess.Login(login);
@ -65,6 +80,15 @@ namespace Api.BusinessLogic
return new ConflictObjectResult(new { message = "Invalid password" });
}
/// <summary>
/// First checks if the mail is a valid one with regex so if there is something before the @ and after and it has a domain
/// Then it checks if the password is to our security standard
/// Finds the user that matches the userId and hashes a new hash with the old salt
/// Then the updated user and the userId is being send to dbaccess
/// </summary>
/// <param name="user">Contains the updated user info</param>
/// <param name="userId">Has the id for the user that is to be updated</param>
/// <returns>returns the updated user in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> EditProfile(User user, int userId)
{
if (!new Regex(@".+@.+\..+").IsMatch(user.Email))
@ -85,11 +109,23 @@ namespace Api.BusinessLogic
return await _dbAccess.UpdateUser(user, userId);
}
/// <summary>
/// Just sends the userid of the user that is to be deleted
/// </summary>
/// <param name="userId">The Id of the user that is to be deleted</param>
/// <returns>returns the true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> DeleteUser(int userId)
{
return await _dbAccess.DeleteUser(userId);
}
/// <summary>
/// Generates a hash from a salt and input using the algorithm that is provided
/// </summary>
/// <param name="input">This is the input that is supposed to be hashed</param>
/// <param name="algorithm">This is the alogorithm that is used to encrypt the input</param>
/// <param name="salt">This is something extra added to make the hashed input more unpredictable</param>
/// <returns>The hashed input</returns>
private static string ComputeHash(string input, HashAlgorithm algorithm, string salt)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
@ -105,6 +141,11 @@ namespace Api.BusinessLogic
return BitConverter.ToString(hashedBytes);
}
/// <summary>
/// Checks if password is up to our security standard
/// </summary>
/// <param name="password">The password that is to be checked</param>
/// <returns>true or false dependeing on if the password is up to standard</returns>
public bool PasswordSecurity(string password)
{
var hasMinimum8Chars = new Regex(@".{8,}");
@ -112,6 +153,11 @@ namespace Api.BusinessLogic
return hasMinimum8Chars.IsMatch(password);
}
/// <summary>
/// Generates a JWT token that last 2 hours
/// </summary>
/// <param name="user">Used for sending the userid and username with the token</param>
/// <returns>Returns a valid JWTToken</returns>
private string GenerateJwtToken(User user)
{
var claims = new[]
@ -129,7 +175,7 @@ namespace Api.BusinessLogic
_configuration["JwtSettings:Issuer"],
_configuration["JwtSettings:Audience"],
claims,
expires: DateTime.Now.AddMinutes(30),
expires: DateTime.Now.AddHours(2),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);

View File

@ -19,15 +19,15 @@ namespace Api.Controllers
_deviceLogic = deviceLogic;
}
// Sends the userId to deviceLogic
[Authorize]
[HttpGet("{userId}")]
public async Task<IActionResult> GetDevices(int userId)
{
List<Device> devices = await _dbAccess.ReadDevices(userId);
if (devices.Count == 0) { return BadRequest(new { error = "There is no devices that belong to this userID" }); }
return await _deviceLogic.GetDevices(userId);
}
// Sends the device and userId to deviceLogic
[Authorize]
[HttpPost("adddevice/{userId}")]
public async Task<IActionResult> AddDevice([FromBody] Device device, int userId)
@ -35,6 +35,7 @@ namespace Api.Controllers
return await _deviceLogic.AddDevice(device, userId);
}
// Sends the deviceId to deviceLogic
[Authorize]
[HttpGet("logs/{deviceId}")]
public async Task<IActionResult> GetLogs(int deviceId)
@ -42,6 +43,7 @@ namespace Api.Controllers
return await _deviceLogic.GetLogs(deviceId);
}
// Sends the deviceId to deviceLogic
[Authorize]
[HttpPut("Edit/{deviceId}")]
public async Task<IActionResult> EditDevice([FromBody] Device device, int deviceId)

View File

@ -21,18 +21,21 @@ namespace Api.Controllers
_userLogic = userLogic;
}
// Sends the login to userLogic
[HttpPost("Login")]
public async Task<IActionResult> Login([FromBody] Login login)
{
return await _userLogic.Login(login);
}
// Sends the user to userLogic
[HttpPost("Create")]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
return await _userLogic.RegisterUser(user);
}
// Sends the user and userId to userLogic
[Authorize]
[HttpPut("Edit/{userId}")]
public async Task<IActionResult> EditUser([FromBody] User user, int userId)
@ -40,6 +43,7 @@ namespace Api.Controllers
return await _userLogic.EditProfile(user, userId);
}
// Sends the userId to userLogic
[Authorize]
[HttpDelete("Delete/{userId}")]
public async Task<IActionResult> DeleteUser(int userId)

View File

@ -22,7 +22,7 @@ namespace Api.DBAccess
/// Creates a user using entityframework core
/// </summary>
/// <param name="user">Need the entire user obj</param>
/// <returns>returns the true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
/// <returns>returns true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> CreateUser(User user)
{
var users = await _context.Users.ToListAsync();
@ -118,7 +118,7 @@ namespace Api.DBAccess
/// Deletes a user from the database
/// </summary>
/// <param name="userId">The Id of the user that is to be deleted</param>
/// <returns>returns the true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
/// <returns>returns true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason</returns>
public async Task<IActionResult> DeleteUser(int userId)
{
var user = await _context.Users.Include(u => u.Devices).FirstOrDefaultAsync(u => u.Id == userId);
@ -189,6 +189,12 @@ namespace Api.DBAccess
return _context.Devices.FirstOrDefault(d => d.ReferenceId == refenreId);
}
// Returns all devices
public List<Device> ReadDevices()
{
return _context.Devices.ToList();
}
/// <summary>
/// Updates a device in the database
/// </summary>

View File

@ -43,7 +43,7 @@ namespace Api.MQTTReciever
string sensorData = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
var messageReceive = JsonSerializer.Deserialize<MQTTMessageReceive>(sensorData);
var messageReceive = JsonSerializer.Deserialize<MessageReceive>(sensorData);
// Checks if the message has the data we need
if (messageReceive == null || messageReceive.device_id == null || messageReceive.timestamp == 0)

View File

@ -0,0 +1,11 @@
namespace Api.Models
{
public class DeviceLimit
{
public double TempHigh { get; set; }
public double TempLow { get; set; }
public string? ReferenceId { get; set; }
}
}

View File

@ -1,6 +1,6 @@
namespace Api.Models
{
public class MQTTMessageReceive
public class MessageReceive
{
public double temperature { get; set; }

View File

@ -1,4 +1,5 @@
using Api;
using Api.AMQP;
using Api.AMQPReciever;
using Api.DBAccess;
using Api.MQTTReciever;
@ -27,8 +28,10 @@ class Program
// Choose to either connect AMQP or MQTT
if (rabbitMQ == "AMQP")
{
AMQPReciever amqp = new AMQPReciever(configuration, dbAccess);
amqp.Handle_Received_Application_Message().Wait();
AMQPReciever amqpReciever = new AMQPReciever(configuration, dbAccess);
amqpReciever.Handle_Received_Application_Message().Wait();
AMQPPublisher aMQPPush = new AMQPPublisher(configuration, dbAccess);
aMQPPush.Handle_Push_Device_Limits().Wait();
}
else if (rabbitMQ == "MQTT")
{