8000 Feature/short url uniqueness using distributed counter by RomanEmreis · Pull Request #22 · RomanEmreis/shorty · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Feature/short url uniqueness using distributed counter #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions backend/src/Shorty.API/Features/Urls/UrlRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,22 @@ INSERT INTO shorty_urls (token, url, created_at)
public async Task<string?> GetAsync(string token, CancellationToken cancellationToken = default)
{
var url = await cache.GetStringAsync(token, cancellationToken);
if (!string.IsNullOrEmpty(url))
{
return url;
}
if (!string.IsNullOrEmpty(url)) return url;

const string sql =
"""
SELECT url
FROM shorty_urls
SELECT url FROM shorty_urls
WHERE token = @token
LIMIT 1
""";

url = await db.QueryFirstOrDefaultAsync<string>(sql, new { token });

if (!string.IsNullOrEmpty(url))
{
await SaveToCacheAsync(token, url, cancellationToken);
}
await SaveToCacheAsync(token, url, cancellationToken);

return url;
}

private async Task SaveToCacheAsync(string token, string url, CancellationToken cancellationToken)
private async Task SaveToCacheAsync(string token, string? url, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(url)) return;

Expand All @@ -67,7 +59,7 @@ private async Task SaveToCacheAsync(string token, string url, CancellationToken

private static DistributedCacheEntryOptions CreateDefaultOptions() => new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromHours(5),
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(30)
SlidingExpiration = TimeSpan.FromHours(1),
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
};
}
11 changes: 6 additions & 5 deletions backend/src/Shorty.API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

builder.Services.AddProblemDetails();

builder.Services.AddScoped<ICounterService, CounterService>();
builder.Services.AddScoped<IUrlRepository, UrlRepository>();
builder.Services.AddScoped<ICounterService, CounterService>();

Expand All @@ -31,18 +32,18 @@

app.MapDefaultEndpoints();

app.MapGet("/health", () => Results.Ok("healthy"));
app.MapGet("/{token}", async (IUrlRepository urlService, string token, CancellationToken cancellationToken) =>
app.MapGet("/health", static () => Results.Ok("healthy"));
app.MapGet("/{token}", async (IUrlRepository repository, string token, CancellationToken cancellationToken) =>
{
var url = await urlService.GetAsync(token, cancellationToken);
var url = await repository.GetAsync(token, cancellationToken);
return string.IsNullOrEmpty(url)
? Results.NotFound()
: Results.Redirect(url);
});

app.MapPost("/create", async (IUrlRepository urlService, CreateShortUrl command, CancellationToken cancellationToken) =>
app.MapPost("/create", async (IUrlRepository repository, CreateShortUrl command, CancellationToken cancellationToken) =>
{
var token = await urlService.SaveAsync(command.Url, cancellationToken);
var token = await repository.SaveAsync(command.Url, cancellationToken);
return Results.Ok(token);
});

Expand Down
6 changes: 4 additions & 2 deletions backend/src/Shorty.API/ShortUrlToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private const int
DefaultLength = 7,
Base = 62;

private const string Chars = "QoNPMlEDkABC06789zyxwvutsrq12435pOnmLKjZYXWVUTSRihgfedcbJIHGFa";
private const string Chars = "QoNPMlEDkABC06789zxyvwustrq21453pOnmLKjZYXWVUTSRihgfedcbJIHGFa";

private readonly string _value;

Expand All @@ -21,7 +21,7 @@ private ShortUrlToken(long count)
var j = DefaultLength;
while (count != 0)
{
var i = (byte) (count % Base);
int i = (int) (count % Base);
token[--j] = Chars[i];
count /= Base;
}
Expand All @@ -48,5 +48,7 @@ internal static ShortUrlToken NewToken(long count)
return new ShortUrlToken(count);
}

public override string ToString() => _value;

public static implicit operator string(ShortUrlToken token) => token._value;
}
4 changes: 2 additions & 2 deletions backend/src/Shorty.API/Shorty.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Npgsql" Version="8.0.2" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="8.0.2" />
<PackageReference Include="Aspire.Npgsql" Version="8.2.0" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="8.2.0" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Dapper.AOT" Version="1.0.31" />
</ItemGroup>
Expand Down
9 changes: 5 additions & 4 deletions backend/src/Shorty.API/Shorty.API.http
6D40
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
@Shorty.API_HostAddress = http://localhost:5254
@host = http://localhost:5254

###

POST {{Shorty.API_HostAddress}}/create
POST {{host}}/create
Accept: application/json
Content-Type: application/json

{ "url": "https://github.com/RomanEmreis/shorty" }

###

GET {{Shorty.API_HostAddress}}/CYWQ6
GET {{host}}/oQQQQQo
Accept: application/json

###

GET {{Shorty.API_HostAddress}}/health
GET {{host}}/health
Accept: application/json

###
8 changes: 4 additions & 4 deletions backend/src/Shorty.AppHost/Shorty.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

<ItemGroup>
<PackageReference Include="Aspirant.Hosting.Yarp" Version="0.0.4" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.0.2" />
<PackageReference Include="Aspire.Hosting.NodeJs" Version="8.0.2" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="8.0.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.0.2" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.0" />
<PackageReference Include="Aspire.Hosting.NodeJs" Version="8.2.0" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="8.2.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions backend/src/Shorty.AppHost/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"Metadata": {
"ConsecutiveFailuresHealthPolicy.Threshold": "3"
},
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api": {
"Address": "http://shorty-api"
Expand Down
1 change: 1 addition & 0 deletions backend/src/Shorty.AppHost/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"Metadata": {
"ConsecutiveFailuresHealthPolicy.Threshold": "3"
},
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api": {
"Address": "http://shorty-api"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />

<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.6.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.8.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.2.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/Shorty.API.Tests/Shorty.API.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="8.0.2" />
<PackageReference Include="Aspire.Hosting.Testing" Version="8.2.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
0