Compare commits

..

1 Commits

Author SHA1 Message Date
tim
c67c549f48 docs: update OpenWiki 2026-08-08 18:09:52 +00:00
16 changed files with 54 additions and 81 deletions

View File

@@ -49,7 +49,7 @@ jobs:
run: git checkout -- .github/workflows/openwiki-update.yml run: git checkout -- .github/workflows/openwiki-update.yml
- name: Create OpenWiki update pull request - name: Create OpenWiki update pull request
uses: infinilabs/gitea-pr@v1 uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with: with:
add-paths: | add-paths: |
openwiki openwiki
@@ -58,10 +58,6 @@ jobs:
.github/workflows/openwiki-update.yml .github/workflows/openwiki-update.yml
branch: openwiki/update branch: openwiki/update
commit-message: "docs: update OpenWiki" commit-message: "docs: update OpenWiki"
url: ${{ secrets.URL }}
token: ${{ secrets.TOKEN }}
base_branch: master
head_branch: openwiki/update
title: "docs: update OpenWiki" title: "docs: update OpenWiki"
body: | body: |
Automated OpenWiki documentation update. Automated OpenWiki documentation update.

View File

@@ -1,7 +1,8 @@
var builder = DistributedApplication.CreateBuilder(args); var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pgsql") var db = builder.AddPostgres("pgsql")
.AddDatabase("postgresdb"); .AddDatabase("postgresdb")
;
var server = builder.AddProject<Projects.SlipItIn_Server>("server") var server = builder.AddProject<Projects.SlipItIn_Server>("server")
.WithReference(db) .WithReference(db)

View File

@@ -8,29 +8,21 @@ public partial class App : Application
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
_ = InitializeAsync();
} }
protected override Window CreateWindow(IActivationState? activationState) protected override Window CreateWindow(IActivationState? activationState)
{ {
var window = new Window(new AppShell()); var window = new Window(new AppShell());
window.Created += async (_, _) =>
{
await InitializeSafeAsync();
};
window.Resumed += async (_, _) => window.Resumed += async (_, _) =>
{ {
var gameStateService = ServiceHelper.GetRequiredService<IGameStateService>(); var gameStateService = ServiceHelper.GetRequiredService<IGameStateService>();
await gameStateService.ResyncAsync(); await gameStateService.ResyncAsync();
}; };
return window; return window;
} }
private static async Task InitializeSafeAsync() private static async Task InitializeAsync()
{
try
{ {
var authSession = ServiceHelper.GetRequiredService<IAuthSessionService>(); var authSession = ServiceHelper.GetRequiredService<IAuthSessionService>();
var signalR = ServiceHelper.GetRequiredService<ISignalRService>(); var signalR = ServiceHelper.GetRequiredService<ISignalRService>();
@@ -46,9 +38,4 @@ public partial class App : Application
await gameStateService.ResyncAsync(); await gameStateService.ResyncAsync();
} }
} }
catch
{
// Start darf nicht abstürzen, wenn Session/Netzwerk fehlschlägt.
}
}
} }

View File

@@ -3,12 +3,22 @@
x:Class="SlipItIn.AppShell" x:Class="SlipItIn.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:SlipItIn.Views" xmlns:views="clr-namespace:SlipItIn.Views">
FlyoutBehavior="Disabled">
<ShellContent <ShellContent
Title="Login"
Route="LoginPage" Route="LoginPage"
ContentTemplate="{DataTemplate views:LoginPage}" /> ContentTemplate="{DataTemplate views:LoginPage}" />
<ShellContent
Route="RegisterPage"
ContentTemplate="{DataTemplate views:RegisterPage}" />
<ShellContent
Route="LobbyPage"
ContentTemplate="{DataTemplate views:LobbyPage}" />
<ShellContent
Route="GameBoardPage"
ContentTemplate="{DataTemplate views:GameBoardPage}" />
</Shell> </Shell>

View File

@@ -7,8 +7,5 @@ public partial class AppShell : Shell
public AppShell() public AppShell()
{ {
InitializeComponent(); InitializeComponent();
Routing.RegisterRoute(nameof(RegisterPage), typeof(RegisterPage));
Routing.RegisterRoute(nameof(LobbyPage), typeof(LobbyPage));
Routing.RegisterRoute(nameof(GameBoardPage), typeof(GameBoardPage));
} }
} }

View File

@@ -50,21 +50,7 @@ public class GameStateService : IGameStateService
}; };
_signalR.ErrorReceived += (_, error) => _messenger.Send(new ErrorOccurredMessage(error)); _signalR.ErrorReceived += (_, error) => _messenger.Send(new ErrorOccurredMessage(error));
_signalR.ConnectionStateChanged += async (_, connected) => _signalR.ConnectionStateChanged += (_, connected) => _messenger.Send(new ConnectionStateChangedMessage(connected));
{
_messenger.Send(new ConnectionStateChangedMessage(connected));
if (connected)
{
try
{
await ResyncAsync();
}
catch (Exception ex)
{
_messenger.Send(new ErrorOccurredMessage($"Resync fehlgeschlagen: {ex.Message}"));
}
}
};
} }
public async Task InitializeAsync() public async Task InitializeAsync()

View File

@@ -108,17 +108,11 @@ public class SignalRService : ISignalRService
_hubConnection = new HubConnectionBuilder() _hubConnection = new HubConnectionBuilder()
.WithUrl(_configuration.HubUrl, options => .WithUrl(_configuration.HubUrl, options =>
{ {
options.AccessTokenProvider = () => Task.FromResult<string?>(_authSession.AccessToken ?? token); options.AccessTokenProvider = () => Task.FromResult<string?>(token);
}) })
.WithAutomaticReconnect([TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)]) .WithAutomaticReconnect([TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)])
.Build(); .Build();
_hubConnection.Reconnecting += _ =>
{
ConnectionStateChanged?.Invoke(this, false);
return Task.CompletedTask;
};
_hubConnection.Closed += _ => _hubConnection.Closed += _ =>
{ {
ConnectionStateChanged?.Invoke(this, false); ConnectionStateChanged?.Invoke(this, false);

View File

@@ -110,9 +110,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<MauiXaml Update="MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Views\ChallengeNotificationOverlay.xaml"> <MauiXaml Update="Views\ChallengeNotificationOverlay.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</MauiXaml> </MauiXaml>

View File

@@ -42,6 +42,7 @@ public partial class GameBoardViewModel : BaseViewModel,
_gameStateService = gameStateService; _gameStateService = gameStateService;
_authSession = authSession; _authSession = authSession;
WeakReferenceMessenger.Default.RegisterAll(this);
IsActive = true; IsActive = true;
} }

View File

@@ -42,6 +42,7 @@ public partial class LobbyViewModel : BaseViewModel,
_apiService = apiService; _apiService = apiService;
IsConnected = _signalR.IsConnected; IsConnected = _signalR.IsConnected;
//WeakReferenceMessenger.Default.RegisterAll(this);
IsActive = true; IsActive = true;
} }
@@ -126,7 +127,7 @@ public partial class LobbyViewModel : BaseViewModel,
public async void Receive(GameStartedMessage message) public async void Receive(GameStartedMessage message)
{ {
await MainThread.InvokeOnMainThreadAsync(() => Shell.Current.GoToAsync(nameof(Views.GameBoardPage))); await MainThread.InvokeOnMainThreadAsync(() => Shell.Current.GoToAsync($"//{nameof(Views.GameBoardPage)}"));
} }
public void Receive(ErrorOccurredMessage message) public void Receive(ErrorOccurredMessage message)

View File

@@ -14,10 +14,10 @@ public partial class LoginViewModel : BaseViewModel
private readonly IGameStateService _gameStateService; private readonly IGameStateService _gameStateService;
[ObservableProperty] [ObservableProperty]
private string email = string.Empty; public partial string Email { get; set; }
[ObservableProperty] [ObservableProperty]
private string password = string.Empty; public partial string Password { get; set; }
public LoginViewModel(IApiService apiService, IAuthSessionService authSession, ISignalRService signalR, IGameStateService gameStateService) public LoginViewModel(IApiService apiService, IAuthSessionService authSession, ISignalRService signalR, IGameStateService gameStateService)
{ {
@@ -28,7 +28,7 @@ public partial class LoginViewModel : BaseViewModel
} }
[RelayCommand] [RelayCommand]
private Task GoToRegisterAsync() => Shell.Current.GoToAsync(nameof(RegisterPage)); private Task GoToRegisterAsync() => Shell.Current.GoToAsync($"//{nameof(RegisterPage)}");
[RelayCommand] [RelayCommand]
private async Task LoginAsync() private async Task LoginAsync()
@@ -44,7 +44,7 @@ public partial class LoginViewModel : BaseViewModel
await _authSession.SetSessionAsync(response); await _authSession.SetSessionAsync(response);
await _signalR.ConnectAsync(response.Token); await _signalR.ConnectAsync(response.Token);
await _gameStateService.InitializeAsync(); await _gameStateService.InitializeAsync();
await Shell.Current.GoToAsync(nameof(LobbyPage)); await Shell.Current.GoToAsync($"//{nameof(LobbyPage)}");
}, "Anmeldung läuft..."); }, "Anmeldung läuft...");
} }
@@ -55,7 +55,7 @@ public partial class LoginViewModel : BaseViewModel
if ( isAuthenticated) if ( isAuthenticated)
{ {
await Shell.Current.GoToAsync(nameof(LobbyPage)); await Shell.Current.GoToAsync($"//{nameof(LobbyPage)}");
} }
} }
} }

View File

