Compare commits

..
24 changed files with 135 additions and 4881 deletions

20
.vscode/tasks.json vendored
View file

@ -1,13 +1,27 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"isRoot": true, "isRoot": true,
"problemMatcher": [
{
"owner": "dotnet",
"fileLocation": ["relative", "${workspaceFolder}"],
"source": "dotnet",
"pattern": {
"regexp": "^\\s*(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"code": 5,
"message": 6
}
}
],
"tasks": [ "tasks": [
{ {
"label": "run-debug-android", "label": "run-debug-android",
"command": "dotnet", "command": "dotnet",
"type": "shell", "type": "shell",
"problemMatcher": ["$msCompile"],
"options": { "options": {
"cwd": "${workspaceFolder}/src/PostIt/PostIt.Android", "cwd": "${workspaceFolder}/src/PostIt/PostIt.Android",
"env": { "env": {
@ -29,7 +43,6 @@
"label": "build", "label": "build",
"command": "dotnet", "command": "dotnet",
"type": "process", "type": "process",
"problemMatcher": ["$msCompile"],
"args": ["build", "/property:GenerateFullPaths=true"], "args": ["build", "/property:GenerateFullPaths=true"],
"group": "build", "group": "build",
"isBuildCommand": true, "isBuildCommand": true,
@ -96,7 +109,6 @@
"isTestCommand": false, "isTestCommand": false,
"isBackground": true, "isBackground": true,
"command": "dotnet", "command": "dotnet",
"problemMatcher": ["$msCompile"],
"args": ["build", "/property:GenerateFullPaths=true"], "args": ["build", "/property:GenerateFullPaths=true"],
"options": { "options": {
"cwd": "src/PostIt/PostIt.Desktop" "cwd": "src/PostIt/PostIt.Desktop"

View file

@ -13,10 +13,6 @@ all:
clean: clean:
dotnet clean -c $(CONFIG) dotnet clean -c $(CONFIG)
brutal_clean:
@find -name "obj" -exec rm -rf {} \;
@find -name "bin" -exec rm -rf {} \;
src/Yavsc/bin/output/wwwroot: src/Yavsc/bin/output/wwwroot:
dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish

View file

@ -13,7 +13,7 @@ using static Yavsc.Constants;
namespace Yavsc.Blogs.Tests; namespace Yavsc.Blogs.Tests;
/// <summary> /// <summary>
/// Behavioral tests for <c>BlogAclApiController.PostCircleAuthorizationToBlogPost</c>: /// Behavioural tests for <c>BlogAclApiController.PostCircleAuthorizationToBlogPost</c>:
/// <c>POST /api/v1/blogacl</c> with a JSON body of /// <c>POST /api/v1/blogacl</c> with a JSON body of
/// <c>CircleAuthorizationToBlogPost</c> (CircleId + BlogPostId). /// <c>CircleAuthorizationToBlogPost</c> (CircleId + BlogPostId).
/// ///
@ -84,7 +84,7 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
// rather than being rewritten to ClaimTypes.NameIdentifier. // rather than being rewritten to ClaimTypes.NameIdentifier.
// The controller, however, reads the user id via // The controller, however, reads the user id via
// User.FindFirstValue(ClaimTypes.NameIdentifier), so we add // User.FindFirstValue(ClaimTypes.NameIdentifier), so we add
// an explicit NameIdentifier claim to keep the legacy lookup happy. // an explicit nameid claim to keep the legacy lookup happy.
http.DefaultRequestHeaders.Authorization = http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue( new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", "Bearer",
@ -106,7 +106,7 @@ public sealed class BlogAclApiTests : IClassFixture<BlogsWebServerFixture>
/// when <see cref="PostAclDialogViewModel.AddAsync"/> POSTs the /// when <see cref="PostAclDialogViewModel.AddAsync"/> POSTs the
/// shape <c>{ "circleId": &lt;id&gt; }</c> — the exact body the /// shape <c>{ "circleId": &lt;id&gt; }</c> — the exact body the
/// PostIt client builds from <see cref="CircleAuthorization"/> /// PostIt client builds from <see cref="CircleAuthorization"/>
/// (which only carries <c>CircleId</c>). The server deserializes /// (which only carries <c>CircleId</c>). The server deserialises
/// it into <see cref="CircleAuthorizationToBlogPost"/>, leaves /// it into <see cref="CircleAuthorizationToBlogPost"/>, leaves
/// <c>BlogPostId</c> at its <c>default(long) = 0</c>, attaches /// <c>BlogPostId</c> at its <c>default(long) = 0</c>, attaches
/// no <c>Target</c> navigation, and EF Core refuses to INSERT /// no <c>Target</c> navigation, and EF Core refuses to INSERT

View file

@ -17,8 +17,8 @@ namespace Yavsc.Helpers
if (string.IsNullOrWhiteSpace(text)) return; if (string.IsNullOrWhiteSpace(text)) return;
try
try
{ {
if (context.AllAttributes.ContainsName("summary")) if (context.AllAttributes.ContainsName("summary"))
{ {

File diff suppressed because it is too large Load diff

View file

@ -1,52 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Yavsc.Migrations
{
/// <inheritdoc />
public partial class blogPostId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_blogSpotPublications_BlogSpot_BlogpostId",
table: "blogSpotPublications");
migrationBuilder.RenameColumn(
name: "BlogpostId",
table: "blogSpotPublications",
newName: "PostId");
migrationBuilder.AddForeignKey(
name: "FK_blogSpotPublications_BlogSpot_PostId",
table: "blogSpotPublications",
column: "PostId",
principalTable: "BlogSpot",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_blogSpotPublications_BlogSpot_PostId",
table: "blogSpotPublications");
migrationBuilder.RenameColumn(
name: "PostId",
table: "blogSpotPublications",
newName: "BlogpostId");
migrationBuilder.AddForeignKey(
name: "FK_blogSpotPublications_BlogSpot_BlogpostId",
table: "blogSpotPublications",
column: "BlogpostId",
principalTable: "BlogSpot",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -1693,10 +1693,10 @@ namespace Yavsc.Migrations
modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b =>
{ {
b.Property<long>("PostId") b.Property<long>("BlogpostId")
.HasColumnType("bigint"); .HasColumnType("bigint");
b.HasKey("PostId"); b.HasKey("BlogpostId");
b.ToTable("blogSpotPublications"); b.ToTable("blogSpotPublications");
}); });
@ -3958,13 +3958,13 @@ namespace Yavsc.Migrations
modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b =>
{ {
b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost")
.WithMany() .WithMany()
.HasForeignKey("PostId") .HasForeignKey("BlogpostId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Post"); b.Navigation("BlogPost");
}); });
modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b =>

View file

@ -69,7 +69,7 @@ $('#commentValidation').html(
<h1 ismarkdown>@Model.Title</h1> <h1 ismarkdown>@Model.Title</h1>
@Html.DisplayFor(m=>m.Author) @Html.DisplayFor(m=>m.Author)
<markdown>@Html.DisplayFor(m=>m.Article)</markdown> <asciidoc>@Html.DisplayFor(m=>m.Article)</asciidoc>
</div> </div>

View file

@ -58,11 +58,11 @@
</a> </a>
<div> <div>
<a asp-action="Details" asp-route-id="@post.Id"> <a asp-action="Details" asp-route-id="@post.Id">
<markdown summary="@maxTextLen">@post.Article</markdown></a> <asciidoc summary="@maxTextLen">@post.Article</asciidoc></a>
<span style="font-size:x-small;">@Html.DisplayFor(m => post.Author)</span> <span style="font-size:x-small;">@Html.DisplayFor(m => post.Author)</span>
<span style="font-size:xx-small;"> <span style="font-size:xx-small;">
posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm") posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((post.DateModified - post.DateCreated).Minutes > 0){ @if ((post.DateModified - post.DateCreated).Minutes > 0){ 
@:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm") @:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm")
} }
</span> </span>

View file

@ -15,11 +15,11 @@
<img src="@item.Photo" class="blogphoto"></a> <img src="@item.Photo" class="blogphoto"></a>
</td> </td>
<td> <td>
<markdown>@((item.Article?.Length > 256) ? item.Article.Substring(0, 256) + " ..." : item.Article)</markdown> <asciidoc>@((item.Article?.Length > 256) ? item.Article.Substring(0, 256) + " ..." : item.Article)</asciidoc>
<span style="font-size:x-small;">(@item.Author.UserName </span>, <span style="font-size:x-small;">(@item.Author.UserName </span>,
<span style="font-size:xx-small;"> <span style="font-size:xx-small;">
posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm") posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((item.DateModified - item.DateCreated).Minutes > 0){ @if ((item.DateModified - item.DateCreated).Minutes > 0){ 
@:- Modifié le @item.DateModified.ToString("dddd d MMM yyyy à H:mm") @:- Modifié le @item.DateModified.ToString("dddd d MMM yyyy à H:mm")
}) })
</span> </span>

View file

@ -2,7 +2,7 @@
ViewBag.Title = "Conditions Générales de Vente"; ViewBag.Title = "Conditions Générales de Vente";
} }
<h2>@ViewBag.Title</h2> <h2>@ViewBag.Title</h2>
<markdown> <asciidoc>
Le paiement intervient à la commande, quelque soit la date d'intervention. Le paiement intervient à la commande, quelque soit la date d'intervention.
Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/),
@ -10,4 +10,4 @@ Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/H
Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution
de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée. de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée.
</markdown> </asciidoc>

View file

@ -5,7 +5,7 @@
<h1>@SiteSettings.Value.Title - objetivo</h1> <h1>@SiteSettings.Value.Title - objetivo</h1>
<environment names="freefield,Development"> <environment names="freefield,Development">
<markdown> <asciidoc>
= O objetivo = O objetivo
Esta aplicação é construída para conectar artistas Esta aplicação é construída para conectar artistas
@ -87,36 +87,36 @@ que imediatamente desativa as publicações associadas às suas informações,
e planeia a eliminação completa desta informação no prazo de quinze dias e planeia a eliminação completa desta informação no prazo de quinze dias
do aplicativo, a menos que seja um pedido contraditório. do aplicativo, a menos que seja um pedido contraditório.
A operação é anulável até duas semanas após a sua programação. A operação é anulável até duas semanas após a sua programação.
</markdown> </asciidoc>
</environment> </environment>
<environment names="Lua,Development"> <environment names="Lua,Development">
<markdown>Este é o meu site perso, uma configuração de _Yavsc_ (outro negócio muito pequeno). <asciidoc>Este é o meu site perso, uma configuração de _Yavsc_ (outro negócio muito pequeno).
* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) * [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md)
* [licença: GNU GPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) * [licença: GNU GPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE)
Outras instalações: Outras instalações:
</markdown> </asciidoc>
<markdown> <asciidoc>
* [Coiffure](http://coiffure.pschneider.fr) * [Coiffure](http://coiffure.pschneider.fr)
* [ZicMoove](http://linkmuse.pschneider.fr) * [ZicMoove](http://linkmuse.pschneider.fr)
* [Yavsc](http://yavsc.pschneider.fr) * [Yavsc](http://yavsc.pschneider.fr)
</markdown> </asciidoc>
</environment> </environment>
<environment names="Yavsc"> <environment names="Yavsc">
<markdown> <asciidoc>
Yet Another Very Small Company ... Yet Another Very Small Company ...
* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) * [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md)
* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) * [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE)
</markdown> </asciidoc>
</environment> </environment>
<environment names="YavscPre"> <environment names="YavscPre">
<markdown> <asciidoc>
## Yet Another Very Small Company : ## Yet Another Very Small Company :
* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) * [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md)
* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) * [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE)
@ -126,12 +126,12 @@ En production:
* [Lua](https://lua.pschneider.fr) * [Lua](https://lua.pschneider.fr)
* [Yavsc](https://yavsc.pschneider.fr) * [Yavsc](https://yavsc.pschneider.fr)
</markdown> </asciidoc>
</environment> </environment>
<environment names="coiffure"> <environment names="coiffure">
<markdown> <asciidoc>
Você está no site da ordem em cabeleireiro em casa de Soraya Boudjouraf, Você está no site da ordem em cabeleireiro em casa de Soraya Boudjouraf,
um ás de cabeleireiro, que trabalha na região de Paris. um ás de cabeleireiro, que trabalha na região de Paris.
@ -139,11 +139,11 @@ Ao validar um formulário de pedido aqui, é para ela que você notifica sua sol
Você pode [Deixe-lhe o seu número de telefone] (/ HairCutCommand / HairCut? ActivityCode = Brush & performerId = 1bd841ab-c305-4971-940d-7ddca818310c) Você pode [Deixe-lhe o seu número de telefone] (/ HairCutCommand / HairCut? ActivityCode = Brush & performerId = 1bd841ab-c305-4971-940d-7ddca818310c)
e / ou detalhes sobre o seu pedido, e / ou detalhes sobre o seu pedido,
ela vai ligar de volta.</markdown> ela vai ligar de volta.</asciidoc>
</environment> </environment>
<environment names="Development"> <environment names="Development">
<markdown> <asciidoc>
## Este é um site de desenvolvimento. ## Este é um site de desenvolvimento.
Este recurso só diz respeito ao desenvolvimento do software que o implementa. Este recurso só diz respeito ao desenvolvimento do software que o implementa.
@ -156,7 +156,7 @@ A "pré-produção" exibe os seguintes sites:
* [Yavsc](https://yavsc.pschneider.fr) * [Yavsc](https://yavsc.pschneider.fr)
* [Lua](https://lua.pschneider.fr) * [Lua](https://lua.pschneider.fr)
</markdown> </asciidoc>
</environment> </environment>
<p>@Model</p> <p>@Model</p>

View file

@ -3,7 +3,7 @@
} }
<h1>@ViewBag.Title</h1> <h1>@ViewBag.Title</h1>
<markdown> <asciidoc>
# Blog et fichiers utilisateurs # Blog et fichiers utilisateurs
@ -35,6 +35,6 @@ Et ils possèdent une addresse permanente de la forme :
</markdown> </asciidoc>

View file

@ -3,7 +3,7 @@
} }
<h1>@ViewBag.Title</h1> <h1>@ViewBag.Title</h1>
<markdown> <asciidoc>
Quelques extensions à un Markdown de base : Quelques extensions à un Markdown de base :
* les video et audio: `![video: Ep. 28 - La France en commun - Une invitation à écrire un nouveau projet d'émancipation humaine](/images/testVideo.mp4)` * les video et audio: `![video: Ep. 28 - La France en commun - Une invitation à écrire un nouveau projet d'émancipation humaine](/images/testVideo.mp4)`
@ -16,4 +16,4 @@ Quelques extensions à un Markdown de base :
* le tag "Titre d'article": "#1_great_title" * le tag "Titre d'article": "#1_great_title"
* le tag "User" = "@@(John Doe)" ou "@@jdoe" * le tag "User" = "@@(John Doe)" ou "@@jdoe"
</markdown> </asciidoc>

View file

@ -2,7 +2,7 @@
ViewBag.Title = "Conditions Générales de Vente"; ViewBag.Title = "Conditions Générales de Vente";
} }
<h2>@ViewBag.Title</h2> <h2>@ViewBag.Title</h2>
<markdown> <asciidoc>
Le paiement intervient à la commande, quelque soit la date d'intervention. Le paiement intervient à la commande, quelque soit la date d'intervention.
Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/),
@ -10,4 +10,4 @@ Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/H
Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution
de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée. de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée.
</markdown> </asciidoc>

View file

@ -2,7 +2,7 @@
ViewBag.Title = Localizer["Home"]; ViewBag.Title = Localizer["Home"];
} }
<markdown> <asciidoc>
= La confidentialité = La confidentialité
:hide-uri-scheme: :hide-uri-scheme:
@ -13,4 +13,4 @@ ne sont transmis à personne. Seul le système et son link:Contact[possesseur] o
De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate]. De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate].
</markdown> </asciidoc>

View file

@ -4,14 +4,14 @@
<h1>@ViewBag.Title</h1> <h1>@ViewBag.Title</h1>
<em>Linkmuse(trox)</em> <em>Linkmuse(trox)</em>
<markdown> <asciidoc>
Les tags. Les tags.
La librairie, les lives. La librairie, les lives.
</markdown> </asciidoc>
@{ @{
ViewBag.Title = @Localizer["TODO"]; ViewBag.Title = @Localizer["TODO"];
} }
<h1>@ViewBag.Title</h1> <h1>@ViewBag.Title</h1>
<em>Faster, stronger, shorter</em> <em>Faster, stronger, shorter</em>
<markdown> <asciidoc>
</markdown> </asciidoc>

View file

@ -2,7 +2,7 @@
<div data-type="blogcomment" data-id="@Model.Id" data-receiver-id="@Model.ReceiverId" data-allow-edit="@(User.GetUserId()==Model.AuthorId?"true":"false")" <div data-type="blogcomment" data-id="@Model.Id" data-receiver-id="@Model.ReceiverId" data-allow-edit="@(User.GetUserId()==Model.AuthorId?"true":"false")"
data-allow-moderate="@ViewBag.moderatoFlag" data-date="@Model.DateCreated.ToString("yyyy-MM-dd hh:mm:ss")" data-username="@Model.Author.UserName" > data-allow-moderate="@ViewBag.moderatoFlag" data-date="@Model.DateCreated.ToString("yyyy-MM-dd hh:mm:ss")" data-username="@Model.Author.UserName" >
<markdown>@Model.Article</markdown> <asciidoc>@Model.Article</asciidoc>
@if (Model.Children!=null) { @if (Model.Children!=null) {
<div class="subcomments"> <div class="subcomments">

View file

@ -31,7 +31,7 @@
<h2 markdown="@n.title"></h2> <h2 markdown="@n.title"></h2>
<a class="close" data-dismiss="alert" aria-label="close" onclick="notifClick(@n.Id)">@((n.click_action == null) ? <a class="close" data-dismiss="alert" aria-label="close" onclick="notifClick(@n.Id)">@((n.click_action == null) ?
Localizer["Fermer"] : Localizer[n.click_action])</a> Localizer["Fermer"] : Localizer[n.click_action])</a>
<markdown>@n.body</markdown> <asciidoc>@n.body</asciidoc>
</div> </div>
} }
} }

