diff --git a/Tarscord.sln b/Tarscord.sln index 53685ad..1952d18 100644 --- a/Tarscord.sln +++ b/Tarscord.sln @@ -3,10 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29102.190 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tarscord.Core", "src\Tarscord.Core.csproj", "{D44364F2-9C2A-4C32-A083-7E2026557FA0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tarscord.Core", "src\Tarscord.Core\Tarscord.Core.csproj", "{D44364F2-9C2A-4C32-A083-7E2026557FA0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tarscord.Core.Tests", "tests\Tarscord.Core.Tests.csproj", "{0465984A-3D61-45DC-8316-F818107F1105}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tarscord.DbMigrator", "src\Tarscord.DbMigrator\Tarscord.DbMigrator.csproj", "{7F849881-B454-405D-9604-C80CB379FE39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {0465984A-3D61-45DC-8316-F818107F1105}.Debug|Any CPU.Build.0 = Debug|Any CPU {0465984A-3D61-45DC-8316-F818107F1105}.Release|Any CPU.ActiveCfg = Release|Any CPU {0465984A-3D61-45DC-8316-F818107F1105}.Release|Any CPU.Build.0 = Release|Any CPU + {7F849881-B454-405D-9604-C80CB379FE39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F849881-B454-405D-9604-C80CB379FE39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F849881-B454-405D-9604-C80CB379FE39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F849881-B454-405D-9604-C80CB379FE39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/global.json b/global.json index 6b7f158..b5b37b6 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "6.0.0", - "rollForward": "major", + "version": "8.0.0", + "rollForward": "latestMajor", "allowPrerelease": false } } \ No newline at end of file diff --git a/src/Domain/EntityBase.cs b/src/Domain/EntityBase.cs deleted file mode 100644 index 1be77be..0000000 --- a/src/Domain/EntityBase.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Tarscord.Core.Domain; - -public abstract class EntityBase -{ - public ulong Id { get; set; } - - public DateTime Created { get; set; } - - public DateTime Updated { get; set; } -} \ No newline at end of file diff --git a/src/Domain/EventInfo.cs b/src/Domain/EventInfo.cs deleted file mode 100644 index 775e748..0000000 --- a/src/Domain/EventInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Tarscord.Core.Domain; - -public class EventInfo : EntityBase -{ - public string EventOrganizer { get; set; } - - public ulong EventOrganizerId { get; set; } - - public string EventName { get; set; } - - public DateTime? EventDate { get; set; } - - public string EventDescription { get; set; } - - public bool IsActive { get; set; } - - public override string ToString() - { - return $"Organizer:\t {EventOrganizer}\n" + - $"Name:\t {EventName}\n" + - $"Date and time:\t {EventDate:F}\n" + - $"Description:\t {EventDescription}\n" + - $"Is active:\t {IsActive}\n" + - $"Date created:\t {Created:s}\n" + - $"Date updated:\t {Updated:s}\n"; - } -} \ No newline at end of file diff --git a/src/Domain/ReminderInfo.cs b/src/Domain/ReminderInfo.cs deleted file mode 100644 index dd9b778..0000000 --- a/src/Domain/ReminderInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Discord; - -namespace Tarscord.Core.Domain; - -public class ReminderInfo -{ - public IUser User { get; set; } - - public string Message { get; set; } -} \ No newline at end of file diff --git a/src/Features/EventAttendees/Details.cs b/src/Features/EventAttendees/Details.cs deleted file mode 100644 index 4a0025d..0000000 --- a/src/Features/EventAttendees/Details.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using FluentValidation; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.EventAttendees; - -public class Details -{ - public class Query : IRequest - { - public ulong EventId { get; init; } - } - - public class QueryValidator : AbstractValidator - { - public QueryValidator() - { - RuleFor(x => x.EventId).NotNull().NotEmpty().GreaterThan((ulong)0); - } - } - - public class QueryHandler : IRequestHandler - { - private readonly IEventAttendeesRepository _eventAttendeesRepository; - - public QueryHandler(IEventAttendeesRepository eventAttendeesRepository) - { - _eventAttendeesRepository = eventAttendeesRepository; - } - - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var events = await _eventAttendeesRepository.FindBy(eventInfo => eventInfo.Id == message.EventId) - .ConfigureAwait(false); - - return new EventAttendeesEnvelope(null); - } - } -} \ No newline at end of file diff --git a/src/Features/EventAttendees/List.cs b/src/Features/EventAttendees/List.cs deleted file mode 100644 index f719cd0..0000000 --- a/src/Features/EventAttendees/List.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.EventAttendees; - -public class List -{ - public record Query : IRequest; - - public class QueryHandler : IRequestHandler - { - private readonly IEventAttendeesRepository _eventAttendeesRepository; - - public QueryHandler(IEventAttendeesRepository eventAttendeesRepository) - { - _eventAttendeesRepository = eventAttendeesRepository; - } - - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var events = - await _eventAttendeesRepository.GetAllAsync().ConfigureAwait(false); - - return new EventAttendeesEnvelope(events.ToList()); - } - } -} \ No newline at end of file diff --git a/src/Features/EventAttendees/Update.cs b/src/Features/EventAttendees/Update.cs deleted file mode 100644 index d001c0f..0000000 --- a/src/Features/EventAttendees/Update.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using MediatR; -using Tarscord.Core.Domain; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.EventAttendees; - -public class Update -{ - public class EventAttendees - { - public ulong EventId { get; set; } - - public IEnumerable Attendees { get; set; } - - public bool Confirmation { get; set; } - } - - public class Attendee - { - public string EventInfoId { get; set; } - - public ulong AttendeeId { get; set; } - - public string AttendeeName { get; set; } - - public bool Confirmed { get; set; } - } - - public class Command : IRequest - { - public EventAttendees EventAttendees { get; init; } - } - - public class CommandValidator : AbstractValidator - { - public CommandValidator() - { - RuleFor(x => x.EventAttendees).NotNull(); - RuleFor(x => x.EventAttendees.EventId).GreaterThan((ulong)0); - RuleForEach(x => x.EventAttendees.Attendees).NotNull().NotEmpty().SetValidator(new AttendeeValidator()); - } - - private class AttendeeValidator : AbstractValidator - { - public AttendeeValidator() - { - RuleFor(x => x.EventInfoId).NotNull().NotEmpty(); - RuleFor(x => x.AttendeeId).GreaterThan((ulong)0); - RuleFor(x => x.AttendeeName).NotNull().NotEmpty(); - } - } - } - - public class CommandHandler : IRequestHandler - { - private readonly IEventAttendeesRepository _eventAttendeesRepository; - private readonly IMapper _mapper; - - public CommandHandler(IEventAttendeesRepository eventAttendeesRepository, IMapper mapper) - { - _eventAttendeesRepository = eventAttendeesRepository; - _mapper = mapper; - } - - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var updatedAttendees = new List(); - - foreach (var attendee in message.EventAttendees.Attendees) - { - var eventAttendee = await _eventAttendeesRepository - .UpdateItem(_mapper.Map(attendee)) - .ConfigureAwait(false); - - updatedAttendees.Add(eventAttendee); - } - - return new EventAttendeesEnvelope(updatedAttendees); - } - } -} \ No newline at end of file diff --git a/src/Features/Events/Create.cs b/src/Features/Events/Create.cs deleted file mode 100644 index e73e386..0000000 --- a/src/Features/Events/Create.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Events; - -public class Create -{ - public class EventInfo - { - public string Id { get; set; } - - public DateTime Created { get; set; } - - public DateTime Updated { get; set; } - - public string EventOrganizer { get; set; } - - public ulong EventOrganizerId { get; set; } - - public string EventName { get; set; } - - public DateTime? EventDate { get; set; } - - public string EventDescription { get; set; } - - public bool IsActive { get; set; } - } - - public class Command : IRequest - { - public EventInfo Event { get; set; } - } - - public class CommandValidator : AbstractValidator - { - public CommandValidator() - { - RuleFor(x => x.Event).NotNull(); - } - } - - public class Handler : IRequestHandler - { - private readonly IEventRepository _eventRepository; - private readonly IMapper _mapper; - - public Handler(IEventRepository eventRepository, IMapper mapper) - { - _eventRepository = eventRepository; - _mapper = mapper; - } - - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var createdEvent = await _eventRepository.InsertAsync(_mapper.Map(message.Event)) - .ConfigureAwait(false); - - return new EventInfoEnvelope(createdEvent); - } - } -} \ No newline at end of file diff --git a/src/Features/Events/Details.cs b/src/Features/Events/Details.cs deleted file mode 100644 index cf051d5..0000000 --- a/src/Features/Events/Details.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using FluentValidation; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Events; - -public class Details -{ - public class Query : IRequest - { - public ulong EventId { get; init; } - } - - public class QueryValidator : AbstractValidator - { - public QueryValidator() - { - RuleFor(x => x.EventId).NotNull().NotEmpty().GreaterThan((ulong)0); - } - } - - public class QueryHandler : IRequestHandler - { - private readonly IEventRepository _eventRepository; - - public QueryHandler(IEventRepository eventRepository) - { - _eventRepository = eventRepository; - } - - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var events = await _eventRepository.FindBy(eventInfo => eventInfo.Id == message.EventId) - .ConfigureAwait(false); - - return new EventInfoEnvelope(events.FirstOrDefault()); - } - } -} \ No newline at end of file diff --git a/src/Features/Events/EnvelopeExtensions.cs b/src/Features/Events/EnvelopeExtensions.cs deleted file mode 100644 index abb526b..0000000 --- a/src/Features/Events/EnvelopeExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Text; -using Discord; -using Tarscord.Core.Extensions; - -namespace Tarscord.Core.Features.Events; - -public static class EnvelopeExtensions -{ - public static Embed ToEmbeddedMessage(this EventInfoListEnvelope events) - { - var eventsInformation = new StringBuilder(); - - foreach (var eventInfo in events.EventInfo) - { - eventsInformation - .Append(eventInfo.Id).Append(": '") - .Append(eventInfo.EventName) - .Append("' by user: ").Append(eventInfo.EventOrganizer).Append(".\n"); - } - - if (eventsInformation.Length == 0) - { - return "No events found".EmbedMessage(); - } - - return eventsInformation.ToString().EmbedMessage(); - } - - public static Embed ToEmbeddedMessage(this EventInfoEnvelope eventInfoEnvelope) - { - if (eventInfoEnvelope.EventInfo == null) - { - return "Event does not exist".EmbedMessage(); - } - - return - $"'{eventInfoEnvelope.EventInfo.EventName}' created by user {eventInfoEnvelope.EventInfo.EventOrganizer}. Use this Id: {eventInfoEnvelope.EventInfo.Id} to get the details of the event" - .EmbedMessage(); - } -} \ No newline at end of file diff --git a/src/Features/Events/EventInfo.cs b/src/Features/Events/EventInfo.cs deleted file mode 100644 index 310371f..0000000 --- a/src/Features/Events/EventInfo.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Discord; -using MediatR; - -namespace Tarscord.Core.Features.Events -{ - // public class EventInfo : IRequest - // { - // public string Id { get; set; } - // - // public DateTime Created { get; set; } - // - // public DateTime Updated { get; set; } - // - // public string EventOrganizer { get; set; } - // - // public ulong EventOrganizerId { get; set; } - // - // public string EventName { get; set; } - // - // public DateTime? EventDate { get; set; } - // - // public string EventDescription { get; set; } - // - // public bool IsActive { get; set; } - // - // public override string ToString() - // { - // return $"Organizer:\t {EventOrganizer}\n" + - // $"Name:\t {EventName}\n" + - // $"Date and time:\t {EventDate:F}\n" + - // $"Description:\t {EventDescription}\n" + - // $"Is active:\t {IsActive}\n" + - // $"Date created:\t {Created:s}\n" + - // $"Date updated:\t {Updated:s}\n"; - // } - // } -} \ No newline at end of file diff --git a/src/Features/Events/EventInfoEnvelope.cs b/src/Features/Events/EventInfoEnvelope.cs deleted file mode 100644 index bf75159..0000000 --- a/src/Features/Events/EventInfoEnvelope.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Features.Events; - -public record EventInfoEnvelope(EventInfo EventInfo); \ No newline at end of file diff --git a/src/Features/Events/EventInfoListEnvelope.cs b/src/Features/Events/EventInfoListEnvelope.cs deleted file mode 100644 index 66429b5..0000000 --- a/src/Features/Events/EventInfoListEnvelope.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Collections.Generic; -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Features.Events; - -public record EventInfoListEnvelope(List EventInfo); \ No newline at end of file diff --git a/src/Features/Events/List.cs b/src/Features/Events/List.cs deleted file mode 100644 index cb58385..0000000 --- a/src/Features/Events/List.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Events; - -public class List -{ - public record Query : IRequest; - - public class QueryHandler : IRequestHandler - { - private readonly IEventRepository _eventRepository; - - public QueryHandler(IEventRepository eventRepository) - { - _eventRepository = eventRepository; - } - - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var events = - await _eventRepository.GetAllAsync().ConfigureAwait(false); - - return new EventInfoListEnvelope(events.ToList()); - } - } -} \ No newline at end of file diff --git a/src/Features/Loans/Create.cs b/src/Features/Loans/Create.cs deleted file mode 100644 index da80597..0000000 --- a/src/Features/Loans/Create.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Loans; - -public class Create -{ - public class Loan - { - public decimal Amount { get; set; } - public ulong LoanedFrom { get; set; } - public string LoanedFromUsername { get; set; } - public ulong LoanedTo { get; set; } - public string LoanedToUsername { get; set; } - public string Description { get; set; } - } - - public class Command : IRequest - { - public Loan Loan { get; set; } - } - - public class CommandValidator : AbstractValidator - { - public CommandValidator() - { - RuleFor(x => x.Loan).NotNull(); - } - } - - public class Handler : IRequestHandler - { - private readonly ILoanRepository _loanRepository; - private readonly IMapper _mapper; - - public Handler(ILoanRepository loanRepository, IMapper mapper) - { - _loanRepository = loanRepository; - _mapper = mapper; - } - - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var createdLoan = await _loanRepository.InsertAsync(_mapper.Map(message.Loan)) - .ConfigureAwait(false); - - return new LoanEnvelope(createdLoan); - } - } -} \ No newline at end of file diff --git a/src/Features/Loans/List.cs b/src/Features/Loans/List.cs deleted file mode 100644 index 1e2eb9f..0000000 --- a/src/Features/Loans/List.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Loans; - -public class List -{ - public record Query : IRequest; - - public class QueryHandler : IRequestHandler - { - private readonly ILoanRepository _loanRepository; - - public QueryHandler(ILoanRepository loanRepository) - { - _loanRepository = loanRepository; - } - - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var loans = - await _loanRepository.GetAllAsync().ConfigureAwait(false); - - return new LoanListEnvelope(loans.ToList()); - } - } -} \ No newline at end of file diff --git a/src/Features/Loans/LoanEnvelope.cs b/src/Features/Loans/LoanEnvelope.cs deleted file mode 100644 index ee1cff9..0000000 --- a/src/Features/Loans/LoanEnvelope.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Features.Loans; - -public record LoanEnvelope(Loan Loan); \ No newline at end of file diff --git a/src/Features/Loans/LoanListEnvelope.cs b/src/Features/Loans/LoanListEnvelope.cs deleted file mode 100644 index cca324f..0000000 --- a/src/Features/Loans/LoanListEnvelope.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Collections.Generic; -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Features.Loans; - -public record LoanListEnvelope(IList Loans); \ No newline at end of file diff --git a/src/Features/Loans/Update.cs b/src/Features/Loans/Update.cs deleted file mode 100644 index 1a57e28..0000000 --- a/src/Features/Loans/Update.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using MediatR; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Features.Loans; - -public class Update -{ - public class Loan - { - public decimal Amount { get; set; } - public ulong LoanedFrom { get; set; } - public string LoanedFromUsername { get; set; } - public ulong LoanedTo { get; set; } - public string LoanedToUsername { get; set; } - } - - public class Command : IRequest - { - public Loan Loan { get; init; } - } - - public class CommandValidator : AbstractValidator - { - public CommandValidator() - { - RuleFor(x => x.Loan).NotNull(); - } - } - - public class CommandHandler : IRequestHandler - { - private readonly ILoanRepository _loanRepository; - private readonly IMapper _mapper; - - public CommandHandler(ILoanRepository loanRepository, IMapper mapper) - { - _loanRepository = loanRepository; - _mapper = mapper; - } - - public async Task Handle(Command request, CancellationToken cancellationToken) - { - var loans = await _loanRepository - .FindBy(x => x.LoanedFrom == request.Loan.LoanedFrom - && x.LoanedTo == request.Loan.LoanedTo); - - var loanToUpdate = loans?.FirstOrDefault(); - if (loanToUpdate == null) - { - return new LoanEnvelope(null); - } - - loanToUpdate.AmountPayed += request.Loan.Amount; - loanToUpdate.AmountLoaned -= request.Loan.Amount; - var updatedLoan = await _loanRepository.UpdateItem(_mapper.Map(loanToUpdate)); - - return new LoanEnvelope(updatedLoan); - } - } -} \ No newline at end of file diff --git a/src/Helpers/CustomRandomNumberGenerator.cs b/src/Helpers/CustomRandomNumberGenerator.cs deleted file mode 100644 index 8942524..0000000 --- a/src/Helpers/CustomRandomNumberGenerator.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Security.Cryptography; - -namespace Tarscord.Core.Helpers; - -public static class CustomRandomNumberGenerator -{ - private static readonly RNGCryptoServiceProvider Generator = new RNGCryptoServiceProvider(); - - public static int GenerateNumber(int minimumValue, int maximumValue) - { - byte[] randomNumber = new byte[1]; - - Generator.GetBytes(randomNumber); - - double asciiValueOfRandomCharacter = Convert.ToDouble(randomNumber[0]); - - // We are using Math.Max, and substracting 0.00000000001, - // to ensure "multiplier" will always be between 0.0 and .99999999999 - // Otherwise, it's possible for it to be "1", which causes problems in our rounding. - double multiplier = Math.Max(0, (asciiValueOfRandomCharacter / 255d) - 0.00000000001d); - - // We need to add one to the range, to allow for the rounding done with Math.Floor - int range = maximumValue - minimumValue + 1; - - double randomValueInRange = Math.Floor(multiplier * range); - - return (int)(minimumValue + randomValueInRange); - } -} \ No newline at end of file diff --git a/src/Helpers/GlobalMessages.cs b/src/Helpers/GlobalMessages.cs deleted file mode 100644 index feb164a..0000000 --- a/src/Helpers/GlobalMessages.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Tarscord.Core.Helpers; - -public static class GlobalMessages -{ - public static string EuroSign { get; set; } -} \ No newline at end of file diff --git a/src/Helpers/MappingProfile.cs b/src/Helpers/MappingProfile.cs deleted file mode 100644 index c849bbf..0000000 --- a/src/Helpers/MappingProfile.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AutoMapper; -using Tarscord.Core.Domain; -using Tarscord.Core.Features.Loans; -using LoanUpdate = Tarscord.Core.Features.Loans.Update; -using EventUpdate = Tarscord.Core.Features.EventAttendees.Update; - -namespace Tarscord.Core.Helpers; - -public class MappingProfile : Profile -{ - public MappingProfile() - { - #region Events - - CreateMap(); - - CreateMap() - .ForMember(x => x.Id, opt => opt.Ignore()) - .ForMember(x => x.Created, opt => opt.Ignore()) - .ForMember(x => x.Updated, opt => opt.Ignore()); - - #endregion - - #region Loans - - CreateMap() - .ForMember(x => x.AmountLoaned, opt => opt.MapFrom(src => src.Amount)) - .ForMember(x => x.Id, opt => opt.Ignore()) - .ForMember(x => x.AmountPayed, opt => opt.Ignore()) - .ForMember(x => x.Confirmed, opt => opt.Ignore()) - .ForMember(x => x.Created, opt => opt.Ignore()) - .ForMember(x => x.Updated, opt => opt.Ignore()); - - CreateMap() - .ForMember(x => x.Amount, opt => opt.MapFrom(src => src.AmountLoaned)); - - CreateMap() - .ForMember(x => x.Description, opt => opt.Ignore()) - .ForMember(x => x.AmountLoaned, opt => opt.Ignore()) - .ForMember(x => x.AmountPayed, opt => opt.Ignore()) - .ForMember(x => x.Confirmed, opt => opt.Ignore()) - .ForMember(x => x.Id, opt => opt.Ignore()) - .ForMember(x => x.Created, opt => opt.Ignore()) - .ForMember(x => x.Updated, opt => opt.Ignore()); - - #endregion - } -} \ No newline at end of file diff --git a/src/Modules/EventGroupModule.cs b/src/Modules/EventGroupModule.cs deleted file mode 100644 index 11da110..0000000 --- a/src/Modules/EventGroupModule.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using Discord; -using Discord.Commands; -using MediatR; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Tarscord.Core.Extensions; -using Tarscord.Core.Features.EventAttendees; -using Tarscord.Core.Features.Events; -using EventAttendanceDetails = Tarscord.Core.Features.EventAttendees.Details; -using EventAttendanceList = Tarscord.Core.Features.EventAttendees.List; - -namespace Tarscord.Core.Modules; - -[Name("Commands to create events")] -public class EventGroupModule -{ - [Group("event")] - public class EventModule : ModuleBase - { - private readonly IMediator _mediator; - - public EventModule(IMediator mediator) - { - _mediator = mediator; - } - - /// - /// Usage: event list - /// - [Command("list"), Summary("Lists all events")] - public async Task ListEventsAsync() - { - var eventInfoList = await _mediator.Send(new Features.Events.List.Query()); - - await ReplyAsync(embed: eventInfoList?.ToEmbeddedMessage()).ConfigureAwait(false); - } - - /// - /// Usage: event display {Event Id} - /// - [Command("show"), Summary("Show information about an event")] - [Alias("info", "get", "display", "details")] - public async Task ShowEventInformationAsync( - [Summary("The event Id")] ulong eventId) - { - var eventInformation = await _mediator.Send(new Features.Events.Details.Query() - { - EventId = eventId - }); - - await ReplyAsync(embed: eventInformation.ToEmbeddedMessage()).ConfigureAwait(false); - } - - /// - /// Usage: event create {Event Name}, {DateTime of Event}, {Description} - /// - [Command("create"), Summary("Create an event")] - [Alias("add", "make", "generate")] - public async Task CreateEventAsync( - [Summary("The event name"), Required(ErrorMessage = "Please provide a name for your event")] - string eventName, - [Summary("The event date and time")] string dateTime = "", - [Summary("The event description")] params string[] eventDescription) - { - string concatenatedDescription = string.Join(" ", eventDescription); - DateTime.TryParse(dateTime, out DateTime parsedDateTime); - - var eventInfo = new Create.Command() - { - Event = new Create.EventInfo() - { - EventOrganizerId = Context.User.ToCommonUser()?.Id ?? 0, - EventOrganizer = Context.User.ToCommonUser()?.Username, - EventName = eventName, - EventDescription = concatenatedDescription, - EventDate = parsedDateTime, - IsActive = true, - Created = DateTime.UtcNow, - Updated = DateTime.UtcNow, - } - }; - - var createdEvent = await _mediator.Send(eventInfo); - await ReplyAsync(embed: createdEvent.ToEmbeddedMessage()).ConfigureAwait(false); - } - - /// - /// Usage: event cancel {eventName} - /// - [Command("remove"), Summary("Cancel an event")] - [Alias("delete")] - public async Task CancelEventAsync([Summary("The event name")] string eventName) - { - // string messageToReplyWith = $"You have successfully canceled the event named '{eventName}'"; - // EventInfo result = await _eventService.CancelEvent(Context.User.ToCommonUser(), eventName); - // - // if (result != null) - // messageToReplyWith = $"The cancellation of the event named '{eventName}' failed."; - // - // await ReplyAsync(embed: messageToReplyWith.EmbedMessage()); - } - - /// - /// Usage: event confirm {Event Id} {User?} - /// - /// The confirmed attendees. - [Command("confirm"), Summary("Confirm your attendance")] - public async Task ConfirmAsync( - [Summary("The event name")] ulong eventId, - [Summary("The (optional) user to confirm for")] - params IUser[] users) - { - if (users.Length == 0) - users = new[] { Context.User }; - - var eventAttendees = await _mediator.Send(new Update.Command() - { - EventAttendees = new Update.EventAttendees() - { - Confirmation = true, - Attendees = users.Select(u => new Update.Attendee - { - AttendeeId = u.Id, - Confirmed = true, - AttendeeName = u.Username, - EventInfoId = eventId.ToString() - }).ToList(), - EventId = eventId - } - }); - - var confirmAttendanceAsList = eventAttendees.EventAttendee.ToList(); - if (confirmAttendanceAsList.Any()) - { - StringBuilder stringBuilder = new StringBuilder(); - - for (int i = 1; i <= confirmAttendanceAsList.Count; i++) - { - stringBuilder.Append($"{i}. {confirmAttendanceAsList[i - 1]}\n"); - } - - await ReplyAsync( - embed: "Thank you for confirming your attendance, these users confirmed their attendance:" - .EmbedMessage(stringBuilder.ToString())).ConfigureAwait(false); - } - else - await ReplyAsync(embed: "Attendance confirmation failed".EmbedMessage()).ConfigureAwait(false); - } - - /// - /// Usage: event confirm {eventName} {user?} - /// - /// The number squared. - [Command("cancel"), Summary("Confirm your attendance")] - [Alias("unattend")] - public async Task CancelAttendanceAsync( - [Summary("The event name")] ulong eventId, - [Summary("The (optional) user to confirm for")] - params IUser[] users) - { - // users ??= new[] {Context.User}; - // - // var eventAttendees = await _mediator.Send(new Update.Command() - // { - // EventAttendance = new Update.EventAttendance() - // { - // Confirmation = true, - // AttendeeIds = users.Select(u => u.Id).ToList(), - // AttendeeNames = users.Select(u => u.Username).ToList(), - // EventId = eventId - // } - // }); - // - // var attendeesAsList = eventAttendees.EventAttendee?.ToList(); - // if (attendeesAsList?.Any() ?? false) - // { - // await ReplyAsync( - // embed: $"You successfully canceled your attendance for the event with Id '{eventId}'" - // .EmbedMessage()).ConfigureAwait(false); - // } - // else - // await ReplyAsync(embed: "Attendance cancellation failed".EmbedMessage()).ConfigureAwait(false); - } - - /// - /// Usage: event confirmed {Event Id} - /// - /// The number squared. - [Command("confirmed"), Summary("Shows confirmed attendees.")] - public async Task ShowConfirmedAsync([Summary("The Event Id")] ulong eventId) - { - var attendees = await _mediator.Send(new EventAttendanceDetails.Query() - { - EventId = eventId - }); - - var attendeesAsList = attendees.EventAttendee?.ToList(); - if (attendeesAsList == null) - { - await ReplyAsync(embed: $"The event named '{eventId}' does not exist".EmbedMessage()) - .ConfigureAwait(false); - return; - } - - if (!attendeesAsList.Any()) - { - await ReplyAsync(embed: "There are no confirmed attendees".EmbedMessage()).ConfigureAwait(false); - return; - } - - var stringBuilder = new StringBuilder(); - for (int i = 1; i <= attendeesAsList.Count; i++) - { - stringBuilder.Append($"{i}. {attendeesAsList[i - 1]}\n"); - } - - await ReplyAsync( - embed: "Users who have confirmed their attendance are:" - .EmbedMessage(stringBuilder.ToString())).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/Persistence/DatabaseConnection.cs b/src/Persistence/DatabaseConnection.cs deleted file mode 100644 index a77a40f..0000000 --- a/src/Persistence/DatabaseConnection.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Data; -using System.Data.SQLite; -using Dapper; -using Microsoft.Extensions.Configuration; -using Tarscord.Core.Persistence.Helpers; - -namespace Tarscord.Core.Persistence; - -public class DatabaseConnection : IDatabaseConnection -{ - public IDbConnection Connection { get; } - - public DatabaseConnection(IConfigurationRoot configuration) - { - string dbPath = configuration["database-file"]; - - Connection = new SQLiteConnection($"Data Source={dbPath}"); - - // If the database file exists, don't create a new one - if (System.IO.File.Exists(dbPath)) return; - - OpenAndSetupDatabase(); - } - - private void OpenAndSetupDatabase() - { - Connection.Open(); - - string setupQuery = DatabaseSetupQueries.GetSetupQuery(); - Connection.Execute(setupQuery); - } -} \ No newline at end of file diff --git a/src/Persistence/Helpers/DatabaseSetupQueries.cs b/src/Persistence/Helpers/DatabaseSetupQueries.cs deleted file mode 100644 index 57bb322..0000000 --- a/src/Persistence/Helpers/DatabaseSetupQueries.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Tarscord.Core.Persistence.Helpers; - -public static class DatabaseSetupQueries -{ - private const string EventInfoQuery = "CREATE TABLE EventInfos (Id INTEGER PRIMARY KEY, " + - "EventOrganizer NVARCHAR(100), EventOrganizerId INTEGER, " + - "EventName NVARCHAR(100) NOT NULL, EventDate datetime, " + - "EventDescription NVARCHAR(100), IsActive bool, " + - "Created datetime, Updated datetime);"; - - private const string EventAttendeesQuery = - "CREATE TABLE EventAttendees (Id INTEGER PRIMARY KEY, AttendeeId INTEGER, " + - "EventInfoId INTEGER, AttendeeName NVARCHAR(100), Confirmed bool, " + - "Created datetime, Updated datetime);"; - - private const string LoanQuery = "CREATE TABLE Loans (Id INTEGER PRIMARY KEY, LoanedFrom INTEGER," + - "LoanedFromUsername NVARCHAR(100), LoanedTo INTEGER, " + - "LoanedToUsername NVARCHAR(100), Description NVARCHAR(1000), AmountLoaned REAL, " + - "AmountPayed REAL, Confirmed BOOL, Created DATETIME, Updated DATETIME);"; - - public static string GetSetupQuery() - { - return EventInfoQuery + EventAttendeesQuery + LoanQuery; - } -} \ No newline at end of file diff --git a/src/Persistence/IDatabaseConnection.cs b/src/Persistence/IDatabaseConnection.cs deleted file mode 100644 index fff1559..0000000 --- a/src/Persistence/IDatabaseConnection.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Data; - -namespace Tarscord.Core.Persistence; - -public interface IDatabaseConnection -{ - IDbConnection Connection { get; } -} \ No newline at end of file diff --git a/src/Persistence/Interfaces/IBaseRepository.cs b/src/Persistence/Interfaces/IBaseRepository.cs deleted file mode 100644 index 0de5677..0000000 --- a/src/Persistence/Interfaces/IBaseRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Tarscord.Core.Persistence.Interfaces; - -public interface IBaseRepository - where T : class -{ - Task> FindBy(Func predicate); - - Task> GetAllAsync(); - - Task InsertAsync(T item); - - Task UpdateItem(T item); - - Task DeleteItem(T item); -} \ No newline at end of file diff --git a/src/Persistence/Interfaces/IEventAttendeesRepository.cs b/src/Persistence/Interfaces/IEventAttendeesRepository.cs deleted file mode 100644 index ba35af9..0000000 --- a/src/Persistence/Interfaces/IEventAttendeesRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Persistence.Interfaces; - -public interface IEventAttendeesRepository : IBaseRepository -{ - Task> InsertAllAsync(IList items); -} \ No newline at end of file diff --git a/src/Persistence/Interfaces/IEventRepository.cs b/src/Persistence/Interfaces/IEventRepository.cs deleted file mode 100644 index d73b8dd..0000000 --- a/src/Persistence/Interfaces/IEventRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Persistence.Interfaces; - -public interface IEventRepository : IBaseRepository -{ -} \ No newline at end of file diff --git a/src/Persistence/Interfaces/ILoanRepository.cs b/src/Persistence/Interfaces/ILoanRepository.cs deleted file mode 100644 index 4b4577d..0000000 --- a/src/Persistence/Interfaces/ILoanRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Persistence.Interfaces; - -public interface ILoanRepository : IBaseRepository -{ -} \ No newline at end of file diff --git a/src/Persistence/Interfaces/IUserRepository.cs b/src/Persistence/Interfaces/IUserRepository.cs deleted file mode 100644 index 1cba35b..0000000 --- a/src/Persistence/Interfaces/IUserRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Tarscord.Core.Domain; - -namespace Tarscord.Core.Persistence.Interfaces; - -public interface IUserRepository : IBaseRepository -{ -} \ No newline at end of file diff --git a/src/Persistence/Repositories/BaseRepository.cs b/src/Persistence/Repositories/BaseRepository.cs deleted file mode 100644 index a990e93..0000000 --- a/src/Persistence/Repositories/BaseRepository.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Dapper.Contrib.Extensions; -using Tarscord.Core.Domain; -using Tarscord.Core.Persistence.Exceptions; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Persistence.Repositories; - -public class BaseRepository : IBaseRepository - where T : EntityBase -{ - protected readonly IDatabaseConnection _connection; - - public BaseRepository(IDatabaseConnection connection) - { - _connection = connection; - } - - public async Task> FindBy(Func predicate) - { - IEnumerable results = await _connection.Connection.GetAllAsync(); - - return results.Where(predicate); - } - - public async Task> GetAllAsync() - { - IEnumerable items = await _connection.Connection.GetAllAsync(); - - return items; - } - - public async Task InsertAsync(T item) - { - int noRowsAffected = await _connection.Connection.InsertAsync(item); - - if (noRowsAffected == 0) - { - throw new OperationFailedException(); - } - - return item; - } - - public async Task UpdateItem(T item) - { - bool modificationSucceeded = await _connection.Connection.UpdateAsync(item); - - if (!modificationSucceeded) - { - throw new OperationFailedException(); - } - - return item; - } - - public async Task DeleteItem(T item) - { - bool deletionSucceeded = await _connection.Connection.DeleteAsync(item); - - if (!deletionSucceeded) - { - throw new OperationFailedException(); - } - } -} \ No newline at end of file diff --git a/src/Persistence/Repositories/EventAttendeesRepository.cs b/src/Persistence/Repositories/EventAttendeesRepository.cs deleted file mode 100644 index f3db59e..0000000 --- a/src/Persistence/Repositories/EventAttendeesRepository.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Tarscord.Core.Domain; -using System.Threading.Tasks; -using Dapper.Contrib.Extensions; -using System.Collections.Generic; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Persistence.Repositories; - -public class EventAttendeesRepository : BaseRepository, IEventAttendeesRepository -{ - public EventAttendeesRepository(IDatabaseConnection connection) - : base(connection) - { - } - - public async Task> InsertAllAsync(IList items) - { - int noRowsAffected = await _connection.Connection.InsertAsync(items); - - if (noRowsAffected == 0) - { - throw new OperationCanceledException(); - } - - return items; - } -} \ No newline at end of file diff --git a/src/Persistence/Repositories/EventRepository.cs b/src/Persistence/Repositories/EventRepository.cs deleted file mode 100644 index f46b57c..0000000 --- a/src/Persistence/Repositories/EventRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Tarscord.Core.Domain; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Persistence.Repositories; - -public class EventRepository : BaseRepository, IEventRepository -{ - public EventRepository(IDatabaseConnection connection) - : base(connection) - { - } -} \ No newline at end of file diff --git a/src/Persistence/Repositories/LoanRepository.cs b/src/Persistence/Repositories/LoanRepository.cs deleted file mode 100644 index 514dceb..0000000 --- a/src/Persistence/Repositories/LoanRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Tarscord.Core.Domain; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Persistence.Repositories; - -public class LoanRepository : BaseRepository, ILoanRepository -{ - public LoanRepository(IDatabaseConnection connection) - : base(connection) - { - } -} \ No newline at end of file diff --git a/src/Persistence/Repositories/UserRepository.cs b/src/Persistence/Repositories/UserRepository.cs deleted file mode 100644 index 2198234..0000000 --- a/src/Persistence/Repositories/UserRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Tarscord.Core.Domain; -using Tarscord.Core.Persistence.Interfaces; - -namespace Tarscord.Core.Persistence.Repositories; - -public class UserRepository : BaseRepository, IUserRepository -{ - public UserRepository(IDatabaseConnection connection) - : base(connection) - { - } -} \ No newline at end of file diff --git a/src/Resources/config.yml b/src/Resources/config.yml new file mode 100644 index 0000000..46e354f --- /dev/null +++ b/src/Resources/config.yml @@ -0,0 +1,9 @@ +prefix: '?' +tokens: + discord: "" +sarcasm-level: 0 +humor-level: 0 +tarscord-context: + connection-string: "Host=localhost;Username=root;Password=password;Database=tarscord_db" +messages: + euro_sign: "€" \ No newline at end of file diff --git a/src/Domain/EventAttendee.cs b/src/Tarscord.Core/Domain/EventAttendee.cs similarity index 59% rename from src/Domain/EventAttendee.cs rename to src/Tarscord.Core/Domain/EventAttendee.cs index 4846579..a84cc80 100644 --- a/src/Domain/EventAttendee.cs +++ b/src/Tarscord.Core/Domain/EventAttendee.cs @@ -1,5 +1,9 @@ -namespace Tarscord.Core.Domain; +using System.ComponentModel.DataAnnotations.Schema; +using Tarscord.Core.Persistence; +namespace Tarscord.Core.Domain; + +[Table("event_attendees")] public class EventAttendee : EntityBase { public string EventInfoId { get; set; } diff --git a/src/Domain/Loan.cs b/src/Tarscord.Core/Domain/Loan.cs similarity index 51% rename from src/Domain/Loan.cs rename to src/Tarscord.Core/Domain/Loan.cs index 371ea4f..946a2a2 100644 --- a/src/Domain/Loan.cs +++ b/src/Tarscord.Core/Domain/Loan.cs @@ -1,16 +1,18 @@ -namespace Tarscord.Core.Domain; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Domain; public class Loan : EntityBase { public ulong LoanedFrom { get; set; } - public string LoanedFromUsername { get; set; } + public required string LoanedFromUsername { get; set; } public ulong LoanedTo { get; set; } - public string LoanedToUsername { get; set; } + public required string LoanedToUsername { get; set; } - public string Description { get; set; } + public required string Description { get; set; } public decimal AmountLoaned { get; set; } diff --git a/src/Tarscord.Core/Domain/ReminderInfo.cs b/src/Tarscord.Core/Domain/ReminderInfo.cs new file mode 100644 index 0000000..ad45217 --- /dev/null +++ b/src/Tarscord.Core/Domain/ReminderInfo.cs @@ -0,0 +1,10 @@ +using Discord; + +namespace Tarscord.Core.Domain; + +public class ReminderInfo +{ + public required IUser User { get; set; } + + public required string Message { get; set; } +} \ No newline at end of file diff --git a/src/Domain/User.cs b/src/Tarscord.Core/Domain/User.cs similarity index 90% rename from src/Domain/User.cs rename to src/Tarscord.Core/Domain/User.cs index 847685b..f22439e 100644 --- a/src/Domain/User.cs +++ b/src/Tarscord.Core/Domain/User.cs @@ -1,4 +1,5 @@ using System; +using Tarscord.Core.Persistence; namespace Tarscord.Core.Domain; diff --git a/src/Persistence/Exceptions/OperationFailedException.cs b/src/Tarscord.Core/Exceptions/OperationFailedException.cs similarity index 87% rename from src/Persistence/Exceptions/OperationFailedException.cs rename to src/Tarscord.Core/Exceptions/OperationFailedException.cs index a0eec28..7f16510 100644 --- a/src/Persistence/Exceptions/OperationFailedException.cs +++ b/src/Tarscord.Core/Exceptions/OperationFailedException.cs @@ -1,6 +1,6 @@ using System; -namespace Tarscord.Core.Persistence.Exceptions; +namespace Tarscord.Core.Exceptions; public class OperationFailedException : Exception { diff --git a/src/Tarscord.Core/Extensions/DateTimeExtensions.cs b/src/Tarscord.Core/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..23d540a --- /dev/null +++ b/src/Tarscord.Core/Extensions/DateTimeExtensions.cs @@ -0,0 +1,51 @@ +using System.Text.RegularExpressions; + +namespace Tarscord.Core.Extensions; + +public static class DateTimeExtensions +{ + public static DateTime? FromTextToDate(this string input) + { + input = input.ToLower().Trim(); + + // Direct keywords + if (input == "today") return DateTime.Today; + if (input == "tomorrow") return DateTime.Today.AddDays(1); + + // "in X minutes/hours/days/weeks/months/years" + Match match = Regex.Match(input, @"in (\d+) (minute|hour|day|week|month|year)s?"); + if (match.Success) + { + int amount = int.Parse(match.Groups[1].Value); + string unit = match.Groups[2].Value; + + return unit switch + { + "minute" => DateTime.Now.AddMinutes(amount), + "hour" => DateTime.Now.AddHours(amount), + "day" => DateTime.Now.AddDays(amount), + "week" => DateTime.Now.AddDays(amount * 7), + "month" => DateTime.Now.AddMonths(amount), + "year" => DateTime.Now.AddYears(amount), + _ => null + }; + } + + // "next Monday", "next Friday", etc. + match = Regex.Match(input, @"next (monday|tuesday|wednesday|thursday|friday|saturday|sunday)", RegexOptions.IgnoreCase); + if (match.Success) + { + DayOfWeek targetDay = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), match.Groups[1].Value, true); + return GetNextWeekday(targetDay); + } + + return null; // Unsupported input + } + + private static DateTime GetNextWeekday(DayOfWeek targetDay) + { + DateTime today = DateTime.Today; + int daysUntilNext = ((int)targetDay - (int)today.DayOfWeek + 7) % 7; + return today.AddDays(daysUntilNext == 0 ? 7 : daysUntilNext); + } +} \ No newline at end of file diff --git a/src/Extensions/Extensions.cs b/src/Tarscord.Core/Extensions/Extensions.cs similarity index 79% rename from src/Extensions/Extensions.cs rename to src/Tarscord.Core/Extensions/Extensions.cs index 39fd427..94ee245 100644 --- a/src/Extensions/Extensions.cs +++ b/src/Tarscord.Core/Extensions/Extensions.cs @@ -4,7 +4,7 @@ namespace Tarscord.Core.Extensions; public static class Extensions { - public static Embed EmbedMessage(this string name, object message = null) + public static Embed EmbedMessage(this string name, object? message = null) { return new EmbedBuilder { diff --git a/src/Extensions/UserExtensions.cs b/src/Tarscord.Core/Extensions/UserExtensions.cs similarity index 100% rename from src/Extensions/UserExtensions.cs rename to src/Tarscord.Core/Extensions/UserExtensions.cs diff --git a/src/Tarscord.Core/Features/EventAttendees/Details.cs b/src/Tarscord.Core/Features/EventAttendees/Details.cs new file mode 100644 index 0000000..de30cf8 --- /dev/null +++ b/src/Tarscord.Core/Features/EventAttendees/Details.cs @@ -0,0 +1,40 @@ +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; + +namespace Tarscord.Core.Features.EventAttendees; + +public class Details +{ + // public class Query : IRequest + // { + // public ulong EventId { get; init; } + // } + // + // public class QueryValidator : AbstractValidator + // { + // public QueryValidator() + // { + // RuleFor(x => x.EventId).NotNull().NotEmpty().GreaterThan((ulong)0); + // } + // } + // + // public class QueryHandler : IRequestHandler + // { + // private readonly IEventAttendeesRepository _eventAttendeesRepository; + // + // public QueryHandler(IEventAttendeesRepository eventAttendeesRepository) + // { + // _eventAttendeesRepository = eventAttendeesRepository; + // } + // + // public async Task Handle(Query message, CancellationToken cancellationToken) + // { + // var events = await _eventAttendeesRepository.FindBy(eventInfo => eventInfo.Id == message.EventId) + // .ConfigureAwait(false); + // + // return new EventAttendeesEnvelope(null); + // } + // } +} \ No newline at end of file diff --git a/src/Features/EventAttendees/EventAttendeesEnvelope.cs b/src/Tarscord.Core/Features/EventAttendees/EventAttendeesEnvelope.cs similarity index 100% rename from src/Features/EventAttendees/EventAttendeesEnvelope.cs rename to src/Tarscord.Core/Features/EventAttendees/EventAttendeesEnvelope.cs diff --git a/src/Tarscord.Core/Features/EventAttendees/List.cs b/src/Tarscord.Core/Features/EventAttendees/List.cs new file mode 100644 index 0000000..908c19f --- /dev/null +++ b/src/Tarscord.Core/Features/EventAttendees/List.cs @@ -0,0 +1,29 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; + +namespace Tarscord.Core.Features.EventAttendees; + +public class List +{ + // public record Query : IRequest; + // + // public class QueryHandler : IRequestHandler + // { + // private readonly IEventAttendeesRepository _eventAttendeesRepository; + // + // public QueryHandler(IEventAttendeesRepository eventAttendeesRepository) + // { + // _eventAttendeesRepository = eventAttendeesRepository; + // } + // + // public async Task Handle(Query message, CancellationToken cancellationToken) + // { + // var events = + // await _eventAttendeesRepository.GetAllAsync().ConfigureAwait(false); + // + // return new EventAttendeesEnvelope(events.ToList()); + // } + // } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/EventAttendees/Update.cs b/src/Tarscord.Core/Features/EventAttendees/Update.cs new file mode 100644 index 0000000..3ccf8da --- /dev/null +++ b/src/Tarscord.Core/Features/EventAttendees/Update.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Tarscord.Core.Domain; + +namespace Tarscord.Core.Features.EventAttendees; + +public class Update +{ + // public class EventAttendees + // { + // public ulong EventId { get; set; } + // + // public IEnumerable Attendees { get; set; } + // + // public bool Confirmation { get; set; } + // } + // + // public class Attendee + // { + // public string EventInfoId { get; set; } + // + // public ulong AttendeeId { get; set; } + // + // public string AttendeeName { get; set; } + // + // public bool Confirmed { get; set; } + // } + // + // public class Command : IRequest + // { + // public EventAttendees EventAttendees { get; init; } + // } + // + // public class CommandValidator : AbstractValidator + // { + // public CommandValidator() + // { + // RuleFor(x => x.EventAttendees).NotNull(); + // RuleFor(x => x.EventAttendees.EventId).GreaterThan((ulong)0); + // RuleForEach(x => x.EventAttendees.Attendees).NotNull().NotEmpty().SetValidator(new AttendeeValidator()); + // } + // + // private class AttendeeValidator : AbstractValidator + // { + // public AttendeeValidator() + // { + // RuleFor(x => x.EventInfoId).NotNull().NotEmpty(); + // RuleFor(x => x.AttendeeId).GreaterThan((ulong)0); + // RuleFor(x => x.AttendeeName).NotNull().NotEmpty(); + // } + // } + // } + // + // public class CommandHandler : IRequestHandler + // { + // private readonly IEventAttendeesRepository _eventAttendeesRepository; + // private readonly IMapper _mapper; + // + // public CommandHandler(IEventAttendeesRepository eventAttendeesRepository, IMapper mapper) + // { + // _eventAttendeesRepository = eventAttendeesRepository; + // _mapper = mapper; + // } + // + // public async Task Handle(Command message, CancellationToken cancellationToken) + // { + // var updatedAttendees = new List(); + // + // foreach (var attendee in message.EventAttendees.Attendees) + // { + // var eventAttendee = await _eventAttendeesRepository + // .UpdateItem(_mapper.Map(attendee)) + // .ConfigureAwait(false); + // + // updatedAttendees.Add(eventAttendee); + // } + // + // return new EventAttendeesEnvelope(updatedAttendees); + // } + // } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/Create.cs b/src/Tarscord.Core/Features/Events/Create.cs new file mode 100644 index 0000000..d74ad28 --- /dev/null +++ b/src/Tarscord.Core/Features/Events/Create.cs @@ -0,0 +1,86 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using OneOf; +using Tarscord.Core.Domain; +using Tarscord.Core.Extensions; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Features.Events; + +internal record Create( + string EventOrganizer, + ulong EventOrganizerId, + string EventName, + string EventDate, + string EventDescription +) : IRequest>; + +internal record CreateResponse( + ulong Id, + string EventOrganizer, + string EventName, + DateTime? EventDate, + string? EventDescription +) +{ + public static CreateResponse MapToResponse(EventInfo eventInfo) => + new( + eventInfo.Id, + eventInfo.EventOrganizer, + eventInfo.EventName, + eventInfo.EventDate, + eventInfo.EventDescription + ); + + public string ToMessage() => + $"'{EventName}' created by user {EventOrganizer}. Use this Id: {Id} to get the details of the event"; +} + +// public class CreateEventCommandValidator : AbstractValidator +// { +// public CreateEventCommandValidator() +// { +// // RuleFor(x => x.Event).NotNull(); +// } +// } + +internal sealed class CreateEventCommandHandler : IRequestHandler> +{ + private readonly ILogger _logger; + private readonly TarscordContext _context; + + public CreateEventCommandHandler( + ILogger logger, + TarscordContext context) + { + _logger = logger; + _context = context; + } + + public async Task> Handle( + Create request, + CancellationToken cancellationToken) + { + var dateOfEvent = request.EventDate.FromTextToDate(); + // var validDateProvided = DateTime.TryParse(dateOfEvent, out var parsedDateTime); + + if (!dateOfEvent.HasValue) + { + return new FailureResponse("Invalid event date provided"); + } + + var createdEvent = await _context.EventInfos.AddAsync(new EventInfo + { + EventOrganizer = request.EventDescription, + EventOrganizerId = request.EventOrganizerId.ToString(), + EventName = request.EventName, + EventDate = dateOfEvent.Value.ToUniversalTime(), + EventDescription = request.EventDescription, + IsActive = true, + Created = DateTime.UtcNow // Possibly replace with TimeProvider + }, cancellationToken); + + await _context.SaveChangesAsync(cancellationToken); + return CreateResponse.MapToResponse(createdEvent.Entity); + } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/Details.cs b/src/Tarscord.Core/Features/Events/Details.cs new file mode 100644 index 0000000..815f683 --- /dev/null +++ b/src/Tarscord.Core/Features/Events/Details.cs @@ -0,0 +1,42 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Tarscord.Core.Domain; + +namespace Tarscord.Core.Features.Events; + +public class Details +{ + // public class Query : IRequest + // { + // public ulong EventId { get; init; } + // } + // + // public class QueryValidator : AbstractValidator + // { + // public QueryValidator() + // { + // RuleFor(x => x.EventId).NotNull().NotEmpty().GreaterThan((ulong)0); + // } + // } + // + // public class QueryHandler : IRequestHandler + // { + // private readonly IEventRepository _eventRepository; + // + // public QueryHandler(IEventRepository eventRepository) + // { + // _eventRepository = eventRepository; + // } + // + // public async Task Handle(Query message, CancellationToken cancellationToken) + // { + // var events = await _eventRepository.FindBy(eventInfo => eventInfo.Id == message.EventId) + // .ConfigureAwait(false); + // + // return new EventInfoEnvelope(events.FirstOrDefault()); + // } + // } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/EnvelopeExtensions.cs b/src/Tarscord.Core/Features/Events/EnvelopeExtensions.cs new file mode 100644 index 0000000..78e16a7 --- /dev/null +++ b/src/Tarscord.Core/Features/Events/EnvelopeExtensions.cs @@ -0,0 +1,40 @@ +using System.Text; +using Discord; +using Tarscord.Core.Extensions; + +namespace Tarscord.Core.Features.Events; + +public static class EnvelopeExtensions +{ + // public static Embed ToEmbeddedMessage(this EventInfoListEnvelope events) + // { + // var eventsInformation = new StringBuilder(); + // + // foreach (var eventInfo in events.EventInfo) + // { + // eventsInformation + // .Append(eventInfo.Id).Append(": '") + // .Append(eventInfo.EventName) + // .Append("' by user: ").Append(eventInfo.EventOrganizer).Append(".\n"); + // } + // + // if (eventsInformation.Length == 0) + // { + // return "No events found".EmbedMessage(); + // } + // + // return eventsInformation.ToString().EmbedMessage(); + // } + // + // public static Embed ToEmbeddedMessage(this EventInfoEnvelope eventInfoEnvelope) + // { + // if (eventInfoEnvelope.EventInfo == null) + // { + // return "Event does not exist".EmbedMessage(); + // } + // + // return + // $"'{eventInfoEnvelope.EventInfo.EventName}' created by user {eventInfoEnvelope.EventInfo.EventOrganizer}. Use this Id: {eventInfoEnvelope.EventInfo.Id} to get the details of the event" + // .EmbedMessage(); + // } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/EventInfo.cs b/src/Tarscord.Core/Features/Events/EventInfo.cs new file mode 100644 index 0000000..30b515c --- /dev/null +++ b/src/Tarscord.Core/Features/Events/EventInfo.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Features.Events; + +[Table("event_infos")] +public class EventInfo : EntityBase +{ + [Column("event_organizer")] + public required string EventOrganizer { get; set; } + + /// + /// The Id of the organizer, can be converted to ulong + /// + [Column("event_organizer_id")] + public required string EventOrganizerId { get; set; } + + [Column("event_name")] + public required string EventName { get; set; } + + [Column("event_date")] + public DateTime? EventDate { get; set; } + + [Column("event_description")] + public required string EventDescription { get; set; } + + [Column("is_active")] + public bool IsActive { get; set; } + + public override string ToString() + { + return $"Organizer:\t {EventOrganizer}\n" + + $"Name:\t {EventName}\n" + + $"Date and time:\t {EventDate:F}\n" + + $"Description:\t {EventDescription}\n" + + $"Is active:\t {IsActive}\n" + + $"Date created:\t {Created:s}\n" + + $"Date updated:\t {Updated:s}\n"; + } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/FailureResponse.cs b/src/Tarscord.Core/Features/Events/FailureResponse.cs new file mode 100644 index 0000000..c820622 --- /dev/null +++ b/src/Tarscord.Core/Features/Events/FailureResponse.cs @@ -0,0 +1,3 @@ +namespace Tarscord.Core.Features.Events; + +internal record FailureResponse(string ErrorMessage, string? ErrorDescription = null); \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Events/List.cs b/src/Tarscord.Core/Features/Events/List.cs new file mode 100644 index 0000000..2c0c15c --- /dev/null +++ b/src/Tarscord.Core/Features/Events/List.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.Logging; +using Tarscord.Core.Domain; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Features.Events; + +public record GetEventInfosQuery : IRequest; + +public class GetEventInfosQueryHandler : IRequestHandler +{ + private readonly ILogger _logger; + + public GetEventInfosQueryHandler(ILogger logger) + { + _logger = logger; + } + + public Task Handle(GetEventInfosQuery request, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Loans/CreateLoanCommand.cs b/src/Tarscord.Core/Features/Loans/CreateLoanCommand.cs new file mode 100644 index 0000000..ba8407e --- /dev/null +++ b/src/Tarscord.Core/Features/Loans/CreateLoanCommand.cs @@ -0,0 +1,43 @@ +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Tarscord.Core.Features.Loans; + +public class CreateLoanCommand : IRequest +{ + public decimal Amount { get; set; } + public ulong LoanedFrom { get; set; } + public string LoanedFromUsername { get; set; } + public ulong LoanedTo { get; set; } + public string LoanedToUsername { get; set; } + public string Description { get; set; } +} + +public class CreateLoanCommandValidator : AbstractValidator +{ + public CreateLoanCommandValidator() + { + // TODO: Add proper validation + // RuleFor(x => x.Loan).NotNull(); + } +} + +public class CreateLoanCommandHandler : IRequestHandler +{ + private readonly ILogger _logger; + + public CreateLoanCommandHandler( + ILogger logger) + { + _logger = logger; + } + + public async Task Handle(CreateLoanCommand command, CancellationToken cancellationToken) + { + // var createdLoan = await _databaseConnection.Connection.InsertAsync(command) + // .ConfigureAwait(false); + + return new LoanDto(); + } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Features/Loans/GetLoansQuery.cs b/src/Tarscord.Core/Features/Loans/GetLoansQuery.cs new file mode 100644 index 0000000..9e08b2c --- /dev/null +++ b/src/Tarscord.Core/Features/Loans/GetLoansQuery.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.Logging; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Features.Loans; + +public record GetLoansQuery : IRequest>; + +public class GetLoansQueryHandler : IRequestHandler> +{ + private readonly ILogger _logger; + + public GetLoansQueryHandler( + ILogger logger) + { + _logger = logger; + } + + public Task> Handle(GetLoansQuery request, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Features/Loans/LoanDto.cs b/src/Tarscord.Core/Features/Loans/LoanDto.cs similarity index 100% rename from src/Features/Loans/LoanDto.cs rename to src/Tarscord.Core/Features/Loans/LoanDto.cs diff --git a/src/Tarscord.Core/Features/Loans/UpdateLoanCommand.cs b/src/Tarscord.Core/Features/Loans/UpdateLoanCommand.cs new file mode 100644 index 0000000..25d7fe9 --- /dev/null +++ b/src/Tarscord.Core/Features/Loans/UpdateLoanCommand.cs @@ -0,0 +1,60 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Logging; +using Tarscord.Core.Persistence; + +namespace Tarscord.Core.Features.Loans; + +public class UpdateLoanCommand : IRequest +{ + public decimal Amount { get; set; } + public ulong LoanedFrom { get; set; } + public string LoanedFromUsername { get; set; } + public ulong LoanedTo { get; set; } + public string LoanedToUsername { get; set; } +} + +public class CommandValidator : AbstractValidator +{ + public CommandValidator() + { + // RuleFor(x => x.Loan).NotNull(); + } +} + +public class UpdateLoanCommandHandler : IRequestHandler +{ + private readonly ILogger _logger; + + public UpdateLoanCommandHandler(ILogger logger) + { + _logger = logger; + } + + // public async Task Handle(UpdateLoanCommandCommand request, CancellationToken cancellationToken) + // { + // var loans = await _loanRepository + // .FindBy(x => x.LoanedFrom == request.Loan.LoanedFrom + // && x.LoanedTo == request.Loan.LoanedTo); + // + // var loanToUpdate = loans?.FirstOrDefault(); + // if (loanToUpdate == null) + // { + // return new LoanEnvelope(null); + // } + // + // loanToUpdate.AmountPayed += request.Loan.Amount; + // loanToUpdate.AmountLoaned -= request.Loan.Amount; + // var updatedLoan = await _loanRepository.UpdateItem(_mapper.Map(loanToUpdate)); + // + // return new LoanDto(); + // } + + public Task Handle(UpdateLoanCommand request, CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Features/Logging/ProcessLog.cs b/src/Tarscord.Core/Features/Logging/ProcessLog.cs similarity index 100% rename from src/Features/Logging/ProcessLog.cs rename to src/Tarscord.Core/Features/Logging/ProcessLog.cs diff --git a/src/Features/Reminders/Commands/AddReminder.cs b/src/Tarscord.Core/Features/Reminders/Commands/AddReminder.cs similarity index 100% rename from src/Features/Reminders/Commands/AddReminder.cs rename to src/Tarscord.Core/Features/Reminders/Commands/AddReminder.cs diff --git a/src/Features/Reminders/Commands/NotifyUser.cs b/src/Tarscord.Core/Features/Reminders/Commands/NotifyUser.cs similarity index 100% rename from src/Features/Reminders/Commands/NotifyUser.cs rename to src/Tarscord.Core/Features/Reminders/Commands/NotifyUser.cs diff --git a/src/Features/Reminders/Commands/SetReminder.cs b/src/Tarscord.Core/Features/Reminders/Commands/SetReminder.cs similarity index 100% rename from src/Features/Reminders/Commands/SetReminder.cs rename to src/Tarscord.Core/Features/Reminders/Commands/SetReminder.cs diff --git a/src/Modules/AdminModule.cs b/src/Tarscord.Core/Modules/AdminModule.cs similarity index 99% rename from src/Modules/AdminModule.cs rename to src/Tarscord.Core/Modules/AdminModule.cs index a9a644b..21ccf9f 100644 --- a/src/Modules/AdminModule.cs +++ b/src/Tarscord.Core/Modules/AdminModule.cs @@ -29,7 +29,7 @@ public async Task MuteUserAsync( [Command("unmute"), Summary("Unmutes a user")] public async Task UnmuteUserAsync( [Summary("The user to be unmuted"), Required(ErrorMessage = "Please provide member of the channel.")] - IUser user = null) + IUser? user = null) { await ExecuteCommandAsync(user, CommandType.Unmute).ConfigureAwait(false); } diff --git a/src/Modules/BotConfigModule.cs b/src/Tarscord.Core/Modules/BotConfigModule.cs similarity index 100% rename from src/Modules/BotConfigModule.cs rename to src/Tarscord.Core/Modules/BotConfigModule.cs diff --git a/src/Tarscord.Core/Modules/EventGroupModule.cs b/src/Tarscord.Core/Modules/EventGroupModule.cs new file mode 100644 index 0000000..6769e02 --- /dev/null +++ b/src/Tarscord.Core/Modules/EventGroupModule.cs @@ -0,0 +1,209 @@ +using Discord; +using Discord.Commands; +using MediatR; +using System.ComponentModel.DataAnnotations; +using Tarscord.Core.Extensions; +using Tarscord.Core.Features.Events; + +namespace Tarscord.Core.Modules; + +[Group("event")] +public class EventModule : ModuleBase +{ + private readonly IMediator _mediator; + + public EventModule(IMediator mediator) + { + _mediator = mediator; + } + + /// + /// Usage: event list + /// + [Command("list"), Summary("Lists all events")] + public async Task ListEventsAsync() + { + // var eventInfoList = await _mediator.Send(new Features.Events.List.Query()); + // + // await ReplyAsync(embed: eventInfoList?.ToEmbeddedMessage()).ConfigureAwait(false); + } + + /// + /// Usage: event display {Event Id} + /// + [Command("show"), Summary("Show information about an event")] + [Alias("info", "get", "display", "details")] + public async Task ShowEventInformationAsync( + [Summary("The event Id")] ulong eventId) + { + // var eventInformation = await _mediator.Send(new Features.Events.Details.Query() + // { + // EventId = eventId + // }); + // + // await ReplyAsync(embed: eventInformation.ToEmbeddedMessage()).ConfigureAwait(false); + } + + /// + /// Usage: event create {Event Name}, {DateTime of Event}, {Description} + /// + [Command("create"), Summary("Create an event")] + [Alias("add", "make", "generate")] + public async Task CreateEvent( + [Summary("The event name"), Required(ErrorMessage = "Please provide a name for your event")] + string eventName, + [Summary("The event date and time")] params string[] date) + { + var user = Context.User.ToCommonUser(); + + var eventInfo = new Create( + user.Username, + user.Id, + eventName, + string.Join(" ", date), + ""); // Event description for now should be empty or removed all together + + var response = await _mediator.Send(eventInfo); + + var message = response.Match( + created => created.ToMessage(), + failed => failed.ErrorMessage); + + await ReplyAsync(embed: message.EmbedMessage()).ConfigureAwait(false); + } + + /// + /// Usage: event cancel {eventName} + /// + [Command("remove"), Summary("Cancel an event")] + [Alias("delete")] + public async Task CancelEventAsync([Summary("The event name")] string eventName) + { + // string messageToReplyWith = $"You have successfully canceled the event named '{eventName}'"; + // EventInfo result = await _eventService.CancelEvent(Context.User.ToCommonUser(), eventName); + // + // if (result != null) + // messageToReplyWith = $"The cancellation of the event named '{eventName}' failed."; + // + // await ReplyAsync(embed: messageToReplyWith.EmbedMessage()); + } + + /// + /// Usage: event confirm {Event Id} {User?} + /// + /// The confirmed attendees. + [Command("confirm"), Summary("Confirm your attendance")] + public async Task ConfirmAsync( + [Summary("The event name")] ulong eventId, + [Summary("The (optional) user to confirm for")] + params IUser[] users) + { + // if (users.Length == 0) + // users = new[] { Context.User }; + // + // var eventAttendees = await _mediator.Send(new Update.Command() + // { + // EventAttendees = new Update.EventAttendees() + // { + // Confirmation = true, + // Attendees = users.Select(u => new Update.Attendee + // { + // AttendeeId = u.Id, + // Confirmed = true, + // AttendeeName = u.Username, + // EventInfoId = eventId.ToString() + // }).ToList(), + // EventId = eventId + // } + // }); + // + // var confirmAttendanceAsList = eventAttendees.EventAttendee.ToList(); + // if (confirmAttendanceAsList.Any()) + // { + // StringBuilder stringBuilder = new StringBuilder(); + // + // for (int i = 1; i <= confirmAttendanceAsList.Count; i++) + // { + // stringBuilder.Append($"{i}. {confirmAttendanceAsList[i - 1]}\n"); + // } + // + // await ReplyAsync( + // embed: "Thank you for confirming your attendance, these users confirmed their attendance:" + // .EmbedMessage(stringBuilder.ToString())).ConfigureAwait(false); + // } + // else + // await ReplyAsync(embed: "Attendance confirmation failed".EmbedMessage()).ConfigureAwait(false); + } + + /// + /// Usage: event confirm {eventName} {user?} + /// + /// The number squared. + [Command("cancel"), Summary("Confirm your attendance")] + [Alias("unattend")] + public async Task CancelAttendanceAsync( + [Summary("The event name")] ulong eventId, + [Summary("The (optional) user to confirm for")] + params IUser[] users) + { + // users ??= new[] {Context.User}; + // + // var eventAttendees = await _mediator.Send(new Update.Command() + // { + // EventAttendance = new Update.EventAttendance() + // { + // Confirmation = true, + // AttendeeIds = users.Select(u => u.Id).ToList(), + // AttendeeNames = users.Select(u => u.Username).ToList(), + // EventId = eventId + // } + // }); + // + // var attendeesAsList = eventAttendees.EventAttendee?.ToList(); + // if (attendeesAsList?.Any() ?? false) + // { + // await ReplyAsync( + // embed: $"You successfully canceled your attendance for the event with Id '{eventId}'" + // .EmbedMessage()).ConfigureAwait(false); + // } + // else + // await ReplyAsync(embed: "Attendance cancellation failed".EmbedMessage()).ConfigureAwait(false); + } + + /// + /// Usage: event confirmed {Event Id} + /// + /// The number squared. + [Command("confirmed"), Summary("Shows confirmed attendees.")] + public async Task ShowConfirmedAsync([Summary("The Event Id")] ulong eventId) + { + // var attendees = await _mediator.Send(new EventAttendanceDetails.Query() + // { + // EventId = eventId + // }); + // + // var attendeesAsList = attendees.EventAttendee?.ToList(); + // if (attendeesAsList == null) + // { + // await ReplyAsync(embed: $"The event named '{eventId}' does not exist".EmbedMessage()) + // .ConfigureAwait(false); + // return; + // } + // + // if (!attendeesAsList.Any()) + // { + // await ReplyAsync(embed: "There are no confirmed attendees".EmbedMessage()).ConfigureAwait(false); + // return; + // } + // + // var stringBuilder = new StringBuilder(); + // for (int i = 1; i <= attendeesAsList.Count; i++) + // { + // stringBuilder.Append($"{i}. {attendeesAsList[i - 1]}\n"); + // } + // + // await ReplyAsync( + // embed: "Users who have confirmed their attendance are:" + // .EmbedMessage(stringBuilder.ToString())).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Modules/HelpModule.cs b/src/Tarscord.Core/Modules/HelpModule.cs similarity index 100% rename from src/Modules/HelpModule.cs rename to src/Tarscord.Core/Modules/HelpModule.cs diff --git a/src/Modules/InteractionModule.cs b/src/Tarscord.Core/Modules/InteractionModule.cs similarity index 100% rename from src/Modules/InteractionModule.cs rename to src/Tarscord.Core/Modules/InteractionModule.cs diff --git a/src/Modules/LoanGroupModule.cs b/src/Tarscord.Core/Modules/LoanGroupModule.cs similarity index 60% rename from src/Modules/LoanGroupModule.cs rename to src/Tarscord.Core/Modules/LoanGroupModule.cs index 521837d..a07e983 100644 --- a/src/Modules/LoanGroupModule.cs +++ b/src/Tarscord.Core/Modules/LoanGroupModule.cs @@ -4,12 +4,8 @@ using Discord; using Discord.Commands; using System.Threading.Tasks; -using AutoMapper; using MediatR; -using Tarscord.Core.Extensions; using Tarscord.Core.Features.Loans; -using Tarscord.Core.Helpers; -using Tarscord.Core.Persistence.Interfaces; namespace Tarscord.Core.Modules; @@ -20,14 +16,10 @@ class LoanGroupModule public class LoanModule : ModuleBase { private readonly IMediator _mediator; - private readonly IMapper _mapper; - private readonly IEventRepository _eventRepository; - public LoanModule(IMediator mediator, IMapper mapper, IEventRepository eventRepository) + public LoanModule(IMediator mediator) { _mediator = mediator; - _mapper = mapper; - _eventRepository = eventRepository; } /// @@ -38,19 +30,19 @@ public LoanModule(IMediator mediator, IMapper mapper, IEventRepository eventRepo [Alias("show")] public async Task ShowLoansAsync() { - var loanList = await _mediator.Send(new List.Query()); - - string messageToReplyWith = "No active loans were found"; - - if (loanList.Loans?.Any() == true) - { - string formattedEventInformation = - FormatEventInformation(_mapper.Map>(loanList.Loans)); - - messageToReplyWith = $"Here are all the loans:\n{formattedEventInformation}"; - } - - await ReplyAsync(embed: messageToReplyWith.EmbedMessage()).ConfigureAwait(false); + // var loanList = await _mediator.Send(new GetLoansQuery.Query()); + // + // string messageToReplyWith = "No active loans were found"; + // + // if (loanList.Loans?.Any() == true) + // { + // string formattedEventInformation = + // FormatEventInformation(_mapper.Map>(loanList.Loans)); + // + // messageToReplyWith = $"Here are all the loans:\n{formattedEventInformation}"; + // } + // + // await ReplyAsync(embed: messageToReplyWith.EmbedMessage()).ConfigureAwait(false); } private string FormatEventInformation(IList loans) @@ -62,7 +54,7 @@ private string FormatEventInformation(IList loans) messageToReply.Append(i + 1).Append(". '") .Append(loans[i].LoanedToUsername).Append("' owns '") .Append(loans[i].LoanedFromUsername).Append("' ") - .Append(loans[i].Amount).Append(GlobalMessages.EuroSign).Append(".\n"); + .Append(loans[i].Amount).Append('€').Append(".\n"); } return messageToReply.ToString(); @@ -80,7 +72,7 @@ public async Task LoanToUserAsync( [Summary("The reason you're loaning the money")] string description) { - var items = await _eventRepository.GetAllAsync(); + // var items = await _eventRepository.GetAllAsync(); // var loanEnvelope = await _mediator.Send(new Create.Command // { // Loan = new Create.Loan @@ -110,32 +102,33 @@ public async Task LoanToUserAsync( /// The generated random number [Command("payback"), Summary("Pays back the amount to the loaner")] [Alias("return", "removeloan", "deleteloan", "payloan")] - public async Task PaybackToUserAsync( + public async Task PaybackToUser( [Summary("The user to loan money to")] IUser user, [Summary("The value of the money being lent")] decimal amountBeingPayedBack) { - var loanEnvelope = await _mediator.Send(new Update.Command - { - Loan = new Update.Loan - { - Amount = amountBeingPayedBack, - LoanedTo = user.Id, - LoanedToUsername = user.Username, - LoanedFrom = Context.User.Id, - LoanedFromUsername = Context.User.Username - } - }); - - var messageToReplyWith = ""; - if (loanEnvelope.Loan != null) - { - var formattedEventInformation = - FormatEventInformation(_mapper.Map>(loanEnvelope.Loan)); - messageToReplyWith = $"Here are all the loans:\n{formattedEventInformation}"; - } - - await ReplyAsync(embed: messageToReplyWith.EmbedMessage()).ConfigureAwait(false); + await Task.CompletedTask; + // var loanEnvelope = await _mediator.Send(new UpdateLoanCommand + // { + // Loan = new UpdateLoanCommand.Loan + // { + // Amount = amountBeingPayedBack, + // LoanedTo = user.Id, + // LoanedToUsername = user.Username, + // LoanedFrom = Context.User.Id, + // LoanedFromUsername = Context.User.Username + // } + // }); + // + // var messageToReplyWith = ""; + // if (loanEnvelope.Loan != null) + // { + // var formattedEventInformation = + // FormatEventInformation(_mapper.Map>(loanEnvelope.Loan)); + // messageToReplyWith = $"Here are all the loans:\n{formattedEventInformation}"; + // } + // + // await ReplyAsync(embed: messageToReplyWith.EmbedMessage()).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Modules/RandomNumberModule.cs b/src/Tarscord.Core/Modules/RandomNumberModule.cs similarity index 76% rename from src/Modules/RandomNumberModule.cs rename to src/Tarscord.Core/Modules/RandomNumberModule.cs index 54af42f..862abe7 100644 --- a/src/Modules/RandomNumberModule.cs +++ b/src/Tarscord.Core/Modules/RandomNumberModule.cs @@ -2,7 +2,6 @@ using System; using System.Threading.Tasks; using Tarscord.Core.Extensions; -using Tarscord.Core.Helpers; namespace Tarscord.Core.Modules; @@ -19,16 +18,16 @@ public async Task GenerateRandomNumberAsync( [Summary("The lower limit")] int min, [Summary("The upper limit")] int max) { - string generatedNumber; + int generatedNumber = 0; try { - generatedNumber = CustomRandomNumberGenerator.GenerateNumber(min, max).ToString(); + generatedNumber = Random.Shared.Next(min, max); } catch (Exception) { throw new Exception("Wrong command usage. Try: random lower-limit upper-limit"); } - await ReplyAsync(embed: generatedNumber.EmbedMessage()).ConfigureAwait(false); + await ReplyAsync(embed: generatedNumber.ToString().EmbedMessage()).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Modules/ReminderModule.cs b/src/Tarscord.Core/Modules/ReminderModule.cs similarity index 100% rename from src/Modules/ReminderModule.cs rename to src/Tarscord.Core/Modules/ReminderModule.cs diff --git a/src/Tarscord.Core/Persistence/DatabaseExtensions.cs b/src/Tarscord.Core/Persistence/DatabaseExtensions.cs new file mode 100644 index 0000000..73a08db --- /dev/null +++ b/src/Tarscord.Core/Persistence/DatabaseExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Tarscord.Core.Persistence; + +public static class DatabaseExtensions +{ + public static IServiceCollection AddDatabase( + this IServiceCollection serviceCollection, + IConfiguration configuration) + { + var connectionString = configuration.GetSection("tarscord-context:connection-string"); + + if (string.IsNullOrEmpty(connectionString.Value)) + { + throw new ArgumentException("Connection string is missing"); + } + + serviceCollection.AddDbContextPool(opt => + opt.UseNpgsql(connectionString.Value)); + return serviceCollection; + } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Persistence/EntityBase.cs b/src/Tarscord.Core/Persistence/EntityBase.cs new file mode 100644 index 0000000..008267f --- /dev/null +++ b/src/Tarscord.Core/Persistence/EntityBase.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Tarscord.Core.Persistence; + +public abstract class EntityBase +{ + [Column("id")] + public ulong Id { get; set; } + + [Column("created")] + public DateTime Created { get; set; } + + [Column("updated")] + public DateTime? Updated { get; set; } +} \ No newline at end of file diff --git a/src/Tarscord.Core/Persistence/TarscordContext.cs b/src/Tarscord.Core/Persistence/TarscordContext.cs new file mode 100644 index 0000000..a45a390 --- /dev/null +++ b/src/Tarscord.Core/Persistence/TarscordContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; +using Tarscord.Core.Domain; +using Tarscord.Core.Features.Events; + +namespace Tarscord.Core.Persistence; + +public class TarscordContext(DbContextOptions options) : DbContext(options) +{ + public DbSet EventAttendees { get; set; } + + public DbSet EventInfos { get; set; } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Tarscord.Core/Program.cs similarity index 61% rename from src/Program.cs rename to src/Tarscord.Core/Program.cs index a198139..b4d3f11 100644 --- a/src/Program.cs +++ b/src/Tarscord.Core/Program.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; - -namespace Tarscord.Core; +namespace Tarscord.Core; class Program { diff --git a/src/Services/AdminService.cs b/src/Tarscord.Core/Services/AdminService.cs similarity index 58% rename from src/Services/AdminService.cs rename to src/Tarscord.Core/Services/AdminService.cs index 89ee757..5d654ce 100644 --- a/src/Services/AdminService.cs +++ b/src/Tarscord.Core/Services/AdminService.cs @@ -1,11 +1,9 @@ -using Tarscord.Core.Persistence.Interfaces; +using Tarscord.Core.Persistence; namespace Tarscord.Core.Services; -public class AdminService(IUserRepository userRepository) +public class AdminService { - private readonly IUserRepository _userRepository = userRepository; - // public async Task MuteUser(string id, int minutesToMute) // { // var userToMute = diff --git a/src/Services/CommandHandler.cs b/src/Tarscord.Core/Services/CommandHandler.cs similarity index 93% rename from src/Services/CommandHandler.cs rename to src/Tarscord.Core/Services/CommandHandler.cs index ecd657d..7cda57a 100644 --- a/src/Services/CommandHandler.cs +++ b/src/Tarscord.Core/Services/CommandHandler.cs @@ -1,6 +1,6 @@ using Discord.WebSocket; using MediatR; -using Tarscord.Core.Features.Commands; +using Tarscord.Core.Setup; namespace Tarscord.Core.Services; diff --git a/src/Services/LoggingService.cs b/src/Tarscord.Core/Services/LoggingService.cs similarity index 100% rename from src/Services/LoggingService.cs rename to src/Tarscord.Core/Services/LoggingService.cs diff --git a/src/Services/StartupService.cs b/src/Tarscord.Core/Services/StartupService.cs similarity index 84% rename from src/Services/StartupService.cs rename to src/Tarscord.Core/Services/StartupService.cs index 831e841..4998d35 100644 --- a/src/Services/StartupService.cs +++ b/src/Tarscord.Core/Services/StartupService.cs @@ -1,5 +1,5 @@ using MediatR; -using Tarscord.Core.Features.Startup; +using Tarscord.Core.Setup; namespace Tarscord.Core.Services; diff --git a/src/Services/TimerService.cs b/src/Tarscord.Core/Services/TimerService.cs similarity index 100% rename from src/Services/TimerService.cs rename to src/Tarscord.Core/Services/TimerService.cs diff --git a/src/Features/Startup/InitializeBot.cs b/src/Tarscord.Core/Setup/InitializeBot.cs similarity index 92% rename from src/Features/Startup/InitializeBot.cs rename to src/Tarscord.Core/Setup/InitializeBot.cs index 0ae2582..bdc74cd 100644 --- a/src/Features/Startup/InitializeBot.cs +++ b/src/Tarscord.Core/Setup/InitializeBot.cs @@ -1,14 +1,11 @@ -using System; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Discord; using Discord.Commands; using Discord.WebSocket; using MediatR; using Microsoft.Extensions.Configuration; -namespace Tarscord.Core.Features.Startup; +namespace Tarscord.Core.Setup; public class InitializeBot { diff --git a/src/Features/Commands/ProcessMessage.cs b/src/Tarscord.Core/Setup/ProcessMessage.cs similarity index 97% rename from src/Features/Commands/ProcessMessage.cs rename to src/Tarscord.Core/Setup/ProcessMessage.cs index 2e816d8..9a5afb0 100644 --- a/src/Features/Commands/ProcessMessage.cs +++ b/src/Tarscord.Core/Setup/ProcessMessage.cs @@ -3,7 +3,7 @@ using MediatR; using Microsoft.Extensions.Configuration; -namespace Tarscord.Core.Features.Commands; +namespace Tarscord.Core.Setup; public abstract class ProcessMessage { diff --git a/src/Startup.cs b/src/Tarscord.Core/Startup.cs similarity index 65% rename from src/Startup.cs rename to src/Tarscord.Core/Startup.cs index 6685e2c..162de2a 100644 --- a/src/Startup.cs +++ b/src/Tarscord.Core/Startup.cs @@ -4,10 +4,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Tarscord.Core.Helpers; using Tarscord.Core.Persistence; -using Tarscord.Core.Persistence.Interfaces; -using Tarscord.Core.Persistence.Repositories; using Tarscord.Core.Services; namespace Tarscord.Core; @@ -26,16 +23,27 @@ public Startup(string[] args) public static async Task RunAsync(string[] args) { - var host = Host.CreateDefaultBuilder(args) - .ConfigureServices((context, services) => - { - var startup = new Startup(args); - startup.ConfigureServices(services); - startup.SetupGlobalMessages(); - }) - .Build(); + var startup = new Startup(args); + await startup.RunAsync(); + } + + private async Task RunAsync() + { + // Create a new instance of a service collection + var services = new ServiceCollection(); + ConfigureServices(services); + + // Build the service provider + var provider = services.BuildServiceProvider(); - await host.RunAsync(); + // Start the logging service, and the command handler service + provider.GetRequiredService(); + provider.GetRequiredService(); + + // Start the startup service + await provider.GetRequiredService().StartAsync(); + + await Task.Delay(-1); } private void ConfigureServices(IServiceCollection services) @@ -58,16 +66,7 @@ private void ConfigureServices(IServiceCollection services) .AddSingleton() .AddLogging() .AddSingleton(Configuration) - .AddAutoMapper(typeof(Startup)) - .AddScoped() - .AddScoped() - .AddScoped() - .AddSingleton() + .AddDatabase(Configuration) .AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); } - - private void SetupGlobalMessages() - { - GlobalMessages.EuroSign = Configuration["messages:euro_sign"]; - } } \ No newline at end of file diff --git a/src/Tarscord.Core.csproj b/src/Tarscord.Core/Tarscord.Core.csproj similarity index 69% rename from src/Tarscord.Core.csproj rename to src/Tarscord.Core/Tarscord.Core.csproj index 50a6eda..70f7608 100644 --- a/src/Tarscord.Core.csproj +++ b/src/Tarscord.Core/Tarscord.Core.csproj @@ -19,23 +19,18 @@ - - - - - - + + - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/src/Tarscord.DbMigrator/Migrations/v1.00-InitialDbSetup.sql b/src/Tarscord.DbMigrator/Migrations/v1.00-InitialDbSetup.sql new file mode 100644 index 0000000..62ad7c6 --- /dev/null +++ b/src/Tarscord.DbMigrator/Migrations/v1.00-InitialDbSetup.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS public.event_infos +( + id SERIAL PRIMARY KEY, + event_organizer VARCHAR(200), + event_organizer_id VARCHAR(2000), + event_name VARCHAR(200) NOT NULL, + event_date TIMESTAMP, + event_description VARCHAR(200), + is_active BOOLEAN, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP DEFAULT NULL +); \ No newline at end of file diff --git a/src/Tarscord.DbMigrator/Migrations/v1.01-AddEventAttendees.sql b/src/Tarscord.DbMigrator/Migrations/v1.01-AddEventAttendees.sql new file mode 100644 index 0000000..bee5027 --- /dev/null +++ b/src/Tarscord.DbMigrator/Migrations/v1.01-AddEventAttendees.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS public.event_attendees +( + id SERIAL PRIMARY KEY, + attendee_id INTEGER, + event_info_id INTEGER, + attendee_name VARCHAR(100), + confirmed BOOLEAN, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP DEFAULT NULL, + FOREIGN KEY (event_info_id) REFERENCES public.event_infos (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/Tarscord.DbMigrator/Migrations/v1.02-AddLoans.sql b/src/Tarscord.DbMigrator/Migrations/v1.02-AddLoans.sql new file mode 100644 index 0000000..f93b20d --- /dev/null +++ b/src/Tarscord.DbMigrator/Migrations/v1.02-AddLoans.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS public.loans +( + id SERIAL PRIMARY KEY NOT NULL, + loaned_from INTEGER, + loaned_from_username VARCHAR(200), + loaned_to INTEGER, + loaned_to_username VARCHAR(200), + description VARCHAR(1000), + amount_loaned BIGINT NOT NULL DEFAULT 0, + amount_payed BIGINT, + confirmed BOOLEAN NOT NULL DEFAULT FALSE, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP DEFAULT NULL +); \ No newline at end of file diff --git a/src/Tarscord.DbMigrator/Program.cs b/src/Tarscord.DbMigrator/Program.cs new file mode 100644 index 0000000..5dcaf13 --- /dev/null +++ b/src/Tarscord.DbMigrator/Program.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using DbUp; + +return RunDbMigration(args); + +static int RunDbMigration(string[] args) +{ + var connectionString = + args.FirstOrDefault() + ?? "Host=localhost;Username=root;Password=password;Database=tarscord_db"; + + var upgrader = + DeployChanges.To + .PostgresqlDatabase(connectionString) + .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()) + .LogToConsole() + .Build(); + + var result = upgrader.PerformUpgrade(); + + if (!result.Successful) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(result.Error); + Console.ResetColor(); +#if DEBUG + Console.ReadLine(); +#endif + return -1; + } + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Success!"); + Console.ResetColor(); + return 0; +} \ No newline at end of file diff --git a/src/Tarscord.DbMigrator/Tarscord.DbMigrator.csproj b/src/Tarscord.DbMigrator/Tarscord.DbMigrator.csproj new file mode 100644 index 0000000..e5674d0 --- /dev/null +++ b/src/Tarscord.DbMigrator/Tarscord.DbMigrator.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/tests/MappingProfileTests/MappingProfileTests.cs b/tests/MappingProfileTests/MappingProfileTests.cs deleted file mode 100644 index 58ef9a9..0000000 --- a/tests/MappingProfileTests/MappingProfileTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AutoMapper; -using Tarscord.Core.Helpers; -using Xunit; - -namespace Tarscord.Core.Tests.MappingProfileTests; - -[Trait("Category", "Unit")] -public class MappingProfileTests -{ - [Fact] - public void CoreMappingProfile_ShouldSucceed() - { - // Arrange - var config = new MapperConfiguration(cfg => cfg.AddProfile()); - - // Act & Assert - config.AssertConfigurationIsValid(); - } -} \ No newline at end of file diff --git a/tests/ServicesTests/TimerServiceTests.cs b/tests/ServicesTests/TimerServiceTests.cs index 57ee258..e9feabd 100644 --- a/tests/ServicesTests/TimerServiceTests.cs +++ b/tests/ServicesTests/TimerServiceTests.cs @@ -1,88 +1,88 @@ -using Discord; -using FluentAssertions; -using MediatR; -using Moq; -using Tarscord.Core.Features.Reminders.Commands; -using Tarscord.Core.Services; -using Xunit; - -namespace Tarscord.Core.Tests.ServicesTests; - -public class TimerServiceTests -{ - private readonly Mock _mediatorMock; - private readonly Mock _timerMock; - private readonly Mock _userMock; - private readonly TimerService _sut; - - public TimerServiceTests() - { - _mediatorMock = new Mock(); - _timerMock = new Mock(); - _userMock = new Mock(); - _sut = new TimerService(_mediatorMock.Object, _timerMock.Object); - } - - [Fact] - public void AddReminder_ShouldStartTimer_WhenAddingFirstReminder() - { - // Arrange - var dateToRemind = DateTime.UtcNow.AddMinutes(5); - const string message = "Test reminder"; - - // Act - _sut.AddReminder(dateToRemind, _userMock.Object, message); - - // Assert - _timerMock.Verify(t => t.Change(It.IsAny(), It.IsAny()), Times.Never); - } - - [Fact] - public async Task NotifyUserWithMessageAsync_ShouldStopTimer_WhenNoRemindersLeft() - { - // Todo: This test sometimes fails. To improve - - // Act - await _sut.NotifyUserWithMessageAsync(); - - // Assert - _timerMock.Verify(t => t.Change(new TimeSpan(Timeout.Infinite), new TimeSpan(0)), Times.Once); - } - - [Fact] - public async Task StartTimerAsync_ShouldInitializeTimer_WithCorrectInterval() - { - // Act - await _sut.StartTimerAsync(); - - // Assert - _timerMock.Verify(t => t.Change(TimeSpan.Zero, TimeSpan.FromSeconds(10)), Times.Never); - } - - [Fact] - public void Dispose_ShouldDisposeTimer() - { - // Act - _sut.Dispose(); - - // Assert - _timerMock.Verify(t => t.Dispose(), Times.Once); - } - - [Theory] - [InlineData(0)] - [InlineData(-1)] - [InlineData(-60)] - public void AddReminder_ShouldAllowPastDates_ForTesting(int minutes) - { - // Arrange - var dateToRemind = DateTime.UtcNow.AddMinutes(minutes); - const string message = "Test reminder"; - - // Act - Action act = () => _sut.AddReminder(dateToRemind, _userMock.Object, message); - - // Assert - act.Should().NotThrow(); - } -} \ No newline at end of file +// using Discord; +// using FluentAssertions; +// using MediatR; +// using Moq; +// using Tarscord.Core.Features.Reminders.Commands; +// using Tarscord.Core.Services; +// using Xunit; +// +// namespace Tarscord.Core.Tests.ServicesTests; +// +// public class TimerServiceTests +// { +// private readonly Mock _mediatorMock; +// private readonly Mock _timerMock; +// private readonly Mock _userMock; +// private readonly TimerService _sut; +// +// public TimerServiceTests() +// { +// _mediatorMock = new Mock(); +// _timerMock = new Mock(); +// _userMock = new Mock(); +// _sut = new TimerService(_mediatorMock.Object, _timerMock.Object); +// } +// +// [Fact] +// public void AddReminder_ShouldStartTimer_WhenAddingFirstReminder() +// { +// // Arrange +// var dateToRemind = DateTime.UtcNow.AddMinutes(5); +// const string message = "Test reminder"; +// +// // Act +// _sut.AddReminder(dateToRemind, _userMock.Object, message); +// +// // Assert +// _timerMock.Verify(t => t.Change(It.IsAny(), It.IsAny()), Times.Never); +// } +// +// [Fact] +// public async Task NotifyUserWithMessageAsync_ShouldStopTimer_WhenNoRemindersLeft() +// { +// // Todo: This test sometimes fails. To improve +// +// // Act +// await _sut.NotifyUserWithMessageAsync(); +// +// // Assert +// _timerMock.Verify(t => t.Change(new TimeSpan(Timeout.Infinite), new TimeSpan(0)), Times.Once); +// } +// +// [Fact] +// public async Task StartTimerAsync_ShouldInitializeTimer_WithCorrectInterval() +// { +// // Act +// await _sut.StartTimerAsync(); +// +// // Assert +// _timerMock.Verify(t => t.Change(TimeSpan.Zero, TimeSpan.FromSeconds(10)), Times.Never); +// } +// +// [Fact] +// public void Dispose_ShouldDisposeTimer() +// { +// // Act +// _sut.Dispose(); +// +// // Assert +// _timerMock.Verify(t => t.Dispose(), Times.Once); +// } +// +// [Theory] +// [InlineData(0)] +// [InlineData(-1)] +// [InlineData(-60)] +// public void AddReminder_ShouldAllowPastDates_ForTesting(int minutes) +// { +// // Arrange +// var dateToRemind = DateTime.UtcNow.AddMinutes(minutes); +// const string message = "Test reminder"; +// +// // Act +// Action act = () => _sut.AddReminder(dateToRemind, _userMock.Object, message); +// +// // Assert +// act.Should().NotThrow(); +// } +// } \ No newline at end of file diff --git a/tests/Tarscord.Core.Tests.csproj b/tests/Tarscord.Core.Tests.csproj index 18d99ad..546f2b6 100644 --- a/tests/Tarscord.Core.Tests.csproj +++ b/tests/Tarscord.Core.Tests.csproj @@ -9,16 +9,14 @@ - - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive