Compare commits
7 changed files with 68 additions and 67 deletions
21
.github/workflows/docker-publish-backend.yml
vendored
21
.github/workflows/docker-publish-backend.yml
vendored
|
|
@ -8,7 +8,8 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
production-deploy:
|
production-deploy:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# 1. Récupération du code
|
# 1. Récupération du code
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
|
|
@ -24,24 +25,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Set up .NET
|
|
||||||
uses: actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: 10.0.x
|
|
||||||
- name: Workloads restore
|
|
||||||
run: dotnet workload restore
|
|
||||||
- name: Test
|
- name: Test
|
||||||
env:
|
run: dotnet test --no-build --verbosity normal --filter="Category!=Platform-Android"
|
||||||
TEST_FAILED: ""
|
|
||||||
run: make test || export TEST_FAILED=$?
|
|
||||||
- uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: my-artifact
|
|
||||||
path: test-reports/**/*
|
|
||||||
- name: Fail if tests failed
|
|
||||||
if: ${{ env.TEST_FAILED != '' && env.TEST_FAILED != '0' }}
|
|
||||||
run: exit $TEST_FAILED
|
|
||||||
|
|
||||||
# 4. Build et Push de l'image de production finale
|
# 4. Build et Push de l'image de production finale
|
||||||
- name: Build and push production image
|
- name: Build and push production image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,6 @@
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* [PostIt.Tests] Les tests du bandeau de session utilisent le bandeau
|
|
||||||
réellement monté dans la fenêtre headless, au lieu d'un contrôle détaché.
|
|
||||||
Les fenêtres sont fermées après chaque test et les quatre boutons
|
|
||||||
de navigation sont vérifiés.
|
|
||||||
|
|
||||||
* [PostIt] Les liens Markdown des pièces jointes utilisent désormais
|
* [PostIt] Les liens Markdown des pièces jointes utilisent désormais
|
||||||
le même filtre de nom que l'upload serveur, avant l'encodage URL.
|
le même filtre de nom que l'upload serveur, avant l'encodage URL.
|
||||||
Le libellé conserve le nom original, notamment ses accents.
|
Le libellé conserve le nom original, notamment ses accents.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,36 @@
|
||||||
# Utilisation de l'image globale pour le build et l'exécution
|
# Utilisation de l'image globale pour le build et l'exécution
|
||||||
FROM pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v4 AS build-env
|
FROM pazof/yavsc-build-env:debian12-dotnet10-android36-v1 AS build-env
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
|
||||||
# 1. Copie de la solution
|
# 1. Copie de la solution
|
||||||
|
COPY *.sln ./
|
||||||
|
COPY Directory.*.props ./
|
||||||
|
|
||||||
|
# 2. Copie de l'intégralité des fichiers de projets (.csproj) pour la restauration
|
||||||
|
COPY src/Yavsc.Org/*.csproj ./src/Yavsc.Org/
|
||||||
|
COPY src/Yavsc.Abstract/*.csproj ./src/Yavsc.Abstract/
|
||||||
|
COPY src/Yavsc.Server/*.csproj ./src/Yavsc.Server/
|
||||||
|
COPY src/Yavsc.Api/*.csproj ./src/Yavsc.Api/
|
||||||
|
COPY src/Yavsc.Blogs/*.csproj ./src/Yavsc.Blogs/
|
||||||
|
COPY src/cli/*.csproj ./src/cli/
|
||||||
|
COPY src/Yavsc.Org.Tests/*.csproj ./src/Yavsc.Org.Tests/
|
||||||
|
|
||||||
|
# Projets associés à PostIt
|
||||||
|
COPY src/PostIt/PostIt/*.csproj ./src/PostIt/PostIt/
|
||||||
|
COPY src/PostIt/PostIt.Android/*.csproj ./src/PostIt/PostIt.Android/
|
||||||
|
COPY src/PostIt/PostIt.Browser/*.csproj ./src/PostIt/PostIt.Browser/
|
||||||
|
COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/
|
||||||
|
|
||||||
|
# 4. Copie de l'intégralité du code source
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN dotnet workload restore
|
|
||||||
# 4. Restauration des dépendances pour tous les projets
|
# 4. Restauration des dépendances pour tous les projets
|
||||||
RUN dotnet restore
|
RUN dotnet restore
|
||||||
|
|
||||||
|
# 5. Copie de la totalité du code source (filtrée par votre .dockerignore)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# 6. Compilation du serveur Web principal
|
# 6. Compilation du serveur Web principal
|
||||||
RUN dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly
|
RUN dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly
|
||||||
|
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -5,6 +5,8 @@ DESTDIR=/tmp/yavsc
|
||||||
APP_PATH=srv/www/yavsc
|
APP_PATH=srv/www/yavsc
|
||||||
APP_FULL_PATH=$(DESTDIR)/$(APP_PATH)
|
APP_FULL_PATH=$(DESTDIR)/$(APP_PATH)
|
||||||
|
|
||||||
|
include .env
|
||||||
|
|
||||||
all:
|
all:
|
||||||
dotnet build --nologo
|
dotnet build --nologo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Headless.XUnit;
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Styling;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
|
@ -22,36 +23,41 @@ namespace PostIt.Tests;
|
||||||
/// <c>new Window().Show()</c> throws because the harness has not
|
/// <c>new Window().Show()</c> throws because the harness has not
|
||||||
/// been initialised for that thread.</para>
|
/// been initialised for that thread.</para>
|
||||||
///
|
///
|
||||||
/// <para>These tests set the DataContext directly on the banner
|
/// <para>The session banner's <c>DataContext</c> is not wired in
|
||||||
/// mounted in the window. The production code path is exercised
|
/// these tests: <c>App.OnFrameworkInitializationCompleted</c> is
|
||||||
|
/// not called in a unit test, so we set the DataContext on the
|
||||||
|
/// banner directly. The production code path is exercised
|
||||||
/// end-to-end by the manual launch, not here.</para>
|
/// end-to-end by the manual launch, not here.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionStatusBannerTests : IDisposable
|
public class SessionStatusBannerTests
|
||||||
{
|
{
|
||||||
private MainWindow? _window;
|
|
||||||
|
|
||||||
[AvaloniaFact]
|
[AvaloniaFact]
|
||||||
public void Banner_renders_four_buttons_in_the_visual_tree()
|
public void Banner_renders_three_buttons_in_the_visual_tree()
|
||||||
{
|
{
|
||||||
SessionStatusBanner banner = CreateBanner();
|
MainWindow window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
var buttons = banner.GetVisualDescendants()
|
var buttons = window.GetVisualDescendants()
|
||||||
.OfType<Button>()
|
.OfType<Button>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Assert.Equal(4, buttons.Count);
|
// Three buttons, named by their content text: Se
|
||||||
|
// déconnecter, Se connecter, Paramètres. If any one is
|
||||||
|
// missing, the user has no way to trigger the
|
||||||
|
// corresponding navigation event.
|
||||||
|
Assert.True(buttons.Count>=4);
|
||||||
Assert.Contains(buttons, b => b.Content as string == "Se déconnecter");
|
Assert.Contains(buttons, b => b.Content as string == "Se déconnecter");
|
||||||
Assert.Contains(buttons, b => b.Content as string == "Se connecter");
|
Assert.Contains(buttons, b => b.Content as string == "Se connecter");
|
||||||
Assert.Contains(buttons, b => b.Content as string == "Paramètres");
|
Assert.Contains(buttons, b => b.Content as string == "Paramètres");
|
||||||
Assert.Contains(buttons, b => b.Content as string == "À propos");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AvaloniaFact]
|
[AvaloniaFact]
|
||||||
public void Banner_login_button_is_visible_when_logged_out()
|
public void Banner_login_button_is_visible_when_logged_out()
|
||||||
{
|
{
|
||||||
SessionStatusBanner banner = CreateBanner();
|
MainWindow window = new MainWindow();
|
||||||
|
window.Show();
|
||||||
|
|
||||||
var login = banner.GetVisualDescendants()
|
var login = window.GetVisualDescendants()
|
||||||
.OfType<Button>()
|
.OfType<Button>()
|
||||||
.Single(b => b.Content as string == "Se connecter");
|
.Single(b => b.Content as string == "Se connecter");
|
||||||
|
|
||||||
|
|
@ -65,7 +71,10 @@ public class SessionStatusBannerTests : IDisposable
|
||||||
{
|
{
|
||||||
SessionStatusBanner banner = CreateBanner();
|
SessionStatusBanner banner = CreateBanner();
|
||||||
|
|
||||||
Assert.False(Assert.IsType<SessionStatusViewModel>(banner.DataContext).IsLoggedIn);
|
Assert.False((banner.DataContext as SessionStatusViewModel)!
|
||||||
|
.IsLoggedIn); // VM default
|
||||||
|
|
||||||
|
|
||||||
var logout = banner.GetVisualDescendants()
|
var logout = banner.GetVisualDescendants()
|
||||||
.OfType<Button>()
|
.OfType<Button>()
|
||||||
.Single(b => b.Content as string == "Se déconnecter");
|
.Single(b => b.Content as string == "Se déconnecter");
|
||||||
|
|
@ -82,23 +91,22 @@ public class SessionStatusBannerTests : IDisposable
|
||||||
.OfType<Button>()
|
.OfType<Button>()
|
||||||
.Single(b => b.Content as string == "Paramètres");
|
.Single(b => b.Content as string == "Paramètres");
|
||||||
|
|
||||||
// Paramètres has no IsVisible binding and is always shown.
|
// Paramètres is the only button with no IsVisible
|
||||||
|
// binding — always shown. The user's only path to the
|
||||||
|
// settings page goes through this button.
|
||||||
Assert.True(settings.IsVisible);
|
Assert.True(settings.IsVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionStatusBanner CreateBanner()
|
private static SessionStatusBanner CreateBanner()
|
||||||
{
|
{
|
||||||
_window = new MainWindow();
|
MainWindow window = new MainWindow();
|
||||||
_window.Show();
|
window.Show();
|
||||||
var banner = _window.GetVisualDescendants()
|
var banner = new SessionStatusBanner();
|
||||||
.OfType<SessionStatusBanner>()
|
var status = new SessionStatusViewModel();
|
||||||
.Single();
|
banner.DataContext = status;
|
||||||
banner.DataContext = new SessionStatusViewModel();
|
|
||||||
return banner;
|
return banner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => _window?.Close();
|
|
||||||
|
|
||||||
[AvaloniaFact]
|
[AvaloniaFact]
|
||||||
public void Banner_session_label_reflects_DataContext()
|
public void Banner_session_label_reflects_DataContext()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ public partial class App : Application, ILiveView, IVMPusherApp
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
this.AttachDeveloperTools();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Yavsc.Abstract.Identity;
|
using Yavsc.Abstract.Identity;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
@ -20,20 +19,14 @@ namespace Yavsc.Controllers
|
||||||
private readonly RoleManager<IdentityRole> _roleManager;
|
private readonly RoleManager<IdentityRole> _roleManager;
|
||||||
|
|
||||||
private readonly ApplicationDbContext _dbContext;
|
private readonly ApplicationDbContext _dbContext;
|
||||||
private readonly SiteSettings _siteSettings;
|
|
||||||
private readonly ILogger<AdministrationController> _logger;
|
|
||||||
|
|
||||||
public AdministrationController(UserManager<ApplicationUser> userManager,
|
public AdministrationController(UserManager<ApplicationUser> userManager,
|
||||||
RoleManager<IdentityRole> roleManager,
|
RoleManager<IdentityRole> roleManager,
|
||||||
ApplicationDbContext context,
|
ApplicationDbContext context)
|
||||||
IOptions<SiteSettings> siteSettings,
|
|
||||||
ILoggerFactory loggerFactory)
|
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_roleManager = roleManager;
|
_roleManager = roleManager;
|
||||||
this._dbContext = context;
|
this._dbContext = context;
|
||||||
this._siteSettings = siteSettings.Value;
|
|
||||||
this._logger = loggerFactory.CreateLogger<AdministrationController>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -99,16 +92,6 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _userManager.FindByIdAsync(User.GetUserId());
|
var user = await _userManager.FindByIdAsync(User.GetUserId());
|
||||||
|
|
||||||
var email = user.Email;
|
|
||||||
var isOk = email == _siteSettings.Owner?.EMail || _siteSettings.Admin?.EMail == email;
|
|
||||||
if (!isOk)
|
|
||||||
{
|
|
||||||
if (_siteSettings.Owner==null && _siteSettings.Admin==null)
|
|
||||||
_logger.LogError("No owner or admin is set in the site settings.");
|
|
||||||
return Forbid();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check all user groups exist
|
// check all user groups exist
|
||||||
if (!await EnsureRoleList())
|
if (!await EnsureRoleList())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue