Skip to content

Commit

Permalink
Merge pull request #8 from InternAcademy/5-setup-mongodb-database
Browse files Browse the repository at this point in the history
Added MongoDb configuration
  • Loading branch information
dpS1lence authored May 18, 2024
2 parents b52fd8f + 2b21cb6 commit 5eaf2bd
Show file tree
Hide file tree
Showing 28 changed files with 1,715 additions and 3 deletions.
4 changes: 3 additions & 1 deletion src/server/CookingApp/CookingApp.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -14,6 +14,8 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
<PackageReference Include="MongoDB.Bson" Version="2.25.0" />
<PackageReference Include="MongoDB.Driver" Version="2.25.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
Expand Down
8 changes: 7 additions & 1 deletion src/server/CookingApp/CookingApp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookingApp", "CookingApp.csproj", "{663DFF4C-237D-4242-831E-D545502FF255}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CookingApp", "CookingApp.csproj", "{663DFF4C-237D-4242-831E-D545502FF255}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CookingApp.UnitTests", "..\..\..\test\CookingApp.UnitTests\CookingApp.UnitTests.csproj", "{ACA9427F-02C7-4F39-B688-BD6792BF9FF2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,6 +17,10 @@ Global
{663DFF4C-237D-4242-831E-D545502FF255}.Debug|Any CPU.Build.0 = Debug|Any CPU
{663DFF4C-237D-4242-831E-D545502FF255}.Release|Any CPU.ActiveCfg = Release|Any CPU
{663DFF4C-237D-4242-831E-D545502FF255}.Release|Any CPU.Build.0 = Release|Any CPU
{ACA9427F-02C7-4F39-B688-BD6792BF9FF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACA9427F-02C7-4F39-B688-BD6792BF9FF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACA9427F-02C7-4F39-B688-BD6792BF9FF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACA9427F-02C7-4F39-B688-BD6792BF9FF2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Diagnostics.CodeAnalysis;

namespace CookingApp.Infrastructure.Attributes
{
/// <summary>
/// Sets the Collection Name for a Mongo Entity
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[ExcludeFromCodeCoverage]
public class CollectionNameAttribute : Attribute
{
public string Name { get; set; }

public CollectionNameAttribute(string name)
{
this.Name = name;
}
}
}
40 changes: 40 additions & 0 deletions src/server/CookingApp/Infrastructure/Common/MongoEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace CookingApp.Infrastructure.Common
{
using CookingApp.Infrastructure.Interfaces;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;

/// <summary>
/// Base Class for a Mongo Entity
/// </summary>
public abstract class MongoEntity : IMongoEntity
{
/// <inheritdoc/>
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("_id", Order = 1)]
public string Id { get; set; } = ObjectId.GenerateNewId().ToString();

/// <inheritdoc/>
[BsonElement("_version", Order = 3)]
public int RowVersion { get; set; } = 1;

/// <inheritdoc/>
[BsonElement("_createdDateTime", Order = 4)]
[BsonRepresentation(BsonType.String)]
public DateTime CreatedDateTime { get; set; }

/// <inheritdoc/>
[BsonElement("_updatedDateTime", Order = 5)]
[BsonRepresentation(BsonType.String)]
public DateTime UpdatedDateTime { get; set; }

/// <inheritdoc/>
[BsonElement("_deletedDateTime", Order = 6)]
public DateTime? DeletedDateTime { get; set; }

/// <inheritdoc/>
[BsonIgnore]
public bool IsDeleted => this.DeletedDateTime != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace CookingApp.Infrastructure.Configurations.Database
{
using MongoDB.Bson;

public class MongoConfiguration
{
public SoftDeleteConfiguration SoftDeleteConfiguration { get; private set; } = new();

public string? ConnectionString { get; private set; }
public string? Database { get; private set; }

public BsonType EnumConvention { get; private set; } = BsonType.Int32;
public bool IgnoreIfDefaultConvention { get; private set; } = true;

public bool IgnoreIfNullConvention { get; private set; } = true;

/// <summary>
/// Sets the Mongo Connection String
/// </summary>
/// <param name="value">Connection String</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration WithConnectionString(string? value)
{
this.ConnectionString = value;
return this;
}

/// <summary>
/// Sets the Database Name
/// </summary>
/// <param name="value">Database Name</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration WithDatabaseName(string? value)
{
this.Database = value;
return this;
}

/// <summary>
/// Configures Soft Deletes
/// </summary>
/// <param name="value">Soft Delete Configuration</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration WithSoftDeletes(Action<SoftDeleteConfiguration> value)
{
var softDeleteConfig = new SoftDeleteConfiguration();
value(softDeleteConfig);
this.SoftDeleteConfiguration = softDeleteConfig;
return this;
}

/// <summary>
/// Sets how enums should be represented in the database
/// </summary>
/// <param name="value">Enum representation</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration RepresentEnumValuesAs(BsonType value = BsonType.Int32)
{
this.EnumConvention = value;
return this;
}

/// <summary>
/// Sets whether to ignore default values during serialization.
/// </summary>
/// <param name="ignoreIfDefault"></param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration WithIgnoreIfDefaultConvention(bool ignoreIfDefault = true)
{
IgnoreIfDefaultConvention = ignoreIfDefault;
return this;
}

/// <summary>
/// Sets whether to ignore null values during serialization.
/// </summary>
/// <param name="ignoreIfNull"></param>
/// <returns><see cref="MongoConfiguration"/></returns>
public MongoConfiguration WithIgnoreIfNullConvention(bool ignoreIfNull = true)
{
IgnoreIfNullConvention = ignoreIfNull;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CookingApp.Infrastructure.Configurations.Database
{
public record MongoSettings
{
public string Database { get; init; } = null!;
public string Url { get; init; } = null!;

public bool SoftDeleteEnabled { get; init; } = true;
public int SoftDeletePollInMinutes { get; init; } = 60;
public int SoftDeleteRetentionInDays { get; init; } = 28;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace CookingApp.Infrastructure.Configurations.Database
{
/// <summary>
/// Soft Delete Configuration
/// </summary>
public class SoftDeleteConfiguration
{
public bool IsEnabled { get; private set; }
public TimeSpan DeleteAfter { get; private set; } = TimeSpan.FromDays(31);

/// <summary>
/// Enables Soft Deletions
/// </summary>
/// <param name="value">True to enable</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public SoftDeleteConfiguration Enabled(bool value = true)
{
this.IsEnabled = value;
return this;
}

/// <summary>
/// Sets the time period that Mongo will determine a record as permanently deleted
/// </summary>
/// <param name="value">Time Delay</param>
/// <returns><see cref="MongoConfiguration"/></returns>
public SoftDeleteConfiguration HardDeleteAfter(TimeSpan value)
{
this.DeleteAfter = value;
return this;
}
}
}
18 changes: 18 additions & 0 deletions src/server/CookingApp/Infrastructure/Enums/SortDirection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace CookingApp.Infrastructure.Enums
{
/// <summary>
/// Sort Direction for Ordering
/// </summary>
public enum SortDirection
{
/// <summary>
/// Ascending Order
/// </summary>
Ascending,

/// <summary>
/// Descending Order
/// </summary>
Descending
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using Serilog.Events;
using Swashbuckle.AspNetCore.SwaggerUI;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace CookingApp.Infrastructure.Extensions
{
[ExcludeFromCodeCoverage]
public static class HostBuilderExtensions
{
public static IHostBuilder UseLogging(this IHostBuilder builder, Action<LoggingConfiguration> configuration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace CookingApp.Infrastructure.Extensions
{
using CookingApp.Infrastructure.Attributes;
using CookingApp.Infrastructure.Interfaces;

public static class MongoEntityExtensions
{
/// <summary>
/// Gets the Collection Name for an Entity
/// </summary>
/// <typeparam name="T">Mongo Entity</typeparam>
/// <returns>Collection Name</returns>
public static string GetCollectionName<T>() where T : class, IMongoEntity
{
var collectionNameAttribute = (CollectionNameAttribute)typeof(T).GetCustomAttributes(typeof(CollectionNameAttribute), true).FirstOrDefault();

Check warning on line 15 in src/server/CookingApp/Infrastructure/Extensions/MongoEntityExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.

Check warning on line 15 in src/server/CookingApp/Infrastructure/Extensions/MongoEntityExtensions.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
return collectionNameAttribute != null ? collectionNameAttribute.Name.ToLower() : typeof(T).Name.ToLower();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
using CookingApp.Infrastructure.Configurations.Swagger;
using System.Diagnostics.CodeAnalysis;
using CookingApp.Infrastructure.Common;
using CookingApp.Infrastructure.Configurations.Database;
using CookingApp.Infrastructure.Configurations.Swagger;
using CookingApp.Infrastructure.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.OpenApi.Models;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

namespace CookingApp.Infrastructure.Extensions
{
[ExcludeFromCodeCoverage]
public static class ServiceCollectionExtensions
{
public static IMvcBuilder AddDefaultMvcOptions(this WebApplicationBuilder builder,
Expand Down Expand Up @@ -66,5 +74,45 @@ public static IHostApplicationBuilder AddSwagger(this WebApplicationBuilder buil
});
return builder;
}

public static IHostApplicationBuilder AddMongoDatabase(this WebApplicationBuilder builder,
Action<MongoConfiguration> configuration)
{
var mongoConfig = new MongoConfiguration();
configuration(mongoConfig);

IConvention ignoreIfDefaultOrNullConvention = mongoConfig.IgnoreIfDefaultConvention
? new IgnoreIfDefaultConvention(true)
: new IgnoreIfNullConvention(mongoConfig.IgnoreIfNullConvention);

var conventionPack = new ConventionPack
{
new CamelCaseElementNameConvention(),
new EnumRepresentationConvention(mongoConfig.EnumConvention),
ignoreIfDefaultOrNullConvention,
new IgnoreExtraElementsConvention(true)
};

ConventionRegistry.Register("conventionPack", conventionPack, t => true);

var settings = MongoClientSettings.FromUrl(new MongoUrl(mongoConfig.ConnectionString));

var client = new MongoClient(settings);
var database = client.GetDatabase(mongoConfig.Database);

builder.Services.AddSingleton(database);
builder.Services.AddSingleton(typeof(IMongoClient), p => client);
builder.Services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));

builder.Services.Configure<MongoConfiguration>(configuration);

BsonClassMap.RegisterClassMap<MongoEntity>(p =>
{
p.AutoMap();
p.SetIgnoreExtraElements(true);
});

return builder;
}
}
}
40 changes: 40 additions & 0 deletions src/server/CookingApp/Infrastructure/Interfaces/IMongoEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace CookingApp.Infrastructure.Interfaces
{
using MongoDB.Bson.Serialization.Attributes;

/// <summary>
/// Interface representing a MongoDB entity with essential metadata properties.
/// </summary>
public interface IMongoEntity
{
/// <summary>
/// Gets or sets the unique identifier for the entity.
/// </summary>
string Id { get; set; }

/// <summary>
/// Gets or sets the date and time when the entity was created.
/// </summary>
DateTime CreatedDateTime { get; set; }

/// <summary>
/// Gets or sets the date and time when the entity was last updated.
/// </summary>
DateTime UpdatedDateTime { get; set; }

/// <summary>
/// Gets or sets the row version for concurrency control.
/// </summary>
int RowVersion { get; set; }

/// <summary>
/// Gets a value indicating whether the entity is marked as deleted.
/// </summary>
bool IsDeleted { get; }

/// <summary>
/// Gets or sets the date and time when the entity was deleted, if applicable.
/// </summary>
DateTime? DeletedDateTime { get; set; }
}
}
Loading

0 comments on commit 5eaf2bd

Please sign in to comment.