code reorg
This commit is contained in:
parent
93426986f6
commit
d8c0678462
11 changed files with 154 additions and 22 deletions
|
|
@ -148,7 +148,7 @@ private void ConfigureRootView(MainView rootView)
|
||||||
public static async Task PushBlogsPageAsync()
|
public static async Task PushBlogsPageAsync()
|
||||||
{
|
{
|
||||||
var app = (App)Current!;
|
var app = (App)Current!;
|
||||||
var mainVm = app.ServiceProvider!.GetRequiredService<MainViewModel>();
|
var mainVm = app.ServiceProvider!.GetRequiredService<BlogsViewModel>();
|
||||||
await mainVm.InitializeAsync();
|
await mainVm.InitializeAsync();
|
||||||
await app.PushPageAsync(mainVm);
|
await app.PushPageAsync(mainVm);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
using PostIt.Views.Blogs;
|
||||||
using PostIt.Views.Commands;
|
using PostIt.Views.Commands;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ public static class ServiceCollectionHelpers
|
||||||
|
|
||||||
// Vues
|
// Vues
|
||||||
services.AddSingleton<MainView>();
|
services.AddSingleton<MainView>();
|
||||||
services.AddSingleton<MainPage>();
|
services.AddSingleton<BlogsPage>();
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
|
|
||||||
// SettingsPage is a singleton: there must be one and only one
|
// SettingsPage is a singleton: there must be one and only one
|
||||||
|
|
@ -89,7 +90,7 @@ public static class ServiceCollectionHelpers
|
||||||
sessionStatus.Refresh();
|
sessionStatus.Refresh();
|
||||||
services.AddSingleton(sessionStatus);
|
services.AddSingleton(sessionStatus);
|
||||||
services.AddSingleton<SessionStatusBanner>();
|
services.AddSingleton<SessionStatusBanner>();
|
||||||
services.AddSingleton<MainViewModel>();
|
services.AddSingleton<BlogsViewModel>();
|
||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.ViewModels.Commands;
|
using PostIt.ViewModels.Commands;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
using PostIt.Views.Blogs;
|
||||||
using PostIt.Views.Commands;
|
using PostIt.Views.Commands;
|
||||||
|
|
||||||
namespace PostIt;
|
namespace PostIt;
|
||||||
|
|
@ -38,7 +39,7 @@ public class ViewLocator : IDataTemplate
|
||||||
var services = app!.ServiceProvider!;
|
var services = app!.ServiceProvider!;
|
||||||
return data switch
|
return data switch
|
||||||
{
|
{
|
||||||
MainViewModel => services.GetRequiredService<MainPage>(),
|
BlogsViewModel => services.GetRequiredService<BlogsPage>(),
|
||||||
Settings => services.GetRequiredService<SettingsPage>(),
|
Settings => services.GetRequiredService<SettingsPage>(),
|
||||||
HomePageViewModel => services.GetRequiredService<HomePage>(),
|
HomePageViewModel => services.GetRequiredService<HomePage>(),
|
||||||
ActivitiesPageViewModel => services.GetRequiredService<ActivitiesPage>(),
|
ActivitiesPageViewModel => services.GetRequiredService<ActivitiesPage>(),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ using PostIt.Helpers;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public partial class MainViewModel : ViewModelBase, IActionStatusViewModel
|
public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel
|
||||||
{
|
{
|
||||||
/// <summary>Window/tab title. Cosmetic — bound by
|
/// <summary>Window/tab title. Cosmetic — bound by
|
||||||
/// <c>MainPage.axaml</c> if at all. Not the post title.</summary>
|
/// <c>MainPage.axaml</c> if at all. Not the post title.</summary>
|
||||||
|
|
@ -336,7 +336,7 @@ public partial class MainViewModel : ViewModelBase, IActionStatusViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public MainViewModel()
|
public BlogsViewModel()
|
||||||
{
|
{
|
||||||
SettingsModel = new Settings();
|
SettingsModel = new Settings();
|
||||||
Init(SettingsModel);
|
Init(SettingsModel);
|
||||||
|
|
@ -396,7 +396,7 @@ public partial class MainViewModel : ViewModelBase, IActionStatusViewModel
|
||||||
/// <see cref="BlogApiClient"/>. Production code uses the
|
/// <see cref="BlogApiClient"/>. Production code uses the
|
||||||
/// (Settings, BlogApiClient) overload below.
|
/// (Settings, BlogApiClient) overload below.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MainViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null)
|
public BlogsViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null)
|
||||||
{
|
{
|
||||||
SettingsModel = new Settings();
|
SettingsModel = new Settings();
|
||||||
BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ;
|
BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ;
|
||||||
|
|
@ -5,15 +5,15 @@
|
||||||
xmlns:vm="using:PostIt.ViewModels"
|
xmlns:vm="using:PostIt.ViewModels"
|
||||||
xmlns:postitControls="using:PostIt.Controls"
|
xmlns:postitControls="using:PostIt.Controls"
|
||||||
xmlns:models="using:Yavsc.Blogspot"
|
xmlns:models="using:Yavsc.Blogspot"
|
||||||
xmlns:views="using:PostIt.Views"
|
xmlns:views="using:PostIt.Views.Blogs"
|
||||||
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="PostIt.Views.MainPage"
|
x:Class="PostIt.Views.Blogs.BlogsPage"
|
||||||
x:DataType="vm:MainViewModel"
|
x:DataType="vm:BlogsViewModel"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainViewModel />
|
<vm:BlogsViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid Margin="12" RowSpacing="12"
|
<Grid Margin="12" RowSpacing="12"
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ using System;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
|
||||||
namespace PostIt.Views;
|
namespace PostIt.Views.Blogs;
|
||||||
|
|
||||||
public partial class MainPage : ContentPage
|
public partial class BlogsPage : ContentPage
|
||||||
{
|
{
|
||||||
public MainPage()
|
public BlogsPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ public partial class MainPage : ContentPage
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
if (DataContext is ViewModels.MainViewModel vm)
|
if (DataContext is ViewModels.BlogsViewModel vm)
|
||||||
{
|
{
|
||||||
if (!vm.IsLoaded)
|
if (!vm.IsLoaded)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
xmlns:views="using:PostIt.Views"
|
xmlns:views="using:PostIt.Views"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="PostIt.Views.MainView"
|
x:Class="PostIt.Views.MainView"
|
||||||
x:DataType="vm:MainViewModel">
|
x:DataType="vm:HomePageViewModel">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
<vm:MainViewModel />
|
<vm:HomePageViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public partial class MainView : UserControl
|
||||||
protected override void OnDataContextChanged(EventArgs e)
|
protected override void OnDataContextChanged(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDataContextChanged(e);
|
base.OnDataContextChanged(e);
|
||||||
if (DataContext is ViewModels.MainViewModel vm)
|
if (DataContext is ViewModels.BlogsViewModel vm)
|
||||||
{
|
{
|
||||||
if (!vm.IsLoaded)
|
if (!vm.IsLoaded)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
<!-- Yavsc.Org.Tests-specific versions -->
|
<!-- Yavsc.Org.Tests-specific versions -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.11" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
129
src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs
Normal file
129
src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yavsc.Models;
|
||||||
|
using Yavsc.Models.Access;
|
||||||
|
using Yavsc.Models.Relationship;
|
||||||
|
using Yavsc.Services;
|
||||||
|
|
||||||
|
namespace Yavsc.Org.Tests.Services;
|
||||||
|
|
||||||
|
public class FileSystemAuthManagerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SetAccess_creates_acl_row_with_owner_path_and_flags()
|
||||||
|
{
|
||||||
|
using var scope = CreateScope();
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read | FileAccessRight.Write);
|
||||||
|
|
||||||
|
var row = scope.Db.CircleAuthorizationToFile.Single();
|
||||||
|
|
||||||
|
Assert.Equal(scope.Circle.Id, row.CircleId);
|
||||||
|
Assert.Equal("alice/documents/report.txt", row.Path);
|
||||||
|
Assert.Equal("alice", row.OwnerId);
|
||||||
|
Assert.Equal(FileAccessRight.Read | FileAccessRight.Write, row.Access);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetAccess_updates_existing_acl_row_without_duplicates()
|
||||||
|
{
|
||||||
|
using var scope = CreateScope();
|
||||||
|
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read);
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Write);
|
||||||
|
|
||||||
|
var rows = scope.Db.CircleAuthorizationToFile.ToList();
|
||||||
|
|
||||||
|
Assert.Single(rows);
|
||||||
|
Assert.Equal(FileAccessRight.Write, rows[0].Access);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetAccess_none_removes_existing_acl_row()
|
||||||
|
{
|
||||||
|
using var scope = CreateScope();
|
||||||
|
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read);
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.None);
|
||||||
|
|
||||||
|
Assert.Empty(scope.Db.CircleAuthorizationToFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SetAccess_ignores_unknown_owner_prefix()
|
||||||
|
{
|
||||||
|
using var scope = CreateScope();
|
||||||
|
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "unknown/documents/report.txt", FileAccessRight.Read);
|
||||||
|
|
||||||
|
Assert.Empty(scope.Db.CircleAuthorizationToFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Deleting_circle_cascades_file_acl_rows()
|
||||||
|
{
|
||||||
|
using var scope = CreateScope();
|
||||||
|
|
||||||
|
scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read);
|
||||||
|
scope.Db.Circle.Remove(scope.Circle);
|
||||||
|
scope.Db.SaveChanges();
|
||||||
|
|
||||||
|
Assert.Empty(scope.Db.CircleAuthorizationToFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestScope CreateScope()
|
||||||
|
{
|
||||||
|
var connection = new SqliteConnection("Data Source=:memory:");
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
var db = new ApplicationDbContext(options);
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
|
||||||
|
db.Users.Add(new ApplicationUser
|
||||||
|
{
|
||||||
|
Id = "alice",
|
||||||
|
UserName = "alice",
|
||||||
|
Email = "alice@example.test"
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
var circle = new Circle
|
||||||
|
{
|
||||||
|
OwnerId = "alice",
|
||||||
|
Name = "shared",
|
||||||
|
Public = false
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Circle.Add(circle);
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
var service = new FileSystemAuthManager(db, Options.Create(new SiteSettings()));
|
||||||
|
return new TestScope(connection, db, service, circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestScope : IDisposable
|
||||||
|
{
|
||||||
|
public TestScope(SqliteConnection connection, ApplicationDbContext db, FileSystemAuthManager service, Circle circle)
|
||||||
|
{
|
||||||
|
Connection = connection;
|
||||||
|
Db = db;
|
||||||
|
Service = service;
|
||||||
|
Circle = circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqliteConnection Connection { get; }
|
||||||
|
public ApplicationDbContext Db { get; }
|
||||||
|
public FileSystemAuthManager Service { get; }
|
||||||
|
public Circle Circle { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Db.Dispose();
|
||||||
|
Connection.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!--
|
<!--
|
||||||
MapStaticAssets() in the production pipeline resolves
|
MapStaticAssets() in the production pipeline resolves
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue