Skip to content

Commit

Permalink
feat: Add demo user with sample data for demo server
Browse files Browse the repository at this point in the history
  • Loading branch information
VMelnalksnis committed May 10, 2024
1 parent ddb39f2 commit 13ca7c3
Show file tree
Hide file tree
Showing 23 changed files with 821 additions and 77 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ jobs:
az webapp config appsettings set --settings Jwt__ValidIssuer="${{ env.AZURE_APP_DOMAIN }}"
az webapp config appsettings set --settings OAuth__GitHub__ClientId="${{ secrets.DEMO_GITHUB_ID }}"
az webapp config appsettings set --settings OAuth__GitHub__ClientSecret="${{ secrets.DEMO_GITHUB_SECRET }}"
az webapp config appsettings set --settings GNOMESHADE_DEMO="true"
- run: wget --retry-connrefused --retry-on-http-error=502 --waitretry=5 --read-timeout=120 --timeout=120 -t 5 ${{ env.AZURE_APP_DOMAIN }}

Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@

A free, open source and self-hosted personal finance manager.

![Overview of all transactions over the past two months](docs/assets/transactions.jpg)
![Overview of all transactions over the past two months](docs/assets/transactions.png)

## Getting Started

Before installing the server yourself, you can try out the client using the
[demo server](https://gnomeshade-demo.azurewebsites.net/).
For instructions on how to install the client see
[demo server](https://www.gnomeshade.org/getting-started#demo). For instructions on how to install the client see
[gnomeshade.org/getting-started#install-client](https://www.gnomeshade.org/getting-started#install-client).
Note that the demo server is publicly accessible by anyone, so be careful entering person information.
The demo server is also periodically wiped.
Expand Down
Binary file removed docs/assets/balance.jpg
Binary file not shown.
Binary file added docs/assets/balance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/assets/transactions.jpg
Binary file not shown.
Binary file added docs/assets/transactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ <h3>Added</h3>
Time axis splits for reports in
<a href="https://github.com/VMelnalksnis/Gnomeshade/pull/1214">#1214</a>
</li>
<li>
Demo user with sample data in
<a href="https://github.com/VMelnalksnis/Gnomeshade/pull/1224">#1224</a>
</li>
</ul>
</section>

Expand Down
11 changes: 11 additions & 0 deletions docs/getting-started.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

<label>Getting started:</label>
<ul>
<li><a href="#demo">Demo server</a></li>
<li><a href="#download">Downloading</a></li>
<li>
<a href="#install">Installing the server</a>
Expand All @@ -43,6 +44,16 @@
<main id="top" class="content">
<h1>Getting started</h1>

<section id="demo">
<h2>Demo server</h2>
Before setting up the server yourself, you can try out the client using
the <a href="https://gnomeshade-demo.azurewebsites.net/">demo server</a>.
You will still need to <a href="#install-client">install the client</a>,
after which you can use the demo user
with username <code>demo</code> and password <code>Demo1!</code>
which has some sample data.
</section>

<section id="download">
<h2>Downloading</h2>
Gnomeshade can be downloaded from
Expand Down
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ <h3>Transaction management</h3>
Gnomeshade features a double-entry bookkeeping system.
You can quickly enter and organize your transactions in multiple currencies.
</p>
<img src="assets/transactions.jpg" width="100%"
<img src="assets/transactions.png" width="850"
alt="Overview of all transactions over the past two months"/>
</section>

Expand Down Expand Up @@ -76,7 +76,7 @@ <h3>Informative reports</h3>
and prices over time. It can also show how much you've paid or received from different accounts
or counterparties.
</p>
<img src="assets/balance.jpg" width="100%"
<img src="assets/balance.png" width="850"
alt="Candlestick graph of account balance for the past three years"/>
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,37 @@
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;

using Gnomeshade.Data;
using Gnomeshade.Data.Identity;
using Gnomeshade.WebApi.Services;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace Gnomeshade.WebApi.Areas.Identity.Pages.Account;

/// <summary>Page for handling new user registration.</summary>
[AllowAnonymous]
public sealed class Register : PageModel
{
private readonly ILogger<Register> _logger;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly UserUnitOfWork _userUnitOfWork;
private readonly UserRegistrationService _registrationService;

/// <summary>Initializes a new instance of the <see cref="Register"/> class.</summary>
/// <param name="logger">Logger for logging in the specified category.</param>
/// <param name="signInManager">Application user sign in manager.</param>
/// <param name="userManager">Application user manager.</param>
/// <param name="userUnitOfWork">Application user persistence store.</param>
public Register(
ILogger<Register> logger,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
UserUnitOfWork userUnitOfWork)
/// <param name="registrationService">Application user registration service.</param>
public Register(SignInManager<ApplicationUser> signInManager, UserRegistrationService registrationService)
{
_logger = logger;
_signInManager = signInManager;
_userManager = userManager;
_userUnitOfWork = userUnitOfWork;
_registrationService = registrationService;
}

/// <summary>Gets or sets the data needed to register a user.</summary>
Expand Down Expand Up @@ -74,39 +62,10 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser(Input.UserName)
{
FullName = Input.FullName,
};

var result = await _userManager.CreateAsync(user, Input.Password);

var result = await _registrationService.RegisterUser(Input.UserName, Input.FullName, Input.Password);

Check warning on line 65 in source/Gnomeshade.WebApi/Areas/Identity/Pages/Account/Register.cshtml.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.WebApi/Areas/Identity/Pages/Account/Register.cshtml.cs#L65

Added line #L65 was not covered by tests
if (result.Succeeded)
{
_logger.UserCreated();

var identityUser = await _userManager.FindByNameAsync(Input.UserName);
if (identityUser is null)
{
throw new InvalidOperationException("Could not find user by name after creating it");
}

try
{
await _userUnitOfWork.CreateUserAsync(identityUser);
}
catch (Exception)
{
await _userManager.DeleteAsync(identityUser);
throw;
}

if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
throw new NotSupportedException("Email confirmation is not supported");
}

await _signInManager.SignInAsync(user, false);
await _signInManager.SignInAsync(new(Input.UserName), false);

Check warning on line 68 in source/Gnomeshade.WebApi/Areas/Identity/Pages/Account/Register.cshtml.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.WebApi/Areas/Identity/Pages/Account/Register.cshtml.cs#L68

Added line #L68 was not covered by tests
return LocalRedirect(returnUrl);
}

Expand All @@ -129,7 +88,7 @@ public sealed class InputModel
[Display(Name = "Full Name")]
public string FullName { get; set; } = null!;

/// <summary>Gets or sets the user name of the user.</summary>
/// <summary>Gets or sets the username of the user.</summary>
[Required]
[Display(Name = "User Name")]
public string UserName { get; set; } = null!;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2021 Valters Melnalksnis
// Licensed under the GNU Affero General Public License v3.0 or later.
// See LICENSE.txt file in the project root for full license information.

using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

using Gnomeshade.Data.Repositories;
using Gnomeshade.WebApi.Client;
using Gnomeshade.WebApi.Services;

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using NodaTime;

namespace Gnomeshade.WebApi.Configuration.StartupFilters;

internal sealed class DemoUserBackgroundService : BackgroundService
{
private readonly IHostApplicationLifetime _lifetime;
private readonly IServiceProvider _serviceProvider;
private readonly Lazy<HttpMessageHandler> _handler;

public DemoUserBackgroundService(IHostApplicationLifetime lifetime, IServiceProvider serviceProvider, Lazy<HttpMessageHandler>? handler = null)
{
_handler = handler ?? new(() => new SocketsHttpHandler());
_lifetime = lifetime;
_serviceProvider = serviceProvider;
}

/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!await WaitForAppStartup(stoppingToken))
{
return;

Check warning on line 43 in source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs#L43

Added line #L43 was not covered by tests
}

using var scope = _serviceProvider.CreateScope();
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
if (configuration.GetChildren().Any(section => section.Key is "GNOMESHADE_DEMO"))
{
await CreateDemoUser(scope.ServiceProvider);
}
}

private async Task<bool> WaitForAppStartup(CancellationToken stoppingToken)
{
var startedSource = new TaskCompletionSource();
var cancelledSource = new TaskCompletionSource();

await using var reg1 = _lifetime.ApplicationStarted.Register(() => startedSource.SetResult());
await using var reg2 = stoppingToken.Register(() => cancelledSource.SetResult());

var completedTask = await Task.WhenAny(startedSource.Task, cancelledSource.Task);

// If the completed tasks was the "app started" task, return true, otherwise false
return completedTask == startedSource.Task;
}

private async Task CreateDemoUser(IServiceProvider services)
{
var repository = services.GetRequiredService<CounterpartyRepository>();
var counterparties = await repository.GetAllAsync();

if (counterparties.SingleOrDefault(counterparty => counterparty.NormalizedName is "DEMO") is not null)
{
return;
}

var registrationService = services.GetRequiredService<UserRegistrationService>();

var registerResult = await registrationService.RegisterUser("demo", "Demo", "Demo1!");
if (!registerResult.Succeeded)
{
throw new("Failed to register demo user");

Check warning on line 83 in source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs#L83

Added line #L83 was not covered by tests
}

// The default address is not provided by the feature, so need to set it manually
var serverAddress = services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()?.Addresses.FirstOrDefault() ?? "http://localhost:5000/";
var baseAddress = new UriBuilder(serverAddress) { Host = "localhost", Path = "/api/" }.Uri;

var clock = services.GetRequiredService<IClock>();
var dateTimeZoneProvider = services.GetRequiredService<IDateTimeZoneProvider>();

// todo This probably needs to be reworked by not using an HttpClient to call the API from the API
var client = new GnomeshadeClient(
new(new TokenDelegatingHandler(new(clock), new(dateTimeZoneProvider), new NullOidcClient())
{
InnerHandler = _handler.Value,
}) { BaseAddress = baseAddress },
new(dateTimeZoneProvider));

var loginResult = await client.LogInAsync(new() { Username = "demo", Password = "Demo1!" });
if (loginResult is FailedLogin)
{
throw new("Failed to log in with demo user");

Check warning on line 104 in source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs

View check run for this annotation

Codecov / codecov/patch

source/Gnomeshade.WebApi/Configuration/StartupFilters/DemoUserBackgroundService.cs#L104

Added line #L104 was not covered by tests
}

var service = new DemoUserService(client, clock, dateTimeZoneProvider);
await service.GenerateData();
}
}
1 change: 0 additions & 1 deletion source/Gnomeshade.WebApi/Gnomeshade.WebApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
<ProjectReference Include="..\Gnomeshade.Data.PostgreSQL\Gnomeshade.Data.PostgreSQL.csproj" />
<ProjectReference Include="..\Gnomeshade.Data.Sqlite\Gnomeshade.Data.Sqlite.csproj" />
<ProjectReference Include="..\Gnomeshade.WebApi.Client\Gnomeshade.WebApi.Client.csproj" />
<ProjectReference Include="..\Gnomeshade.WebApi.Models\Gnomeshade.WebApi.Models.csproj" />
</ItemGroup>

<Target Name="JavascriptLibraries" BeforeTargets="Build;Publish">
Expand Down
23 changes: 12 additions & 11 deletions source/Gnomeshade.WebApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"profiles": {
"Gnomeshade.WebApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": "true"
}
}
"profiles": {
"Gnomeshade.WebApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"GNOMESHADE_DEMO": "true"
},
"dotnetRunMessages": "true"
}
}
}
Loading

0 comments on commit 13ca7c3

Please sign in to comment.