mirror of
https://github.com/farcasclaudiu/PracticeCalendar.git
synced 2026-06-22 07:01:16 +03:00
Add project files.
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PracticeCalendar.API.Model;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Entities.Specifications;
|
||||||
|
|
||||||
|
namespace PrcaticeCalendar.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class EventsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
private readonly ILogger<EventsController> _logger;
|
||||||
|
|
||||||
|
public EventsController(IRepository<PracticeEvent> eventsRepo,
|
||||||
|
IMapper mapper,
|
||||||
|
ILogger<EventsController> logger)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
this.mapper = mapper;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet(Name = "GetAll")]
|
||||||
|
public async Task<ActionResult<List<EventModel>>> Get()
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventsWithAttendees();
|
||||||
|
var repoList = await eventsRepo.ListAsync(spec);
|
||||||
|
var evList = repoList.Select(x=> {
|
||||||
|
var model = mapper.Map<EventModel>(x);
|
||||||
|
model.Attendees = x.Attendees.Select(m=>mapper.Map<AttendeeModel>(m)).ToArray();
|
||||||
|
return model;
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
return evList;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost(Name = "Create practice event")]
|
||||||
|
public async Task<IActionResult> CreateEvent(EventModel eventModel)
|
||||||
|
{
|
||||||
|
var practiceEvent = new PracticeEvent(eventModel.Title, eventModel.Description);
|
||||||
|
foreach (var att in eventModel.Attendees)
|
||||||
|
{
|
||||||
|
practiceEvent.AddAttendee(new Attendee(att.Name, att.EmailAddress));
|
||||||
|
}
|
||||||
|
var result = await eventsRepo.AddAsync(practiceEvent);
|
||||||
|
await eventsRepo.SaveChangesAsync();
|
||||||
|
return Ok(mapper.Map<EventModel>(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut(Name = "Update practice event")]
|
||||||
|
public async Task<IActionResult> UpdateEvent(EventModel eventModel)
|
||||||
|
{
|
||||||
|
var practiceEvent = await eventsRepo.GetByIdAsync(eventModel.Id);
|
||||||
|
if(practiceEvent == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
practiceEvent.UpdateTitleAndDescription(eventModel.Title, eventModel.Description);
|
||||||
|
await eventsRepo.UpdateAsync(practiceEvent);
|
||||||
|
await eventsRepo.SaveChangesAsync();
|
||||||
|
return Ok(mapper.Map<EventModel>(practiceEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete(Name = "Delete practice event")]
|
||||||
|
public async Task<IActionResult> DeleteEvent(int practiceEventId)
|
||||||
|
{
|
||||||
|
var org = await eventsRepo.GetByIdAsync(practiceEventId);
|
||||||
|
await eventsRepo.DeleteAsync(org);
|
||||||
|
await eventsRepo.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("accept/{eventId}/{attendeeId}")]
|
||||||
|
public async Task<IActionResult> AttendeeAcceptEvent(int eventId, int attendeeId)
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventByIdWithAttendees(eventId);
|
||||||
|
var practiceEvent = await eventsRepo.GetBySpecAsync(spec);
|
||||||
|
if (practiceEvent == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
practiceEvent.AttendeeAcceptEvent(attendeeId);
|
||||||
|
await eventsRepo.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("decline/{eventId}/{attendeeId}")]
|
||||||
|
public async Task<IActionResult> AttendeeDeclineEvent(int eventId, int attendeeId)
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventByIdWithAttendees(eventId);
|
||||||
|
var practiceEvent = await eventsRepo.GetBySpecAsync(spec);
|
||||||
|
if (practiceEvent == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
practiceEvent.AttendeeDeclineEvent(attendeeId);
|
||||||
|
await eventsRepo.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["PrcaticeCalendar/PrcaticeCalendar.csproj", "PrcaticeCalendar/"]
|
||||||
|
RUN dotnet restore "PrcaticeCalendar/PrcaticeCalendar.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/PrcaticeCalendar"
|
||||||
|
RUN dotnet build "PrcaticeCalendar.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "PrcaticeCalendar.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "PrcaticeCalendar.dll"]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.API.Model
|
||||||
|
{
|
||||||
|
public class EventModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public DateTime EndTime { get; set; }
|
||||||
|
|
||||||
|
public AttendeeModel[] Attendees { get; set; } = new AttendeeModel[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttendeeModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
public bool IsAttending { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EventModelProfile : Profile
|
||||||
|
{
|
||||||
|
public EventModelProfile()
|
||||||
|
{
|
||||||
|
CreateMap<PracticeEvent, EventModel>();
|
||||||
|
CreateMap<EventModel, PracticeEvent>();
|
||||||
|
CreateMap<Attendee, AttendeeModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>78351d12-8544-4ae0-877a-3522681401ca</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
|
||||||
|
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Hellang.Middleware.ProblemDetails;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using PracticeCalendar.Infrastructure;
|
||||||
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PracticeCalendar
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
builder.Services.AddProblemDetails();
|
||||||
|
|
||||||
|
builder.Services.AddAutoMapper(typeof(Program));
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
//inject infrastructure
|
||||||
|
builder.Services.AddInfrastructure(builder.Configuration);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseProblemDetails();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
|
||||||
|
// Seed Database
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var services = scope.ServiceProvider;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = services.GetRequiredService<ApplicationDbContext>();
|
||||||
|
//context.Database.Migrate();
|
||||||
|
context.Database.EnsureCreated();
|
||||||
|
//SeedData.Initialize(services);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var logger = services.GetRequiredService<ILogger<Program>>();
|
||||||
|
logger.LogError(ex, "An error occurred seeding the DB. {exceptionMessage}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"PracticeCalendar": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7267;http://localhost:5267"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:58588",
|
||||||
|
"sslPort": 44388
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"UseInMemoryDatabase": true,
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"SqliteConnection": "Data Source=practicecalendar.sqlite"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
public class DomainEventBase
|
||||||
|
{
|
||||||
|
public DateTime EventDate { get; protected set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
public abstract class DomainException : Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
public abstract class EntityBase
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
private List<DomainEventBase> _domainEvents = new();
|
||||||
|
[NotMapped]
|
||||||
|
public IEnumerable<DomainEventBase> DomainEvents => _domainEvents.AsReadOnly();
|
||||||
|
|
||||||
|
protected void RegisterDomainEvent(DomainEventBase domainEvent) => _domainEvents.Add(domainEvent);
|
||||||
|
internal void ClearDomainEvents() => _domainEvents.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
// source: https://github.com/jhewlett/ValueObject
|
||||||
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||||
|
public class IgnoreMemberAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Common.Interfaces
|
||||||
|
{
|
||||||
|
// Apply this marker interface only to aggregate root entities
|
||||||
|
// Repositories will only work with aggregate roots, not their children
|
||||||
|
public interface IAggregateRoot { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Common.Interfaces
|
||||||
|
{
|
||||||
|
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
// source: https://github.com/jhewlett/ValueObject
|
||||||
|
public abstract class ValueObject : IEquatable<ValueObject>
|
||||||
|
{
|
||||||
|
private List<PropertyInfo>? properties;
|
||||||
|
private List<FieldInfo>? fields;
|
||||||
|
|
||||||
|
public static bool operator ==(ValueObject? obj1, ValueObject? obj2)
|
||||||
|
{
|
||||||
|
if (object.Equals(obj1, null))
|
||||||
|
{
|
||||||
|
if (object.Equals(obj2, null))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return obj1.Equals(obj2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ValueObject? obj1, ValueObject? obj2)
|
||||||
|
{
|
||||||
|
return !(obj1 == obj2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ValueObject? obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null || GetType() != obj.GetType()) return false;
|
||||||
|
|
||||||
|
return GetProperties().All(p => PropertiesAreEqual(obj, p))
|
||||||
|
&& GetFields().All(f => FieldsAreEqual(obj, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PropertiesAreEqual(object obj, PropertyInfo p)
|
||||||
|
{
|
||||||
|
return object.Equals(p.GetValue(this, null), p.GetValue(obj, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FieldsAreEqual(object obj, FieldInfo f)
|
||||||
|
{
|
||||||
|
return object.Equals(f.GetValue(this), f.GetValue(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<PropertyInfo> GetProperties()
|
||||||
|
{
|
||||||
|
if (this.properties == null)
|
||||||
|
{
|
||||||
|
this.properties = GetType()
|
||||||
|
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) == null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Not available in Core
|
||||||
|
// !Attribute.IsDefined(p, typeof(IgnoreMemberAttribute))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<FieldInfo> GetFields()
|
||||||
|
{
|
||||||
|
if (this.fields == null)
|
||||||
|
{
|
||||||
|
this.fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) == null)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked //allow overflow
|
||||||
|
{
|
||||||
|
int hash = 17;
|
||||||
|
foreach (var prop in GetProperties())
|
||||||
|
{
|
||||||
|
var value = prop.GetValue(this, null);
|
||||||
|
hash = HashValue(hash, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in GetFields())
|
||||||
|
{
|
||||||
|
var value = field.GetValue(this);
|
||||||
|
hash = HashValue(hash, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int HashValue(int seed, object? value)
|
||||||
|
{
|
||||||
|
var currentHash = value != null
|
||||||
|
? value.GetHashCode()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return seed * 23 + currentHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Entities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attendee to an event
|
||||||
|
/// </summary>
|
||||||
|
public class Attendee: EntityBase
|
||||||
|
{
|
||||||
|
public Attendee(string name, string emailAddress)
|
||||||
|
{
|
||||||
|
Guard.Against.NullOrEmpty(name);
|
||||||
|
Guard.Against.NullOrEmpty(emailAddress);
|
||||||
|
Name = name;
|
||||||
|
EmailAddress = emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PracticeEventId { get; private set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string EmailAddress { get; set; } = string.Empty;
|
||||||
|
public bool IsAttending { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set if the Attendee is attending
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isAttending"></param>
|
||||||
|
public void SetIsAttending (bool isAttending)
|
||||||
|
{
|
||||||
|
this.IsAttending = isAttending;
|
||||||
|
//TODO - raise event
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assign Attendee to the practice event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="practiceEventId"></param>
|
||||||
|
public void AssignToEvent(int practiceEventId)
|
||||||
|
{
|
||||||
|
this.PracticeEventId = practiceEventId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Events;
|
||||||
|
using PracticeCalendar.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Entities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Practice event aggregate
|
||||||
|
/// </summary>
|
||||||
|
public class PracticeEvent : EntityBase, IAggregateRoot
|
||||||
|
{
|
||||||
|
public PracticeEvent(string title, string description)
|
||||||
|
{
|
||||||
|
Guard.Against.NullOrEmpty(title, nameof(title));
|
||||||
|
Guard.Against.NullOrEmpty(description, nameof(description));
|
||||||
|
this.Title = title;
|
||||||
|
this.Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; private set; } = string.Empty;
|
||||||
|
public string Description{ get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
private List<Attendee> attendees = new List<Attendee>();
|
||||||
|
public IEnumerable<Attendee> Attendees => attendees.AsReadOnly();
|
||||||
|
|
||||||
|
public DateTime StartTime { get; private set; }
|
||||||
|
public DateTime EndTime { get; private set; }
|
||||||
|
|
||||||
|
public void AddAttendee(Attendee attendee)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(attendee, nameof(attendee));
|
||||||
|
attendee.AssignToEvent(this.Id);
|
||||||
|
attendees.Add(attendee);
|
||||||
|
|
||||||
|
var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee);
|
||||||
|
base.RegisterDomainEvent(attendeeAddedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTitleAndDescription(string title, string description)
|
||||||
|
{
|
||||||
|
this.Title = title;
|
||||||
|
this.Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttendeeAcceptEvent(int attendeeId)
|
||||||
|
{
|
||||||
|
var attendee = this.Attendees.FirstOrDefault(x => x.Id == attendeeId);
|
||||||
|
if (attendee == null)
|
||||||
|
throw new InvalidAttendeeException(attendeeId);
|
||||||
|
attendee.SetIsAttending(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttendeeDeclineEvent(int attendeeId)
|
||||||
|
{
|
||||||
|
var attendee = this.Attendees.FirstOrDefault(x => x.Id == attendeeId);
|
||||||
|
if (attendee == null)
|
||||||
|
throw new InvalidAttendeeException(attendeeId);
|
||||||
|
attendee.SetIsAttending(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Entities.Specifications
|
||||||
|
{
|
||||||
|
public class PracticeEventByIdWithAttendees : Specification<PracticeEvent>
|
||||||
|
{
|
||||||
|
public PracticeEventByIdWithAttendees(int eventId)
|
||||||
|
{
|
||||||
|
Query.Where(x=>x.Id == eventId)
|
||||||
|
.Include(x => x.Attendees);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Entities.Specifications
|
||||||
|
{
|
||||||
|
public class PracticeEventsWithAttendees : Specification<PracticeEvent>
|
||||||
|
{
|
||||||
|
public PracticeEventsWithAttendees()
|
||||||
|
{
|
||||||
|
Query.Include(x => x.Attendees);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Events
|
||||||
|
{
|
||||||
|
public sealed class AttendeeAddedEvent : DomainEventBase
|
||||||
|
{
|
||||||
|
public AttendeeAddedEvent(PracticeEvent eventAggregate, Attendee addedAtendee)
|
||||||
|
{
|
||||||
|
EventAggregate = eventAggregate;
|
||||||
|
AddedAtendee = addedAtendee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEvent EventAggregate { get; }
|
||||||
|
public Attendee AddedAtendee { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Exceptions
|
||||||
|
{
|
||||||
|
public class InvalidAttendeeException : DomainException
|
||||||
|
{
|
||||||
|
private int attendeeId;
|
||||||
|
|
||||||
|
public InvalidAttendeeException(int attendeeId)
|
||||||
|
{
|
||||||
|
this.attendeeId = attendeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Message => $"Invalid Attendee with id: {attendeeId}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface IEmailSender
|
||||||
|
{
|
||||||
|
Task SendEmailAsync(string to, string from, string subject, string body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="ValueObjects\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
|
||||||
|
<PackageReference Include="Ardalis.Specification" Version="6.1.0" />
|
||||||
|
<PackageReference Include="MediatR" Version="11.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Interfaces;
|
||||||
|
using PracticeCalendar.Infrastructure.Notification;
|
||||||
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure
|
||||||
|
{
|
||||||
|
public static class InfrastructureDI
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfigurationRoot configuration)
|
||||||
|
{
|
||||||
|
string connectionString = configuration.GetConnectionString("SqliteConnection");
|
||||||
|
|
||||||
|
services.AddDbContext(connectionString);
|
||||||
|
services.AddTransient<IEmailSender, FileEmailSender>();
|
||||||
|
|
||||||
|
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddDbContext(this IServiceCollection services, string connectionString) =>
|
||||||
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
options.UseSqlite(connectionString));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PracticeCalendar.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Notification
|
||||||
|
{
|
||||||
|
public class FileEmailSender : IEmailSender
|
||||||
|
{
|
||||||
|
private readonly ILogger<FileEmailSender> logger;
|
||||||
|
|
||||||
|
public FileEmailSender(ILogger<FileEmailSender> logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
public Task SendEmailAsync(string to, string from, string subject, string body)
|
||||||
|
{
|
||||||
|
logger.LogDebug($"Sending email from {from}, to {to}, subject {subject}, body {body}");
|
||||||
|
//TODO save email content to a file
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Persistence
|
||||||
|
{
|
||||||
|
public class ApplicationDbContext : DbContext
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Attendee> Atendees => Set<Attendee>();
|
||||||
|
public DbSet<PracticeEvent> PracticeEvents => Set<PracticeEvent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Persistence
|
||||||
|
{
|
||||||
|
public class ApplicationDbContextInitialiser
|
||||||
|
{
|
||||||
|
private readonly ILogger<ApplicationDbContextInitialiser> logger;
|
||||||
|
private readonly ApplicationDbContext context;
|
||||||
|
|
||||||
|
public ApplicationDbContextInitialiser(ILogger<ApplicationDbContextInitialiser> logger,
|
||||||
|
ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitialiseAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (context.Database.IsSqlite())
|
||||||
|
{
|
||||||
|
await context.Database.MigrateAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An error occurred while initialising the database.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SeedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await TrySeedAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An error occurred while seeding the database.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TrySeedAsync()
|
||||||
|
{
|
||||||
|
// Default data
|
||||||
|
// Seed, if necessary
|
||||||
|
if (!context.PracticeEvents.Any())
|
||||||
|
{
|
||||||
|
context.PracticeEvents.Add(new PracticeEvent("Event 1", "Event 1 desc"));
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Persistence.Configuration
|
||||||
|
{
|
||||||
|
public class AttendeeConfiguration : IEntityTypeConfiguration<Attendee>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Attendee> builder)
|
||||||
|
{
|
||||||
|
builder.Property(x => x.Name)
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.IsRequired();
|
||||||
|
builder.Property(x => x.EmailAddress)
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Persistence.Configuration
|
||||||
|
{
|
||||||
|
public class PracticeEventConfiguration : IEntityTypeConfiguration<PracticeEvent>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PracticeEvent> builder)
|
||||||
|
{
|
||||||
|
builder.Property(x => x.Title)
|
||||||
|
.HasMaxLength(120)
|
||||||
|
.IsRequired();
|
||||||
|
builder.Property(x => x.Description)
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Ardalis.Specification.EntityFrameworkCore;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Persistence
|
||||||
|
{
|
||||||
|
public class EfRepository<T> : RepositoryBase<T>, IRepository<T> where T : class, IAggregateRoot
|
||||||
|
{
|
||||||
|
public EfRepository(ApplicationDbContext dbContext) : base(dbContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="6.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests
|
||||||
|
{
|
||||||
|
public class PracticeEventTest
|
||||||
|
{
|
||||||
|
string _eventTitle = "Event1";
|
||||||
|
string _eventDescription = "Description";
|
||||||
|
|
||||||
|
string _attendeeName = "Claudiu Farcas";
|
||||||
|
string _atendeeEmail = "claudiu.farcas@testingbee.com";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InitializeProperties()
|
||||||
|
{
|
||||||
|
var practiceEvent = new PracticeEvent(_eventTitle, _eventDescription);
|
||||||
|
practiceEvent.Title.Should().Be(_eventTitle);
|
||||||
|
practiceEvent.Description.Should().Be(_eventDescription);
|
||||||
|
practiceEvent.Attendees.Should().HaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InitializeWithNullShouldThrowException()
|
||||||
|
{
|
||||||
|
Action act = () => {
|
||||||
|
var practiceEvent = new PracticeEvent(null, _eventDescription);
|
||||||
|
};
|
||||||
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
|
||||||
|
act = () => {
|
||||||
|
var practiceEvent = new PracticeEvent(_eventTitle, null);
|
||||||
|
};
|
||||||
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
|
||||||
|
act = () => {
|
||||||
|
var practiceEvent = new PracticeEvent(string.Empty, _eventDescription);
|
||||||
|
};
|
||||||
|
act.Should().Throw<ArgumentException>();
|
||||||
|
|
||||||
|
act = () => {
|
||||||
|
var practiceEvent = new PracticeEvent(_eventTitle, string.Empty);
|
||||||
|
};
|
||||||
|
act.Should().Throw<ArgumentException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddAttendeeToEvent()
|
||||||
|
{
|
||||||
|
var practiceEvent = new PracticeEvent(_eventTitle, _eventDescription);
|
||||||
|
var testAttendee = new Attendee(_attendeeName, _atendeeEmail);
|
||||||
|
practiceEvent.AddAttendee(testAttendee);
|
||||||
|
practiceEvent.Attendees.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
global using Xunit;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.3.32922.545
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.API", "PracticeCalendar.Api\PracticeCalendar.API.csproj", "{4B183BF7-16D7-4DA8-B80A-E08DB2CBC827}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.Domain", "PracticeCalendar.Domain\PracticeCalendar.Domain.csproj", "{002B8118-8B5A-4CF3-A29D-12A06803221B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PracticeCalendar.Infrastructure", "PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj", "{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PracticeCalendar.UnitTests", "PracticeCalendar.UnitTests\PracticeCalendar.UnitTests.csproj", "{74849455-5E08-43FE-A718-0872DE7BC350}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5F2B7855-F03D-48C9-8733-FF1E077F18F5}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
README.md = README.md
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4B183BF7-16D7-4DA8-B80A-E08DB2CBC827}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4B183BF7-16D7-4DA8-B80A-E08DB2CBC827}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4B183BF7-16D7-4DA8-B80A-E08DB2CBC827}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4B183BF7-16D7-4DA8-B80A-E08DB2CBC827}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{002B8118-8B5A-4CF3-A29D-12A06803221B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{002B8118-8B5A-4CF3-A29D-12A06803221B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{002B8118-8B5A-4CF3-A29D-12A06803221B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{002B8118-8B5A-4CF3-A29D-12A06803221B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {D24E61FE-EF01-414B-B09C-1B5F9575BD3C}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Practice Calendar Api project
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
- API - (REST) webservice endpoint with OpenAPI capabilities (swagger)
|
||||||
|
- Domain - containd core elements of the domain (entities, domain events, interfaces)
|
||||||
|
- Infrastructure - plugged dependencies like persitence, email, files operations
|
||||||
|
- UnitTests - for aggregate and application domain testing
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the api service
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet run --project .\PracticeCalendar.Api\PracticeCalendar.API.csproj
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user