Compare commits
No commits in common. "7b9274515344bb149da640486ba2fdd28026f1e6" and "6e2fec1620a292e821d5b886380da3ace15304c8" have entirely different histories.
7b92745153
...
6e2fec1620
21 changed files with 81 additions and 780 deletions
|
|
@ -1,13 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [1.0.8-rc10] - unstable
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
## [1.0.8-rc9] - unstable
|
## [1.0.8-rc9] - unstable
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ public class ActivitiesPageViewModelTests
|
||||||
var client = new ActivityApiClient(api, "https://business.example/api/v1/");
|
var client = new ActivityApiClient(api, "https://business.example/api/v1/");
|
||||||
|
|
||||||
await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken);
|
await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken);
|
||||||
await client.GetUsersAsync("brush-pro", TestContext.Current.CancellationToken);
|
await client.GetPerformersAsync("brush-pro", TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.Equal("https://business.example/api/v1/activity/catalog?parentCode=brush", api.Paths[0]);
|
Assert.Equal("https://business.example/api/v1/activity/catalog?parentCode=brush", api.Paths[0]);
|
||||||
Assert.Equal("https://business.example/api/v1/activity/brush-pro/users", api.Paths[1]);
|
Assert.Equal("https://business.example/api/v1/activity/brush-pro/performers", api.Paths[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -34,20 +34,12 @@ public class ActivitiesPageViewModelTests
|
||||||
Assert.Equal("brush", vm.CurrentActivity?.Code);
|
Assert.Equal("brush", vm.CurrentActivity?.Code);
|
||||||
Assert.Single(vm.Performers);
|
Assert.Single(vm.Performers);
|
||||||
Assert.Equal("Alice", vm.Performers[0].UserName);
|
Assert.Equal("Alice", vm.Performers[0].UserName);
|
||||||
Assert.True(vm.Performers[0].HasPerformerProfile);
|
|
||||||
Assert.True(vm.Performers[0].IsPerformerActive);
|
|
||||||
Assert.Equal("Actif", vm.Performers[0].PerformerStatusBadgeLabel);
|
|
||||||
Assert.Equal("Pas d'autre activité", vm.Performers[0].ExtraActivityLabel);
|
|
||||||
|
|
||||||
await vm.ShowSpecializationAsync(vm.Specializations[0]);
|
await vm.ShowSpecializationAsync(vm.Specializations[0]);
|
||||||
|
|
||||||
Assert.Equal("brush-pro", vm.CurrentActivity?.Code);
|
Assert.Equal("brush-pro", vm.CurrentActivity?.Code);
|
||||||
Assert.Single(vm.Performers);
|
Assert.Single(vm.Performers);
|
||||||
Assert.Equal("Bob", vm.Performers[0].UserName);
|
Assert.Equal("Bob", vm.Performers[0].UserName);
|
||||||
Assert.True(vm.Performers[0].HasPerformerProfile);
|
|
||||||
Assert.False(vm.Performers[0].IsPerformerActive);
|
|
||||||
Assert.Equal("Inactif", vm.Performers[0].PerformerStatusBadgeLabel);
|
|
||||||
Assert.Equal("Autres spécialisations: 2", vm.Performers[0].ExtraActivityLabel);
|
|
||||||
Assert.Contains("brush pro", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
|
Assert.Contains("brush pro", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
await vm.ShowSpecializationAsync(null);
|
await vm.ShowSpecializationAsync(null);
|
||||||
|
|
@ -94,14 +86,14 @@ public class ActivitiesPageViewModelTests
|
||||||
|
|
||||||
if (typeof(T) == typeof(List<ActivityPerformerDto>))
|
if (typeof(T) == typeof(List<ActivityPerformerDto>))
|
||||||
{
|
{
|
||||||
var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal)
|
var performers = path.EndsWith("brush-pro/performers", StringComparison.Ordinal)
|
||||||
? new List<ActivityPerformerDto>
|
? new List<ActivityPerformerDto>
|
||||||
{
|
{
|
||||||
new() { PerformerId = "pro-2", HasPerformerProfile = true, Active = false, UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro", ExtraActivityCount = 2 }
|
new() { PerformerId = "pro-2", UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro" }
|
||||||
}
|
}
|
||||||
: new List<ActivityPerformerDto>
|
: new List<ActivityPerformerDto>
|
||||||
{
|
{
|
||||||
new() { PerformerId = "pro-1", HasPerformerProfile = true, Active = true, UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush", ExtraActivityCount = 0 }
|
new() { PerformerId = "pro-1", UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush" }
|
||||||
};
|
};
|
||||||
|
|
||||||
return Task.FromResult((T)(object)performers);
|
return Task.FromResult((T)(object)performers);
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,11 @@ private void ConfigureRootView(MainView rootView)
|
||||||
rootView.NavRoot.PopToRootAsync();
|
rootView.NavRoot.PopToRootAsync();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sessionStatus.LoginSucceeded += async () =>
|
||||||
|
{
|
||||||
|
await PushMainPageAsync();
|
||||||
|
};
|
||||||
|
|
||||||
rootView.SessionBanner.DataContext = sessionStatus;
|
rootView.SessionBanner.DataContext = sessionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
||||||
public partial ActivityBrowseItemDto? SelectedSpecialization { get; set; }
|
public partial ActivityBrowseItemDto? SelectedSpecialization { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial ObservableCollection<ActivityUserDisplayItem> Performers { get; set; } = new();
|
public partial ObservableCollection<ActivityPerformerDto> Performers { get; set; } = new();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsBusy { get; set; }
|
public partial bool IsBusy { get; set; }
|
||||||
|
|
@ -122,14 +122,14 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
||||||
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||||
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
||||||
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||||
StatusMessage = $"Erreur: {ex.Message}";
|
StatusMessage = $"Erreur: {ex.Message}";
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -158,7 +158,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
||||||
|
|
||||||
if (activity is null)
|
if (activity is null)
|
||||||
{
|
{
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,19 +197,18 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
||||||
IsBusy = true;
|
IsBusy = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var list = await _client.GetUsersAsync(activity.Code);
|
var list = await _client.GetPerformersAsync(activity.Code);
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>((list ?? new())
|
Performers = new ObservableCollection<ActivityPerformerDto>(list ?? new());
|
||||||
.Select(ActivityUserDisplayItem.FromDto));
|
StatusMessage = $"{activity.Name} · {Performers.Count} prestataire(s)";
|
||||||
StatusMessage = $"{activity.Name} · {Performers.Count} utilisateur(s)";
|
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||||
{
|
{
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||||
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||||
StatusMessage = $"Erreur: {ex.Message}";
|
StatusMessage = $"Erreur: {ex.Message}";
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using Yavsc.Abstract.Workflow;
|
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
|
||||||
|
|
||||||
public sealed class ActivityUserDisplayItem
|
|
||||||
{
|
|
||||||
public string PerformerId { get; init; } = string.Empty;
|
|
||||||
public bool HasPerformerProfile { get; init; }
|
|
||||||
public string PerformerBadgeLabel { get; init; } = "Profil pro";
|
|
||||||
public bool IsPerformerActive { get; init; }
|
|
||||||
public string PerformerStatusBadgeLabel { get; init; } = "Inactif";
|
|
||||||
public string PerformerStatusBadgeBackground { get; init; } = "#FDECEA";
|
|
||||||
public string PerformerStatusBadgeBorder { get; init; } = "#C62828";
|
|
||||||
public string PerformerStatusBadgeForeground { get; init; } = "#8E0000";
|
|
||||||
public string UserName { get; init; } = string.Empty;
|
|
||||||
public string WebSite { get; init; } = string.Empty;
|
|
||||||
public int ExtraActivityCount { get; init; }
|
|
||||||
public string ExtraActivityLabel { get; init; } = "Pas d'autre activité";
|
|
||||||
|
|
||||||
public static ActivityUserDisplayItem FromDto(ActivityPerformerDto dto)
|
|
||||||
{
|
|
||||||
return new ActivityUserDisplayItem
|
|
||||||
{
|
|
||||||
PerformerId = dto.PerformerId,
|
|
||||||
HasPerformerProfile = dto.HasPerformerProfile,
|
|
||||||
UserName = dto.UserName,
|
|
||||||
WebSite = dto.WebSite,
|
|
||||||
IsPerformerActive = dto.Active,
|
|
||||||
PerformerStatusBadgeLabel = dto.Active ? "Actif" : "Inactif",
|
|
||||||
PerformerStatusBadgeBackground = dto.Active ? "#E6F7EC" : "#FDECEA",
|
|
||||||
PerformerStatusBadgeBorder = dto.Active ? "#2E7D32" : "#C62828",
|
|
||||||
PerformerStatusBadgeForeground = dto.Active ? "#1B5E20" : "#8E0000",
|
|
||||||
ExtraActivityCount = dto.ExtraActivityCount,
|
|
||||||
ExtraActivityLabel = dto.ExtraActivityCount == 0
|
|
||||||
? "Pas d'autre activité"
|
|
||||||
: $"Autres spécialisations: {dto.ExtraActivityCount}"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,20 +6,6 @@
|
||||||
x:DataType="vm:ActivitiesPageViewModel"
|
x:DataType="vm:ActivitiesPageViewModel"
|
||||||
Header="Activités">
|
Header="Activités">
|
||||||
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
|
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
|
||||||
<Grid.Styles>
|
|
||||||
<Style Selector="Border.user-badge">
|
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
|
||||||
<Setter Property="CornerRadius" Value="6" />
|
|
||||||
<Setter Property="Padding" Value="5,0" />
|
|
||||||
<Setter Property="MinHeight" Value="16" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="TextBlock.user-badge-text">
|
|
||||||
<Setter Property="FontSize" Value="9" />
|
|
||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
</Style>
|
|
||||||
</Grid.Styles>
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8">
|
||||||
<Button Content="Rafraîchir" Command="{Binding RefreshCommand}" />
|
<Button Content="Rafraîchir" Command="{Binding RefreshCommand}" />
|
||||||
<TextBlock Text="Catalogue des activités" FontWeight="Bold" VerticalAlignment="Center" />
|
<TextBlock Text="Catalogue des activités" FontWeight="Bold" VerticalAlignment="Center" />
|
||||||
|
|
@ -36,7 +22,7 @@
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
|
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
|
||||||
FontSize="11" Opacity="0.6" />
|
FontSize="11" Opacity="0.6" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
@ -57,7 +43,7 @@
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
|
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
|
||||||
FontSize="11" Opacity="0.6" />
|
FontSize="11" Opacity="0.6" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
@ -66,39 +52,17 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Column="4" RowDefinitions="Auto,Auto,*">
|
<Grid Grid.Column="4" RowDefinitions="Auto,Auto,*">
|
||||||
<TextBlock Grid.Row="0" Text="Utilisateurs" FontWeight="Bold" Margin="0,0,0,8" />
|
<TextBlock Grid.Row="0" Text="Prestataires" FontWeight="Bold" Margin="0,0,0,8" />
|
||||||
<TextBlock Grid.Row="1"
|
<TextBlock Grid.Row="1"
|
||||||
Text="{Binding CurrentActivityLabel, StringFormat='Activité affichée : {0}'}"
|
Text="{Binding CurrentActivityLabel, StringFormat='Activité affichée : {0}'}"
|
||||||
FontSize="11" Opacity="0.7" Margin="0,0,0,8" />
|
FontSize="11" Opacity="0.7" Margin="0,0,0,8" />
|
||||||
<ListBox Grid.Row="2" ItemsSource="{Binding Performers}">
|
<ListBox Grid.Row="2" ItemsSource="{Binding Performers}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:ActivityUserDisplayItem">
|
<DataTemplate x:DataType="wf:ActivityPerformerDto">
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
|
||||||
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
|
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
|
||||||
<Border IsVisible="{Binding HasPerformerProfile}"
|
|
||||||
Classes="user-badge"
|
|
||||||
Background="#D9F5E5"
|
|
||||||
BorderBrush="#2E7D32"
|
|
||||||
>
|
|
||||||
<TextBlock Text="{Binding PerformerBadgeLabel}"
|
|
||||||
Classes="user-badge-text"
|
|
||||||
Foreground="#1B5E20"
|
|
||||||
/>
|
|
||||||
</Border>
|
|
||||||
<Border IsVisible="{Binding HasPerformerProfile}"
|
|
||||||
Classes="user-badge"
|
|
||||||
Background="{Binding PerformerStatusBadgeBackground}"
|
|
||||||
BorderBrush="{Binding PerformerStatusBadgeBorder}"
|
|
||||||
>
|
|
||||||
<TextBlock Text="{Binding PerformerStatusBadgeLabel}"
|
|
||||||
Classes="user-badge-text"
|
|
||||||
Foreground="{Binding PerformerStatusBadgeForeground}"
|
|
||||||
/>
|
|
||||||
</Border>
|
|
||||||
</StackPanel>
|
|
||||||
<TextBlock Text="{Binding WebSite}" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="{Binding WebSite}" FontSize="11" Opacity="0.7" />
|
||||||
<TextBlock Text="{Binding ExtraActivityLabel}"
|
<TextBlock Text="{Binding ExtraActivityCount, StringFormat='Autres spécialisations: {0}'}"
|
||||||
FontSize="11" Opacity="0.6" />
|
FontSize="11" Opacity="0.6" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
@ -108,16 +72,7 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="2" ColumnDefinitions="*,Auto">
|
<Grid Grid.Row="2" ColumnDefinitions="*,Auto">
|
||||||
<TextBox Grid.Column="0"
|
<TextBlock Grid.Column="0" Text="{Binding StatusMessage}" VerticalAlignment="Center" />
|
||||||
Text="{Binding StatusMessage}"
|
|
||||||
IsReadOnly="True"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
BorderThickness="0"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
Padding="0"
|
|
||||||
Background="Transparent"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
<ProgressBar Grid.Column="1" Width="120" IsIndeterminate="True" IsVisible="{Binding IsBusy}" />
|
<ProgressBar Grid.Column="1" Width="120" IsIndeterminate="True" IsVisible="{Binding IsBusy}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ namespace Yavsc.Abstract.Workflow;
|
||||||
public sealed class ActivityPerformerDto
|
public sealed class ActivityPerformerDto
|
||||||
{
|
{
|
||||||
public string PerformerId { get; set; } = string.Empty;
|
public string PerformerId { get; set; } = string.Empty;
|
||||||
public bool HasPerformerProfile { get; set; }
|
|
||||||
public string UserName { get; set; } = string.Empty;
|
public string UserName { get; set; } = string.Empty;
|
||||||
public bool Active { get; set; }
|
public bool Active { get; set; }
|
||||||
public bool AcceptNotifications { get; set; }
|
public bool AcceptNotifications { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public sealed class ActivityApiClient
|
||||||
return _api.CallAsync<List<ActivityBrowseItemDto>>(HttpMethod.Get, Absolute(path), ct: ct);
|
return _api.CallAsync<List<ActivityBrowseItemDto>>(HttpMethod.Get, Absolute(path), ct: ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<ActivityPerformerDto>> GetUsersAsync(
|
public Task<List<ActivityPerformerDto>> GetPerformersAsync(
|
||||||
string activityCode,
|
string activityCode,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
|
@ -48,14 +48,9 @@ public sealed class ActivityApiClient
|
||||||
|
|
||||||
return _api.CallAsync<List<ActivityPerformerDto>>(
|
return _api.CallAsync<List<ActivityPerformerDto>>(
|
||||||
HttpMethod.Get,
|
HttpMethod.Get,
|
||||||
Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/users"),
|
Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/performers"),
|
||||||
ct: ct);
|
ct: ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<ActivityPerformerDto>> GetPerformersAsync(
|
|
||||||
string activityCode,
|
|
||||||
CancellationToken ct = default)
|
|
||||||
=> GetUsersAsync(activityCode, ct);
|
|
||||||
|
|
||||||
private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString();
|
private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Yavsc.Abstract.Workflow;
|
|
||||||
using Yavsc.Api.Test.Fixtures;
|
|
||||||
using Yavsc.Tests.Shared;
|
|
||||||
|
|
||||||
namespace Yavsc.Api.Test;
|
|
||||||
|
|
||||||
[Collection("Yavsc Api")]
|
|
||||||
public sealed class ActivityApiControllerTests : IClassFixture<ApiWebServerFixture>
|
|
||||||
{
|
|
||||||
private readonly ApiWebServerFixture _fixture;
|
|
||||||
|
|
||||||
public ActivityApiControllerTests(ApiWebServerFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClient NewClient(string subject = "alice", string scope = "api")
|
|
||||||
{
|
|
||||||
var handler = new HttpClientHandler
|
|
||||||
{
|
|
||||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
|
||||||
};
|
|
||||||
var http = new HttpClient(handler)
|
|
||||||
{
|
|
||||||
BaseAddress = new Uri(_fixture.BaseAddress)
|
|
||||||
};
|
|
||||||
http.DefaultRequestHeaders.Authorization =
|
|
||||||
new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
|
|
||||||
return http;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetUsers_returns_declared_user_for_exact_activity_code()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using var http = NewClient();
|
|
||||||
|
|
||||||
var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
|
|
||||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(payload);
|
|
||||||
Assert.Single(payload!);
|
|
||||||
Assert.Equal("alice", payload[0].PerformerId);
|
|
||||||
Assert.Equal("alice", payload[0].UserName);
|
|
||||||
Assert.True(payload[0].HasPerformerProfile);
|
|
||||||
Assert.True(payload[0].Active);
|
|
||||||
Assert.Equal("dev", payload[0].ActivityCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task GetUsers_returns_user_even_when_performer_inactive()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using (var scope = _fixture.Services.CreateScope())
|
|
||||||
{
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
|
||||||
var performer = db.Performers.Single(p => p.PerformerId == "alice");
|
|
||||||
performer.Active = false;
|
|
||||||
db.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
using var http = NewClient();
|
|
||||||
var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
|
|
||||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(payload);
|
|
||||||
Assert.Single(payload!);
|
|
||||||
Assert.Equal("alice", payload[0].PerformerId);
|
|
||||||
Assert.False(payload[0].Active);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Catalog_and_Performers_are_consistent_for_dev_activity()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using var http = NewClient();
|
|
||||||
|
|
||||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
|
||||||
|
|
||||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(catalog);
|
|
||||||
|
|
||||||
var dev = catalog!.Single(a => a.Code == "dev");
|
|
||||||
Assert.True(dev.PerformerCount > 0);
|
|
||||||
|
|
||||||
var performersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
|
|
||||||
|
|
||||||
var performers = await performersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(performers);
|
|
||||||
Assert.Equal(dev.PerformerCount, performers!.Count);
|
|
||||||
Assert.Contains(performers, p => p.PerformerId == "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Catalog_does_not_list_activity_without_declaration()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using var http = NewClient();
|
|
||||||
|
|
||||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
|
||||||
|
|
||||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(catalog);
|
|
||||||
Assert.DoesNotContain(catalog!, a => a.Code == "ghost");
|
|
||||||
Assert.Contains(catalog!, a => a.Code == "dev");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Catalog_lists_declared_activity_even_when_performer_inactive()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using var http = NewClient();
|
|
||||||
|
|
||||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
|
||||||
|
|
||||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(catalog);
|
|
||||||
Assert.Contains(catalog!, a => a.Code == "declared-only");
|
|
||||||
|
|
||||||
var response = await http.GetAsync("/api/v1/activity/declared-only/users", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
Assert.NotNull(payload);
|
|
||||||
Assert.Single(payload!);
|
|
||||||
Assert.Equal("bob", payload[0].PerformerId);
|
|
||||||
Assert.Equal("bob", payload[0].UserName);
|
|
||||||
Assert.True(payload[0].HasPerformerProfile);
|
|
||||||
Assert.False(payload[0].Active);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Performers_alias_returns_same_payload_as_users_endpoint()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
using var http = NewClient();
|
|
||||||
|
|
||||||
var usersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
|
||||||
var performersResponse = await http.GetAsync("/api/v1/activity/dev/performers", TestContext.Current.CancellationToken);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, usersResponse.StatusCode);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
|
|
||||||
|
|
||||||
var usersPayload = await usersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
var performersPayload = await performersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
|
||||||
|
|
||||||
Assert.NotNull(usersPayload);
|
|
||||||
Assert.NotNull(performersPayload);
|
|
||||||
Assert.Equal(usersPayload!.Count, performersPayload!.Count);
|
|
||||||
Assert.Equal(usersPayload[0].PerformerId, performersPayload[0].PerformerId);
|
|
||||||
Assert.Equal(usersPayload[0].UserName, performersPayload[0].UserName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Yavsc.Api.Test;
|
|
||||||
|
|
||||||
[CollectionDefinition("Yavsc Api")]
|
|
||||||
public sealed class ApiCollection
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<Project>
|
|
||||||
<!--
|
|
||||||
Yavsc.Api.Test has no project-specific package versions. All
|
|
||||||
package versions are declared at the repository root.
|
|
||||||
-->
|
|
||||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Yavsc.Controllers;
|
|
||||||
using Yavsc.Models;
|
|
||||||
using Yavsc.Models.Relationship;
|
|
||||||
using Yavsc.Models.Workflow;
|
|
||||||
using Yavsc.Tests.Shared;
|
|
||||||
|
|
||||||
namespace Yavsc.Api.Test.Fixtures;
|
|
||||||
|
|
||||||
public sealed class ApiWebServerFixture : WebHostFixture
|
|
||||||
{
|
|
||||||
protected override int HttpsPort => 5104;
|
|
||||||
|
|
||||||
private static SqliteConnection? _sharedSqliteConnection;
|
|
||||||
private static readonly object _sqliteLock = new();
|
|
||||||
|
|
||||||
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
SqliteConnection sharedConnection;
|
|
||||||
lock (_sqliteLock)
|
|
||||||
{
|
|
||||||
if (_sharedSqliteConnection is null)
|
|
||||||
{
|
|
||||||
_sharedSqliteConnection = new SqliteConnection(
|
|
||||||
"Data Source=YavscApiTests;Mode=Memory;Cache=Shared");
|
|
||||||
_sharedSqliteConnection.Open();
|
|
||||||
}
|
|
||||||
sharedConnection = _sharedSqliteConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
|
||||||
opt.UseSqlite(sharedConnection));
|
|
||||||
|
|
||||||
builder.Services.AddControllers()
|
|
||||||
.AddApplicationPart(typeof(ActivityApiController).Assembly);
|
|
||||||
|
|
||||||
builder.Services.AddAuthorization();
|
|
||||||
|
|
||||||
builder.Services.AddAuthentication("Bearer")
|
|
||||||
.AddJwtBearer("Bearer", options =>
|
|
||||||
{
|
|
||||||
options.IncludeErrorDetails = true;
|
|
||||||
options.MapInboundClaims = false;
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateIssuer = true,
|
|
||||||
ValidIssuer = TestTokenIssuer.Issuer,
|
|
||||||
ValidateAudience = false,
|
|
||||||
ValidateLifetime = true,
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
IssuerSigningKey = TestTokenIssuer.SigningKey,
|
|
||||||
NameClaimType = "sub",
|
|
||||||
RoleClaimType = Yavsc.Constants.RoleClaimType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return builder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
|
|
||||||
{
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
app.UseRouting();
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseAuthorization();
|
|
||||||
app.MapControllers();
|
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
|
||||||
{
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
|
||||||
db.Database.EnsureCreated();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal));
|
|
||||||
|
|
||||||
public void ResetAndSeedActivityGraph()
|
|
||||||
{
|
|
||||||
using var scope = Services.CreateScope();
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
|
||||||
|
|
||||||
db.Database.EnsureDeleted();
|
|
||||||
db.Database.EnsureCreated();
|
|
||||||
|
|
||||||
var user = new ApplicationUser
|
|
||||||
{
|
|
||||||
Id = "alice",
|
|
||||||
UserName = "alice",
|
|
||||||
Email = "alice@example.test",
|
|
||||||
EmailConfirmed = true,
|
|
||||||
FullName = "Alice",
|
|
||||||
};
|
|
||||||
db.Users.Add(user);
|
|
||||||
|
|
||||||
var location = new Location
|
|
||||||
{
|
|
||||||
Address = "1 rue du Test",
|
|
||||||
Latitude = 48.8566,
|
|
||||||
Longitude = 2.3522,
|
|
||||||
};
|
|
||||||
db.Add(location);
|
|
||||||
db.SaveChanges();
|
|
||||||
|
|
||||||
var activity = new Activity
|
|
||||||
{
|
|
||||||
Code = "dev",
|
|
||||||
Name = "Dev",
|
|
||||||
Hidden = false,
|
|
||||||
DateCreated = DateTime.UtcNow,
|
|
||||||
DateModified = DateTime.UtcNow,
|
|
||||||
};
|
|
||||||
db.Activities.Add(activity);
|
|
||||||
|
|
||||||
db.Activities.Add(new Activity
|
|
||||||
{
|
|
||||||
Code = "ghost",
|
|
||||||
Name = "Ghost",
|
|
||||||
Hidden = false,
|
|
||||||
DateCreated = DateTime.UtcNow,
|
|
||||||
DateModified = DateTime.UtcNow,
|
|
||||||
});
|
|
||||||
|
|
||||||
var performer = new PerformerProfile
|
|
||||||
{
|
|
||||||
PerformerId = "alice",
|
|
||||||
SIREN = "123456789",
|
|
||||||
OrganizationAddressId = location.Id,
|
|
||||||
AcceptNotifications = true,
|
|
||||||
AcceptPublicContact = true,
|
|
||||||
Active = true,
|
|
||||||
Rate = 5,
|
|
||||||
WebSite = "https://alice.dev",
|
|
||||||
};
|
|
||||||
db.Performers.Add(performer);
|
|
||||||
|
|
||||||
db.UserActivities.Add(new UserActivity
|
|
||||||
{
|
|
||||||
UserId = "alice",
|
|
||||||
DoesCode = "dev",
|
|
||||||
Weight = 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
db.Users.Add(new ApplicationUser
|
|
||||||
{
|
|
||||||
Id = "bob",
|
|
||||||
UserName = "bob",
|
|
||||||
Email = "bob@example.test",
|
|
||||||
EmailConfirmed = true,
|
|
||||||
FullName = "Bob",
|
|
||||||
});
|
|
||||||
|
|
||||||
db.Activities.Add(new Activity
|
|
||||||
{
|
|
||||||
Code = "declared-only",
|
|
||||||
Name = "Declared Only",
|
|
||||||
Hidden = false,
|
|
||||||
DateCreated = DateTime.UtcNow,
|
|
||||||
DateModified = DateTime.UtcNow,
|
|
||||||
});
|
|
||||||
|
|
||||||
db.Performers.Add(new PerformerProfile
|
|
||||||
{
|
|
||||||
PerformerId = "bob",
|
|
||||||
SIREN = "987654321",
|
|
||||||
OrganizationAddressId = location.Id,
|
|
||||||
AcceptNotifications = false,
|
|
||||||
AcceptPublicContact = false,
|
|
||||||
Active = false,
|
|
||||||
Rate = 0,
|
|
||||||
WebSite = "",
|
|
||||||
});
|
|
||||||
|
|
||||||
db.UserActivities.Add(new UserActivity
|
|
||||||
{
|
|
||||||
UserId = "bob",
|
|
||||||
DoesCode = "declared-only",
|
|
||||||
Weight = 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
db.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
base.Dispose();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
lock (_sqliteLock)
|
|
||||||
{
|
|
||||||
if (_sharedSqliteConnection is not null)
|
|
||||||
{
|
|
||||||
_sharedSqliteConnection.Close();
|
|
||||||
_sharedSqliteConnection.Dispose();
|
|
||||||
_sharedSqliteConnection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Yavsc.Api.Test.Fixtures;
|
|
||||||
using Yavsc.Controllers;
|
|
||||||
using Yavsc.Models.Workflow;
|
|
||||||
|
|
||||||
namespace Yavsc.Api.Test;
|
|
||||||
|
|
||||||
[Collection("Yavsc Api")]
|
|
||||||
public sealed class HomeControllerTests : IClassFixture<ApiWebServerFixture>
|
|
||||||
{
|
|
||||||
private readonly ApiWebServerFixture _fixture;
|
|
||||||
|
|
||||||
public HomeControllerTests(ApiWebServerFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Index_does_not_list_activity_without_declaration()
|
|
||||||
{
|
|
||||||
_fixture.ResetAndSeedActivityGraph();
|
|
||||||
|
|
||||||
using var scope = _fixture.Services.CreateScope();
|
|
||||||
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
|
||||||
|
|
||||||
var controller = new HomeController(
|
|
||||||
NullLogger<HomeController>.Instance,
|
|
||||||
localizer: null!,
|
|
||||||
context: db,
|
|
||||||
settingsOptions: Options.Create(new SiteSettings()),
|
|
||||||
env: new TestEnvironment());
|
|
||||||
|
|
||||||
controller.ControllerContext = new ControllerContext
|
|
||||||
{
|
|
||||||
HttpContext = new DefaultHttpContext
|
|
||||||
{
|
|
||||||
User = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
|
||||||
{
|
|
||||||
new Claim("sub", "alice"),
|
|
||||||
new Claim(ClaimTypes.NameIdentifier, "alice")
|
|
||||||
}, "Bearer"))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await controller.Index(id: null);
|
|
||||||
var view = Assert.IsType<ViewResult>(result);
|
|
||||||
var model = Assert.IsAssignableFrom<IEnumerable<Activity>>(view.Model);
|
|
||||||
|
|
||||||
Assert.Contains(model, a => a.Code == "dev");
|
|
||||||
Assert.DoesNotContain(model, a => a.Code == "ghost");
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TestEnvironment : IWebHostEnvironment
|
|
||||||
{
|
|
||||||
public string ApplicationName { get; set; } = "Yavsc.Api.Test";
|
|
||||||
public IFileProvider WebRootFileProvider { get; set; } = null!;
|
|
||||||
public string WebRootPath { get; set; } = string.Empty;
|
|
||||||
public string EnvironmentName { get; set; } = "Development";
|
|
||||||
public string ContentRootPath { get; set; } = string.Empty;
|
|
||||||
public IFileProvider ContentRootFileProvider { get; set; } = null!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<RootNamespace>Yavsc.Api.Test</RootNamespace>
|
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="coverlet.collector" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
|
||||||
<PackageReference Include="xunit.v3" />
|
|
||||||
<PackageReference Include="xunit.v3.common" />
|
|
||||||
<PackageReference Include="xunit.v3.extensibility.core" />
|
|
||||||
<PackageReference Include="xunit.runner.visualstudio" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
|
|
||||||
<ProjectReference Include="..\Yavsc.Server\Yavsc.Server.csproj" />
|
|
||||||
<ProjectReference Include="..\Yavsc.Api\Yavsc.Api.csproj" />
|
|
||||||
<ProjectReference Include="..\Yavsc.Org\Yavsc.Org.csproj" />
|
|
||||||
<ProjectReference Include="..\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Using Include="Xunit" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -44,37 +44,26 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
var codes = activities
|
var codes = activities
|
||||||
.Select(a => a.Code)
|
.Select(a => a.Code)
|
||||||
.Concat(activities.SelectMany(a => (a.Children ?? new List<Activity>())
|
.Concat(activities.SelectMany(a => a.Children.Where(c => !c.Hidden).Select(c => c.Code)))
|
||||||
.Where(c => !c.Hidden)
|
|
||||||
.Select(c => c.Code)))
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var performerCounts = await (
|
var performerCounts = await _context.Performers
|
||||||
from ua in _context.UserActivities.AsNoTracking()
|
.AsNoTracking()
|
||||||
where !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode)
|
.Where(p => p.Active)
|
||||||
group ua by ua.DoesCode into g
|
.SelectMany(
|
||||||
select new
|
p => p.Activity
|
||||||
{
|
.Where(a => codes.Contains(a.DoesCode))
|
||||||
Code = g.Key,
|
.Select(a => new { a.DoesCode, p.PerformerId }))
|
||||||
Count = g.Select(x => x.UserId).Distinct().Count()
|
.GroupBy(x => x.DoesCode)
|
||||||
})
|
.Select(g => new { Code = g.Key, Count = g.Select(x => x.PerformerId).Distinct().Count() })
|
||||||
.ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken);
|
.ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken);
|
||||||
|
|
||||||
var filteredActivities = activities
|
return Ok(activities.Select(a => ToBrowseItem(a, performerCounts)).ToList());
|
||||||
.Where(a =>
|
|
||||||
(performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
|
|
||||||
|| (a.Children ?? new List<Activity>())
|
|
||||||
.Where(c => !c.Hidden)
|
|
||||||
.Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return Ok(filteredActivities.Select(a => ToBrowseItem(a, performerCounts)).ToList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/users")]
|
[HttpGet("{id}/performers")]
|
||||||
public async Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetUsers(
|
public async Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetPerformers(
|
||||||
[FromRoute] string id,
|
[FromRoute] string id,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
@ -91,54 +80,32 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var users = await QueryDeclaredUsersAsync(id, activity.Name, cancellationToken);
|
var performers = await _context.Performers
|
||||||
|
.AsNoTracking()
|
||||||
return Ok(users);
|
.Include(p => p.Performer)
|
||||||
}
|
.Include(p => p.Activity)
|
||||||
|
.ThenInclude(a => a.Does)
|
||||||
[HttpGet("{id}/performers")]
|
.Where(p => p.Active && p.Activity.Any(a => a.DoesCode == id))
|
||||||
public Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetPerformers(
|
.OrderBy(p => p.Rate)
|
||||||
[FromRoute] string id,
|
.Select(p => new ActivityPerformerDto
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
// Backward-compatible alias kept for existing clients.
|
PerformerId = p.PerformerId,
|
||||||
return GetUsers(id, cancellationToken);
|
UserName = p.Performer.UserName,
|
||||||
}
|
Active = p.Active,
|
||||||
|
AcceptNotifications = p.AcceptNotifications,
|
||||||
private Task<List<ActivityPerformerDto>> QueryDeclaredUsersAsync(
|
AcceptPublicContact = p.AcceptPublicContact,
|
||||||
string activityCode,
|
WebSite = p.WebSite,
|
||||||
string activityName,
|
ActivityCode = id,
|
||||||
CancellationToken cancellationToken)
|
ActivityName = activity.Name,
|
||||||
{
|
SettingsClassName = p.Activity
|
||||||
return (
|
.Where(a => a.DoesCode == id)
|
||||||
from ua in _context.UserActivities.AsNoTracking()
|
.Select(a => a.Does.SettingsClassName)
|
||||||
join u in _context.ApplicationUser.AsNoTracking() on ua.UserId equals u.Id into users
|
.FirstOrDefault(),
|
||||||
from user in users.DefaultIfEmpty()
|
ExtraActivityCount = p.Activity.Count(a => a.DoesCode != id)
|
||||||
join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId into performerProfiles
|
|
||||||
from performer in performerProfiles.DefaultIfEmpty()
|
|
||||||
where ua.DoesCode == activityCode
|
|
||||||
orderby user != null ? user.UserName : ua.UserId
|
|
||||||
select new ActivityPerformerDto
|
|
||||||
{
|
|
||||||
PerformerId = ua.UserId,
|
|
||||||
HasPerformerProfile = performer != null,
|
|
||||||
UserName = user != null ? (user.UserName ?? string.Empty) : string.Empty,
|
|
||||||
Active = performer != null && performer.Active,
|
|
||||||
AcceptNotifications = performer != null && performer.AcceptNotifications,
|
|
||||||
AcceptPublicContact = performer != null && performer.AcceptPublicContact,
|
|
||||||
WebSite = performer != null ? (performer.WebSite ?? string.Empty) : string.Empty,
|
|
||||||
ActivityCode = activityCode,
|
|
||||||
ActivityName = activityName,
|
|
||||||
SettingsClassName = _context.Activities
|
|
||||||
.Where(a => a.Code == activityCode)
|
|
||||||
.Select(a => a.SettingsClassName)
|
|
||||||
.FirstOrDefault() ?? string.Empty,
|
|
||||||
ExtraActivityCount = _context.UserActivities
|
|
||||||
.Where(x => x.UserId == ua.UserId && x.DoesCode != activityCode)
|
|
||||||
.Count()
|
|
||||||
})
|
})
|
||||||
.Distinct()
|
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Ok(performers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/ActivityApi/5
|
// GET: api/ActivityApi/5
|
||||||
|
|
@ -273,7 +240,7 @@ namespace Yavsc.Controllers
|
||||||
Photo = activity.Photo,
|
Photo = activity.Photo,
|
||||||
Rate = activity.Rate,
|
Rate = activity.Rate,
|
||||||
PerformerCount = performerCounts.TryGetValue(activity.Code, out var count) ? count : 0,
|
PerformerCount = performerCounts.TryGetValue(activity.Code, out var count) ? count : 0,
|
||||||
Forms = (activity.Forms ?? Enumerable.Empty<CommandForm>())
|
Forms = activity.Forms
|
||||||
.Select(f => new CommandFormSummaryDto
|
.Select(f => new CommandFormSummaryDto
|
||||||
{
|
{
|
||||||
Id = f.Id,
|
Id = f.Id,
|
||||||
|
|
@ -281,9 +248,8 @@ namespace Yavsc.Controllers
|
||||||
Title = f.Title,
|
Title = f.Title,
|
||||||
})
|
})
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Children = (activity.Children ?? Enumerable.Empty<Activity>())
|
Children = activity.Children
|
||||||
.Where(c => !c.Hidden)
|
.Where(c => !c.Hidden)
|
||||||
.Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
|
|
||||||
.OrderByDescending(c => c.Rate)
|
.OrderByDescending(c => c.Rate)
|
||||||
.Select(c => new ActivityBrowseItemDto
|
.Select(c => new ActivityBrowseItemDto
|
||||||
{
|
{
|
||||||
|
|
@ -294,7 +260,7 @@ namespace Yavsc.Controllers
|
||||||
Photo = c.Photo,
|
Photo = c.Photo,
|
||||||
Rate = c.Rate,
|
Rate = c.Rate,
|
||||||
PerformerCount = performerCounts.TryGetValue(c.Code, out var childCount) ? childCount : 0,
|
PerformerCount = performerCounts.TryGetValue(c.Code, out var childCount) ? childCount : 0,
|
||||||
Forms = (c.Forms ?? Enumerable.Empty<CommandForm>())
|
Forms = c.Forms
|
||||||
.Select(f => new CommandFormSummaryDto
|
.Select(f => new CommandFormSummaryDto
|
||||||
{
|
{
|
||||||
Id = f.Id,
|
Id = f.Id,
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,9 @@ internal class Program
|
||||||
services.AddAuthentication("Bearer")
|
services.AddAuthentication("Bearer")
|
||||||
.AddYavscJwtBearer(builder.Configuration);
|
.AddYavscJwtBearer(builder.Configuration);
|
||||||
|
|
||||||
// DbContextBuilder
|
|
||||||
services.AddDbContext<ApplicationDbContext>(options =>
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
options.UseNpgsql(builder.Configuration.GetConnectionString(
|
|
||||||
Yavsc.Constants.YavscConnectionStringName)));
|
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
|
||||||
|
|
||||||
services.AddLocalization(options =>
|
services.AddLocalization(options =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Billing;
|
using Yavsc.Models.Billing;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -52,41 +50,15 @@ namespace Yavsc.Controllers
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public IActionResult Create(ExceptionSIREN exceptionSIREN)
|
public IActionResult Create(ExceptionSIREN exceptionSIREN)
|
||||||
{
|
{
|
||||||
exceptionSIREN ??= new ExceptionSIREN();
|
|
||||||
exceptionSIREN.SIREN = NormalizeSiren(exceptionSIREN.SIREN);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(exceptionSIREN.SIREN) || exceptionSIREN.SIREN.Length != 9 || !exceptionSIREN.SIREN.All(char.IsDigit))
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Le SIREN doit contenir exactement 9 chiffres.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_context.ExceptionsSIREN.Any(e => e.SIREN == exceptionSIREN.SIREN))
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Ce SIREN est deja dans la liste des exceptions.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
_context.ExceptionsSIREN.Add(exceptionSIREN);
|
_context.ExceptionsSIREN.Add(exceptionSIREN);
|
||||||
try
|
|
||||||
{
|
|
||||||
_context.SaveChanges(User.GetUserId());
|
_context.SaveChanges(User.GetUserId());
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, "Impossible d'enregistrer cette exception SIREN.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return View(exceptionSIREN);
|
return View(exceptionSIREN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeSiren(string? siren)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(siren)) return string.Empty;
|
|
||||||
return new string(siren.Where(char.IsDigit).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET: SIRENExceptions/Edit/5
|
// GET: SIRENExceptions/Edit/5
|
||||||
public IActionResult Edit(string id)
|
public IActionResult Edit(string id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -63,35 +63,9 @@ namespace Yavsc.Controllers
|
||||||
.Where(a => a.ParentCode == id)
|
.Where(a => a.ParentCode == id)
|
||||||
.OrderByDescending(a => a.Rate).ToList();
|
.OrderByDescending(a => a.Rate).ToList();
|
||||||
|
|
||||||
var candidateCodes = toShow
|
|
||||||
.Select(a => a.Code)
|
|
||||||
.Concat(toShow.SelectMany(a => (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
|
||||||
.Where(c => !c.Hidden)
|
|
||||||
.Select(c => c.Code)))
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var performerCounts = _dbContext.UserActivities
|
|
||||||
.Where(ua => candidateCodes.Contains(ua.DoesCode))
|
|
||||||
.GroupBy(ua => ua.DoesCode)
|
|
||||||
.Select(g => new { Code = g.Key, Count = g.Select(x => x.UserId).Distinct().Count() })
|
|
||||||
.ToDictionary(x => x.Code, x => x.Count);
|
|
||||||
|
|
||||||
toShow = toShow
|
|
||||||
.Where(a =>
|
|
||||||
(performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
|
|
||||||
|| (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
|
||||||
.Where(c => !c.Hidden)
|
|
||||||
.Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var a in toShow)
|
foreach (var a in toShow)
|
||||||
{
|
{
|
||||||
a.Children = (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
a.Children = a.Children.Where(c => !c.Hidden).ToList();
|
||||||
.Where(c => !c.Hidden)
|
|
||||||
.Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
return View(toShow);
|
return View(toShow);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<label asp-for="Active" class="col-md-2 control-label">ActivateMyProSettings</label>
|
<label asp-for="Active" class="col-md-2 control-label">ActivateMyProSettings</label>
|
||||||
<div>
|
<div>
|
||||||
<input class="form-check-input" type="checkbox" asp-for="Active" class="form-control" />
|
<input asp-for="Active" class="form-control" />
|
||||||
|
|
||||||
<span asp-validation-for="Active" class="text-danger"></span>
|
<span asp-validation-for="Active" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,28 +5,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<form asp-action="Create" method="post">
|
<form asp-action="Create" method="post">
|
||||||
@Html.AntiForgeryToken()
|
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
<h4>Exception SIREN</h4>
|
<h4>Exception SIREN</h4>
|
||||||
<hr />
|
<hr />
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label asp-for="SIREN" class="control-label">SIREN</label>
|
|
||||||
<input asp-for="SIREN" class="form-control" maxlength="9" pattern="[0-9]{9}" inputmode="numeric" autocomplete="off" />
|
|
||||||
<span asp-validation-for="SIREN" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@Html.TextBox("SIREN")
|
||||||
|
<div class="col-md-offset-1 col-md-10">
|
||||||
|
<div class="col-md-offset-2 col-md-10">
|
||||||
<input type="submit" value="Create" class="btn btn-primary" />
|
<input type="submit" value="Create" class="btn btn-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a asp-action="Index">Back to List</a>
|
<a asp-action="Index">Back to List</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
|
||||||
}
|
|
||||||
|
|
|
||||||
15
yavsc.sln
15
yavsc.sln
|
|
@ -39,8 +39,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Client", "src\Yav
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt\PostIt.Tests\PostIt.Tests.csproj", "{021E6F5A-B81D-42A7-9918-DFE39B424FC2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt\PostIt.Tests\PostIt.Tests.csproj", "{021E6F5A-B81D-42A7-9918-DFE39B424FC2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Test", "src\Yavsc.Api.Test\Yavsc.Api.Test.csproj", "{49A42F0E-6E18-4617-9716-3E728966D96F}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -231,18 +229,6 @@ Global
|
||||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x64.Build.0 = Release|Any CPU
|
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.ActiveCfg = Release|Any CPU
|
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.Build.0 = Release|Any CPU
|
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -264,6 +250,5 @@ Global
|
||||||
{34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
{34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||||
{59AF5DEA-D349-495A-BC44-FC7BD4E55099} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
{59AF5DEA-D349-495A-BC44-FC7BD4E55099} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
{021E6F5A-B81D-42A7-9918-DFE39B424FC2} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
||||||
{49A42F0E-6E18-4617-9716-3E728966D96F} = {76A7FBA6-B7EC-4864-85C0-1F470794BA0F}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue