diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 64e522bab..5246e6c81 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -31,7 +31,6 @@ jobs: steps: - name: Clone yavsc run: | - set -e cd /src git clone https://forgejo.pschneider.fr/notazof/yavsc.git _src cd _src @@ -45,10 +44,12 @@ jobs: run: | echo "🔍 Scanning for secrets..." cd /src/_src && dotnet tool restore && dotnet picket git --verbose --redact --exit-code 1 --log-opts -n1 \ + && echo "✅ Success !" || echo "❌ Fail ($?)!" - name: Test run: | echo "🚀 Lancement des tests..." cd /src/_src && dotnet test \ --verbosity normal \ --filter="Category!=Platform-Android" \ - --logger "xunit;LogFileName=test-results.xml" + --logger "xunit;LogFileName=test-results.xml" \ + && echo "✅ Success !" || { echo "❌ Fail ($?)!"; exit 1; } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f5bcc21a2..82721d8a2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -66,28 +66,6 @@ "isDefault": false } }, - { - "label": "test api backend (npgsql)", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": [ - "test", - "Yavsc.Api.Test.csproj", - "-v", - "minimal" - ], - "options": { - "cwd": "src/Yavsc.Api.Test", - "env": { - "YAVSC_API_TEST_DB_PROVIDER": "npgsql", - } - }, - "group": { - "kind": "test", - "isDefault": false - } - }, { "label": "build-webapi", "type": "process", diff --git a/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs index 4f9d457a5..7a7d17df6 100644 --- a/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs @@ -12,9 +12,6 @@ namespace PostIt.ViewModels.Commands; public partial class RdvViewModel : BillingCommandPageViewModel { - private long? _existingLocationId; - private bool _hydratingExistingQuery; - public override string SupportMessage => "Complétez les informations du rendez-vous puis postez la commande."; [ObservableProperty] @@ -59,7 +56,6 @@ public partial class RdvViewModel : BillingCommandPageViewModel protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery) { - _hydratingExistingQuery = true; ExistingQueryId = existingQuery.Id; CommandStatus = existingQuery.Status; Consent = existingQuery.Consent; @@ -74,18 +70,11 @@ public partial class RdvViewModel : BillingCommandPageViewModel if (existingQuery.Location is not null) { - _existingLocationId = existingQuery.Location.Id; Address = existingQuery.Location.Address ?? string.Empty; SuggestedAddress = string.Empty; Latitude = existingQuery.Location.Latitude; Longitude = existingQuery.Location.Longitude; } - else - { - _existingLocationId = null; - } - - _hydratingExistingQuery = false; this.SetInfoStatus($"Commande #{existingQuery.Id} chargée."); } @@ -128,22 +117,20 @@ public partial class RdvViewModel : BillingCommandPageViewModel } } - protected static BillingLocationDto BuildLocationPayload(string address, double? latitude, double? longitude, long? locationId = null) + protected static object BuildLocationPayload(string address, double? latitude, double? longitude) { if (latitude.HasValue && longitude.HasValue) { - return new BillingLocationDto + return new { - Id = locationId, Address = address, Latitude = latitude.Value, Longitude = longitude.Value, }; } - return new BillingLocationDto + return new { - Id = locationId, Address = address, }; } @@ -236,30 +223,6 @@ public partial class RdvViewModel : BillingCommandPageViewModel OnPropertyChanged(nameof(EventDateSelection)); } - partial void OnAddressChanged(string value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - - partial void OnLatitudeChanged(double? value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - - partial void OnLongitudeChanged(double? value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - protected override async Task SubmitAsync() { @@ -294,7 +257,7 @@ public partial class RdvViewModel : BillingCommandPageViewModel try { var address = Address.Trim(); - var locationPayload = BuildLocationPayload(address, Latitude, Longitude, IsEditingExisting ? _existingLocationId : null); + var locationPayload = BuildLocationPayload(address, Latitude, Longitude); var payload = new BillingQueryDetailsDto { @@ -307,7 +270,12 @@ public partial class RdvViewModel : BillingCommandPageViewModel Status = CommandStatus, Reason = Reason.Trim(), AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(), - Location = locationPayload + Location = new BillingLocationDto + { + Address = address, + Latitude = Latitude, + Longitude = Longitude, + } }; if (IsEditingExisting) diff --git a/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs b/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs index c87af390c..972c64350 100644 --- a/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs +++ b/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs @@ -23,8 +23,8 @@ public sealed class StatusNotice (Glyph, Background, BorderBrush, Foreground) = severity switch { - StatusSeverity.Error => ("!", "#7F1D1D", "#C62828", "#e1f0f6"), - StatusSeverity.Warning => ("~", "#7C4A03", "#E6A700", "#eaeaea"), + StatusSeverity.Error => ("!", "#FDECEA", "#C62828", "#7F1D1D"), + StatusSeverity.Warning => ("~", "#FFF8E1", "#E6A700", "#7C4A03"), _ => ("i", "#E8F0FE", "#5B8DEF", "#1E3A8A"), }; } diff --git a/src/Yavsc.Api.Client/BillingApiClient.cs b/src/Yavsc.Api.Client/BillingApiClient.cs index a9e9deda6..3907dda25 100644 --- a/src/Yavsc.Api.Client/BillingApiClient.cs +++ b/src/Yavsc.Api.Client/BillingApiClient.cs @@ -164,7 +164,6 @@ public sealed class BillingApiClient ? null : new BillingLocationDto { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, @@ -192,7 +191,6 @@ public sealed class BillingApiClient ? null : new BillingLocationDto { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, @@ -222,7 +220,6 @@ public sealed class BillingApiClient ? null : new BillingLocationDto { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, @@ -316,11 +313,6 @@ public sealed class BillingApiClient ["Address"] = location.Address, }; - if (location.Id.HasValue && location.Id.Value > 0) - { - payload["Id"] = location.Id.Value; - } - if (location.Latitude.HasValue) { payload["Latitude"] = location.Latitude.Value; @@ -336,7 +328,6 @@ public sealed class BillingApiClient private sealed class BillingLocationResponse { - public long Id { get; set; } public string? Address { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } diff --git a/src/Yavsc.Api.Client/BlogApiClient.cs b/src/Yavsc.Api.Client/BlogApiClient.cs index 3de5b37ec..6c0f538ea 100644 --- a/src/Yavsc.Api.Client/BlogApiClient.cs +++ b/src/Yavsc.Api.Client/BlogApiClient.cs @@ -1,5 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Yavsc.Blogspot; namespace Yavsc.Api.Client; diff --git a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs b/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs index eb33ba816..6d658ce49 100644 --- a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs +++ b/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; using Yavsc; namespace Yavsc.Api.Client; @@ -30,14 +29,7 @@ public sealed class BillingQueryDetailsDto public sealed class BillingLocationDto { - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public long? Id { get; set; } - public string Address { get; set; } = string.Empty; - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public double? Latitude { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public double? Longitude { get; set; } } diff --git a/src/Yavsc.Api.Test/BillingControllerTests.cs b/src/Yavsc.Api.Test/BillingControllerTests.cs index 43c2df729..a6445c776 100644 --- a/src/Yavsc.Api.Test/BillingControllerTests.cs +++ b/src/Yavsc.Api.Test/BillingControllerTests.cs @@ -1,13 +1,11 @@ using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Yavsc.Api.Test.Fixtures; using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; using Yavsc.Models.Workflow; using Yavsc.Tests.Shared; @@ -103,126 +101,6 @@ public sealed class BillingControllerTests : IClassFixture Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv); } - [Fact] - public async Task GetProviderOngoingCommands_ignores_rows_with_invalid_discriminator() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedActivityGraph(); - - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - - db.Database.ExecuteSqlInterpolated($@" -INSERT INTO ""NominativeServiceCommand"" -(""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"") -VALUES -({"dev"}, {"bob"}, {true}, {DateTime.UtcNow.AddMinutes(-5)}, {DateTime.UtcNow.AddMinutes(-4)}, {"Legacy malformed row"}, {""}, {"alice"}, {(int)QueryStatus.Accepted}, {"alice"}, {"alice"}); -"); - - var location = db.Locations.Single(); - db.RdvQueries.Add(new RdvQuery - { - ActivityCode = "dev", - ClientId = "bob", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow.AddMinutes(-3), - DateModified = DateTime.UtcNow.AddMinutes(-2), - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Commande valide", - Status = QueryStatus.InProgress, - Description = "Commande fournisseur valide", - }); - - db.SaveChanges(); - } - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.NotEmpty(payload!); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv && item.PerformerId == "alice"); - Assert.DoesNotContain(payload!, item => string.IsNullOrWhiteSpace(item.BillingCode)); - } - - [Fact] - public async Task GetProviderOngoingCommands_returns_haircut_and_grouped_haircut_requests() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedHaircutGraph(); - - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - db.UserActivities.Add(new UserActivity - { - UserId = "alice", - DoesCode = "brush", - Weight = 50, - }); - db.UserActivities.Add(new UserActivity - { - UserId = "alice", - DoesCode = "mbrush", - Weight = 50, - }); - db.CommandForm.Add(new CommandForm - { - ActivityCode = "brush", - ActionName = BillingCodes.Brush, - Title = "Brush", - }); - db.CommandForm.Add(new CommandForm - { - ActivityCode = "mbrush", - ActionName = BillingCodes.MBrush, - Title = "MBrush", - }); - db.SaveChanges(); - } - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Brush && item.PerformerId == "alice"); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.MBrush && item.PerformerId == "alice"); - } - - [Fact] - public async Task GetProviderOngoingCommands_excludes_requests_outside_performer_declared_activities() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedHaircutGraph(); - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.DoesNotContain(payload!, item => item.BillingCode == BillingCodes.Brush); - Assert.DoesNotContain(payload!, item => item.BillingCode == BillingCodes.MBrush); - } - private sealed class ProviderOngoingCommandDto { public long Id { get; set; } diff --git a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs b/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs index 29d282a32..96c220bff 100644 --- a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs +++ b/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs @@ -1,10 +1,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; -using Npgsql; using Yavsc.Controllers; using Yavsc.Interfaces.Workflow; using Yavsc.Models; @@ -20,44 +18,28 @@ namespace Yavsc.Api.Test.Fixtures; public sealed class ApiWebServerFixture : WebHostFixture { - private const string DbProviderEnvVar = "YAVSC_API_TEST_DB_PROVIDER"; - private const string NpgsqlAdminConnectionEnvVar = "YAVSC_API_TEST_NPGSQL_ADMIN_CONNECTION"; - private const string DedicatedNpgsqlDatabaseName = "yavscTestDb"; - private const string DefaultDevelopmentConnectionString = "Server=localhost;Port=5432;Database=yavscdev;Username=yavscdev;Password=8*5idas;Include Error Detail=true"; - protected override int HttpsPort => 5104; private static SqliteConnection? _sharedSqliteConnection; private static readonly object _sqliteLock = new(); - private static readonly object _npgsqlLock = new(); - private static string? _sharedNpgsqlConnectionString; protected override WebApplication BuildApp(WebApplicationBuilder builder) { - if (UseNpgsqlProvider()) + SqliteConnection sharedConnection; + lock (_sqliteLock) { - var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated(); - builder.Services.AddDbContext(opt => - opt.UseNpgsql(npgsqlConnectionString)); - } - else - { - SqliteConnection sharedConnection; - lock (_sqliteLock) + if (_sharedSqliteConnection is null) { - if (_sharedSqliteConnection is null) - { - _sharedSqliteConnection = new SqliteConnection( - "Data Source=YavscApiTests;Mode=Memory;Cache=Shared"); - _sharedSqliteConnection.Open(); - } - sharedConnection = _sharedSqliteConnection; + _sharedSqliteConnection = new SqliteConnection( + "Data Source=YavscApiTests;Mode=Memory;Cache=Shared"); + _sharedSqliteConnection.Open(); } - - builder.Services.AddDbContext(opt => - opt.UseSqlite(sharedConnection)); + sharedConnection = _sharedSqliteConnection; } + builder.Services.AddDbContext(opt => + opt.UseSqlite(sharedConnection)); + builder.Services.AddControllers() .AddApplicationPart(typeof(ActivityApiController).Assembly); @@ -108,76 +90,6 @@ public sealed class ApiWebServerFixture : WebHostFixture public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal)); - public static bool UseNpgsqlProvider() - => string.Equals( - Environment.GetEnvironmentVariable(DbProviderEnvVar), - "npgsql", - StringComparison.OrdinalIgnoreCase); - - private static string EnsureNpgsqlDatabaseCreated() - { - lock (_npgsqlLock) - { - if (!string.IsNullOrWhiteSpace(_sharedNpgsqlConnectionString)) - { - return _sharedNpgsqlConnectionString; - } - - var adminConnectionString = BuildAdminConnectionString(); - var databaseName = DedicatedNpgsqlDatabaseName; - - using (var adminConnection = new NpgsqlConnection(adminConnectionString)) - { - adminConnection.Open(); - using var existsCommand = adminConnection.CreateCommand(); - existsCommand.CommandText = "SELECT 1 FROM pg_database WHERE datname = @databaseName"; - existsCommand.Parameters.AddWithValue("databaseName", databaseName); - - if (existsCommand.ExecuteScalar() is null) - { - using var createCommand = adminConnection.CreateCommand(); - createCommand.CommandText = $"CREATE DATABASE \"{databaseName}\""; - createCommand.ExecuteNonQuery(); - } - } - - var testConnectionBuilder = new NpgsqlConnectionStringBuilder(adminConnectionString) - { - Database = databaseName, - Pooling = false, - IncludeErrorDetail = true - }; - - _sharedNpgsqlConnectionString = testConnectionBuilder.ToString(); - return _sharedNpgsqlConnectionString; - } - } - - private static string BuildAdminConnectionString() - { - var configured = Environment.GetEnvironmentVariable(NpgsqlAdminConnectionEnvVar); - var source = string.IsNullOrWhiteSpace(configured) - ? DefaultDevelopmentConnectionString - : configured; - - var builder = new NpgsqlConnectionStringBuilder(source) - { - Pooling = false, - IncludeErrorDetail = true - }; - - if (string.IsNullOrWhiteSpace(configured)) - { - builder.Database = "postgres"; - } - else if (string.IsNullOrWhiteSpace(builder.Database)) - { - builder.Database = "postgres"; - } - - return builder.ToString(); - } - private sealed class NoopMessageSender : IYavscMessageSender { public Task NotifyBookQueryAsync(IEnumerable connectionIds, RdvQueryEvent ev) @@ -198,7 +110,7 @@ public sealed class ApiWebServerFixture : WebHostFixture using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); - ResetDatabase(db); + db.Database.EnsureDeleted(); db.Database.EnsureCreated(); var user = new ApplicationUser @@ -299,94 +211,6 @@ public sealed class ApiWebServerFixture : WebHostFixture db.SaveChanges(); } - private static void ResetDatabase(ApplicationDbContext db) - { - if (UseNpgsqlProvider()) - { - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - - db.SaveChanges(); - return; - } - - db.Database.EnsureDeleted(); - } - - - - private static IReadOnlyList GetDeletionOrder(IModel model) - { - var entityTypes = model - .GetEntityTypes() - .Where(et => - et.ClrType is not null && - !et.IsOwned() && - et.FindPrimaryKey() is not null) - .ToArray(); - - var included = new HashSet(entityTypes); - var dependencies = new Dictionary>(); - - foreach (var entityType in entityTypes) - { - var principals = entityType - .GetForeignKeys() - .Where(fk => !fk.IsOwnership) - .Select(fk => fk.PrincipalEntityType) - .Where(included.Contains) - .ToHashSet(); - - dependencies[entityType] = principals; - } - - var queue = new Queue( - dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key)); - - var order = new List(entityTypes.Length); - - while (queue.Count > 0) - { - var current = queue.Dequeue(); - if (!order.Contains(current)) - { - order.Add(current); - } - - foreach (var kvp in dependencies) - { - if (!kvp.Value.Remove(current) || kvp.Value.Count != 0) - { - continue; - } - - if (!order.Contains(kvp.Key) && !queue.Contains(kvp.Key)) - { - queue.Enqueue(kvp.Key); - } - } - } - - // If cycles remain (rare), append unresolved types last and rely on DB cascades. - foreach (var entityType in entityTypes) - { - if (!order.Contains(entityType)) - { - order.Add(entityType); - } - } - - return order; - } - public void ResetAndSeedRdvQueryGraph() { ResetAndSeedActivityGraph(); diff --git a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs b/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs index c3e4aad2b..668814239 100644 --- a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs +++ b/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs @@ -143,87 +143,4 @@ public sealed class RdvQueryApiControllerTests : IClassFixture(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotNull(created!.Location); - Assert.True(created.Location.Id > 0); - Assert.NotEqual(999999L, created.Location.Id); - Assert.Equal("alice", created.ClientId); - } - - [Fact] - public async Task PostQuery_without_location_returns_bad_request() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(1), - Reason = "Rendez-vous sans location", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode); - } - - [Fact] - public async Task PostQuery_with_unknown_location_id_and_missing_address_returns_bad_request() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(1), - Location = new - { - Id = 777777L, - Address = "", - Latitude = 0.0, - Longitude = 0.0, - }, - Reason = "Rendez-vous location invalide", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode); - } } diff --git a/src/Yavsc.Api/Controllers/Business/BillingController.cs b/src/Yavsc.Api/Controllers/Business/BillingController.cs index d10e166e3..5f9349559 100644 --- a/src/Yavsc.Api/Controllers/Business/BillingController.cs +++ b/src/Yavsc.Api/Controllers/Business/BillingController.cs @@ -7,8 +7,6 @@ using Yavsc.Billing; using Yavsc.Helpers; using Yavsc.ViewModels; using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Workflow; using Yavsc.Server.Models.FileSystem; namespace Yavsc.ApiControllers @@ -123,69 +121,12 @@ namespace Yavsc.ApiControllers WorkflowHelpers.ConfigureBillingService(); } - var allowedActivityCodes = dbContext.UserActivities + var commands = dbContext.Set() .AsNoTracking() - .Where(a => a.UserId == uid) - .Select(a => a.DoesCode) - .Distinct() - .ToList(); - - if (allowedActivityCodes.Count == 0) - { - return Ok(Array.Empty()); - } - - var allowedBillingCodes = dbContext.CommandForm - .AsNoTracking() - .Where(form => allowedActivityCodes.Contains(form.ActivityCode)) - .Select(form => form.ActionName) - .Where(actionName => !string.IsNullOrWhiteSpace(actionName)) - .Distinct() - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - var fallbackToActivityFilteringOnly = allowedBillingCodes.Count == 0; - - // Query only the command types allowed by the performer's declared - // activities; this avoids touching unrelated legacy slices. - var rdvCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.Rdv) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var hairCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.Brush) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var hairMultiCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.MBrush) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var commands = rdvCommands - .Concat(hairCommands) - .Concat(hairMultiCommands) + .Where(q => q.PerformerId == uid) + .Where(q => q.Status == QueryStatus.Inserted + || q.Status == QueryStatus.Accepted + || q.Status == QueryStatus.InProgress) .OrderByDescending(q => q.DateModified) .ThenByDescending(q => q.Id) .ToList(); diff --git a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs new file mode 100644 index 000000000..293cca9ae --- /dev/null +++ b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs @@ -0,0 +1,193 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Yavsc.Controllers +{ + using System; + using Yavsc.Models; + using Yavsc.Models.Workflow; + using Yavsc.Models.Billing; + using Yavsc.Abstract.Identity; + using Microsoft.EntityFrameworkCore; + using Yavsc.Server.Helpers; + + [Authorize] + [Produces("application/json")] + [Route(Constants.APIPrefix + "/bookquery"), Authorize("Performer")] + public class BookQueryApiController : Controller + { + private ApplicationDbContext _context; + private ILogger _logger; + + public BookQueryApiController(ApplicationDbContext context, ILoggerFactory loggerFactory) + { + _context = context; + _logger = loggerFactory.CreateLogger(); + } + + // GET: api/BookQueryApi + /// + /// Book queries, by creation order + /// + /// returned Ids must be lower than this value + /// book queries + [HttpGet] + public IEnumerable GetCommands(long maxId=long.MaxValue) + { + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + var now = DateTime.UtcNow; + + var result = _context.RdvQueries.Include(c => c.Location). + Include(c => c.Client).Where(c => c.PerformerId == uid && c.Id < maxId && c.EventDate > now + && c.ValidationDate == null). + Select(c => new RdvQueryProviderInfo + { + Client = new ClientProviderInfo { + UserName = c.Client.UserName, + UserId = c.ClientId, + Avatar = c.Client.Avatar }, + Location = c.Location, + EventDate = c.EventDate, + Id = c.Id, + Previsional = c.Provisional, + Reason = c.Reason, + ActivityCode = c.ActivityCode, + BillingCode = BillingCodes.Rdv + }). + OrderBy(c=>c.Id). + Take(25); + return result; + } + + // GET: api/BookQueryApi/5 + [HttpGet("{id}", Name = "GetBookQuery")] + public IActionResult GetBookQuery([FromRoute] long id) + { + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + + RdvQuery bookQuery = _context.RdvQueries.Where(c => c.ClientId == uid || c.PerformerId == uid).Single(m => m.Id == id); + + if (bookQuery == null) + { + return NotFound(); + } + + return Ok(bookQuery); + } + + // PUT: api/BookQueryApi/5 + [HttpPut("{id}")] + public IActionResult PutBookQuery(long id, [FromBody] RdvQuery bookQuery) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + if (id != bookQuery.Id) + { + return BadRequest(); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (bookQuery.ClientId != uid) + return NotFound(); + + _context.Entry(bookQuery).State = EntityState.Modified; + + try + { + _context.SaveChanges(User.GetUserId()); + } + catch (DbUpdateConcurrencyException) + { + if (!BookQueryExists(id)) + { + return NotFound(); + } + else + { + throw; + } + } + + return new StatusCodeResult(StatusCodes.Status204NoContent); + } + + // POST: api/BookQueryApi + [HttpPost] + public IActionResult PostBookQuery([FromBody] RdvQuery bookQuery) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (bookQuery.ClientId != uid) + { + ModelState.AddModelError("ClientId", "You must be the client at creating a book query"); + return new BadRequestObjectResult(ModelState); + } + _context.RdvQueries.Add(bookQuery); + try + { + _context.SaveChanges(User.GetUserId()); + } + catch (DbUpdateException) + { + if (BookQueryExists(bookQuery.Id)) + { + return new StatusCodeResult(StatusCodes.Status409Conflict); + } + else + { + throw; + } + } + + return CreatedAtRoute("GetBookQuery", new { id = bookQuery.Id }, bookQuery); + } + + // DELETE: api/BookQueryApi/5 + [HttpDelete("{id}")] + public IActionResult DeleteBookQuery(long id) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + RdvQuery bookQuery = _context.RdvQueries.Single(m => m.Id == id); + + if (bookQuery == null) + { + return NotFound(); + } + if (bookQuery.ClientId != uid) return NotFound(); + + _context.RdvQueries.Remove(bookQuery); + _context.SaveChanges(User.GetUserId()); + + return Ok(bookQuery); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _context.Dispose(); + } + base.Dispose(disposing); + } + + private bool BookQueryExists(long id) + { + return _context.RdvQueries.Count(e => e.Id == id) > 0; + } + } +} diff --git a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs index 06256a987..48e3be2e1 100644 --- a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs @@ -1,13 +1,8 @@ -#nullable enable annotations - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Npgsql; using Yavsc.Models; using Yavsc.Models.Billing; -using Yavsc.Models.Relationship; using Yavsc.Models.Workflow; using Yavsc.Server.Helpers; @@ -88,32 +83,30 @@ public class RdvQueryApiController : Controller return BadRequest(ModelState); } - if (query.Location is null) + if (query.Location is not null) { - return BadRequest(new { Error = "location is required" }); + var existingLocation = await _context.Locations.FirstOrDefaultAsync( + x => x.Address == query.Location.Address + && x.Longitude == query.Location.Longitude + && x.Latitude == query.Location.Latitude, + cancellationToken); + + if (existingLocation is not null) + { + query.Location = existingLocation; + } + else + { + _context.Attach(query.Location); + } } - var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken); - if (resolvedLocation is null) - { - return BadRequest(new { Error = "location payload is invalid" }); - } - - await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken); - - query.Location = resolvedLocation; - - var addedEntry = _context.RdvQueries.Add(query); - EnsureLocationForeignKey(addedEntry, resolvedLocation.Id); + _context.RdvQueries.Add(query); try { await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); } - catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex)) - { - return BadRequest(new { Error = "location reference is invalid" }); - } catch (DbUpdateException) { if (QueryExists(query.Id)) @@ -156,16 +149,17 @@ public class RdvQueryApiController : Controller if (query.Location is not null) { - var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken); + var resolvedLocation = await _context.Locations.FirstOrDefaultAsync( + x => x.Address == query.Location.Address + && x.Longitude == query.Location.Longitude + && x.Latitude == query.Location.Latitude, + cancellationToken); + + existing.Location = resolvedLocation ?? query.Location; if (resolvedLocation is null) { - return BadRequest(new { Error = "location payload is invalid" }); + _context.Attach(query.Location); } - - await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken); - - existing.Location = resolvedLocation; - EnsureLocationForeignKey(_context.Entry(existing), resolvedLocation.Id); } try @@ -181,10 +175,6 @@ public class RdvQueryApiController : Controller throw; } - catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex)) - { - return BadRequest(new { Error = "location reference is invalid" }); - } return NoContent(); } @@ -218,78 +208,6 @@ public class RdvQueryApiController : Controller return _context.RdvQueries.Any(e => e.Id == id); } - private async Task ResolveLocationAsync(Location postedLocation, CancellationToken cancellationToken) - { - if (postedLocation.Id > 0) - { - var byId = await _context.Locations - .FirstOrDefaultAsync(x => x.Id == postedLocation.Id, cancellationToken); - if (byId is not null) - { - return byId; - } - } - - if (string.IsNullOrWhiteSpace(postedLocation.Address)) - { - return null; - } - - var existingByCoordinates = await _context.Locations.FirstOrDefaultAsync( - x => x.Address == postedLocation.Address - && x.Longitude == postedLocation.Longitude - && x.Latitude == postedLocation.Latitude, - cancellationToken); - - if (existingByCoordinates is not null) - { - return existingByCoordinates; - } - - // Treat unknown location ids as client-side placeholders and insert a new row. - postedLocation.Id = 0; - _context.Locations.Add(postedLocation); - return postedLocation; - } - - private async Task PersistLocationIfNeededAsync(Location location, string userId, CancellationToken cancellationToken) - { - if (_context.Entry(location).State != EntityState.Added) - { - return; - } - - await _context.SaveChangesAsync(userId, cancellationToken); - } - - private static bool IsLocationForeignKeyViolation(DbUpdateException ex) - { - if (ex.InnerException is not PostgresException pg) - { - return false; - } - - return pg.SqlState == PostgresErrorCodes.ForeignKeyViolation - && string.Equals(pg.ConstraintName, "FK_NominativeServiceCommand_Locations_LocationId", StringComparison.Ordinal); - } - - private static void EnsureLocationForeignKey(EntityEntry entry, long locationId) - { - SetFkIfPresent(entry, "LocationId", locationId); - SetFkIfPresent(entry, "RdvQuery_LocationId", locationId); - } - - private static void SetFkIfPresent(EntityEntry entry, string propertyName, long value) - { - var property = entry.Metadata.FindProperty(propertyName); - if (property is null) - { - return; - } - - entry.Property(propertyName).CurrentValue = value; - } - private static DateTime EnsureUtc(DateTime value) { return value.Kind switch diff --git a/src/Yavsc.Api/Program.cs b/src/Yavsc.Api/Program.cs index 5f7ef25c0..8117fb1d4 100644 --- a/src/Yavsc.Api/Program.cs +++ b/src/Yavsc.Api/Program.cs @@ -62,9 +62,6 @@ internal class Program services.AddAuthentication("Bearer") .AddYavscJwtBearer(builder.Configuration); - services.AddSignalR(); - services.AddSingleton(); - // DbContextBuilder services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString( diff --git a/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs b/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs deleted file mode 100644 index dc68aa9f1..000000000 --- a/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260913022000_cleanupLegacyNominativeServiceCommandLocationId")] - public partial class cleanupLegacyNominativeServiceCommandLocationId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" -UPDATE ""NominativeServiceCommand"" -SET ""RdvQuery_LocationId"" = COALESCE(""RdvQuery_LocationId"", ""LocationId"") -WHERE ""Discriminator"" = 'RdvQuery' - AND ""LocationId"" IS NOT NULL; -"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "LocationId", - table: "NominativeServiceCommand"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "LocationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.Sql(@" -UPDATE ""NominativeServiceCommand"" -SET ""LocationId"" = ""RdvQuery_LocationId"" -WHERE ""Discriminator"" = 'RdvQuery' - AND ""RdvQuery_LocationId"" IS NOT NULL; -"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_LocationId", - table: "NominativeServiceCommand", - column: "LocationId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - } - } -} \ No newline at end of file diff --git a/src/Yavsc.Server/Services/HubConnectionManager.cs b/src/Yavsc.Org/Services/ChatHubConnexionManager.cs similarity index 81% rename from src/Yavsc.Server/Services/HubConnectionManager.cs rename to src/Yavsc.Org/Services/ChatHubConnexionManager.cs index 05ed49dda..a97826616 100644 --- a/src/Yavsc.Server/Services/HubConnectionManager.cs +++ b/src/Yavsc.Org/Services/ChatHubConnexionManager.cs @@ -1,7 +1,5 @@ using System.Collections.Concurrent; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging; using Yavsc.Abstract.Chat; using Yavsc.Models; using Yavsc.ViewModels.Chat; @@ -121,10 +119,10 @@ namespace Yavsc.Services public bool Part(string cxId, string roomName, string reason) { - ChatRoomInfo channelInfo; - if (Channels.TryGetValue(roomName, out channelInfo)) + ChatRoomInfo chanInfo; + if (Channels.TryGetValue(roomName, out chanInfo)) { - if (!channelInfo.Users.Contains(cxId)) + if (!chanInfo.Users.Contains(cxId)) { // TODO NotifyErrorToCaller(roomName, "you didn't join."); return false; @@ -132,11 +130,11 @@ namespace Yavsc.Services // FIXME only remove cx, not username, // as long as he might be connected // from another device, to the same room - channelInfo.Users.Remove(cxId); - if (channelInfo.Users.Count == 0) + chanInfo.Users.Remove(cxId); + if (chanInfo.Users.Count == 0) { - ChatRoomInfo deadChannelInfo; - if (Channels.TryRemove(roomName, out deadChannelInfo)) + ChatRoomInfo deadchanInfo; + if (Channels.TryRemove(roomName, out deadchanInfo)) { var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName); room.LatestJoinPart = DateTime.UtcNow; @@ -157,67 +155,67 @@ namespace Yavsc.Services var userName = ChatUserNames[cxId]; _logger.LogInformation($"Join: {userName}=>{roomName}"); - ChatRoomInfo channelInfo; + ChatRoomInfo chanInfo; // if channel already is open if (Channels.ContainsKey(roomName)) { - if (Channels.TryGetValue(roomName, out channelInfo)) + if (Channels.TryGetValue(roomName, out chanInfo)) { if (IsPresent(roomName, userName)) { // TODO implement some unique connection sharing protocol // between all terminals from a single user. - return channelInfo; + return chanInfo; } else { if (IsCop(userName)) { - channelInfo.Ops.Add(cxId); + chanInfo.Ops.Add(cxId); } else{ - channelInfo.Users.Add(cxId); + chanInfo.Users.Add(cxId); } _logger.LogInformation($"existing room joint: {userName}=>{roomName}"); if (!ChatRoomPresence[userName].Contains(roomName)) ChatRoomPresence[userName].Add(roomName); - return channelInfo; + return chanInfo; } } else { - string msg = "room seemed to be available ... but we could get no info on it."; + string msg = "room seemd to be avaible ... but we could get no info on it."; _errorHandler(roomName, msg); return null; } } // room was closed. var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName); - channelInfo = new ChatRoomInfo(); + chanInfo = new ChatRoomInfo(); if (room != null) { - channelInfo.Topic = room.Topic; - channelInfo.Name = room.Name; - channelInfo.Users.Add(cxId); + chanInfo.Topic = room.Topic; + chanInfo.Name = room.Name; + chanInfo.Users.Add(cxId); } else { // a first join, we create it. - channelInfo.Name = roomName; - channelInfo.Topic = _localizer.GetString(ChatHubConstants.JustCreatedBy)+userName; - channelInfo.Ops.Add(cxId); + chanInfo.Name = roomName; + chanInfo.Topic = _localizer.GetString(ChatHubConstants.JustCreatedBy)+userName; + chanInfo.Ops.Add(cxId); } - if (Channels.TryAdd(roomName, channelInfo)) + if (Channels.TryAdd(roomName, chanInfo)) { ChatRoomPresence[userName].Add(roomName); _logger.LogInformation("new room joint"); - return (channelInfo); + return (chanInfo); } else { - string msg = "Chan create failed unexpectedly..."; + string msg = "Chan create failed unexpectly..."; _errorHandler(roomName, msg); return null; } @@ -228,7 +226,7 @@ namespace Yavsc.Services throw new System.NotImplementedException(); } - public bool DeOp(string roomName, string userName) + public bool Deop(string roomName, string userName) { throw new System.NotImplementedException(); } @@ -248,9 +246,9 @@ namespace Yavsc.Services return ChatUserNames[cxId]; } - public bool TryGetChanInfo(string room, out ChatRoomInfo channelInfo) + public bool TryGetChanInfo(string room, out ChatRoomInfo chanInfo) { - return Channels.TryGetValue(room, out channelInfo); + return Channels.TryGetValue(room, out chanInfo); } public IEnumerable ListChannels(string pattern) @@ -279,22 +277,22 @@ namespace Yavsc.Services public bool Kick(string cxId, string userName, string roomName, string reason) { - ChatRoomInfo channelInfo; + ChatRoomInfo chanInfo; if (!Channels.ContainsKey(roomName)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan).ToString()); return false; } - if (!Channels.TryGetValue(roomName, out channelInfo)) + if (!Channels.TryGetValue(roomName, out chanInfo)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan).ToString()); return false; } var kickerName = GetUserName(cxId); - if (!channelInfo.Ops.Contains(cxId)) - if (!channelInfo.Hops.Contains(cxId)) + if (!chanInfo.Ops.Contains(cxId)) + if (!chanInfo.Hops.Contains(cxId)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabYouNotOp).ToString()); return false; @@ -305,9 +303,9 @@ namespace Yavsc.Services _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchUser).ToString()); return false; } - var userConnectionIds = GetConnexionIds(userName); - if (channelInfo.Hops.Contains(cxId)) - if (channelInfo.Ops.Any(c => userConnectionIds.Contains(c))) + var ucxs = GetConnexionIds(userName); + if (chanInfo.Hops.Contains(cxId)) + if (chanInfo.Ops.Any(c => ucxs.Contains(c))) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.HopWontKickOp).ToString()); return false; @@ -319,15 +317,15 @@ namespace Yavsc.Services } // all good, time to kick :-) - foreach (var ucx in userConnectionIds) { - if (channelInfo.Users.Contains(ucx)) - channelInfo.Users.Remove(ucx); + foreach (var ucx in ucxs) { + if (chanInfo.Users.Contains(ucx)) + chanInfo.Users.Remove(ucx); - else if (channelInfo.Ops.Contains(ucx)) - channelInfo.Ops.Remove(ucx); + else if (chanInfo.Ops.Contains(ucx)) + chanInfo.Ops.Remove(ucx); - else if (channelInfo.Hops.Contains(ucx)) - channelInfo.Hops.Remove(ucx); + else if (chanInfo.Hops.Contains(ucx)) + chanInfo.Hops.Remove(ucx); } return true; diff --git a/src/Yavsc.Server/Interfaces/IConnexionManager.cs b/src/Yavsc.Server/Interfaces/IConnexionManager.cs index 434054249..46374d40a 100644 --- a/src/Yavsc.Server/Interfaces/IConnexionManager.cs +++ b/src/Yavsc.Server/Interfaces/IConnexionManager.cs @@ -19,7 +19,7 @@ namespace Yavsc.Services bool Kick(string cxId, string userName, string roomName, string reason); bool Op(string roomName, string userName); - bool DeOp(string roomName, string userName); + bool Deop(string roomName, string userName); bool Hop(string roomName, string userName); bool DeHop(string roomName, string userName); bool TryGetChanInfo(string room, out ChatRoomInfo channelInfo);