Skip to content

Commit 14c6236

Browse files
committed
feat: add slug based retrieval
1 parent 1b13c1e commit 14c6236

File tree

10 files changed

+39
-8
lines changed

10 files changed

+39
-8
lines changed

.idea/.idea.KroppensHyllning/.idea/indexLayout.xml

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

KroppensHyllning.API/ApiEndpoints.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static class Campaigns
1010

1111
public const string Create = Base;
1212

13-
public const string Get = $"{Base}/{{id:guid}}";
13+
public const string Get = $"{Base}/{{idOrSlug}}";
1414

1515
public const string GetAll = Base;
1616

KroppensHyllning.API/Controllers/CampaignsController.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ public async Task<IActionResult> Create([FromBody] CreateCampaignRequest request
2323
await _campaignRepository.CreateAsync(campaign);
2424
return CreatedAtAction(
2525
nameof(Get),
26-
new {id = campaign.Id},
26+
new {idOrSlug = campaign.Id},
2727
campaign.MapToResponse()
2828
);
2929
}
3030

3131
[HttpGet]
3232
[Route(ApiEndpoints.Campaigns.Get)]
33-
public async Task<IActionResult> Get([FromRoute] Guid id)
33+
public async Task<IActionResult> Get([FromRoute] string idOrSlug)
3434
{
35-
var campaign = await _campaignRepository.GetByIdAsync(id);
35+
var campaign = Guid.TryParse(idOrSlug, out var id)
36+
? await _campaignRepository.GetByIdAsync(id)
37+
: await _campaignRepository.GetBySlugAsync(idOrSlug);
3638
if (campaign is null) return NotFound();
3739
var response = campaign.MapToResponse();
3840
return Ok(response);

KroppensHyllning.API/Mapping/ContractMapping.cs

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public static CampaignResponse MapToResponse(this Campaign campaign)
3535
Id = campaign.Id,
3636
Name = campaign.Name,
3737
StartYear = campaign.StartYear,
38+
Slug = campaign.Slug,
3839
Tags = campaign.Tags
3940
};
4041
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Global using directives
2+
3+
global using System.Text.RegularExpressions;
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
namespace KroppensHyllning.Application.Models;
22

3-
public class Campaign
3+
public partial class Campaign
44
{
55
public required Guid Id { get; init; }
66
public required string Name { get; init; }
7+
public string Slug => GenerateSlug();
78

89
public required int StartYear { get; init; }
910

1011
public required List<string> Tags { get; init; } = new();
12+
13+
private string GenerateSlug()
14+
{
15+
var replaced = SlugRegex()
16+
.Replace(Name, "-")
17+
.ToLowerInvariant();
18+
19+
return $"{replaced}-{StartYear}";
20+
}
21+
22+
[GeneratedRegex("[^a-zA-Z0-9]", RegexOptions.NonBacktracking, 5)]
23+
private static partial Regex SlugRegex();
1124
}

KroppensHyllning.Application/Repositories/CampaignRepository.cs

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public Task<bool> CreateAsync(Campaign campaign)
1919
return Task.FromResult(campaign);
2020
}
2121

22+
public Task<Campaign?> GetBySlugAsync(string slug)
23+
{
24+
var campaign = _campaigns.FirstOrDefault(c => c.Slug == slug);
25+
return Task.FromResult(campaign);
26+
}
27+
2228
public Task<IEnumerable<Campaign>> GetAllAsync()
2329
{
2430
return Task.FromResult(_campaigns.AsEnumerable());

KroppensHyllning.Application/Repositories/ICampaignRepository.cs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public interface ICampaignRepository
77
Task<bool> CreateAsync(Campaign campaign);
88

99
Task<Campaign?> GetByIdAsync(Guid id);
10+
Task<Campaign?> GetBySlugAsync(string slug);
1011

1112
Task<IEnumerable<Campaign>> GetAllAsync();
1213

KroppensHyllning.Contracts/Responses/CampaignResponse.cs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ public class CampaignResponse
55
public required Guid Id { get; init; }
66
public required string Name { get; init; }
77

8+
public required string Slug { get; init; }
9+
810
public required int StartYear { get; init; }
911

1012
public required IEnumerable<string> Tags { get; init; } = Enumerable.Empty<string>();

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
_The Body's Celebration_ is in stark contrast to Yxans Klagan _The Axe's Lamentations_.
44

5-
This is a Restful API built in .Net Core for managing a Forbidden Lands RPG campaign, this would be used by https://github.com/syradar/yxans-klagan to render the UI.
5+
This is a Restful API built in .Net Core for managing a Forbidden Lands RPG campaign, this would be used
6+
by https://github.com/syradar/yxans-klagan to render the UI.
67

78
## Features
89

910
- [X] Manage Campaigns, CRUD
10-
- [ ] Slug based URLs
11+
- [X] Slug based URLs
1112
- [ ] Versioning
1213
- [ ] Authentication
1314
- [ ] Authorization

0 commit comments

Comments
 (0)