Skip to content

Commit

Permalink
Move Redis interaction and validation into data project
Browse files Browse the repository at this point in the history
  • Loading branch information
Swimburger committed Sep 23, 2022
1 parent b72fdd9 commit 81e07b4
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 164 deletions.
64 changes: 20 additions & 44 deletions UrlShortener.BackOffice/Program.cs
Original file line number Diff line number Diff line change
@@ -1,67 +1,43 @@
using System.Net;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using StackExchange.Redis;
using UrlShortener.Data;

var builder = WebApplication.CreateBuilder(args);
var forwarderBaseUrl = builder.Configuration["UrlShortener:ForwarderBaseUrl"]
?? throw new Exception("Missing 'UrlShortener:ForwarderBaseUrl' configuration");

{
var connectionString = builder.Configuration.GetConnectionString("UrlsDb")
?? throw new Exception("Missing 'UrlsDb' connection string");
var redisDb = await ConnectionMultiplexer.ConnectAsync(connectionString);
builder.Services.AddSingleton<ConnectionMultiplexer>(redisDb);
builder.Services.AddTransient(provider =>
provider.GetRequiredService<ConnectionMultiplexer>().GetDatabase()
);
}
var connectionString = builder.Configuration.GetConnectionString("UrlsDb")
?? throw new Exception("Missing 'UrlsDb' connection string");
var redisConnection = await ConnectionMultiplexer.ConnectAsync(connectionString);
builder.Services.AddSingleton(redisConnection);
builder.Services.AddTransient<ShortUrlRepository>();

var app = builder.Build();

app.UseDefaultFiles()
.UseStaticFiles();

var pathRegex = new Regex(
"^[a-zA-Z0-9_]*$",
RegexOptions.None,
TimeSpan.FromMilliseconds(1)
);

