Compare commits
1 Commits
936c8bde28
...
openwiki/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c67c549f48 |
6
.github/workflows/openwiki-update.yml
vendored
6
.github/workflows/openwiki-update.yml
vendored
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -8,47 +8,34 @@ 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 signalR = ServiceHelper.GetRequiredService<ISignalRService>();
|
||||||
var authSession = ServiceHelper.GetRequiredService<IAuthSessionService>();
|
var gameStateService = ServiceHelper.GetRequiredService<IGameStateService>();
|
||||||
var signalR = ServiceHelper.GetRequiredService<ISignalRService>();
|
|
||||||
var gameStateService = ServiceHelper.GetRequiredService<IGameStateService>();
|
|
||||||
|
|
||||||
await authSession.InitializeAsync();
|
await authSession.InitializeAsync();
|
||||||
await gameStateService.InitializeAsync();
|
await gameStateService.InitializeAsync();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(authSession.AccessToken))
|
if (!string.IsNullOrWhiteSpace(authSession.AccessToken))
|
||||||
{
|
|
||||||
var connected = await signalR.ConnectAsync(authSession.AccessToken);
|
|
||||||
if (connected)
|
|
||||||
await gameStateService.ResyncAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
// Start darf nicht abstürzen, wenn Session/Netzwerk fehlschlägt.
|
var connected = await signalR.ConnectAsync(authSession.AccessToken);
|
||||||
|
if (connected)
|
||||||
|
await gameStateService.ResyncAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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...");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +53,9 @@ public partial class LoginViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
bool isAuthenticated = _authSession.IsAuthenticated;
|
bool isAuthenticated = _authSession.IsAuthenticated;
|
||||||
|
|
||||||
if (isAuthenticated)
|
if ( isAuthenticated)
|
||||||
{
|
{
|
||||||
await Shell.Current.GoToAsync(nameof(LobbyPage));
|
await Shell.Current.GoToAsync($"//{nameof(LobbyPage)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.*
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.*
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user