Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/CB.Accessors/Contracts/IChannelAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CB.Data.Entities;
using CB.Shared.Dtos;

namespace CB.Accessors.Contracts;

public interface IChannelAccessor
{
Task<List<ChannelDto>> GetAllAsync();

Task<ChannelDto?> GetByIdAsync(string id);

Check warning on line 10 in src/CB.Accessors/Contracts/IChannelAccessor.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Task<ChannelDto> CreateAsync(Channel entity);

Task<ChannelDto?> UpdateAsync(string id,

Check warning on line 14 in src/CB.Accessors/Contracts/IChannelAccessor.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Channel entity);

Task<bool> DeleteAsync(string id);
}
18 changes: 18 additions & 0 deletions src/CB.Accessors/Contracts/IChannelConfigurationAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CB.Data.Entities;
using CB.Shared.Dtos;

namespace CB.Accessors.Contracts;

public interface IChannelConfigurationAccessor
{
Task<List<ChannelConfigurationDto>> GetAllAsync();

Task<ChannelConfigurationDto?> GetByIdAsync(string id);

Task<ChannelConfigurationDto> CreateAsync(ChannelConfiguration entity);

Task<ChannelConfigurationDto?> UpdateAsync(string id,
ChannelConfigurationDto dto);

Task<bool> DeleteAsync(string id);
}
82 changes: 82 additions & 0 deletions src/CB.Accessors/Implementations/ChannelAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using AutoMapper;
using AutoMapper.QueryableExtensions;
using CB.Accessors.Contracts;
using CB.Data;
using CB.Data.Entities;
using CB.Shared.Dtos;
using Microsoft.EntityFrameworkCore;

namespace CB.Accessors.Implementations;

public class ChannelAccessor(CbContext context,
IMapper mapper)
: IChannelAccessor
{
public Task<List<ChannelDto>> GetAllAsync() => context
.Channels
.AsNoTracking()
.ProjectTo<ChannelDto>(mapper.ConfigurationProvider)
.ToListAsync();

public Task<ChannelDto?> GetByIdAsync(string id) => context.Channels
.AsNoTracking()
.Where(g => g.Id == id)
.ProjectTo<ChannelDto>(mapper.ConfigurationProvider)
.FirstOrDefaultAsync();

public async Task<ChannelDto> CreateAsync(Channel entity)
{
entity.CreatedDate = DateTime.UtcNow;
entity.ModifiedDate = DateTime.UtcNow;

context.Channels.Add(entity);
await context
.SaveChangesAsync()
.ConfigureAwait(false);

return mapper.Map<ChannelDto>(entity);
}

public async Task<ChannelDto?> UpdateAsync(string id,
Channel updated)
{
var channel = await context
.Channels
.FindAsync(id)
.ConfigureAwait(false);

if (channel == null)
{
return null;
}

channel.DisplayName = updated.DisplayName;
channel.ModifiedDate = DateTime.UtcNow;

await context
.SaveChangesAsync()
.ConfigureAwait(false);

return mapper.Map<ChannelDto>(channel);
}

public async Task<bool> DeleteAsync(string id)
{
var channel = await context
.Channels
.FindAsync(id)
.ConfigureAwait(false);

if (channel == null)
{
return false;
}

context.Channels.Remove(channel);
await context
.SaveChangesAsync()
.ConfigureAwait(false);

return true;
}
}
81 changes: 81 additions & 0 deletions src/CB.Accessors/Implementations/ChannelConfigurationAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using AutoMapper;
using AutoMapper.QueryableExtensions;
using CB.Accessors.Contracts;
using CB.Data;
using CB.Data.Entities;
using CB.Shared.Dtos;
using Microsoft.EntityFrameworkCore;

namespace CB.Accessors.Implementations;