View file

@ -14,9 +14,9 @@ namespace Yavsc.Server.Helpers
if (readerId == null) if (readerId == null)
{ {
var userPosts = dbContext.blogSpotPublications.Include( var userPosts = dbContext.blogSpotPublications.Include(
b => b.Post b => b.BlogPost
).Where(x => x.Post.AuthorId == posterId) ).Where(x => x.BlogPost.AuthorId == posterId)
.Select(x => x.Post).ToArray(); .Select(x => x.BlogPost).ToArray();
return userPosts; return userPosts;
} }
else else

View file

@ -57,7 +57,7 @@ namespace Yavsc.Models.Blog
get; set; get; set;
} }
[Display(Name = "Utilisateur ayant modifié le dernier")] [Display(Name = "Utilisateur ayant modifé le dernier")]
public string? UserModified public string? UserModified
{ {
get; set; get; set;
@ -96,9 +96,6 @@ namespace Yavsc.Models.Blog
[InverseProperty("Post")] [InverseProperty("Post")]
public virtual List<Comment> Comments { get; set; } public virtual List<Comment> Comments { get; set; }
[InverseProperty("Post")]
public virtual BlogSpotPublication Publication { get; set; }
/// <summary> /// <summary>
/// Whether this post is published. Not a column: the /// Whether this post is published. Not a column: the
/// existence of a row in <c>BlogSpotPublication</c> /// existence of a row in <c>BlogSpotPublication</c>
@ -106,10 +103,10 @@ namespace Yavsc.Models.Blog
/// <c>[NotMapped]</c> so no migration is needed. The /// <c>[NotMapped]</c> so no migration is needed. The
/// service hydrates it after each fetch (single bulk /// service hydrates it after each fetch (single bulk
/// lookup, not N+1) and it surfaces through the wire /// lookup, not N+1) and it surfaces through the wire
/// as part of the JSON-serialized <c>BlogPost</c>. /// as part of the JSON-serialised <c>BlogPost</c>.
/// </summary> /// </summary>
[NotMapped] [NotMapped]
public bool IsPublished { get => Publication != null; } public bool IsPublished { get; set; }
[JsonIgnore] [JsonIgnore]
/// <summary> /// <summary>
@ -150,7 +147,5 @@ namespace Yavsc.Models.Blog
}).ToList() ?? new List<CircleAuthorization>(); }).ToList() ?? new List<CircleAuthorization>();
} }
} }
} }
} }

