Skip to content

Commit e3a5d3e

Browse files
committed
Change tenants/{id} endpoints to tenants/current and update frontend, commands, and test names accordingly
1 parent 7a92dd4 commit e3a5d3e

File tree

8 files changed

+105
-190
lines changed

8 files changed

+105
-190
lines changed

application/account-management/Api/Endpoints/TenantEndpoints.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ public void MapEndpoints(IEndpointRouteBuilder routes)
1414
{
1515
var group = routes.MapGroup(RoutesPrefix).WithTags("Tenants").RequireAuthorization();
1616

17-
group.MapGet("/{id}", async Task<ApiResult<TenantResponse>> ([AsParameters] GetTenantQuery query, IMediator mediator)
18-
=> await mediator.Send(query)
17+
group.MapGet("/current", async Task<ApiResult<TenantResponse>> (IMediator mediator)
18+
=> await mediator.Send(new GetTenantQuery())
1919
).Produces<TenantResponse>();
2020

21-
group.MapPut("/{id}", async Task<ApiResult> (TenantId id, UpdateTenantCommand command, IMediator mediator)
22-
=> await mediator.Send(command with { Id = id })
21+
group.MapPut("/current", async Task<ApiResult> (UpdateCurrentTenantCommand command, IMediator mediator)
22+
=> await mediator.Send(command)
2323
);
2424

2525
routes.MapDelete("/internal-api/account-management/tenants/{id}", async Task<ApiResult> (TenantId id, IMediator mediator)

application/account-management/Core/Features/Tenants/Commands/UpdateTenant.cs renamed to application/account-management/Core/Features/Tenants/Commands/UpdateCurrentTenant.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,19 @@
22
using JetBrains.Annotations;
33
using PlatformPlatform.AccountManagement.Features.Tenants.Domain;
44
using PlatformPlatform.SharedKernel.Cqrs;
5-
using PlatformPlatform.SharedKernel.Domain;
65
using PlatformPlatform.SharedKernel.Telemetry;
76

87
namespace PlatformPlatform.AccountManagement.Features.Tenants.Commands;
98

109
[PublicAPI]
11-
public sealed record UpdateTenantCommand : ICommand, IRequest<Result>
10+
public sealed record UpdateCurrentTenantCommand : ICommand, IRequest<Result>
1211
{
13-
[JsonIgnore] // Removes this property from the API contract
14-
public TenantId Id { get; init; } = null!;
15-
1612
public required string Name { get; init; }
1713
}
1814

19-
public sealed class UpdateTenantValidator : AbstractValidator<UpdateTenantCommand>
15+
public sealed class UpdateCurrentTenantValidator : AbstractValidator<UpdateCurrentTenantCommand>
2016
{
21-
public UpdateTenantValidator()
17+
public UpdateCurrentTenantValidator()
2218
{
2319
RuleFor(x => x.Name).NotEmpty();
2420
RuleFor(x => x.Name).Length(1, 30)
@@ -28,12 +24,11 @@ public UpdateTenantValidator()
2824
}
2925

3026
public sealed class UpdateTenantHandler(ITenantRepository tenantRepository, ITelemetryEventsCollector events)
31-
: IRequestHandler<UpdateTenantCommand, Result>
27+
: IRequestHandler<UpdateCurrentTenantCommand, Result>
3228
{
33-
public async Task<Result> Handle(UpdateTenantCommand command, CancellationToken cancellationToken)
29+
public async Task<Result> Handle(UpdateCurrentTenantCommand command, CancellationToken cancellationToken)
3430
{
35-
var tenant = await tenantRepository.GetByIdAsync(command.Id, cancellationToken);
36-
if (tenant is null) return Result.NotFound($"Tenant with id '{command.Id}' not found.");
31+
var tenant = await tenantRepository.GetCurrentTenantAsync(cancellationToken);
3732

3833
tenant.Update(command.Name);
3934
tenantRepository.Update(tenant);

application/account-management/Core/Features/Tenants/Queries/GetTenant.cs renamed to application/account-management/Core/Features/Tenants/Queries/GetCurrentTenant.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace PlatformPlatform.AccountManagement.Features.Tenants.Queries;
88

99
[PublicAPI]
10-
public sealed record GetTenantQuery(TenantId Id) : IRequest<Result<TenantResponse>>;
10+
public sealed record GetTenantQuery : IRequest<Result<TenantResponse>>;
1111

1212
[PublicAPI]
1313
public sealed record TenantResponse(TenantId Id, DateTimeOffset CreatedAt, DateTimeOffset? ModifiedAt, string Name, TenantState State);
@@ -17,7 +17,7 @@ public sealed class GetTenantHandler(ITenantRepository tenantRepository)
1717
{
1818
public async Task<Result<TenantResponse>> Handle(GetTenantQuery query, CancellationToken cancellationToken)
1919
{
20-
var tenant = await tenantRepository.GetByIdAsync(query.Id, cancellationToken);
21-
return tenant?.Adapt<TenantResponse>() ?? Result<TenantResponse>.NotFound($"Tenant with id '{query.Id}' not found.");
20+
var tenant = await tenantRepository.GetCurrentTenantAsync(cancellationToken);
21+
return tenant.Adapt<TenantResponse>();
2222
}
2323
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using FluentAssertions;
2+
using NJsonSchema;
3+
using PlatformPlatform.AccountManagement.Database;
4+
using PlatformPlatform.SharedKernel.Tests;
5+
using Xunit;
6+
7+
namespace PlatformPlatform.AccountManagement.Tests.Tenants;
8+
9+
public sealed class GetCurrentTenantTests : EndpointBaseTest<AccountManagementDbContext>
10+
{
11+
[Fact]
12+
public async Task GetCurrentTenant_WhenTenantExists_ShouldReturnTenantWithValidContract()
13+
{
14+
// Act
15+
var response = await AuthenticatedHttpClient.GetAsync("/api/account-management/tenants/current");
16+
17+
// Assert
18+
response.ShouldBeSuccessfulGetRequest();
19+
20+
var schema = await JsonSchema.FromJsonAsync(
21+
"""
22+
{
23+
'type': 'object',
24+
'properties': {
25+
'id': {'type': 'string', 'pattern': '^(?=.{3,30}$)(?!-)[a-z0-9-]*(?<!-)$'},
26+
'createdAt': {'type': 'string', 'format': 'date-time'},
27+
'modifiedAt': {'type': ['null', 'string'], 'format': 'date-time'},
28+
'name': {'type': 'string', 'minLength': 1, 'maxLength': 30},
29+
'state': {'type': 'string', 'minLength': 1, 'maxLength':20}
30+
},
31+
'required': ['id', 'createdAt', 'modifiedAt', 'name', 'state'],
32+
'additionalProperties': false
33+
}
34+
"""
35+
);
36+
37+
var responseBody = await response.Content.ReadAsStringAsync();
38+
schema.Validate(responseBody).Should().BeEmpty();
39+
}
40+
}

application/account-management/Tests/Tenants/GetTenantTests.cs

Lines changed: 0 additions & 70 deletions
This file was deleted.

application/account-management/Tests/Tenants/UpdateTenantTests.cs renamed to application/account-management/Tests/Tenants/UpdateCurrentTenantTests.cs

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@
99

1010
namespace PlatformPlatform.AccountManagement.Tests.Tenants;
1111

12-
public sealed class UpdateTenantTests : EndpointBaseTest<AccountManagementDbContext>
12+
public sealed class UpdateCurrentTenantTests : EndpointBaseTest<AccountManagementDbContext>
1313
{
1414
[Fact]
15-
public async Task UpdateTenant_WhenValid_ShouldUpdateTenant()
15+
public async Task UpdateCurrentTenant_WhenValid_ShouldUpdateTenant()
1616
{
1717
// Arrange
18-
var existingTenantId = DatabaseSeeder.Tenant1.Id;
19-
var command = new UpdateTenantCommand { Name = Faker.TenantName() };
18+
var command = new UpdateCurrentTenantCommand { Name = Faker.TenantName() };
2019

2120
// Act
22-
var response = await AuthenticatedHttpClient.PutAsJsonAsync($"/api/account-management/tenants/{existingTenantId}", command);
21+
var response = await AuthenticatedHttpClient.PutAsJsonAsync("/api/account-management/tenants/current", command);
2322

2423
// Assert
2524
response.ShouldHaveEmptyHeaderAndLocationOnSuccess();
@@ -30,15 +29,14 @@ public async Task UpdateTenant_WhenValid_ShouldUpdateTenant()
3029
}
3130

3231
[Fact]
33-
public async Task UpdateTenant_WhenInvalid_ShouldReturnBadRequest()
32+
public async Task UpdateCurrentTenant_WhenInvalid_ShouldReturnBadRequest()
3433
{
3534
// Arrange
36-
var existingTenantId = DatabaseSeeder.Tenant1.Id;
3735
var invalidName = Faker.Random.String2(31);
38-
var command = new UpdateTenantCommand { Name = invalidName };
36+
var command = new UpdateCurrentTenantCommand { Name = invalidName };
3937

4038
// Act
41-
var response = await AuthenticatedHttpClient.PutAsJsonAsync($"/api/account-management/tenants/{existingTenantId}", command);
39+
var response = await AuthenticatedHttpClient.PutAsJsonAsync("/api/account-management/tenants/current", command);
4240

4341
// Assert
4442
var expectedErrors = new[]
@@ -49,20 +47,4 @@ public async Task UpdateTenant_WhenInvalid_ShouldReturnBadRequest()
4947

5048
TelemetryEventsCollectorSpy.AreAllEventsDispatched.Should().BeFalse();
5149
}
52-
53-
[Fact]
54-
public async Task UpdateTenant_WhenTenantDoesNotExists_ShouldReturnNotFound()
55-
{
56-
// Arrange
57-
var unknownTenantId = Faker.Subdomain();
58-
var command = new UpdateTenantCommand { Name = Faker.TenantName() };
59-
60-
// Act
61-
var response = await AuthenticatedHttpClient.PutAsJsonAsync($"/api/account-management/tenants/{unknownTenantId}", command);
62-
63-
//Assert
64-
await response.ShouldHaveErrorStatusCode(HttpStatusCode.NotFound, $"Tenant with id '{unknownTenantId}' not found.");
65-
66-
TelemetryEventsCollectorSpy.AreAllEventsDispatched.Should().BeFalse();
67-
}
6850
}

application/account-management/WebApp/routes/admin/account/index.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import { Trash2 } from "lucide-react";
66
import { t } from "@lingui/core/macro";
77
import { Trans } from "@lingui/react/macro";
88
import { createFileRoute } from "@tanstack/react-router";
9-
import { useUserInfo } from "@repo/infrastructure/auth/hooks";
109
import { Form } from "@repo/ui/components/Form";
11-
import { useActionState, useState, useEffect } from "react";
10+
import { useActionState, useEffect, useState } from "react";
1211
import { api } from "@/shared/lib/api/client";
1312
import DeleteAccountConfirmation from "./-components/DeleteAccountConfirmation";
1413
import { SharedSideMenu } from "@/shared/components/SharedSideMenu";
@@ -21,16 +20,9 @@ export const Route = createFileRoute("/admin/account/")({
2120

2221
export function AccountSettings() {
2322
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
24-
const userInfo = useUserInfo();
25-
const {
26-
data: tenant,
27-
loading,
28-
refresh
29-
} = api.useApi("/api/account-management/tenants/{id}", {
30-
id: userInfo?.tenantId ?? ""
31-
});
23+
const { data: tenant, loading, refresh } = api.useApi("/api/account-management/tenants/current", {}, {});
3224

33-
const [{ errors, success }, action] = useActionState(api.actionPut("/api/account-management/tenants/{id}"), {});
25+
const [{ errors, success }, action] = useActionState(api.actionPut("/api/account-management/tenants/current"), {});
3426

3527
useEffect(() => {
3628
if (success) {
@@ -65,7 +57,6 @@ export function AccountSettings() {
6557
</div>
6658

6759
<Form action={action} validationErrors={errors} validationBehavior="aria" className="flex flex-col gap-4">
68-
<input type="hidden" name="id" value={userInfo?.tenantId ?? ""} />
6960
<Label>
7061
<Trans>Logo</Trans>
7162
</Label>
@@ -86,7 +77,7 @@ export function AccountSettings() {
8677
<TextField
8778
name="domain"
8879
label={t`Domain`}
89-
value={`${userInfo?.tenantId ?? ""}.platformplatform.net`}
80+
value={`${tenant?.id ?? ""}.platformplatform.net`}
9081
isDisabled={true}
9182
/>
9283
</div>

0 commit comments

Comments
 (0)