public class ChannelConfigurationAccessor(CbContext context,
IMapper mapper)
: IChannelConfigurationAccessor
{
public Task<List<ChannelConfigurationDto>> GetAllAsync() => context
.ChannelConfigurations
.AsNoTracking()
.ProjectTo<ChannelConfigurationDto>(mapper.ConfigurationProvider)
.ToListAsync();

public Task<ChannelConfigurationDto?> GetByIdAsync(string id) => context.ChannelConfigurations
.AsNoTracking()
.Where(g => g.GuildId == id)
.ProjectTo<ChannelConfigurationDto>(mapper.ConfigurationProvider)
.FirstOrDefaultAsync();

public async Task<ChannelConfigurationDto> CreateAsync(ChannelConfiguration entity)
{
context.ChannelConfigurations.Add(entity);
await context
.SaveChangesAsync()
.ConfigureAwait(false);

return mapper.Map<ChannelConfigurationDto>(entity);
}

public async Task<ChannelConfigurationDto?> UpdateAsync(string id,
ChannelConfigurationDto dto)
{
var channelConfiguration = await context
.ChannelConfigurations
.FindAsync(id)
.ConfigureAwait(false);

if (channelConfiguration == null)
{
return null;
}

channelConfiguration.GreetingChannelId = dto.GreetingChannelId;
channelConfiguration.GoodbyeChannelId = dto.GoodbyeChannelId;
channelConfiguration.LiveChannelId = dto.LiveChannelId;
channelConfiguration.DiscordLiveChannelId = dto.DiscordLiveChannelId;

await context
.SaveChangesAsync()
.ConfigureAwait(false);

return mapper.Map<ChannelConfigurationDto>(channelConfiguration);
}

public async Task<bool> DeleteAsync(string id)
{
var channelConfiguration = await context
.ChannelConfigurations
.FindAsync(id)
.ConfigureAwait(false);

if (channelConfiguration == null)
{
return false;
}

context.ChannelConfigurations.Remove(channelConfiguration);
await context
.SaveChangesAsync()
.ConfigureAwait(false);

return true;
}
}
36 changes: 36 additions & 0 deletions src/CB.Bot/Commands/Application/BaseSlashCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;

namespace CB.Bot.Commands.Application;

/// <summary>
/// Base class to provide easier access to helper functions for our slash command implementations.
/// </summary>
public class BaseSlashCommands() : InteractionModuleBase
{
public IGuildUser GuildUser => (IGuildUser)Context.User;
public IGuildChannel GuildChannel => (IGuildChannel)Context.Channel;
public SocketInteraction SocketInteraction => (SocketInteraction)Context.Interaction;
public SocketSlashCommand SocketSlashCommand => (SocketSlashCommand)SocketInteraction;

/// <summary>
/// Validate if the user executing the slash command is an approved admin, or not.
/// </summary>
/// <returns>Is user admin? true or false</returns>
public async Task<bool> IsUserAdmin(bool sendResponse = true)
{
// TODO MS - We need to add ApprovedAdmin code.
// This'll do for now.
var isAdmin = GuildUser.GuildPermissions.ManageGuild;

if (!isAdmin && sendResponse)
{
await SocketInteraction.FollowupAsync("Sorry, you have to be an admin to use that command.",
ephemeral: true)
.ConfigureAwait(false);
}

return isAdmin;
}
}
116 changes: 116 additions & 0 deletions src/CB.Bot/Commands/Application/ChannelSlashCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using CB.Accessors.Contracts;
using CB.Shared.Enums;
using Discord;
using Discord.Interactions;
// ReSharper disable UnusedMember.Local
// ReSharper disable UnusedMember.Global

namespace CB.Bot.Commands.Application;

