Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collections added + retrieval of latest conditions logs #12

Merged
merged 8 commits into from
May 10, 2024
77 changes: 77 additions & 0 deletions Core/Services/CollectionsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Infrastructure.Repositories;
using Shared.Dtos;
using Shared.Dtos.FromClient.Collections;
using Shared.Exceptions;
using Shared.Models;

namespace Core.Services;

public class CollectionsService(CollectionsRepository collectionsRepository, PlantService plantService)
{
public async Task<IEnumerable<GetCollectionDto>> GetCollectionsForUser(string loggedInUser)
{
var collectionsWithoutPlants = await collectionsRepository.GetCollectionsForUser(loggedInUser);
return collectionsWithoutPlants.Select(collection => new GetCollectionDto
{
CollectionId = collection.CollectionId,
Name = collection.Name,
});
}

public async Task<Collection> GetCollection(Guid collectionId, string loggedInUser)
{
return await VerifyCollectionExistsAndUserHasAccess(collectionId, loggedInUser);
}

public async Task<IEnumerable<Plant>> GetPlantsInCollection(Guid collectionId, string loggedInUser)
{
await VerifyCollectionExistsAndUserHasAccess(collectionId, loggedInUser);
return await plantService.GetPlantsForCollection(collectionId);
}

public async Task<Collection> CreateCollection(CreateCollectionDto createCollectionDto, string loggedInUser)
{
var collection = new Collection
{
CollectionId = Guid.NewGuid(),
UserEmail = loggedInUser,
Name = createCollectionDto.Name
};
return await collectionsRepository.CreateCollection(collection);
}

public async Task<Collection> UpdateCollection(UpdateCollectionDto updateCollectionDto, string loggedInUser)
{
var collection = await VerifyCollectionExistsAndUserHasAccess(updateCollectionDto.CollectionId, loggedInUser);
collection.Name = updateCollectionDto.Name;
return await collectionsRepository.UpdateCollection(collection);
}

public async Task DeleteCollection(Guid collectionId, string loggedInUser)
{
var collection = await VerifyCollectionExistsAndUserHasAccess(collectionId, loggedInUser);
await collectionsRepository.DeleteCollection(collection);
}

public async Task AddPlantToCollection(Guid collectionId, Guid plantId, string loggedInUser)
{
var collection = await VerifyCollectionExistsAndUserHasAccess(collectionId, loggedInUser);
var plant = await plantService.GetPlantById(plantId, loggedInUser);
await collectionsRepository.AddPlantToCollection(collection, plant);
}

public async Task RemovePlantFromCollection(Guid collectionId, Guid plantId, string loggedInUser)
{
var collection = await VerifyCollectionExistsAndUserHasAccess(collectionId, loggedInUser);
var plant = await plantService.GetPlantById(plantId, loggedInUser);
await collectionsRepository.RemovePlantFromCollection(collection, plant);
}

private async Task<Collection> VerifyCollectionExistsAndUserHasAccess(Guid collectionId, string loggedInUser)
{
var collection = await collectionsRepository.GetCollectionWithoutPlants(collectionId);
if (collection is null) throw new NotFoundException("Collection not found");
if (collection.UserEmail != loggedInUser) throw new NoAccessException("You don't have access to this collection");
return collection;
}
}
29 changes: 19 additions & 10 deletions Core/Services/ConditionsLogsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

namespace Core.Services;

public class ConditionsLogsService (ConditionsLogsRepository conditionsLogsRepository, PlantRepository plantRepository, MqttPublisherService mqttPublisherService)
public class ConditionsLogsService (ConditionsLogsRepository conditionsLogsRepository, PlantService plantService, RequirementService requirementService ,MqttPublisherService mqttPublisherService)
{
public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditionsLogDto)
{
var plantId = await plantRepository.GetPlantIdByDeviceIdAsync(createConditionsLogDto.DeviceId.ToString());
var plantId = await plantService.GetPlantIdByDeviceIdAsync(createConditionsLogDto.DeviceId.ToString());

if (plantId == Guid.Empty)
{
Expand All @@ -26,10 +26,11 @@ public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditio
Light = createConditionsLogDto.Light,
Temperature = createConditionsLogDto.Temperature,
Humidity = createConditionsLogDto.Humidity,
PlantId = plantId
PlantId = plantId,
Mood = -1
};

var newMood = CalculateMood(conditionsLog);
var newMood = await CalculateMood(conditionsLog);
conditionsLog.Mood = newMood;

await conditionsLogsRepository.CreateConditionsLogAsync(conditionsLog);
Expand Down Expand Up @@ -91,18 +92,18 @@ private RequirementLevel CalculateHumidityLevel (double value)
};
}

