First commit that builds.
This commit is contained in:
8
.gitmodules
vendored
Normal file
8
.gitmodules
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
[submodule "messengerapi"]
|
||||
path = messengerapi
|
||||
url = https://gitea.masita.net/mc/messengerapi.git
|
||||
branch = feature/v2
|
||||
[submodule "sub/messengerapi"]
|
||||
path = sub/messengerapi
|
||||
url = https://gitea.masita.net/mc/messengerapi.git
|
||||
branch = feature/v2
|
||||
30
code/.dockerignore
Normal file
30
code/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
||||
32
code/MessengerBroker.Db/BrokerDbContext.cs
Normal file
32
code/MessengerBroker.Db/BrokerDbContext.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using MessengerBroker.Db.Model;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MessengerBroker.Db
|
||||
{
|
||||
public class BrokerDbContext : DbContext
|
||||
{
|
||||
private string connectionString;
|
||||
|
||||
public DbSet<Sync> Syncs { get; }
|
||||
|
||||
public DbSet<User> Users { get; }
|
||||
|
||||
public DbSet<Message> Messages { get; }
|
||||
|
||||
public BrokerDbContext(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.UseSqlServer(this.connectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
code/MessengerBroker.Db/MessengerBroker.Db.csproj
Normal file
14
code/MessengerBroker.Db/MessengerBroker.Db.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
15
code/MessengerBroker.Db/Model/Contracts/IForeignEntity.cs
Normal file
15
code/MessengerBroker.Db/Model/Contracts/IForeignEntity.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace MessengerBroker.Db.Model.Contracts
|
||||
{
|
||||
public interface IForeignEntity
|
||||
{
|
||||
Guid Id { get; }
|
||||
|
||||
Guid BrokerId { get; }
|
||||
|
||||
bool IsDeleted { get; }
|
||||
|
||||
Sync? FirstSync { get; }
|
||||
|
||||
Sync? LastSync { get; }
|
||||
}
|
||||
}
|
||||
9
code/MessengerBroker.Db/Model/Message.cs
Normal file
9
code/MessengerBroker.Db/Model/Message.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace MessengerBroker.Db.Model
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid BrokerId { get; set; }
|
||||
}
|
||||
}
|
||||
15
code/MessengerBroker.Db/Model/Sync.cs
Normal file
15
code/MessengerBroker.Db/Model/Sync.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace MessengerBroker.Db.Model
|
||||
{
|
||||
public class Sync
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public Guid BrokerId { get; set; }
|
||||
|
||||
public DateTime StartedUtc { get; set; }
|
||||
|
||||
public DateTime? FinishedUtc { get; set; }
|
||||
|
||||
public long? Changes { get; set; }
|
||||
}
|
||||
}
|
||||
11
code/MessengerBroker.Db/Model/User.cs
Normal file
11
code/MessengerBroker.Db/Model/User.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using MessengerBroker.Db.Model.Contracts;
|
||||
|
||||
namespace MessengerBroker.Db.Model
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid BrokerId { get; set; }
|
||||
}
|
||||
}
|
||||
45
code/MessengerBroker.sln
Normal file
45
code/MessengerBroker.sln
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36127.28
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerBroker", "MessengerBroker\MessengerBroker.csproj", "{1DE4146C-00DD-4141-84C6-4B74A2F2D0E1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerBroker.Db", "MessengerBroker.Db\MessengerBroker.Db.csproj", "{52CF80F3-A938-437B-B9DD-5E64A206A641}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Db", "Db", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessengerApi", "MessengerApi", "{D520DC2F-BD81-4588-8818-D493553ACD3D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessengerApi.Db", "..\sub\messengerapi\code\MessengerApi.Db\MessengerApi.Db.csproj", "{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1DE4146C-00DD-4141-84C6-4B74A2F2D0E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1DE4146C-00DD-4141-84C6-4B74A2F2D0E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1DE4146C-00DD-4141-84C6-4B74A2F2D0E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1DE4146C-00DD-4141-84C6-4B74A2F2D0E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{52CF80F3-A938-437B-B9DD-5E64A206A641}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{52CF80F3-A938-437B-B9DD-5E64A206A641} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{2F366BD9-AA41-7C4B-C6FA-1C5CCDF56E34} = {D520DC2F-BD81-4588-8818-D493553ACD3D}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F0F93DDE-2CDC-4A7D-9D70-A7A12B3AF9CE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
29
code/MessengerBroker/Dockerfile
Normal file
29
code/MessengerBroker/Dockerfile
Normal file
@ -0,0 +1,29 @@
|
||||
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
# This stage is used when running from VS in fast mode (Default for Debug configuration)
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
|
||||
# This stage is used to build the service project
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["MessengerBroker/MessengerBroker.csproj", "MessengerBroker/"]
|
||||
RUN dotnet restore "./MessengerBroker/MessengerBroker.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/MessengerBroker"
|
||||
RUN dotnet build "./MessengerBroker.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# This stage is used to publish the service project to be copied to the final stage
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./MessengerBroker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "MessengerBroker.dll"]
|
||||
22
code/MessengerBroker/Extensions/EntityExtensions.cs
Normal file
22
code/MessengerBroker/Extensions/EntityExtensions.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace MessengerBroker
|
||||
{
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static IQueryable<Db.Model.User> GetUsers(this IQueryable<Db.Model.User> source, Guid[] guids)
|
||||
{
|
||||
return source.Where(x => guids.Any(g => g == x.Id));
|
||||
}
|
||||
|
||||
public static IQueryable<Db.Model.User> GetUsersExcept(this IQueryable<Db.Model.User> source, IEnumerable<Guid> guids)
|
||||
{
|
||||
return source.Where(x => !guids.Any(g => g == x.Id));
|
||||
}
|
||||
|
||||
public static bool GetUserExists(this IQueryable<Db.Model.User> source, Guid id)
|
||||
{
|
||||
return source.Any(x=> x.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
code/MessengerBroker/Handlers/AuthHandler.cs
Normal file
28
code/MessengerBroker/Handlers/AuthHandler.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace MessengerBroker.Handlers
|
||||
{
|
||||
public class AuthHandler
|
||||
{
|
||||
private readonly Settings settings;
|
||||
|
||||
public AuthHandler(Settings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public Guid? Auth(HttpContext context)
|
||||
{
|
||||
var authHeader = context.Request.Headers["Authorization"].ToString();
|
||||
|
||||
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
var token = authHeader.Substring("Bearer ".Length).Trim();
|
||||
if (Guid.TryParse(token, out Guid brokerId) && this.settings.Slaves.Any(x => x.BrokerId == brokerId))
|
||||
{
|
||||
return brokerId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
code/MessengerBroker/Handlers/DataHandler.cs
Normal file
81
code/MessengerBroker/Handlers/DataHandler.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using MessengerApi.Db;
|
||||
using MessengerBroker.Db;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MessengerBroker.Handlers
|
||||
{
|
||||
public class DataHandler
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
|
||||
public Task<Tuple<MessengerApi.Db.Entities.User[], MessengerApi.Db.Entities.UserRoute[]>> GetLocalUsersAndRoutes()
|
||||
{
|
||||
var foreignUserIds = (Guid[])null;
|
||||
using(var broCtx = new BrokerDbContext(this._settings.MessengerBrokerDbConnectionString))
|
||||
{
|
||||
foreignUserIds = broCtx.Users.Select(x => x.Id).ToArray();
|
||||
}
|
||||
|
||||
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
|
||||
{
|
||||
var localUsers = apiCtx.Users
|
||||
.Where(x => !foreignUserIds.Any(f => f == x.Id))
|
||||
.ToArray();
|
||||
var localRoutes = apiCtx.UserRoutes
|
||||
.Include(x => x.From)
|
||||
.Include(x => x.To)
|
||||
.Where(x => localUsers.Any(l => l.Id == x.From.Id) && localUsers.Any(l => l.Id == x.To.Id))
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult(new Tuple<MessengerApi.Db.Entities.User[], MessengerApi.Db.Entities.UserRoute[]>(
|
||||
localUsers,
|
||||
localRoutes));
|
||||
}
|
||||
}
|
||||
|
||||
private Task<MessengerApi.Db.Entities.User[]> GetForeignUsers(Guid brokerId)
|
||||
{
|
||||
var foreignUserIds = (Guid[])null;
|
||||
using (var broCtx = new BrokerDbContext(this._settings.MessengerBrokerDbConnectionString))
|
||||
{
|
||||
foreignUserIds = broCtx.Users.Where(x=>x.BrokerId == brokerId).Select(x => x.Id).ToArray();
|
||||
}
|
||||
|
||||
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
|
||||
{
|
||||
var localUsers = apiCtx.Users
|
||||
.Where(x => !foreignUserIds.Any(f => f == x.Id))
|
||||
.ToArray();
|
||||
|
||||
return Task.FromResult(localUsers);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MessengerApi.Db.Entities.Message[]> GetMessages(Guid brokerId, DateTime sinceUtc)
|
||||
{
|
||||
var userIds = (Guid[])null;
|
||||
|
||||
if(brokerId == this._settings.BrokerId)
|
||||
{
|
||||
// Our messages.
|
||||
var users = await this.GetLocalUsersAndRoutes();
|
||||
userIds = users.Item1.Select(x => x.Id).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var users = await this.GetForeignUsers(brokerId);
|
||||
userIds = users.Select(x => x.Id).ToArray();
|
||||
}
|
||||
|
||||
using (var apiCtx = new MessengerDbContext(this._settings.MessengerApiDbConnectionString))
|
||||
{
|
||||
var messages = apiCtx.Messages
|
||||
.Include(x => x.From).Include(x => x.To)
|
||||
.Where(x => x.CreatedUtc >= sinceUtc && userIds.Contains(x.From.Id))
|
||||
.ToArray();
|
||||
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
code/MessengerBroker/Handlers/MasterHandler.cs
Normal file
196
code/MessengerBroker/Handlers/MasterHandler.cs
Normal file
@ -0,0 +1,196 @@
|
||||
using MessengerApi.Db;
|
||||
using MessengerBroker.Db;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MessengerBroker.Handlers
|
||||
{
|
||||
public class MasterHandler
|
||||
{
|
||||
private readonly Settings settings;
|
||||
|
||||
private readonly HttpClient httpClient = new HttpClient();
|
||||
|
||||
public async Task BeginSyncingWithMaster(Settings.MasterServer masterServer, CancellationToken ct = default)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
var usersRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/users");
|
||||
usersRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString());
|
||||
|
||||
var usersResponseMessage = await this.httpClient.SendAsync(usersRequest, ct);
|
||||
|
||||
if (!usersResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var usersResponse = JsonSerializer.Deserialize<Model.Http.Users.UsersResponse>(await usersResponseMessage.Content.ReadAsStringAsync());
|
||||
|
||||
using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString))
|
||||
using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString))
|
||||
{
|
||||
var updatedUsers = usersResponse.Users.Where(x => broCtx.Users.GetUserExists(x.Id)).ToList();
|
||||
updatedUsers.ForEach(async x => await this.UpdateUser(x, apiCtx));
|
||||
|
||||
var deletedUsers = broCtx.Users.GetUsersExcept(usersResponse.Users.Select(x => x.Id)).ToList();
|
||||
deletedUsers.ForEach(async x => await this.RemoveUser(x.Id, broCtx, apiCtx));
|
||||
|
||||
var addedUsers = usersResponse.Users.Where(x => !broCtx.Users.GetUserExists(x.Id)).ToList();
|
||||
addedUsers.ForEach(async x => await this.AddUser(x, masterServer.BrokerId, broCtx, apiCtx));
|
||||
|
||||
foreach (var route in usersResponse.UserRoutes
|
||||
.Where(r => apiCtx.UserRoutes
|
||||
.Include(x => x.From)
|
||||
.Include(x => x.To)
|
||||
.Any(apir => apir.Id == r.Id && (apir.From.Id != r.FromId || apir.To.Id != r.ToId))))
|
||||
{
|
||||
var existing = apiCtx.UserRoutes.Include(x => x.From).Include(x => x.To).Single(x => x.Id == route.Id);
|
||||
existing.From = apiCtx.Users.Single(x => x.Id == route.FromId);
|
||||
existing.To = apiCtx.Users.Single(x => x.Id == route.ToId);
|
||||
}
|
||||
|
||||
foreach (var deletedRoute in apiCtx.UserRoutes
|
||||
.Where(x => !usersResponse.UserRoutes.Any(r => r.Id == x.Id)))
|
||||
{
|
||||
apiCtx.UserRoutes.Remove(deletedRoute);
|
||||
}
|
||||
|
||||
foreach (var addedRoute in usersResponse.UserRoutes
|
||||
.Where(r => !apiCtx.UserRoutes.Any(x => x.Id == r.Id)))
|
||||
{
|
||||
apiCtx.UserRoutes.Add(new MessengerApi.Db.Entities.UserRoute
|
||||
{
|
||||
Id = addedRoute.Id,
|
||||
From = apiCtx.Users.Single(x => x.Id == addedRoute.FromId),
|
||||
To = apiCtx.Users.Single(x => x.Id == addedRoute.ToId)
|
||||
});
|
||||
}
|
||||
|
||||
broCtx.SaveChanges();
|
||||
apiCtx.SaveChanges();
|
||||
}
|
||||
|
||||
var messagesRequest = new HttpRequestMessage(HttpMethod.Get, $"{masterServer.BrokerApiUrl}/messages");
|
||||
messagesRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", this.settings.BrokerId.ToString());
|
||||
|
||||
var messagesResponseMessage = await this.httpClient.SendAsync(messagesRequest, ct);
|
||||
|
||||
if (!messagesResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var messagesResponse = JsonSerializer.Deserialize<Model.Http.Sync.SyncResponse>(await messagesResponseMessage.Content.ReadAsStringAsync());
|
||||
|
||||
using (var broCtx = new BrokerDbContext(this.settings.MessengerBrokerDbConnectionString))
|
||||
using (var apiCtx = new MessengerDbContext(this.settings.MessengerApiDbConnectionString))
|
||||
{
|
||||
var addedMessages = messagesResponse.Messages
|
||||
.Where(m => broCtx.Messages.Any(x => x.BrokerId == masterServer.BrokerId && x.Id == m.Id) == false)
|
||||
.ToList();
|
||||
|
||||
addedMessages.ForEach(x =>
|
||||
{
|
||||
broCtx.Messages.Add(new Db.Model.Message
|
||||
{
|
||||
BrokerId = masterServer.BrokerId,
|
||||
Id = x.Id
|
||||
});
|
||||
|
||||
apiCtx.Messages.Add(new MessengerApi.Db.Entities.Message
|
||||
{
|
||||
Id = x.Id,
|
||||
CreatedUtc = x.CreatedUtc,
|
||||
From = x.From != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.From.Value)) ?? null : null,
|
||||
To = x.To != null ? (apiCtx.Users.SingleOrDefault(u => u.Id == x.To.Value)) ?? null : null,
|
||||
IsAcknowledged = x.IsAcknowledged,
|
||||
IsDelivered = x.IsDelivered,
|
||||
Payload = x.Payload,
|
||||
PayloadId = x.PayloadId,
|
||||
PayloadLifespanInSeconds = x.PayloadLifespanInSeconds,
|
||||
PayloadTimestamp = x.PayloadTimestamp,
|
||||
PayloadType = x.PayloadType,
|
||||
});
|
||||
});
|
||||
|
||||
var existingMessages = messagesResponse.Messages
|
||||
.Except(addedMessages)
|
||||
.ToList();
|
||||
|
||||
existingMessages.ForEach(x =>
|
||||
{
|
||||
var existing = apiCtx.Messages.SingleOrDefault(a => a.Id == x.Id);
|
||||
|
||||
if(existing != null && (existing.IsDelivered != x.IsDelivered || existing.IsAcknowledged != x.IsAcknowledged))
|
||||
{
|
||||
existing.IsDelivered = x.IsDelivered;
|
||||
existing.IsAcknowledged = x.IsAcknowledged;
|
||||
}
|
||||
});
|
||||
|
||||
broCtx.SaveChanges();
|
||||
apiCtx.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveUser(Guid id, BrokerDbContext broCtx, MessengerDbContext apiCtx)
|
||||
{
|
||||
var broUser = await broCtx.Users.SingleOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (broUser != null)
|
||||
{
|
||||
broCtx.Users.Remove(broUser);
|
||||
}
|
||||
|
||||
var apiUser = await apiCtx.Users.SingleOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (apiUser != null)
|
||||
{
|
||||
apiCtx.Users.Remove(apiUser);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddUser(Model.Http.Users.UsersResponse.User user, Guid brokerId, BrokerDbContext broCtx, MessengerDbContext apiCtx)
|
||||
{
|
||||
if (broCtx.Users.GetUserExists(user.Id) == false)
|
||||
{
|
||||
broCtx.Users.Add(new Db.Model.User
|
||||
{
|
||||
Id = user.Id,
|
||||
BrokerId = brokerId
|
||||
});
|
||||
}
|
||||
|
||||
if (apiCtx.Users.Any(x => x.Id == user.Id))
|
||||
{
|
||||
await this.UpdateUser(user, apiCtx);
|
||||
}
|
||||
else
|
||||
{
|
||||
await apiCtx.Users.AddAsync(new MessengerApi.Db.Entities.User
|
||||
{
|
||||
Id = user.Id,
|
||||
ApiKey = user.ApiKey,
|
||||
CanReceive = user.CanReceive,
|
||||
CanSend = user.CanSend,
|
||||
IsEnabled = user.IsEnabled,
|
||||
Name = user.Name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateUser(Model.Http.Users.UsersResponse.User user, MessengerDbContext apiCtx)
|
||||
{
|
||||
var apiUser = await apiCtx.Users.SingleAsync(x => x.Id == user.Id);
|
||||
apiUser.ApiKey = user.ApiKey;
|
||||
apiUser.CanReceive = user.CanReceive;
|
||||
apiUser.CanSend = user.CanSend;
|
||||
apiUser.IsEnabled = user.IsEnabled;
|
||||
apiUser.Name = user.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
code/MessengerBroker/MessengerBroker.csproj
Normal file
19
code/MessengerBroker/MessengerBroker.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1-Preview.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\sub\messengerapi\code\MessengerApi.Db\MessengerApi.Db.csproj" />
|
||||
<ProjectReference Include="..\MessengerBroker.Db\MessengerBroker.Db.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
code/MessengerBroker/MessengerBroker.http
Normal file
6
code/MessengerBroker/MessengerBroker.http
Normal file
@ -0,0 +1,6 @@
|
||||
@MessengerBroker_HostAddress = http://localhost:5048
|
||||
|
||||
GET {{MessengerBroker_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
42
code/MessengerBroker/Model/Http/Sync.cs
Normal file
42
code/MessengerBroker/Model/Http/Sync.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace MessengerBroker.Model.Http
|
||||
{
|
||||
public class Sync
|
||||
{
|
||||
public class SyncRequest
|
||||
{
|
||||
public Guid BrokerId { get; set; }
|
||||
|
||||
public DateTime SinceUtc { get; set; }
|
||||
}
|
||||
|
||||
public class SyncResponse
|
||||
{
|
||||
public Message[] Messages { get; set; }
|
||||
|
||||
public class Message
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
|
||||
public Guid? From { get; set; }
|
||||
|
||||
public Guid? To { get; set; }
|
||||
|
||||
public bool IsDelivered { get; set; }
|
||||
|
||||
public bool IsAcknowledged { get; set; }
|
||||
|
||||
public string PayloadId { get; set; }
|
||||
|
||||
public string PayloadType { get; set; }
|
||||
|
||||
public string Payload { get; set; }
|
||||
|
||||
public DateTime? PayloadTimestamp { get; set; }
|
||||
|
||||
public int? PayloadLifespanInSeconds { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
code/MessengerBroker/Model/Http/Users.cs
Normal file
36
code/MessengerBroker/Model/Http/Users.cs
Normal file
@ -0,0 +1,36 @@
|
||||
namespace MessengerBroker.Model.Http
|
||||
{
|
||||
public class Users
|
||||
{
|
||||
public class UsersResponse
|
||||
{
|
||||
public User[] Users { get; set; }
|
||||
|
||||
public UserRoute[] UserRoutes { get; set; }
|
||||
|
||||
public class User
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid ApiKey { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public bool CanSend { get; set; }
|
||||
|
||||
public bool CanReceive { get; set; }
|
||||
}
|
||||
|
||||
public class UserRoute
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid FromId { get; set; }
|
||||
|
||||
public Guid ToId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
code/MessengerBroker/Program.cs
Normal file
102
code/MessengerBroker/Program.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using MessengerBroker.Handlers;
|
||||
using MessengerBroker.Model.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MessengerBroker
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var settings = (Settings)null;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/users", async (HttpContext httpContext) =>
|
||||
{
|
||||
var authHandler = app.Services.GetService<AuthHandler>();
|
||||
var brokerId = authHandler.Auth(httpContext);
|
||||
|
||||
if(brokerId == null)
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
|
||||
var dataHandler = app.Services.GetService<DataHandler>();
|
||||
var usersAndRoutes = await dataHandler.GetLocalUsersAndRoutes();
|
||||
|
||||
var response = new Users.UsersResponse
|
||||
{
|
||||
Users = usersAndRoutes.Item1.Select(x => new Users.UsersResponse.User
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
ApiKey = x.ApiKey,
|
||||
CanReceive = x.CanReceive,
|
||||
CanSend = x.CanSend,
|
||||
IsEnabled = x.IsEnabled,
|
||||
}).ToArray(),
|
||||
UserRoutes = usersAndRoutes.Item2.Select(x => new Users.UsersResponse.UserRoute
|
||||
{
|
||||
Id = x.Id,
|
||||
FromId = x.From.Id,
|
||||
ToId = x.To.Id
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
return Results.Json(response);
|
||||
});
|
||||
|
||||
app.MapGet("/sync", async (HttpContext httpContext, [AsParameters] Sync.SyncRequest request) =>
|
||||
{
|
||||
var authHandler = app.Services.GetService<AuthHandler>();
|
||||
var brokerId = authHandler.Auth(httpContext);
|
||||
|
||||
if(brokerId == null)
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
else if(request.BrokerId != brokerId && request.BrokerId != settings.BrokerId)
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
|
||||
var dataHandler = app.Services.GetService<DataHandler>();
|
||||
var messages = await dataHandler.GetMessages(request.BrokerId, request.SinceUtc);
|
||||
|
||||
var response = new Sync.SyncResponse
|
||||
{
|
||||
Messages = messages.Select(x => new Sync.SyncResponse.Message
|
||||
{
|
||||
Id = x.Id,
|
||||
CreatedUtc = x.CreatedUtc,
|
||||
From = x.From.Id,
|
||||
To = x.To.Id,
|
||||
IsAcknowledged = x.IsAcknowledged,
|
||||
IsDelivered = x.IsDelivered,
|
||||
Payload = x.Payload,
|
||||
PayloadId = x.PayloadId,
|
||||
PayloadLifespanInSeconds = x.PayloadLifespanInSeconds,
|
||||
PayloadTimestamp = x.PayloadTimestamp,
|
||||
PayloadType = x.PayloadType
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
return Results.Json(response);
|
||||
});
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var handler = app.Services.GetService<MasterHandler>();
|
||||
|
||||
foreach(var master in settings.Masters)
|
||||
{
|
||||
_ = handler.BeginSyncingWithMaster(master, cts.Token);
|
||||
}
|
||||
});
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
code/MessengerBroker/Properties/launchSettings.json
Normal file
22
code/MessengerBroker/Properties/launchSettings.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5048"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker",
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||
},
|
||||
"publishAllPorts": true,
|
||||
"useSSL": false
|
||||
}
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json"
|
||||
}
|
||||
36
code/MessengerBroker/Settings.cs
Normal file
36
code/MessengerBroker/Settings.cs
Normal file
@ -0,0 +1,36 @@
|
||||
namespace MessengerBroker
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection string to Messenger API DB.
|
||||
/// </summary>
|
||||
public string MessengerApiDbConnectionString { get; set; }
|
||||
|
||||
public string MessengerBrokerDbConnectionString { get; set; }
|
||||
|
||||
public Guid BrokerId { get; set; }
|
||||
|
||||
public MasterServer[] Masters { get; set; }
|
||||
|
||||
public SlaveServer[] Slaves { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A server that we are a slave to. If this server goes down, their users will alternate to us and we have to provide service during outage. We pull data from this server.
|
||||
/// </summary>
|
||||
public class MasterServer
|
||||
{
|
||||
public string BrokerApiUrl { get; set; }
|
||||
|
||||
public Guid BrokerId { set; get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A server that slaves to us in case of our own outage. They pull from us.
|
||||
/// </summary>
|
||||
public class SlaveServer
|
||||
{
|
||||
public Guid BrokerId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
8
code/MessengerBroker/appsettings.Development.json
Normal file
8
code/MessengerBroker/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
code/MessengerBroker/appsettings.json
Normal file
9
code/MessengerBroker/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
1
sub/messengerapi
Submodule
1
sub/messengerapi
Submodule
Submodule sub/messengerapi added at 1f3952b143
Reference in New Issue
Block a user