@@ -13,13 +13,13 @@ public partial class RegisterViewModel : BaseViewModel
private readonly ISignalRService _signalR; private readonly ISignalRService _signalR;
[ObservableProperty] [ObservableProperty]
private string username = string.Empty; public partial string Username { get; set; } = string.Empty;
[ObservableProperty] [ObservableProperty]
private string email = string.Empty; public partial string Email { get; set; } = string.Empty;
[ObservableProperty] [ObservableProperty]
private string password = string.Empty; public partial string Password { get; set; } = string.Empty;
public RegisterViewModel(IApiService apiService, IAuthSessionService authSession, ISignalRService signalR) public RegisterViewModel(IApiService apiService, IAuthSessionService authSession, ISignalRService signalR)
{ {
@@ -45,7 +45,7 @@ public partial class RegisterViewModel : BaseViewModel
await _authSession.SetSessionAsync(response); await _authSession.SetSessionAsync(response);
await _signalR.ConnectAsync(response.Token); await _signalR.ConnectAsync(response.Token);
await Shell.Current.GoToAsync(nameof(LobbyPage)); await Shell.Current.GoToAsync($"//{nameof(LobbyPage)}");
}, "Registrierung läuft..."); }, "Registrierung läuft...");
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"updatedAt": "2026-08-08T10:49:34.141Z", "updatedAt": "2026-08-08T18:09:49.042Z",
"command": "update", "command": "update",
"gitHead": "bbc6ad60802ba4e19218d1529c927c67389d973d", "gitHead": "37616a93166c2ac0a46eedab276164f811043f4e",
"model": "gemini-3.6-flash", "model": "gemini-3.6-flash",
"status": "complete", "status": "complete",
"language": "en" "language": "en"

View File

@@ -108,7 +108,7 @@ sequenceDiagram
Client->>Hub: JoinLobby(lobbyCode) Client->>Hub: JoinLobby(lobbyCode)
Hub->>Hub: GetAuthenticatedUserId() Hub->>Hub: GetAuthenticatedUserId()
Hub->>Service: JoinGameAsync(lobbyCode, userId, connectionId) Hub->>Service: JoinGameAsync(lobbyCode, userId, connectionId)
Hub-->>Client: Broadcast "PlayerJoined" (GameStateDto) Hub-->>Client: Broadcast PlayerJoined (GameStateDto)
``` ```
*Sequence diagram showing user authentication via REST and subsequent authenticated SignalR WebSocket connection.* *Sequence diagram showing user authentication via REST and subsequent authenticated SignalR WebSocket connection.*

View File

@@ -96,3 +96,6 @@ SlipItIn.slnx
### `Documentation & Specifications` (`Agents/`) ### `Documentation & Specifications` (`Agents/`)
- **`Agents/Architecture.md`**: Specification document defining backend security and client MVVM/stability patterns. - **`Agents/Architecture.md`**: Specification document defining backend security and client MVVM/stability patterns.
- **`Agents/ProjectPlan.md`**: Project plan breakdown covering development phases. - **`Agents/ProjectPlan.md`**: Project plan breakdown covering development phases.
### `Automation & CI/CD` (`.github/workflows/`)
- **`.github/workflows/openwiki-update.yml`**: Scheduled GitHub Actions workflow executing `openwiki code --update` via Gemini Flash (`gemini-3.6-flash`) to keep repository documentation in sync with source changes.

View File

@@ -95,15 +95,15 @@ sequenceDiagram
GameBoardVM->>Hub: SubmitSlip(gameId, playerAId, cardId) GameBoardVM->>Hub: SubmitSlip(gameId, playerAId, cardId)
Hub->>Service: SubmitSlipAsync() Hub->>Service: SubmitSlipAsync()
Service->>DB: Set PlayerCard.IsUsed = true Service->>DB: Set PlayerCard.IsUsed = true
Hub-->>Challenger: Broadcast "SlipSubmitted" Hub-->>Challenger: Broadcast SlipSubmitted
Note over Challenger: Suspects invalid phrase Note over Challenger: Suspects invalid phrase
Challenger->>GameBoardVM: ChallengeCardAsync(card) Challenger->>GameBoardVM: ChallengeCardAsync(card)
GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId) GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId)
Hub->>Service: CreateChallengeAsync() Hub->>Service: CreateChallengeAsync()
Service->>DB: Insert SlipChallenge (Status = Pending) Service->>DB: Insert SlipChallenge (Status = Pending)
Hub-->>Challenger: Broadcast "SlipChallenged" Hub-->>Challenger: Broadcast SlipChallenged
Hub-->>Slipper: Unicast "ChallengeReceived" Hub-->>Slipper: Unicast ChallengeReceived
Note over Slipper: Notification Overlay Displays Note over Slipper: Notification Overlay Displays
Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved) Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved)
@@ -116,7 +116,7 @@ sequenceDiagram
else approved == true (Justified Catch) else approved == true (Justified Catch)
Service->>DB: Set Challenge.Status = Approved Service->>DB: Set Challenge.Status = Approved
end end
Hub-->>Challenger: Broadcast "ChallengeResolved" Hub-->>Challenger: Broadcast ChallengeResolved
``` ```
*Sequence diagram showing phrase submission, challenge initiation, target notification, and penalty resolution.* *Sequence diagram showing phrase submission, challenge initiation, target notification, and penalty resolution.*