diff --git a/Alchegos.Core.csproj b/Alchegos.Core.csproj
index 17b910f..e620ce3 100644
--- a/Alchegos.Core.csproj
+++ b/Alchegos.Core.csproj
@@ -3,7 +3,17 @@
net9.0
enable
- enable
+ disable
+
+
+
+
+
+
+ ..\..\..\..\.nuget\packages\microsoft.extensions.options\9.0.2\lib\net9.0\Microsoft.Extensions.Options.dll
+
+
+
diff --git a/Class1.cs b/Class1.cs
deleted file mode 100644
index 220e893..0000000
--- a/Class1.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Alchegos.Core;
-
-public class Class1
-{
-}
\ No newline at end of file
diff --git a/src/Attributes/AutoRegister.cs b/src/Attributes/AutoRegister.cs
new file mode 100644
index 0000000..e762e19
--- /dev/null
+++ b/src/Attributes/AutoRegister.cs
@@ -0,0 +1,6 @@
+namespace Alchegos.Core.Attributes;
+[AttributeUsage(AttributeTargets.Class)]
+public class AutoRegister : Attribute
+{
+
+}
\ No newline at end of file
diff --git a/src/DataStructures/Exchange.cs b/src/DataStructures/Exchange.cs
new file mode 100644
index 0000000..2f606fc
--- /dev/null
+++ b/src/DataStructures/Exchange.cs
@@ -0,0 +1,35 @@
+using System.Threading.Channels;
+using RabbitMQ.Client;
+
+namespace Alchegos.Core.DataStructures;
+
+public class Exchange
+{
+ public string Name { get; set; }
+ public string ExchangeType { get; set; }
+ public bool Durable { get; set; }
+ public bool AutoDelete { get; set; }
+ public IDictionary Arguments { get; set; }
+ public HashSet Queues { get; set; } = new();
+
+ public async Task CreateAsync(IChannel channel)
+ {
+ await channel.ExchangeDeclareAsync(Name, ExchangeType, Durable, AutoDelete, Arguments);
+ foreach (MessageQueue queue in Queues)
+ {
+ await channel.QueueDeclareAsync(
+ queue: queue.Name,
+ durable: queue.Durable,
+ exclusive: queue.Exclusive,
+ autoDelete: queue.AutoDelete,
+ arguments: queue.Arguments
+ );
+ await channel.QueueBindAsync(
+ queue: queue.Name,
+ exchange: Name,
+ routingKey: queue.Name,
+ arguments: queue.Arguments
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataStructures/MessageQueue.cs b/src/DataStructures/MessageQueue.cs
new file mode 100644
index 0000000..d53d6bf
--- /dev/null
+++ b/src/DataStructures/MessageQueue.cs
@@ -0,0 +1,10 @@
+namespace Alchegos.Core.DataStructures;
+
+public class MessageQueue
+{
+ public string Name { get; set; }
+ public bool Exclusive { get; set; }
+ public bool AutoDelete { get; set; }
+ public bool Durable { get; set; }
+ public IDictionary Arguments { get; set; }
+}
\ No newline at end of file
diff --git a/src/GlobalRegistry.cs b/src/GlobalRegistry.cs
new file mode 100644
index 0000000..15d7925
--- /dev/null
+++ b/src/GlobalRegistry.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+using Alchegos.Core.Attributes;
+using Alchegos.Core.DataStructures;
+
+namespace Alchegos.Core;
+
+public class GlobalRegistry
+{
+ private static readonly Lock _lock = new Lock();
+
+ private GlobalRegistry()
+ {
+
+ }
+
+ private static readonly Lazy BackingInstance = new(() => new GlobalRegistry());
+ public static GlobalRegistry Instance {
+ get
+ {
+ lock (_lock)
+ {
+ return BackingInstance.Value;
+ }
+ }
+ }
+
+ public void Start()
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ IEnumerable types = assembly
+ .GetTypes()
+ .Where(t => t.IsClass && t.GetCustomAttributes(typeof(AutoRegister), false).Length > 0);
+ foreach (Type t in types)
+ {
+ MethodInfo registerMethod = t.GetMethod("Register", BindingFlags.Static | BindingFlags.Public);
+ registerMethod?.Invoke(null, null);
+ }
+ }
+
+ public HashSet Exchanges { get; set; } = new();
+
+}
\ No newline at end of file
diff --git a/src/Registers/ExchangeRegister.cs b/src/Registers/ExchangeRegister.cs
new file mode 100644
index 0000000..1887999
--- /dev/null
+++ b/src/Registers/ExchangeRegister.cs
@@ -0,0 +1,64 @@
+using Alchegos.Core.Attributes;
+using Alchegos.Core.DataStructures;
+using RabbitMQ.Client;
+
+namespace Alchegos.Core.Registers;
+[AutoRegister]
+public static class ExchangeRegister
+{
+
+ private static readonly HashSet WebhookEvents = [
+ "create",
+ "delete",
+ "fork",
+ "push",
+ "issue_assign",
+ "issue_label",
+ "issue_milestone",
+ "issue_comment",
+ "pull_request",
+ "pull_request_assign",
+ "pull_request_label",
+ "pull_request_milestone",
+ "pull_request_comment",
+ "pull_request_review_approved",
+ "pull_request_review_rejected",
+ "pull_request_review_comment",
+ "pull_request_sync",
+ "pull_request_review_request",
+ "wiki",
+ "repository",
+ "release",
+ "package",
+ "status",
+ "schedule"
+ ];
+
+ public static void Register()
+ {
+ Exchange giteaExchange = new Exchange
+ {
+ Name = "gitea.webhook.exchange",
+ ExchangeType = ExchangeType.Topic,
+ AutoDelete = false,
+ Durable = true,
+ Queues = new()
+ };
+
+ foreach (string webhookEvent in WebhookEvents)
+ {
+ giteaExchange.Queues.Add(new MessageQueue
+ {
+ Name = webhookEvent,
+ Exclusive = false,
+ AutoDelete = false,
+ Durable = true
+ });
+ }
+
+ GlobalRegistry.Instance.Exchanges =
+ [
+ giteaExchange
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/Services/RabbitMQ/IRabbitPublisher.cs b/src/Services/RabbitMQ/IRabbitPublisher.cs
new file mode 100644
index 0000000..d2cbc96
--- /dev/null
+++ b/src/Services/RabbitMQ/IRabbitPublisher.cs
@@ -0,0 +1,6 @@
+namespace Alchegos.Core.Services.RabbitMQ;
+
+public interface IRabbitPublisher
+{
+ Task PublishAsync(string exchange, string routingKey, string message);
+}
\ No newline at end of file
diff --git a/src/Services/RabbitMQ/IRabbitService.cs b/src/Services/RabbitMQ/IRabbitService.cs
new file mode 100644
index 0000000..902b84c
--- /dev/null
+++ b/src/Services/RabbitMQ/IRabbitService.cs
@@ -0,0 +1,8 @@
+using RabbitMQ.Client;
+
+namespace Alchegos.Core.Services.RabbitMQ;
+
+public interface IRabbitService
+{
+ Task GetChannelAsync();
+}
diff --git a/src/Services/RabbitMQ/RabbitConnectionOptions.cs b/src/Services/RabbitMQ/RabbitConnectionOptions.cs
new file mode 100644
index 0000000..ba1ba01
--- /dev/null
+++ b/src/Services/RabbitMQ/RabbitConnectionOptions.cs
@@ -0,0 +1,10 @@
+namespace Alchegos.Core.Services.RabbitMQ;
+
+public class RabbitConnectionOptions
+{
+
+ public string HostName { get; set; } = "localhost";
+ public string UserName { get; set; } = "guest";
+ public string Password { get; set; } = "guest";
+ public int Port { get; set; } = 5672;
+}
\ No newline at end of file
diff --git a/src/Services/RabbitMQ/RabbitPublisher.cs b/src/Services/RabbitMQ/RabbitPublisher.cs
new file mode 100644
index 0000000..3cac0c8
--- /dev/null
+++ b/src/Services/RabbitMQ/RabbitPublisher.cs
@@ -0,0 +1,28 @@
+using System.Text;
+using RabbitMQ.Client;
+
+namespace Alchegos.Core.Services.RabbitMQ;
+
+public class RabbitPublisher(IRabbitService service) : IRabbitPublisher
+{
+ private IRabbitService RabbitService { get; } = service;
+ private readonly SemaphoreSlim _lock = new(1, 1);
+ public async Task PublishAsync(string exchange, string routingKey, string message)
+ {
+ ReadOnlyMemory body = Encoding.UTF8.GetBytes(message).AsMemory();
+ await _lock.WaitAsync();
+ try
+ {
+ IChannel channel = await RabbitService.GetChannelAsync();
+ await channel.BasicPublishAsync(
+ exchange: exchange,
+ routingKey: routingKey,
+ body: body
+ );
+ }
+ finally
+ {
+ _lock.Release();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Services/RabbitMQ/RabbitService.cs b/src/Services/RabbitMQ/RabbitService.cs
new file mode 100644
index 0000000..a7ed8fa
--- /dev/null
+++ b/src/Services/RabbitMQ/RabbitService.cs
@@ -0,0 +1,69 @@
+using Alchegos.Core.DataStructures;
+using Microsoft.Extensions.Options;
+using RabbitMQ.Client;
+
+namespace Alchegos.Core.Services.RabbitMQ;
+
+public class RabbitService : IAsyncDisposable, IRabbitService
+{
+ private readonly SemaphoreSlim _initLock = new (1, 1);
+ private Lazy LazyInit { get; init; }
+
+ public async Task EnsureInitializedAsync()
+ {
+ if (Connection is not null)
+ return;
+ await _initLock.WaitAsync();
+ try
+ {
+ if (Connection is null)
+ await LazyInit.Value;
+ }
+ finally
+ {
+ _initLock.Release();
+ }
+ }
+
+ private IConnection Connection { get; set; }
+
+ public async Task GetChannelAsync()
+ {
+ await EnsureInitializedAsync();
+ return await Connection.CreateChannelAsync();
+ }
+
+ public RabbitService(IOptions options)
+ {
+ ConnectionFactory = new()
+ {
+ HostName = options.Value.HostName,
+ Port = options.Value.Port,
+ UserName = options.Value.UserName,
+ Password = options.Value.Password,
+ };
+ LazyInit = new (InitAsync);
+ }
+
+ private ConnectionFactory ConnectionFactory { get; set; }
+ private async Task InitAsync()
+ {
+ if (Connection is null)
+ {
+ Connection = await ConnectionFactory.CreateConnectionAsync();
+ IChannel channel = await Connection.CreateChannelAsync();
+ foreach (Exchange exchange in GlobalRegistry.Instance.Exchanges)
+ await exchange.CreateAsync(channel);
+ }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (Connection is not null)
+ {
+ await Connection.CloseAsync();
+ await Connection.DisposeAsync();
+ Connection = null;
+ }
+ }
+}
\ No newline at end of file