private int CalculateMood (ConditionsLog conditionsLog)
private async Task<int> CalculateMood (ConditionsLog conditionsLog)
{
// Compare ideal requirements for humidity, temperature, soil moisture and light level with actual conditions and calculate mood from 0-4
// get ideal requirements from plant
var requirementsForPlant = plantRepository.GetRequirementsForPlant(conditionsLog.PlantId);
var requirementsForPlant = await requirementService.GetRequirements(conditionsLog.PlantId);
// compare with actual conditions
var mood = 0;
// calculate mood
mood += CalculateScore((int)requirementsForPlant.Result.HumidityLevel, (int)CalculateHumidityLevel(conditionsLog.Humidity));
mood += CalculateScore((int)requirementsForPlant.Result.TemperatureLevel, (int)CalculateTemperatureLevel(conditionsLog.Temperature));
mood += CalculateScore((int)requirementsForPlant.Result.SoilMoistureLevel, (int)CalculateSoilMoistureLevel(conditionsLog.SoilMoisture));
mood += CalculateScore((int)requirementsForPlant.Result.LightLevel, (int)CalculateLightLevel(conditionsLog.Light));
mariaruth1 marked this conversation as resolved.
Show resolved Hide resolved
mood += CalculateScore((int)requirementsForPlant.HumidityLevel, (int)CalculateHumidityLevel(conditionsLog.Humidity));
mood += CalculateScore((int)requirementsForPlant.TemperatureLevel, (int)CalculateTemperatureLevel(conditionsLog.Temperature));
mood += CalculateScore((int)requirementsForPlant.SoilMoistureLevel, (int)CalculateSoilMoistureLevel(conditionsLog.SoilMoisture));
mood += CalculateScore((int)requirementsForPlant.LightLevel, (int)CalculateLightLevel(conditionsLog.Light));

if (mood == 0)
{
Expand All @@ -124,4 +125,12 @@ private int CalculateScore(int ideal, int actual)
return 0; // Two away
}
}

