diff --git a/.gitignore b/.gitignore
index 51eecb3..547b140 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,9 +11,4 @@ appsettings.Local*.json
nixpkgs/
*.DotSettings.user
-test.tsv
-test-proxy.tsv
-homeservers.txt
-LoginPayload.txt
-LoginPayload.txt.old
-BugMine.DevTools.CLI/auth.json
+**/auth.json
diff --git a/BugMine.DevTools.CLI/Worker.cs b/BugMine.DevTools.CLI/Worker.cs
index 900d742..4b3d3f4 100644
--- a/BugMine.DevTools.CLI/Worker.cs
+++ b/BugMine.DevTools.CLI/Worker.cs
@@ -115,7 +115,7 @@ public class Worker(ILogger<Worker> logger, HomeserverProviderService hsProvider
// await proj.SendStateEventAsync(RoomCanonicalAliasEventContent.EventId, new RoomCanonicalAliasEventContent() {
// Alias = null
// });
- await proj.LeaveAsync("Disbanded room.");
+ await proj.LeaveAsync("[BugMine.DevTools.CLI] Disbanding project.");
// ss.Release();
});
}
diff --git a/BugMine.Sdk/BugMineClient.cs b/BugMine.Sdk/BugMineClient.cs
index 21be614..d55ff4d 100644
--- a/BugMine.Sdk/BugMineClient.cs
+++ b/BugMine.Sdk/BugMineClient.cs
@@ -1,3 +1,4 @@
+using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using ArcaneLibs.Extensions;
using BugMine.Web.Classes.Exceptions;
@@ -11,14 +12,14 @@ namespace BugMine.Web.Classes;
public class BugMineClient(AuthenticatedHomeserverGeneric homeserver) {
public AuthenticatedHomeserverGeneric Homeserver { get; } = homeserver;
- public async IAsyncEnumerable<BugMineProject> GetProjects(SemaphoreSlim? semaphore = null) {
+ public async IAsyncEnumerable<BugMineProject> GetProjects(SemaphoreSlim? semaphore = null, bool ignoreInvalidBoards = false) {
List<Task<BugMineProject>> tasks = [];
int count = 0;
await foreach (var room in homeserver.GetJoinedRoomsByType(BugMineProject.RoomType, 64)) {
tasks.Add(room.AsBugMineProject(semaphore));
}
- var results = tasks.ToAsyncEnumerable();
+ var results = tasks.ToAsyncEnumerable(skipExceptions: ignoreInvalidBoards);
await foreach (var result in results) {
yield return result;
}
@@ -75,7 +76,16 @@ public class BugMineClient(AuthenticatedHomeserverGeneric homeserver) {
}
else {
var alias = $"#{projectSlug}";
- var resolveResult = await Homeserver.ResolveRoomAliasAsync(alias);
+ AliasResult? resolveResult = null;
+ try {
+ resolveResult = await Homeserver.ResolveRoomAliasAsync(alias);
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == MatrixException.ErrorCodes.M_NOT_FOUND)
+ throw new BugMineException(BugMineException.ErrorCodes.ProjectNotFound, $"Project with slug {projectSlug} not found");
+ throw;
+ }
+
if (string.IsNullOrEmpty(resolveResult?.RoomId)) return null; //TODO: fallback to finding via joined rooms' canonical alias event?
room = homeserver.GetRoom(resolveResult.RoomId);
diff --git a/BugMine.Sdk/Exceptions/BugMineException.cs b/BugMine.Sdk/Exceptions/BugMineException.cs
index 7e843b3..5c81e48 100644
--- a/BugMine.Sdk/Exceptions/BugMineException.cs
+++ b/BugMine.Sdk/Exceptions/BugMineException.cs
@@ -19,5 +19,6 @@ public class BugMineException : MatrixException {
public new static class ErrorCodes {
public const string UserNotInRoom = "BUGMINE_USER_NOT_IN_ROOM";
+ public const string ProjectNotFound = "BUGMINE_PROJECT_NOT_FOUND";
}
}
\ No newline at end of file
diff --git a/BugMine.Web/Components/ProjectContainer.razor b/BugMine.Web/Components/ProjectContainer.razor
new file mode 100644
index 0000000..f7621be
--- /dev/null
+++ b/BugMine.Web/Components/ProjectContainer.razor
@@ -0,0 +1,152 @@
+@using System.Text.Json.Serialization
+@using ArcaneLibs.Extensions
+@using BugMine.Web.Classes.Exceptions
+@using LibMatrix
+@inject ILogger<ProjectContainer> Logger
+
+@if (Constants.Debug) {
+ <p>Debug, beware: here be dragons!</p>
+ <p>ProjectContainer debug info:</p>
+ <pre>Slug: @ProjectSlug</pre>
+ <pre>Progress: @Progress.ToString()</pre>
+ @if (ProjectContext is null) {
+ <pre>ProjectContext is null!</pre>
+ }
+ else {
+ <details>
+ <summary>Context json dump</summary>
+ <pre>@ProjectContext.ToJson()</pre>
+ </details>
+ @if (ProjectContext?.Project?.Room is not null) {
+ <LinkButton OnClick="@ProjectContext.Project.Room.PermanentlyBrickRoomAsync">Dispose room</LinkButton>
+ }
+ }
+
+ <hr/>
+}
+@if (ProjectContext.Client is null) {
+ <p>Authenticating</p>
+}
+else if (ProjectContext?.Project is null) {
+ @if (Progress == Status.Loading) {
+ <p>Loading project <SimpleSpinner/></p>
+ }
+ else if (Progress == Status.NotInRoom) {
+ <p>You are not in the project room.</p>
+ <p>You must join before you can view or interact with this project.</p>
+ <LinkButton OnClick="TryJoin">Attempt to join</LinkButton>
+ }
+ else if (Progress == Status.RoomNotFound) {
+ <p>Project not found.</p>
+ <p>If you believe this is an error, please contact the project in order to obtain a new room.</p>
+ }
+}
+else {
+ @ChildContent
+}
+
+@code {
+ private Status? _progress = Status.Loading;
+
+ [Parameter]
+ public string ProjectSlug { get; set; } = null!;
+
+ [Parameter]
+ public ProjectContainerContext? ProjectContext { get; set; }
+
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+ [Parameter]
+ public Func<Task>? Loaded { get; set; }
+
+ private Status? Progress {
+ get => _progress;
+ set {
+ _progress = value;
+ StateHasChanged();
+ }
+ }
+
+ protected override async Task OnInitializedAsync() {
+ if (ProjectContext is null) {
+ Logger.LogError("ProjectContext is null");
+ ProjectContext = new();
+ }
+
+ if (ProjectContext.Project != null) {
+ Logger.LogWarning("ProjectContext.Project is not null");
+ }
+
+ ProjectContext.Client ??= await BugMineStorage.GetCurrentSessionOrNavigate();
+ if (ProjectContext.Client == null) {
+ return;
+ }
+
+ Progress = Status.Loading;
+
+ try {
+ ProjectContext.Project = await ProjectContext.Client.GetProject(ProjectSlug);
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == BugMineException.ErrorCodes.UserNotInRoom) {
+ Progress = Status.NotInRoom;
+ return;
+ }
+ else if (e.ErrorCode == BugMineException.ErrorCodes.ProjectNotFound) {
+ Progress = Status.RoomNotFound;
+ return;
+ }
+
+ throw;
+ }
+
+ Progress = Status.Done;
+ if (Loaded != null) {
+ await Loaded.Invoke();
+ }
+
+
+ StateHasChanged();
+ }
+
+ private async Task TryJoin() {
+ var room = await ProjectContext.Client.ResolveProjectSlug(ProjectSlug);
+ bool success = false;
+ while (!success) {
+ try {
+ await room.JoinAsync();
+ if (!string.IsNullOrWhiteSpace(room.RoomId)) {
+ success = true;
+ }
+ else {
+ await Task.Delay(1000);
+ }
+ }
+ catch (MatrixException e) {
+ // if (e.ErrorCode == MatrixException.ErrorCodes.) {
+ // await Task.Delay(1000);
+ // continue;
+ // }
+
+ throw;
+ }
+ }
+
+ await OnInitializedAsync();
+ }
+
+ public class ProjectContainerContext {
+ public BugMineClient? Client { get; set; }
+
+ public BugMineProject? Project { get; set; }
+ }
+
+ private enum Status {
+ Loading,
+ NotInRoom,
+ RoomNotFound,
+ Done
+ }
+
+}
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/Index.razor b/BugMine.Web/Pages/Projects/Index.razor
index 8f46d02..9755dc3 100644
--- a/BugMine.Web/Pages/Projects/Index.razor
+++ b/BugMine.Web/Pages/Projects/Index.razor
@@ -46,7 +46,7 @@ else {
int count = 0;
SemaphoreSlim semaphore = new(16, 16);
- await foreach (var project in Client.GetProjects(semaphore)) {
+ await foreach (var project in Client.GetProjects(semaphore, ignoreInvalidBoards: true)) {
Projects ??= [];
Projects.Add(project);
if(count++ <= 250 || count % 4 == 0)
diff --git a/BugMine.Web/Pages/Projects/Issues/NewIssue.razor b/BugMine.Web/Pages/Projects/Issues/NewIssue.razor
new file mode 100644
index 0000000..159e20d
--- /dev/null
+++ b/BugMine.Web/Pages/Projects/Issues/NewIssue.razor
@@ -0,0 +1,15 @@
+@page "/Projects/{ProjectSlug}/Issues/New"
+<h3>New issue</h3>
+
+<ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext">
+ <h1>Hi from NewIssue!</h1>
+</ProjectContainer>
+
+@code {
+
+ [Parameter]
+ public string ProjectSlug { get; set; } = null!;
+
+ public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; } = new();
+
+}
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/NewProject.razor b/BugMine.Web/Pages/Projects/NewProject.razor
index 00b7b21..f8c7dfd 100644
--- a/BugMine.Web/Pages/Projects/NewProject.razor
+++ b/BugMine.Web/Pages/Projects/NewProject.razor
@@ -1,5 +1,6 @@
@page "/Projects/New"
@using ArcaneLibs.Extensions
+@using LibMatrix
<h3>New project</h3>
<span>Project name: </span>
@@ -20,10 +21,25 @@
<br/>
}
-<LinkButton OnClick="@CreateProject">Create project</LinkButton>
+@if (!_busy) {
+ <LinkButton OnClick="@CreateProject">Create project</LinkButton>
+}
+else {
+ <p>Powering up the framework... <SimpleSpinner/></p>
+}
@code {
+ private bool _busy = false;
+
+ private bool Busy {
+ get => _busy;
+ set {
+ _busy = value;
+ StateHasChanged();
+ }
+ }
+
private BugMineClient? Client { get; set; }
private readonly ProjectInfo _request = new();
@@ -39,9 +55,15 @@
if (Client == null) {
return;
}
+ Busy = true;
+ try {
+ var proj = await Client.CreateProject(_request);
+ NavigationManager.NavigateTo($"/Projects/{proj.ProjectSlug}/");
- var proj = await Client.CreateProject(_request);
- NavigationManager.NavigateTo($"/Projects/{proj.ProjectSlug}/");
+ }
+ catch (MatrixException e) {
+
+ }
}
}
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/ViewProject.razor b/BugMine.Web/Pages/Projects/ViewProject.razor
index ec10d1c..91cc4d0 100644
--- a/BugMine.Web/Pages/Projects/ViewProject.razor
+++ b/BugMine.Web/Pages/Projects/ViewProject.razor
@@ -5,48 +5,30 @@
<ProgressLog ></ProgressLog>
-@if (Client is null) {
- <p>Authenticating</p>
-}
-else if (Project is null) {
- @if (Progress == "loading") {
- <p>Loading project <SimpleSpinner/></p>
- }
- else if (Progress == "not-in-room") {
- <p>You are not in the project room.</p>
- <p>You must join before you can view or interact with this project.</p>
- <LinkButton OnClick="TryJoin">Attempt to join</LinkButton>
- }
-}
-else {
- <h1>@Project.Info.Name</h1>
+<ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext" Loaded="@OnProjectLoaded">
+ <h1>Hi from ViewProject!</h1>
+ <h1>@ProjectContext!.Project!.Info.Name</h1>
- @if (Constants.Debug) {
- <p>Debug, beware: here be dragons!</p>
- <LinkButton OnClick="@Project.Room.PermanentlyBrickRoomAsync">Dispose room</LinkButton>
- }
-
@if (Progress == "loading-issues") {
<p>Loading issues, got @(Issues?.Count ?? 0) so far... <SimpleSpinner/></p>
}
+
@* <p>@Project.Description</p> *@
@if (Issues != null) {
- @foreach(var issue in Issues) {
+ @foreach (var issue in Issues) {
<pre>@issue.Data.RawContent.ToJson()</pre>
}
}
-}
+</ProjectContainer>
@code {
private string? _progress = "loading";
+ public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; } = new();
+
[Parameter]
public string ProjectSlug { get; set; } = null!;
- private BugMineClient? Client { get; set; }
-
- private BugMineProject? Project { get; set; }
-
private List<BugMineIssue>? Issues { get; set; }
private string? Progress {
@@ -57,64 +39,15 @@ else {
}
}
- protected override async Task OnInitializedAsync() {
- Client ??= await BugMineStorage.GetCurrentSessionOrNavigate();
- if (Client == null) {
- return;
- }
-
- Progress = "loading";
- StateHasChanged();
-
- try {
- Project = await Client.GetProject(ProjectSlug);
- }
- catch (MatrixException e) {
- if (e.ErrorCode == BugMineException.ErrorCodes.UserNotInRoom) {
- Progress = "not-in-room";
- StateHasChanged();
- return;
- }
-
- throw;
- }
-
+ protected async Task OnProjectLoaded() {
Progress = "loading-issues";
- await foreach (var issue in Project.GetIssues()) {
+ await foreach (var issue in ProjectContext.Project.GetIssues()) {
Issues ??= new List<BugMineIssue>();
Issues.Add(issue);
StateHasChanged();
}
-
StateHasChanged();
}
- private async Task TryJoin() {
- var room = await Client.ResolveProjectSlug(ProjectSlug);
- bool success = false;
- while (!success) {
- try {
- await room.JoinAsync();
- if (!string.IsNullOrWhiteSpace(room.RoomId)) {
- success = true;
- }
- else {
- await Task.Delay(1000);
- }
- }
- catch (MatrixException e) {
- // if (e.ErrorCode == MatrixException.ErrorCodes.) {
- // await Task.Delay(1000);
- // continue;
- // }
-
- throw;
- }
- }
-
- await OnInitializedAsync();
- }
-
-}
-
+}
\ No newline at end of file
diff --git a/LibMatrix b/LibMatrix
-Subproject b5860ce2011b96a2919d5306445b0e8bd8408b3
+Subproject e1f99073f3d9788a4b48d2bb7091e3894dcefa1
diff --git a/README.MD b/README.MD
index 8a83d25..0566989 100644
--- a/README.MD
+++ b/README.MD
@@ -1,14 +1,22 @@
-# Rory&::MatrixUtils
+# BugMine
-Power tools for Matrix.
+Decentralised issue tracking system, built on [Matrix](https://matrix.org) using [Rory&::LibMatrix](https://cgit.rory.gay/matrix/LibMatrix.git).
-# Installation
+# Deploying the web interface
+Make sure to follow your web server's instructions to deploy a single-page application.
```sh
git clone --recursive $REPO
-cd MatrixUtils/MatrixUtils.Web
+cd BugMine/BugMine.Web
dotnet publish
-cp -rv bin/Release/net8.0/publish/wwwroot /var/www/html_rmu
+cp -rv bin/Release/net8.0/publish/wwwroot /var/www/html_bugmine
+```
+
+# Running a project
+
+```sh
+cd $PROJECT
+dotnet run
```
# Contributing
@@ -25,7 +33,7 @@ git format-patch --output-directory "./patches" @{u}..
Error reporting upon file save:
```sh
-inotifywait -rmqe CLOSE_WRITE --include '.*\.cs$' . | while read l; do clear; dotnet build --property WarningLevel=0; done
+dotnet watch build --property WarningLevel=0
```
Hot rebuild on file save:
|