diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cb334e0..915683fc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,5 @@ "copilotcli/gpt-5.3-codex" ] } - }, - "dotnet.defaultSolution": "yavsc.sln" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3faedae0..3c0c0bf2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,21 +1,5 @@ { "version": "2.0.0", - "isRoot": true, - "problemMatcher": [ - { - "owner": "dotnet", - "fileLocation": ["relative", "${workspaceFolder}"], - "source": "dotnet", - "pattern": { - "regexp": "^\\s+(.*)\\((\\d+):(\\d+)\\):\\s+(error|warning)\\s+(.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "message": 5 - } - } - ], "tasks": [ { "label": "run-debug-android", @@ -36,6 +20,21 @@ "-p:AndroidAttachDebugger=true", "-p:AndroidSdbHostPort=55555", "-p:AndroidSdbTargetPort=55555" + ], + "problemMatcher": [ + { + "owner": "dotnet", + "fileLocation": ["relative", "${workspaceFolder}"], + "source": "dotnet", + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } ] }, { @@ -46,6 +45,7 @@ "group": "build", "isBuildCommand": true, "isTestCommand": false, + "problemMatcher": ["$msCompile"], "isBackground": true }, { diff --git a/CHANGELOG.md b/CHANGELOG.md index 8425c2c6..6adf462a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,16 +16,6 @@ Cette convention est partagée avec le dépôt [`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) pour la production des paquets `.deb`. -## [1.0.8-rc4] - unstable - -### Added - -### Changed - -### Fixed - -* [TODO] bug loading a blog post from PostIt, ACL come along with and don't need any "Refresh" button. - ## [1.0.8-rc3] - unstable ### Added diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 7f44645d..62b494f8 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -9,22 +9,23 @@ 1.0 apk false - 1.1.0.0 - 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 - 1.1.0-beta.1 + Resources\drawable\Icon.png + - + + + - \ No newline at end of file + + diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 67600a72..2816b2f8 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 30ba65c4..e47c5b6d 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj index 86a7998a..663bdd12 100644 --- a/src/PostIt/PostIt.Tests/PostIt.Tests.csproj +++ b/src/PostIt/PostIt.Tests/PostIt.Tests.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -16,7 +16,9 @@ + + @@ -26,5 +28,6 @@ - - \ No newline at end of file + + + diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index 1ae36a44..bde8fd41 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -1,16 +1,14 @@ - + net10.0 enable latest - 1.1.0.0 - 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 - 1.1.0-beta.1 + + PreserveNewest @@ -33,8 +31,9 @@ + - \ No newline at end of file + diff --git a/src/PostIt/PostIt/Services/UiDispatcher.cs b/src/PostIt/PostIt/Services/UiDispatcher.cs new file mode 100644 index 00000000..e935ac1a --- /dev/null +++ b/src/PostIt/PostIt/Services/UiDispatcher.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Threading; + +namespace PostIt.Services; + +/// +/// Tiny marshalling helper around so +/// the rest of the codebase does not have to import Avalonia.Threading +/// directly. We want exactly one place that decides "is the current +/// thread the Avalonia UI thread, and if not, post there" so that +/// -derived types (Settings, the various +/// ViewModels) can fire PropertyChanged safely from background +/// work — which is exactly the cross-thread case that previously blew +/// up inside DataValidationErrors.SetErrors on Avalonia 11. +/// +/// The helper is intentionally tiny: a sync post when we are off the +/// UI thread, a no-op when we are already on it, and an async fire- +/// and-forget variant for places where awaiting would deadlock the +/// caller (e.g. Settings.Load continuation paths). +/// +public static class UiDispatcher +{ + /// + /// True when the calling thread is the Avalonia UI thread. Property + /// setters that touch bindings should check this before mutating + /// state; the safe path is . + /// + public static bool IsOnUiThread => Dispatcher.UIThread.CheckAccess(); + + /// + /// Run on the UI thread. If the caller is + /// already on the UI thread, run synchronously to preserve stack + /// traces and ordering; otherwise post to the dispatcher and wait. + /// Never throws on shutdown — a missing dispatcher is treated as + /// "best-effort skipped", matching Avalonia's own behaviour when + /// the application lifetime has been torn down. + /// + public static void InvokeIfNeeded(Action action) + { + if (action is null) return; + if (IsOnUiThread) { action(); return; } + try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } + catch (InvalidOperationException) { /* dispatcher gone, nothing to do */ } + } + + /// + /// Fire-and-forget variant: schedules on + /// the UI thread but does not block the caller. Use this from + /// background workers (OIDC discovery, HTTP callbacks, file I/O) + /// where awaiting the dispatcher would deadlock the calling sync + /// context. + /// + public static void Post(Action action) + { + if (action is null) return; + try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } + catch (InvalidOperationException) { /* dispatcher gone */ } + } + + /// + /// Awaitable variant. Useful inside async ViewModel methods + /// that must touch bindings only after the dispatcher has processed + /// a queued update (e.g. "load file then refresh observable state"). + /// + public static Task InvokeAsync(Action action) + { + if (action is null) return Task.CompletedTask; + if (IsOnUiThread) { action(); return Task.CompletedTask; } + return Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Normal).GetTask(); + } +} diff --git a/src/Yavsc.Abstract/Constants.cs b/src/Yavsc.Abstract/Constants.cs index a2649491..78ff0838 100644 --- a/src/Yavsc.Abstract/Constants.cs +++ b/src/Yavsc.Abstract/Constants.cs @@ -7,13 +7,6 @@ namespace Yavsc { public const string APIPrefix = "api/v1"; - - public const string BlogSpotPath = "blogspot"; - public const string BlogAclPath = "blogacl"; - public const string BlogTagPath = "blogtag"; - public const string CirclePath = "circle"; - public const string CommentsPath = "blogcomments"; - public static readonly Scope[] SiteScopes = { new Scope { Id = "profile", Description = "Your profile informations" }, new Scope { Id = "book" , Description ="Your booking interface"}, diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 5094b14d..ebbb2d1e 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,10 +11,10 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 - \ No newline at end of file + diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 4c560f23..047223a6 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,7 +17,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index 3655079b..2ddeb72d 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index 4aef805f..92dd3b2c 100644 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -1,12 +1,10 @@ using System.Net; using System.Net.Http.Json; -using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Yavsc.Abstract.BlogSpot; using Yavsc.Models; using Yavsc.Models.Access; -using Yavsc.Models.Blog; using Yavsc.Tests.Shared; using static Yavsc.Constants; @@ -40,16 +38,15 @@ public sealed class BlogAclApiTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; + public BlogAclApiTests(BlogsWebServerFixture fixture) { _fixture = fixture; } - private string BlogUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogSpotPath}"; private string BlogAclUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}"; + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/blogacl"; /// Delete any ACL rows tied to the fixture's seeded /// (CircleId, BlogPostId) pair. The shared SQLite store @@ -123,7 +120,7 @@ public sealed class BlogAclApiTests : IClassFixture // owned by the caller. We seed the same shape pre-POST so the // test reproduces the prod scenario end-to-end. CleanupAcl(); - using var http = NewClient(_fixture.DefaultUserLogin); + using var http = NewClient("alice"); var payload = new PostAccessControlRulePayload { @@ -131,8 +128,7 @@ public sealed class BlogAclApiTests : IClassFixture BlogPostId = _fixture.PostId }; - var response = await http.PostAsJsonAsync( - BlogAclUrl(), payload, + var response = await http.PostAsJsonAsync(BlogAclUrl(), payload, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -182,7 +178,7 @@ public sealed class BlogAclApiTests : IClassFixture [MemberData(nameof(BlogAclPayloadsForNever500))] public async Task PostCircleAuthorization_never_returns_500(PostAccessControlRulePayload payload) { - using var http = NewClient(_fixture.DefaultUserLogin); + using var http = NewClient("alice"); var response = await http.PostAsJsonAsync( BlogAclUrl(), payload, @@ -220,83 +216,4 @@ public sealed class BlogAclApiTests : IClassFixture ); } - - [Fact] - public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list() - { - CleanupAcl(); - _fixture.SeedUser(_fixture.DefaultUserLogin); - _fixture.SeedUser("tester"); - _fixture.SeedCircle(_fixture.DefaultUserLogin, "test", - false, - new String[] - { - _fixture.DefaultUserLogin, - "tester" - }); - using var http = NewClient(_fixture.DefaultUserLogin ); - - // Create a minimal BlogPost. The server assigns Id, so we - // send 0 + an explicit AuthorId; the production - // BlogSpotService.Create() tolerates that. - var draft = new BlogPost - { - Id = 0, - Title = "Premier billet", - AuthorId = "tester", - Article = "Contenu de test.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - ACL = new List( - new CircleAuthorizationToBlogPost[] - { - new CircleAuthorizationToBlogPost - { - CircleId = _fixture.CircleId, - BlogPostId = _fixture.PostId - } - } - ) - }; - - var postResponse = await http.PostAsJsonAsync( - BlogUrl(), - draft, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - - // The POST returns the server-issued post (with a real Id). - var created = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - Assert.Equal(draft.Title, created.Title); - - // The list should now contain exactly one entry. - var listResponse = await http.GetAsync( - BlogUrl(), - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - - using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); - Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); - Assert.Equal(2, doc.RootElement.GetArrayLength()); - Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64()); - - // detail should return the same post, with ACL and tags. - var detailResponse = await http.GetAsync( - $"{BlogUrl()}/{created.Id}", - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); - using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); - Assert.Equal(JsonValueKind.Object, detailDoc.RootElement.ValueKind); - Assert.Equal(created.Id, detailDoc.RootElement.GetProperty("id").GetInt64()); - Assert.Equal(1, detailDoc.RootElement.GetProperty("acl").GetArrayLength()); - } - } diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index f6b46ba9..f4878860 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -8,21 +8,14 @@ using Microsoft.IdentityModel.Tokens; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : -IClassFixture, -IBackendFixture +public sealed class BlogApiMappedClaimsTests : IClassFixture { private readonly MappedClaimsBlogsWebServerFixture _fixture; - public IReadOnlyList Addresses => throw new NotImplementedException(); - - public IServiceProvider Services => throw new NotImplementedException(); - public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture) { _fixture = fixture; @@ -86,10 +79,7 @@ IBackendFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync( - _fixture.BlogSpotUrl(), - draft, - TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, response.StatusCode); var created = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); @@ -103,7 +93,7 @@ IBackendFixture ResetDatabase(); using var http = NewClient(subject: "mapped-owner"); - var createdResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost + var createdResponse = await http.PostAsJsonAsync("/api/v1/blog", new BlogPost { Id = 0, Title = "Billet à modifier", @@ -117,7 +107,7 @@ IBackendFixture var created = await createdResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(created); - var updateResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost + var updateResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost { Id = created.Id, Title = "Billet modifié", @@ -136,7 +126,7 @@ IBackendFixture ResetDatabase(); using var ownerHttp = NewClient(subject: "mapped-owner"); - var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost + var createdResponse = await ownerHttp.PostAsJsonAsync("/api/v1/blog", new BlogPost { Id = 0, Title = "Billet protégé", @@ -151,7 +141,7 @@ IBackendFixture Assert.NotNull(created); using var otherHttp = NewClient(subject: "mapped-other"); - var updateResponse = await otherHttp.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost + var updateResponse = await otherHttp.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost { Id = created.Id, Title = "Tentative de modification", diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index 11f5c0a2..5a218316 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -2,11 +2,11 @@ using System.Net; using System.Net.Http.Json; using System.Security.Claims; using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -31,6 +31,18 @@ public sealed class BlogApiTests : IClassFixture _fixture = fixture; } + /// Reset the in-memory database to a known empty state. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. + private void ResetDatabase() + { + using var scope = _fixture.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + } /// Reset the database and seed the /// tester row. Required @@ -43,10 +55,18 @@ public sealed class BlogApiTests : IClassFixture /// at SaveChanges and the controller returns 500. private void ResetAndSeedDefaultUser() { - _fixture.ResetDatabase(); + ResetDatabase(); _fixture.SeedUser("tester"); } + /// The fixture's WebApplication is bound to + /// https://localhost:<random> via + /// . We pick the first + /// https URL and append the controller route + /// (/api/v1/blog, matching the production + /// [Route(APIPrefix + "/blog")]). + private string BlogsUrl => + _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT @@ -91,11 +111,10 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlogs_returns_200_with_empty_list_when_no_posts() { - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewClient(); - var response = await http.GetAsync( - _fixture.BlogSpotUrl(), + var response = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -128,7 +147,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -141,7 +160,7 @@ public sealed class BlogApiTests : IClassFixture Assert.Equal(draft.Title, created.Title); // The list should now contain exactly one entry. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), + var listResponse = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -169,7 +188,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -179,7 +198,7 @@ public sealed class BlogApiTests : IClassFixture Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), + var listResponse = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); @@ -207,7 +226,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -247,7 +266,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlog_returns_401_when_no_token_is_provided() { - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewAnonymousClient(); // No Authorization header → the JwtBearer middleware @@ -256,7 +275,7 @@ public sealed class BlogApiTests : IClassFixture // the framework returns 401. This is the proof that the // production policy is wired in the test host and not // short-circuited by a test-only auth bypass. - var response = await http.GetAsync(_fixture.BlogSpotUrl(), + var response = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } @@ -284,7 +303,7 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); @@ -303,13 +322,13 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", + var putResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created.Id}", update, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); // The list should now reflect the new title. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), + var listResponse = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); using var doc = JsonDocument.Parse( @@ -337,19 +356,19 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); var created = (await postResponse.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken ))!; - var deleteResponse = await http.DeleteAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", + var deleteResponse = await http.DeleteAsync($"/api/v1/blog/{created.Id}", TestContext.Current.CancellationToken ); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), + var listResponse = await http.GetAsync("/api/v1/blog", TestContext.Current.CancellationToken); String response = await listResponse.Content.ReadAsStringAsync( TestContext.Current.CancellationToken @@ -392,7 +411,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var response = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); // Dump the body on failure so the test name + the response @@ -424,7 +443,7 @@ public sealed class BlogApiTests : IClassFixture // behaviour so a future change that, say, makes Title // nullable in the model or drops [Required], triggers a // conscious update of the test (and probably of the VM). - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost @@ -437,7 +456,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, + var response = await http.PostAsJsonAsync("/api/v1/blog", draft, TestContext.Current.CancellationToken); if (response.StatusCode != HttpStatusCode.BadRequest) diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs similarity index 90% rename from src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 6e8ae31f..218904ef 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -10,7 +10,7 @@ using Yavsc.Models.Blog; using Yavsc.Models.Relationship; using Yavsc.Services; using Yavsc.Tests.Shared; -using static Yavsc.Constants; + namespace Yavsc.Blogs.Tests; /// @@ -61,7 +61,6 @@ public sealed class BlogsWebServerFixture : WebHostFixture public long CircleId { get; private set; } public long PostId { get; private set; } - public string DefaultUserLogin { get => "alice"; } // A single SqliteConnection held open at the static level, // mirroring how Yavsc.Org.Tests.WebServerFixture hoists its @@ -170,8 +169,7 @@ public sealed class BlogsWebServerFixture : WebHostFixture // PermissionHandler ownership check sees a null // user id and rejects every PUT. options.MapInboundClaims = false; - options.TokenValidationParameters - = new TokenValidationParameters + options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = TestTokenIssuer.Issuer, @@ -265,27 +263,6 @@ public sealed class BlogsWebServerFixture : WebHostFixture await Task.CompletedTask; return app; } -/// Reset the in-memory database to a known empty state. - /// UseInMemoryDatabase shares its store across the - /// lifetime of the instance, - /// so without a per-test reset the test order would leak - /// state between tests. - public void ResetDatabase() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - } - public void CleanupAcl() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.CircleAuthorizationToBlogPost - .Where(a => a.CircleId == CircleId - && a.BlogPostId == PostId) - .ExecuteDelete(); - } public override void Dispose() { @@ -333,8 +310,7 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Optional hook to fill in fields /// like FullName / Avatar / EmailConfirmed /// that downstream tests assert on. - public ApplicationUser SeedUser(string userName, - Action? configure = null) + public ApplicationUser SeedUser(string userName, Action? configure = null) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -375,31 +351,20 @@ public sealed class BlogsWebServerFixture : WebHostFixture /// Create a circle owned by /// directly in the SQLite store and return its server-assigned /// id. - public long SeedCircle(string ownerId, string name, bool isPublic = false, - ICollection members = null - ) + private long SeedCircle(string ownerId, string name, bool isPublic = false) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var circle = new Circle { OwnerId = ownerId, Name = name, Public = isPublic }; db.Circle.Add(circle); db.SaveChanges(); - if (members != null && members.Count > 0) - { - foreach (String memberId in members) - { - var member = new CircleMember { CircleId = circle.Id, MemberId = memberId }; - db.CircleMembers.Add(member); - } - db.SaveChanges(); - } return circle.Id; } /// Create a blog post owned by /// directly in the SQLite store and return its server-assigned /// id. - public long SeedBlogPost(string authorId, string title) + private long SeedBlogPost(string authorId, string title) { using var scope = Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -415,5 +380,4 @@ public sealed class BlogsWebServerFixture : WebHostFixture db.SaveChanges(); return post.Id; } - } diff --git a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs similarity index 99% rename from src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs index 2546de0a..127f38fe 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs @@ -21,7 +21,7 @@ namespace Yavsc.Blogs.Tests; /// This is the closest in-process reproduction of the production /// authentication surface for the blog API. /// -public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFixture +public sealed class MappedClaimsBlogsWebServerFixture : IDisposable { private readonly InMemoryDatabaseRoot _inMemoryRoot = new(); private readonly Dictionary _savedInboundMap; diff --git a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs index 113707ca..7d5a02a9 100644 --- a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs +++ b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -72,6 +71,12 @@ public sealed class PublishEndpointTests : IClassFixture return post.Id; } + private string PublishUrl(long id) + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/v1/blog/{id}/publish"; + + private string BlogsUrl + => _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; + private HttpClient NewClient(string subject) { var handler = new HttpClientHandler @@ -95,13 +100,12 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, get.StatusCode); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); - Assert.Equal($"post-by-alice", doc.RootElement.GetProperty("title").GetString()); Assert.True(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -112,12 +116,11 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); + await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, get.StatusCode); + var get = await http.GetAsync($"{BlogsUrl}/{postId}", TestContext.Current.CancellationToken); using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); Assert.False(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -127,7 +130,7 @@ public sealed class PublishEndpointTests : IClassFixture { ResetDatabase(); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.NotFound, put.StatusCode); } @@ -138,7 +141,7 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("bob"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); // 401 Challenge (the controller returns Challenge() // for AuthorizationFailureException). The exact code // is framework-dependent; what matters is "not 204". diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index bc4e34ba..34536806 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Blogs/Constants.cs b/src/Yavsc.Blogs/Constants.cs index 9d400032..3e499da4 100644 --- a/src/Yavsc.Blogs/Constants.cs +++ b/src/Yavsc.Blogs/Constants.cs @@ -1,6 +1,6 @@ namespace Yavsc.Blogs; -public static class BlogConstants +public static class Constants { public const string AdminRole = "Admin"; public const string ModeratorRole = "Moderator"; diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs index 8c76f7ee..fcd3a336 100644 --- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs @@ -9,7 +9,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize("BlogScope")] [Produces("application/json")] - [Route(APIPrefix + "/" + BlogSpotPath)] + [Route(APIPrefix + "/blog")] public class BlogApiController : Controller { private readonly BlogSpotService blogSpotService; @@ -19,14 +19,14 @@ namespace Yavsc.Blogs.Controllers this.blogSpotService = blogSpotService; } - // GET: api/v1/blogspot + // GET: api/BlogApi [HttpGet] public async Task> GetBlogspot(int start = 0, int take = 25) { return await blogSpotService.Index(User, null, start, take); } - // GET: api/v1/blogspot/5 + // GET: api/BlogApi/5 [HttpGet("{id}", Name = "GetBlog")] public async Task GetBlog([FromRoute] long id) { @@ -43,7 +43,7 @@ namespace Yavsc.Blogs.Controllers return NotFound(); } - return Ok(blog.GetPayload()); + return Ok(blog); } catch (AuthorizationFailureException) { @@ -51,7 +51,7 @@ namespace Yavsc.Blogs.Controllers } } - // PUT: api/v1/blogspot/5 + // PUT: api/BlogApi/5 [HttpPut("{id}")] public async Task PutBlog(long id, [FromBody] Models.Blog.BlogPost blog) { @@ -83,7 +83,7 @@ namespace Yavsc.Blogs.Controllers return new StatusCodeResult(StatusCodes.Status204NoContent); } - // POST: api/v1/blogspot + // POST: api/v1/blog [HttpPost] public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog) { @@ -116,8 +116,7 @@ namespace Yavsc.Blogs.Controllers : (IFormFileCollection)new FormFileCollection(); var uid = User.GetUserId(); var post = blogSpotService.Create(uid, blog, files); - return CreatedAtRoute("GetBlog", new { id = post.Id }, - post.GetPayload()); + return CreatedAtRoute("GetBlog", new { id = post.Id }, post); } // DELETE: api/BlogApi/5 @@ -136,7 +135,7 @@ namespace Yavsc.Blogs.Controllers } await blogSpotService.Delete(User, id); - return Ok(blog.GetPayload()); + return Ok(blog); } /// diff --git a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs index 533e5594..ad6a0893 100644 --- a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs @@ -6,7 +6,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix + "/" + BlogTagPath )] + [Route(APIPrefix + "/blogtags")] public class BlogTagsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CircleApiController.cs b/src/Yavsc.Blogs/Controllers/CircleApiController.cs index fafd00ac..524f0471 100644 --- a/src/Yavsc.Blogs/Controllers/CircleApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CircleApiController.cs @@ -8,7 +8,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix +"/" + CirclePath)] + [Route(APIPrefix +"/circle")] public class CircleApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs index d0747c29..d4c80f99 100644 --- a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs @@ -11,7 +11,7 @@ namespace Yavsc.Blogs.Controllers { [Authorize] [Produces("application/json")] - [Route(APIPrefix + "/" + CommentsPath)] + [Route(APIPrefix + "/blogcomments")] public class CommentsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 8692b28a..019fb102 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,7 +8,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 8019a78f..34de5bd8 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -89,4 +89,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml index 1582041c..52cf3b88 100644 --- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml @@ -70,7 +70,7 @@
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded) { - Details + Details } else { diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index 6dbceb4a..aeb94a8e 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,7 +9,7 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Server/Helpers/PayloadHelpers.cs b/src/Yavsc.Server/Helpers/PayloadHelpers.cs deleted file mode 100644 index cd359bb4..00000000 --- a/src/Yavsc.Server/Helpers/PayloadHelpers.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Yavsc.Models.Blog; - -public static class PayloadHelpers -{ - public static object GetPayload(this BlogPost post) - { - return new - { - post.Id, - post.Title, - post.Article, - post.DateCreated, - post.UserCreated, - post.DateModified, - post.UserModified, - post.AuthorId, - ACL = post.GetACL(), - Tags = post.GetTags(), - post.IsPublished - }; - } -} diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index e5fd615d..5a2dc58d 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -85,7 +85,7 @@ namespace Yavsc.Models.Blog public string[] GetTags() { - return Tags?.Select(t => t.Tag.Name).ToArray() ?? Array.Empty(); + return Tags.Select(t => t.Tag.Name).ToArray(); } [InverseProperty("Post")] @@ -106,7 +106,6 @@ namespace Yavsc.Models.Blog [NotMapped] public bool IsPublished { get; set; } - [JsonIgnore] /// /// Explicit interface implementation of /// . The underlying diff --git a/src/Yavsc.Server/Models/Blog/BlogTag.cs b/src/Yavsc.Server/Models/Blog/BlogTag.cs index 1f15a081..69d428de 100644 --- a/src/Yavsc.Server/Models/Blog/BlogTag.cs +++ b/src/Yavsc.Server/Models/Blog/BlogTag.cs @@ -1,17 +1,14 @@ using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; using Yavsc.Models.Relationship; namespace Yavsc.Models.Blog { public partial class BlogTag { - [JsonIgnore] [ForeignKey("PostId")] public virtual BlogPost Post { get; set; } public long PostId { get; set; } - [JsonIgnore] [ForeignKey("TagId")] public virtual Tag Tag{ get; set; } public long TagId { get; set; } diff --git a/src/Yavsc.Server/Models/Blog/Comment.cs b/src/Yavsc.Server/Models/Blog/Comment.cs index 4d39d0c2..acb5e003 100644 --- a/src/Yavsc.Server/Models/Blog/Comment.cs +++ b/src/Yavsc.Server/Models/Blog/Comment.cs @@ -13,17 +13,15 @@ namespace Yavsc.Models.Blog [YaStringLength(1024)] public string Article { get; set; } - - [JsonIgnore] - [ForeignKeyAttribute(nameof(ReceiverId))] + + [ForeignKeyAttribute(nameof(ReceiverId))][JsonIgnore] public virtual BlogPost Post { get; set; } [Required] public long ReceiverId { get; set; } public bool Visible { get; set; } - [ForeignKeyAttribute("AuthorId")] - [JsonIgnore] + [ForeignKeyAttribute("AuthorId")][JsonIgnore] public virtual ApplicationUser Author { get; set; } diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 839e476a..7adb3a78 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/src/Yavsc.Tests.Shared/BlogHelpers.cs b/src/Yavsc.Tests.Shared/BlogHelpers.cs deleted file mode 100644 index ef5a7688..00000000 --- a/src/Yavsc.Tests.Shared/BlogHelpers.cs +++ /dev/null @@ -1,31 +0,0 @@ - -namespace Yavsc.Blogs.Tests.Fixtures; -using static Yavsc.Constants; - -public static class BlogHelpers -{ - public static string ApiUrl(this IBackendFixture fixture, string apiSubPath) - { - var secured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("https://")); - if (secured is null) - { - var unsecured = fixture.Addresses.FirstOrDefault(a => a.StartsWith("http://")); - if (unsecured is null) - { - throw new InvalidOperationException("No backend address found"); - } - return $"{unsecured}/{APIPrefix}/{apiSubPath}"; - } - return $"{secured}/{APIPrefix}/{apiSubPath}"; - } - - public static string BlogAclUrl(this IBackendFixture fixture) - => fixture.ApiUrl(BlogAclPath); - - public static string BlogSpotUrl(this IBackendFixture fixture) - => fixture.ApiUrl(BlogSpotPath); - - public static string PublishUrl(this IBackendFixture fixture, long id) - => fixture.ApiUrl(BlogSpotPath) +"/" + id + "/publish"; - -} diff --git a/src/Yavsc.Tests.Shared/IBackendFixture.cs b/src/Yavsc.Tests.Shared/IBackendFixture.cs deleted file mode 100644 index 4e2f009d..00000000 --- a/src/Yavsc.Tests.Shared/IBackendFixture.cs +++ /dev/null @@ -1,13 +0,0 @@ - -public interface IBackendFixture -{ - /// - /// The addresses the fixture bound to. - /// - IReadOnlyList Addresses { get; } - - /// - /// The service provider for the fixture host. - /// - IServiceProvider Services { get; } -} diff --git a/src/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs index 95b23082..417d29d8 100644 --- a/src/Yavsc.Tests.Shared/WebHostFixture.cs +++ b/src/Yavsc.Tests.Shared/WebHostFixture.cs @@ -30,7 +30,7 @@ namespace Yavsc.Tests.Shared; /// mapping are the responsibility of the subclass, through /// . /// -public abstract class WebHostFixture : IDisposable, IBackendFixture +public abstract class WebHostFixture : IDisposable { private static readonly Lazy _selfSignedCertificate = new Lazy(CreateSelfSignedCertificate); diff --git a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index 6e7a4c5a..7f2f382a 100644 --- a/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,7 +14,7 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1 @@ -23,7 +23,4 @@ - - - diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 49aa64b1..237ccdd8 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,7 +7,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0 + 1.1.0-beta.1+49.Branch.release-1.0.8-rc3.Sha.5274d7bdf5d14406748a2063b5ae15191aac4359 1.1.0-beta.1