app.MapPost("/api/urls", async (
HttpRequest request,
IDatabase redisDb
ShortUrlRepository shortUrlRepository
) =>
{
var jsonObject = await request.ReadFromJsonAsync<JsonObject>();
var path = jsonObject?["path"]?.GetValue<string?>()?.Trim('/');
var destination = jsonObject?["destination"]?.GetValue<string?>();

if (string.IsNullOrEmpty(path))
return Results.Problem("Path cannot be empty.");

if (path.Length > 10)
return Results.Problem("Path cannot be longer than 10 characters.");

if (pathRegex.IsMatch(path) == false)
return Results.Problem("Path can only contain alphanumeric characters and underscores.");

if (string.IsNullOrEmpty(destination))
return Results.Problem("Destination cannot be empty.");

if (!Uri.IsWellFormedUriString(destination, UriKind.Absolute))
return Results.Problem("Destination has to be a valid absolute URL.");

if (await redisDb.KeyExistsAsync(path))
return Results.Problem("Path is already in use.");

var urlWasSet = await redisDb.StringSetAsync(path, destination);
if (!urlWasSet)
return Results.Problem(
"Failed to create shortened URL.",
statusCode: (int)HttpStatusCode.InternalServerError
);

var shortUrl = new ShortUrl(destination, path);
if (shortUrl.Validate(out var validationResults) == false)
{
return Results.ValidationProblem(validationResults);
}

if (await shortUrlRepository.Exists(path))
{
return Results.Problem($"Path is already in use.");
}

await shortUrlRepository.Create(shortUrl);

return Results.Created(
uri: new Uri($"{request.Scheme}://{request.Host}{request.PathBase}/api/urls/{path}"),
value: new
Expand Down
15 changes: 0 additions & 15 deletions UrlShortener.BackOffice/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:24346",
"sslPort": 44304
}
},
"profiles": {
"http": {
"commandName": "Project",
Expand All @@ -25,13 +17,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
4 changes: 4 additions & 0 deletions UrlShortener.BackOffice/UrlShortener.BackOffice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.6.66" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\UrlShortener.Data\UrlShortener.Data.csproj" />
</ItemGroup>
</Project>
125 changes: 45 additions & 80 deletions UrlShortener.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.CommandLine;
using System.Text.RegularExpressions;
using StackExchange.Redis;
using UrlShortener.Data;

#region Options

Expand All @@ -12,9 +12,9 @@
destinationOption.AddValidator(result =>
{
var destination = result.Tokens[0].Value;
if (!Uri.IsWellFormedUriString(destination, UriKind.Absolute))
if (ShortUrlValidator.ValidateDestination(destination, out var validationResults) == false)
{
result.ErrorMessage = "Destination has to be a valid absolute URL.";
result.ErrorMessage = string.Join(", ", validationResults);
}
});

Expand All @@ -26,17 +26,9 @@
pathOption.AddValidator(result =>
{
var path = result.Tokens[0].Value;

if (path.Length > 10)
{
result.ErrorMessage = "Path cannot be longer than 10 characters.";
return;
}

var pathRegex = new Regex("^[a-zA-Z0-9_]*$");
if (!pathRegex.IsMatch(path))
if (ShortUrlValidator.ValidatePath(path, out var validationResults) == false)
{
result.ErrorMessage = "Path can only contain alphanumeric characters and underscores.";
result.ErrorMessage = string.Join(", ", validationResults);
}
});

Expand All @@ -55,6 +47,16 @@

var rootCommand = new RootCommand("Manage the shortened URLs.");

async Task<ConnectionMultiplexer> GetRedisConnection(string? connectionString)
{
var redisConnection = await ConnectionMultiplexer.ConnectAsync(
connectionString ??
envConnectionString ??
throw new Exception("Missing connection string")
);
return redisConnection;
}

#region Create Command

var createCommand = new Command("create", "Create a shortened URL")
Expand All @@ -66,27 +68,15 @@

createCommand.SetHandler(async (destination, path, connectionString) =>
{
var redisConnection = await ConnectionMultiplexer.ConnectAsync(
connectionString ??
envConnectionString ??
throw new Exception("Missing connection string")
);
var redisDb = redisConnection.GetDatabase();

if (await redisDb.KeyExistsAsync(path))
var shortUrlRepository = new ShortUrlRepository(await GetRedisConnection(connectionString));
try
{
Console.Error.WriteLine($"Path {path} is already in use.");
return;
await shortUrlRepository.Create(new ShortUrl(destination, path));
Console.WriteLine($"Shortened URL created.");
}

var urlWasSet = await redisDb.StringSetAsync(path, destination);
if (urlWasSet)
{
Console.WriteLine("Created shortened URL.");
}
else
catch (Exception e)
{
Console.Error.WriteLine("Failed to create shortened URL.");
Console.Error.WriteLine(e.Message);
}
}, destinationOption, pathOption, connectionStringOption);

Expand All @@ -104,27 +94,15 @@

deleteCommand.SetHandler(async (path, connectionString) =>
{
var redisConnection = await ConnectionMultiplexer.ConnectAsync(
connectionString ??
envConnectionString ??
throw new Exception("Missing connection string")
);
var redisDb = redisConnection.GetDatabase();

if (await redisDb.KeyExistsAsync(path) == false)
{
Console.WriteLine($"Path does not exist.");
return;
}

var urlWasDeleted = await redisDb.KeyDeleteAsync(path);
if (urlWasDeleted)
var shortUrlRepository = new ShortUrlRepository(await GetRedisConnection(connectionString));
try
{
Console.WriteLine("Deleted shortened URL.");
await shortUrlRepository.Delete(path);
Console.WriteLine($"Shortened URL deleted.");
}
else
catch (Exception e)
{
Console.Error.WriteLine("Failed to delete shortened URL.");
Console.Error.WriteLine(e.Message);
}
}, pathOption, connectionStringOption);

Expand All @@ -142,21 +120,19 @@

getCommand.SetHandler(async (path, connectionString) =>
{
var redisConnection = await ConnectionMultiplexer.ConnectAsync(
connectionString ??
envConnectionString ??
throw new Exception("Missing connection string")
);
var redisDb = redisConnection.GetDatabase();

if (await redisDb.KeyExistsAsync(path) == false)
var shortUrlRepository = new ShortUrlRepository(await GetRedisConnection(connectionString));
try
{
Console.Error.WriteLine($"Path does not exist.");
return;
var shortUrl = await shortUrlRepository.GetByPath(path);
if (shortUrl == null)
Console.Error.WriteLine($"Shortened URL for path '{path}' not found.");
else
Console.WriteLine($"Destination URL: {shortUrl.Destination}, Path: {path}");
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
}

var redisValue = await redisDb.StringGetAsync(path);
Console.WriteLine($"Destination URL: {redisValue.ToString()}, Path: {path}");
}, pathOption, connectionStringOption);

rootCommand.AddCommand(getCommand);
Expand All @@ -172,29 +148,18 @@

listCommand.SetHandler(async (connectionString) =>
{
var redisConnection = await ConnectionMultiplexer.ConnectAsync(
connectionString ??
envConnectionString ??
throw new Exception("Missing connection string")
);
var redisServers = redisConnection.GetServers();
var keys = new List<string>();
foreach (var redisServer in redisServers)
var shortUrlRepository = new ShortUrlRepository(await GetRedisConnection(connectionString));
try
{
await foreach (var redisKey in redisServer.KeysAsync())
var shortUrls = await shortUrlRepository.GetAll();
foreach (var shortUrl in shortUrls)
{
var key = redisKey.ToString();
if (keys.Contains(key)) continue;
keys.Add(key);
Console.WriteLine($"Destination URL: {shortUrl.Destination}, Path: {shortUrl.Path}");
}
}

var redisDb = redisConnection.GetDatabase();

foreach (var key in keys)
catch (Exception e)
{
var redisValue = redisDb.StringGet(key);
Console.WriteLine($"Destination URL: {redisValue.ToString()}, Path: {key}");
Console.Error.WriteLine(e.Message);
}
}, connectionStringOption);

Expand Down
4 changes: 4 additions & 0 deletions UrlShortener.Cli/UrlShortener.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\UrlShortener.Data\UrlShortener.Data.csproj" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions UrlShortener.Data/ShortUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace UrlShortener.Data;

public record ShortUrl(string? Destination, string? Path);
Loading

0 comments on commit 81e07b4

Please sign in to comment.