[Group("channel", "Channel configuration")]
public class ChannelSlashCommands(IGuildAccessor guildAccessor,
IChannelAccessor channelAccessor,
IChannelConfigurationAccessor channelConfigurationAccessor) : BaseSlashCommands
{
[SlashCommand(
"live",
"Configure server 'Channel' settings",
false,
RunMode.Async)]
private async Task LiveChannelConfigurationAsync(IGuildChannel channel)
{
await ConfigureChannelAsync(ConfiguredChannelType.Live,
channel);
}

[SlashCommand(
"greetings",
"Configure server 'Channel' settings",
false,
RunMode.Async)]
private async Task GreetingChannelConfigurationAsync(IGuildChannel channel)
{
await ConfigureChannelAsync(ConfiguredChannelType.Greetings,
channel);
}

[SlashCommand(
"goodbyes",
"Configure server 'Channel' settings",
false,
RunMode.Async)]
private async Task GoodbyeChannelConfigurationAsync(IGuildChannel channel)
{
await ConfigureChannelAsync(ConfiguredChannelType.Goodbyes,
channel);
}

[SlashCommand(
"discordlive",
"Configure server 'Discord Live Channel' setting",
false,
RunMode.Async)]
private async Task DiscordLiveChannelConfigurationAsync(IGuildChannel channel)
{
await ConfigureChannelAsync(ConfiguredChannelType.DiscordLive,
channel);
}

private async Task ConfigureChannelAsync(ConfiguredChannelType configuredChannelType,
IGuildChannel discordChannel)
{
await SocketInteraction
.DeferAsync(true)
.ConfigureAwait(false);

if (!await IsUserAdmin())
{
return;
}

var guild = await guildAccessor
.GetByIdAsync(Context.Guild.Id.ToString())
.ConfigureAwait(false);

if (guild == null)
{
await FollowupAsync($"There was an issue setting your '{configuredChannelType}' channel. Contact support.", ephemeral: true)
.ConfigureAwait(false);
return;
}

var existingChannel = await channelAccessor.GetByIdAsync(discordChannel.Id.ToString()).ConfigureAwait(false)
?? await channelAccessor.CreateAsync(new()
{
CreatedDate = DateTime.UtcNow,
ModifiedDate = DateTime.UtcNow,
DisplayName = discordChannel.Name,
GuildId = guild.Id,
Id = discordChannel.Id.ToString()
}).ConfigureAwait(false);

switch (configuredChannelType)
{
case ConfiguredChannelType.Greetings:
guild.ChannelConfiguration.GreetingChannelId = existingChannel.Id;
break;
case ConfiguredChannelType.Goodbyes:
guild.ChannelConfiguration.GoodbyeChannelId = existingChannel.Id;
break;
case ConfiguredChannelType.Live:
guild.ChannelConfiguration.LiveChannelId = existingChannel.Id;
break;
case ConfiguredChannelType.DiscordLive:
guild.ChannelConfiguration.DiscordLiveChannelId = existingChannel.Id;
break;
}

await channelConfigurationAccessor.UpdateAsync(guild.ChannelConfiguration.GuildId,
guild.ChannelConfiguration)
.ConfigureAwait(false);

await FollowupAsync($"Your '{configuredChannelType}' channel is now #{existingChannel.DisplayName}",
ephemeral: true)
.ConfigureAwait(false);
}
}
3 changes: 3 additions & 0 deletions src/CB.Bot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
builder.Services.AddSingleton(new InteractionService(client));
builder.Services.AddSingleton<GuildInteractionService>();
builder.Services.AddScoped<IGuildAccessor, GuildAccessor>();
builder.Services.AddScoped<IChannelAccessor, ChannelAccessor>();
builder.Services.AddScoped<IChannelConfigurationAccessor, ChannelConfigurationAccessor>();
builder.Services.AddScoped<IGuildAccessor, GuildAccessor>();
builder.Services.AddScoped<IUserAccessor, UserAccessor>();

builder.Services.AddDbContext<CbContext>(options =>
Expand Down
1 change: 1 addition & 0 deletions src/CB.Data/CbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace CB.Data;
public class CbContext(DbContextOptions<CbContext> options) : DbContext(options)
{
public DbSet<Channel> Channels => Set<Channel>();
public DbSet<ChannelConfiguration> ChannelConfigurations => Set<ChannelConfiguration>();
public DbSet<Creator> Creators => Set<Creator>();
public DbSet<Guild> Guilds => Set<Guild>();
public DbSet<User> Users => Set<User>();
Expand Down
Loading
Loading