public async Task<ConditionsLog> GetLatestConditionsLogForPlant(Guid plantId, string loggedInUser)
{
var plant = await plantService.GetPlantById(plantId, loggedInUser);
var conditionsLog = await conditionsLogsRepository.GetLatestConditionsLogForPlant(plantId);
if (conditionsLog is null) throw new NotFoundException($"No conditions log found for plant with id {plantId}");
return conditionsLog;
}
}
20 changes: 15 additions & 5 deletions Core/Services/PlantService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ public class PlantService(
IOptions<AzureBlobStorageOptions> azureBlobStorageOptions)
{

public async Task<Plant> CreatePlant(CreatePlantDto createPlantDto, string loggedInUserEmail)
public async Task<Plant> CreatePlant(CreatePlantDto createPlantDto, string loggedInUser)
{
if (loggedInUserEmail != createPlantDto.UserEmail) throw new NoAccessException("You can't create a plant for another user");

if (string.IsNullOrEmpty(createPlantDto.Nickname))
{
createPlantDto.Nickname = GenerateRandomNickname();
Expand All @@ -27,14 +25,14 @@ public async Task<Plant> CreatePlant(CreatePlantDto createPlantDto, string logge
var ímageUrl = azureBlobStorageOptions.Value.DefaultPlantImageUrl;
if (createPlantDto.Base64Image is not null)
{
ímageUrl = await blobStorageService.SaveImageToBlobStorage(createPlantDto.Base64Image, createPlantDto.UserEmail);
ímageUrl = await blobStorageService.SaveImageToBlobStorage(createPlantDto.Base64Image, loggedInUser);
}

// Insert plant first to get the plantId
var plant = new Plant
{
PlantId = Guid.NewGuid(),
UserEmail = createPlantDto.UserEmail,
UserEmail =loggedInUser,
// CollectionId = Guid.Empty, // TODO: fix when collections are implemented
Nickname = createPlantDto.Nickname,
ImageUrl = ímageUrl,
Expand Down Expand Up @@ -64,6 +62,13 @@ public async Task<List<Plant>> GetPlantsForUser(string userEmail, int pageNumber
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl)); // Otherwise the client can't access the image
return plants;
}

public async Task<List<Plant>> GetPlantsForCollection(Guid collectionId)
{
var plants = await plantRepository.GetPlantsForCollection(collectionId);
plants.ForEach(plant => plant.ImageUrl = blobStorageService.GenerateSasUri(plant.ImageUrl)); // Otherwise the client can't access the image
return plants;
}

public async Task<Plant> UpdatePlant(UpdatePlantDto updatePlantDto, string requesterEmail)
{
Expand Down Expand Up @@ -105,6 +110,11 @@ public async Task DeletePlant(Guid id, string requesterEmail)
await plantRepository.DeletePlant(plant);
}

public async Task<Guid> GetPlantIdByDeviceIdAsync(string deviceId)
{
return await plantRepository.GetPlantIdByDeviceIdAsync(deviceId);
}

private string GenerateRandomNickname()
{
var firstName = new List<string>
Expand Down
106 changes: 103 additions & 3 deletions Infrastructure/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Shared.Dtos.FromClient.Identity;
using Shared.Models;
using Shared.Models.Identity;
using Shared.Models.Information;
Expand All @@ -22,7 +25,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasMany(e => e.Collections)
.WithOne()
.HasForeignKey(e => e.UserEmail);

modelBuilder.Entity<User>()
.HasMany(e => e.Plants)
.WithOne()
Expand All @@ -37,18 +40,115 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasMany(e => e.ConditionsLogs)
.WithOne()
.HasForeignKey(e => e.PlantId);

modelBuilder.Entity<Plant>()
.HasOne(e => e.Requirements)
.WithOne()
.HasForeignKey<Requirements>(e => e.PlantId);

modelBuilder.Entity<Requirements>()
.HasKey(e => e.RequirementsId);

modelBuilder.Entity<ConditionsLog>()
.HasKey(e => e.ConditionsId);

base.OnModelCreating(modelBuilder);
}

public async Task SeedDevelopmentDataAsync(IServiceScope scope, string defaultPlantImage)
{
var userRepository = scope.ServiceProvider.GetRequiredService<UserRepository>();
await userRepository.CreateUser(new RegisterUserDto
{
Email = "[email protected]",
Password = "password",
Username = "bob"
});

var collectionsRepository = scope.ServiceProvider.GetRequiredService<CollectionsRepository>();
var collection1 = await collectionsRepository.CreateCollection(
new Collection
{
CollectionId = Guid.NewGuid(),
Name = "Succulents",
UserEmail = "[email protected]",
}
);
var collection2 = await collectionsRepository.CreateCollection(
new Collection
{
CollectionId = Guid.NewGuid(),
Name = "Cacti",
UserEmail = "[email protected]",
}
);

var plantRepository = scope.ServiceProvider.GetRequiredService<PlantRepository>();
await plantRepository.CreatePlant(
new Plant
{
PlantId = Guid.NewGuid(),
Nickname = "Aloe Vera",
UserEmail = "[email protected]",
ImageUrl = defaultPlantImage,
CollectionId = collection1.CollectionId,
}
);

await plantRepository.CreatePlant(
new Plant
{
PlantId = Guid.NewGuid(),
Nickname = "Prickly Pear",
UserEmail = "[email protected]",
ImageUrl = defaultPlantImage,
CollectionId = collection2.CollectionId,
}
);

var plants = await plantRepository.GetPlantsForUser("[email protected]", 1, 5);
Console.WriteLine(plants.Count);

var requirementsRepository = scope.ServiceProvider.GetRequiredService<RequirementsRepository>();
var requirements1 = await requirementsRepository.CreateRequirements(
new Requirements
{
RequirementsId = Guid.NewGuid(),
PlantId = plants[0].PlantId,
LightLevel = RequirementLevel.Low,
SoilMoistureLevel = RequirementLevel.Medium,
HumidityLevel = RequirementLevel.High,
TemperatureLevel = RequirementLevel.Medium,
}
);
plants[0].Requirements = requirements1;

var requirements2 = await requirementsRepository.CreateRequirements(
new Requirements
{
RequirementsId = Guid.NewGuid(),
PlantId = plants[1].PlantId,
LightLevel = RequirementLevel.High,
SoilMoistureLevel = RequirementLevel.Low,
HumidityLevel = RequirementLevel.Low,
TemperatureLevel = RequirementLevel.Medium,
}
);
plants[1].Requirements = requirements2;

var conditionsLogRepository = scope.ServiceProvider.GetRequiredService<ConditionsLogsRepository>();
await conditionsLogRepository.CreateConditionsLogAsync(
new ConditionsLog
{
ConditionsId = Guid.NewGuid(),
PlantId = plants[0].PlantId,
TimeStamp = DateTime.UtcNow,
Mood = 2,
Light = 33.0,
SoilMoisture = 74.0,
Humidity = 50.0,
Temperature = 22.0,
}
);
}
}
56 changes: 55 additions & 1 deletion Infrastructure/Repositories/CollectionsRepository.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
using Microsoft.EntityFrameworkCore;
using Shared.Models;

namespace Infrastructure.Repositories;

public class CollectionsRepository
public class CollectionsRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
public async Task<IEnumerable<Collection>> GetCollectionsForUser(string userEmail)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
return await applicationDbContext.Collections
.Where(collection => collection.UserEmail == userEmail)
.ToListAsync();
}

public async Task<Collection?> GetCollectionWithoutPlants(Guid collectionId)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
return await applicationDbContext.Collections
.FirstOrDefaultAsync(collection => collection.CollectionId == collectionId);
}

public async Task<Collection> CreateCollection(Collection collection)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
var createdCollection = (await applicationDbContext.Collections.AddAsync(collection)).Entity;
await applicationDbContext.SaveChangesAsync();
return createdCollection;
}

public async Task<Collection> UpdateCollection(Collection collection)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
var updatedCollection = applicationDbContext.Collections.Update(collection).Entity;
await applicationDbContext.SaveChangesAsync();
return updatedCollection;
}

public async Task DeleteCollection(Collection collection)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
applicationDbContext.Collections.Remove(collection);
await applicationDbContext.SaveChangesAsync();
}

public async Task AddPlantToCollection(Collection collection, Plant plant)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
collection.Plants.Add(plant);
await applicationDbContext.SaveChangesAsync();
}

public async Task RemovePlantFromCollection(Collection collection, Plant plant)
{
await using var applicationDbContext = await dbContextFactory.CreateDbContextAsync();
collection.Plants.Remove(plant);
await applicationDbContext.SaveChangesAsync();
}
}
Loading
Loading