View file

@ -7,9 +7,9 @@ namespace Yavsc.Models
public class BlogSpotPublication public class BlogSpotPublication
{ {
[Key] [Key]
public long PostId { get; set; } public long BlogpostId { get; set; }
[ForeignKey("PostId")] [ForeignKey("BlogpostId")]
public virtual BlogPost Post{ get; set; } public virtual BlogPost BlogPost{ get; set; }
} }
} }

View file

@ -108,7 +108,7 @@ public class BlogSpotService
public BlogPost Create(string userId, BlogPost post ) public BlogPost Create(string userId, BlogPost post )
{ {
// Sauvegarder le post d'abord pour obtenir son ID // Sauvegarder le post d'abord pour obtenir son ID
// Le créateur vient de l'authentification, donc on ne le prend pas du post // Le createur vient de l'authentification, donc on ne le prend pas du post
post.AuthorId = userId; post.AuthorId = userId;
_context.BlogSpot.Add(post); _context.BlogSpot.Add(post);
_context.SaveChanges(userId); _context.SaveChanges(userId);
@ -126,7 +126,7 @@ public class BlogSpotService
if (!auth.Succeeded) if (!auth.Succeeded)
throw new AuthorizationFailureException(auth); throw new AuthorizationFailureException(auth);
var pub = await _context.blogSpotPublications.AnyAsync(x => x.PostId == blog.Id); var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id);
ScrubAclForViewer(blog, user); ScrubAclForViewer(blog, user);
return new BlogPostEditViewModel(blog, pub); return new BlogPostEditViewModel(blog, pub);
@ -139,12 +139,15 @@ public class BlogSpotService
.Include(p => p.Tags) .Include(p => p.Tags)
.Include(p => p.Comments) .Include(p => p.Comments)
.Include(p => p.ACL) .Include(p => p.ACL)
.Include(p => p.Publication)
.SingleAsync(m => m.Id == blogPostId); .SingleAsync(m => m.Id == blogPostId);
if (blog == null) if (blog == null)
return null; return null;
// Hydrate le flag [NotMapped] depuis la table de publication.
blog.IsPublished = await _context.blogSpotPublications
.AnyAsync(pub => pub.BlogpostId == blogPostId);
var auth = await _authorizationService.AuthorizeAsync(user, blog, new ReadPermission()); var auth = await _authorizationService.AuthorizeAsync(user, blog, new ReadPermission());
if (!auth.Succeeded) if (!auth.Succeeded)
throw new AuthorizationFailureException(auth); throw new AuthorizationFailureException(auth);
@ -173,7 +176,7 @@ public class BlogSpotService
_context.Update(blog); _context.Update(blog);
var publication = await _context.blogSpotPublications var publication = await _context.blogSpotPublications
.SingleOrDefaultAsync(p => p.PostId == blogEdit.Id); .SingleOrDefaultAsync(p => p.BlogpostId == blogEdit.Id);
if (publication != null) if (publication != null)
{ {
@ -182,7 +185,7 @@ public class BlogSpotService
} }
else if (blogEdit.Publish) else if (blogEdit.Publish)
{ {
_context.blogSpotPublications.Add(new BlogSpotPublication { PostId = blogEdit.Id }); _context.blogSpotPublications.Add(new BlogSpotPublication { BlogpostId = blogEdit.Id });
} }
_context.SaveChanges(user.GetUserId()); _context.SaveChanges(user.GetUserId());
@ -228,29 +231,43 @@ public class BlogSpotService
.Include(p => p.ACL) .Include(p => p.ACL)
.Include(p => p.Tags) .Include(p => p.Tags)
.Include(p => p.Comments) .Include(p => p.Comments)
.Include(p => p.Publication) .Where(p => p.ACL == null
.Where(p => p.Publication != null || p.ACL.Count == 0
|| p.AuthorId == viewerId || p.AuthorId == viewerId
|| (userCircles != null && p.ACL.Any(a => userCircles.Contains(a.CircleId)))); || (userCircles != null && p.ACL.Any(a => userCircles.Contains(a.CircleId))));
} }
else else
{ {
posts = _context.blogSpotPublications posts = _context.blogSpotPublications
.Include(p => p.Post) .Include(p => p.BlogPost)
.Include(b => b.Post.Author) .Include(b => b.BlogPost.Author)
.Include(p => p.Post.ACL) .Include(p => p.BlogPost.ACL)
.Include(p => p.Post.Tags) .Include(p => p.BlogPost.Tags)
.Include(p => p.Post.Comments) .Include(p => p.BlogPost.Comments)
.Select(p => p.Post) .Where(p => p.BlogPost.ACL == null || p.BlogPost.ACL.Count == 0)
.Select(p => p.BlogPost)
.ToArray(); .ToArray();
} }
var materialized = posts.ToList(); var materialised = posts.ToList();
foreach (var post in materialized.OfType<BlogPost>()) var postIds = materialised.Select(p => p.Id).ToList();
if (postIds.Count > 0)
{
var publishedIds = await _context.blogSpotPublications
.Where(pub => postIds.Contains(pub.BlogpostId))
.Select(pub => pub.BlogpostId)
.ToListAsync();
var publishedSet = publishedIds.ToHashSet();
foreach (var post in materialised.OfType<BlogPost>())
post.IsPublished = publishedSet.Contains(post.Id);
}
foreach (var post in materialised.OfType<BlogPost>())
ScrubAclForViewer(post, user); ScrubAclForViewer(post, user);
return materialized return materialised
.OrderByDescending(p => p.DateModified) .OrderByDescending(p => p.DateModified)
.Skip(skip) .Skip(skip)
.Take(take); .Take(take);
@ -306,11 +323,11 @@ public class BlogSpotService
if (!auth.Succeeded) if (!auth.Succeeded)
throw new AuthorizationFailureException(auth); throw new AuthorizationFailureException(auth);
var existing = await _context.blogSpotPublications.SingleOrDefaultAsync(p => p.PostId == postId); var existing = await _context.blogSpotPublications.SingleOrDefaultAsync(p => p.BlogpostId == postId);
if (publish) if (publish)
{ {
if (existing == null) if (existing == null)
_context.blogSpotPublications.Add(new BlogSpotPublication { PostId = postId }); _context.blogSpotPublications.Add(new BlogSpotPublication { BlogpostId = postId });
} }
else else
{ {

View file

@ -59,7 +59,7 @@ public class PermissionHandler : IAuthorizationHandler
{ {
return return
applicationDbContext.blogSpotPublications applicationDbContext.blogSpotPublications
.Any(p=>p.PostId == blogPost.Id); .Any(p=>p.BlogpostId == blogPost.Id);
} }
return false; return false;
} }