Skip to content

Commit

Permalink
Merge branch 'main' into check-duplicate
Browse files Browse the repository at this point in the history
  • Loading branch information
thisisnabi authored Aug 2, 2024
2 parents b8ed518 + d911bf8 commit 488a92f
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 35 deletions.
43 changes: 38 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ This is a URL shortener service implemented in ASP.NET Core and MongoDB. It allo

## Features

- Shorten long URLs into unique, short codes
- Shorten long URLs into unique, shortcodes
- Redirect users from short URLs to original long URLs
- MongoDB database for persisting data
- RESTful API for easy integration with other applications
- Metrics

## Technologies Used

Expand All @@ -16,6 +17,8 @@ This is a URL shortener service implemented in ASP.NET Core and MongoDB. It allo
- C#
- Minimal APIs
- InMemory Cache
- Open Telemetry
- Prometheus

## Installation

Expand All @@ -26,11 +29,11 @@ git clone https://github.com/thisisnabi/Shortener.git
```

## Give a Star! ⭐
If you find this project helpful or interesting, please consider giving it a star on GitHub. It helps to support the project and gives recognition to the contributors.
If you find this project helpful or interesting, please consider giving it a star on GitHub. It helps support the project and recognizes the contributors.


## Getting Started
To get started with the URL shortener service, follow the installation instructions provided in the Installation section above. Once the service is up and running, you can begin using the API endpoints to shorten URLs, track statistics, and manage your shortened links.
To start with the URL shortener service, follow the installation instructions in the Installation section above. Once the service is up and running, you can begin using the API endpoints to shorten URLs, track statistics, and manage your shortened links.

### Problem

Expand All @@ -48,11 +51,11 @@ To address the problem of long URLs and make them more manageable for users, a U


### Shortening URLs
Implement a URL shortening algorithm to generate unique, short codes or aliases for long URLs. This algorithm should produce short codes that are both compact and unlikely to collide with existing codes in the system.
Implement a URL shortening algorithm to generate unique, shortcodes or aliases for long URLs. This algorithm should produce compact shortcodes that are unlikely to collide with existing codes in the system.
![image](https://github.com/thisisnabi/Shortener/assets/3371886/9d53ddd5-b68a-4899-9843-3d3b4185de18)

```csharp
public async Task<string> GenerateShortenUrlAsync(string destinationUrl, CancellationToken cancellation)
public async Task<string> GenerateShortenUrlAsync(string destination, CancellationToken cancellation)
{
var shortenCode = GenerateCode(destinationUrl);

Expand Down Expand Up @@ -88,6 +91,36 @@ app.MapGet("/{short_code}", async (
> The service should redirect them seamlessly to the original destination URL without any noticeable delay.

### Metrics
By systematically capturing and analyzing these metrics, marketing teams can gain insights into the effectiveness of their campaigns, identify potential issues, and optimize their strategies to enhance user engagement and achieve better outcomes.

```csharp
public sealed class ShortenDiagnostic
{
public const string MeterName = "ThisIsNabi.Shorten";

public const string RedirectionMetricName = "ThisIsNabi.Shorten.Redirection";
public const string FailedRedirectionMetricName = "ThisIsNabi.Shorten.Redirection.Failed";

private readonly Counter<long> _redirectionCounter;
private readonly Counter<long> _failedRedirectionCounter;

public ShortenDiagnostic(IMeterFactory meterFactory)
{
var meter = meterFactory.Create(MeterName);
_redirectionCounter = meter.CreateCounter<long>(RedirectionMetricName);
_failedRedirectionCounter = meter.CreateCounter<long>(FailedRedirectionMetricName);
}

private const string RedirectionTagName = "Label";
public void AddRedirection(string title)
=> _redirectionCounter.Add(1, new KeyValuePair<string, object?>(RedirectionTagName, title));

public void AddFailedRedirection()
=> _failedRedirectionCounter.Add(1);
}
```



## License
Expand Down
40 changes: 40 additions & 0 deletions src/Shortener/Extensions/WebApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

namespace Shortener.Extensions;

public static class WebApplicationBuilderExtensions
{
public static void ConfigureObservability(this WebApplicationBuilder builder)
{
builder.Services.AddOpenTelemetry()
.ConfigureResource(builder => builder.AddService(serviceName: "ShortenService",
serviceVersion: "v1.0.1"))
.WithMetrics(builder =>
{
builder.AddPrometheusExporter();
var meters = new string[] { ShortenDiagnostic.MeterName };
builder.AddMeter(meters);
});

builder.Services.AddSingleton<ShortenDiagnostic>();
}

public static void ConfigureAppSettings(this WebApplicationBuilder builder)
{
builder.Services.Configure<AppSettings>(builder.Configuration);
}

public static void ConfigureDbContext(this WebApplicationBuilder builder)
{
var settings = builder.Configuration.Get<AppSettings>();
builder.Services.AddDbContext<ShortenerDbContext>(options =>
{
if (settings is null)
throw new ArgumentNullException(nameof(settings));

options.UseMongoDB(settings.MongoDbSetting.Host,
settings.MongoDbSetting.DatabaseName);
});
}
}
4 changes: 3 additions & 1 deletion src/Shortener/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
global using Shortener.Filters;
global using Shortener.Persistence;
global using Shortener.Services;
global using Shortener.Endpoints;
global using Shortener.Endpoints;
global using Shortener.Diagnostics;
global using Shortener.Extensions;
33 changes: 10 additions & 23 deletions src/Shortener/Program.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
using Shortener.Diagnostics;


var builder = WebApplication.CreateBuilder(args);

builder.ConfigureObservability();
builder.ConfigureAppSettings();
builder.ConfigureDbContext();

builder.Configuration.AddEnvironmentVariables();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddMemoryCache();

builder.Services.AddScoped<ShortenUrlService>();
builder.Services.AddSingleton<ShortenDiagnostic>();

var settings = builder.Configuration.Get<AppSettings>();
builder.Services.Configure<AppSettings>(builder.Configuration);

builder.Services.AddDbContext<ShortenerDbContext>(options =>
{
if (settings is null)
{
// TODO: create custom excetion
throw new Exception("Invalid settings!");
}
options.UseMongoDB(settings.MongoDbSetting.Host,
settings.MongoDbSetting.DatabaseName);
});


var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();

app.MapShortenEndpoint();
app.MapRedirectEndpoint();



app.MapPrometheusScrapingEndpoint();

app.Run();


8 changes: 6 additions & 2 deletions src/Shortener/Services/ShortenUrlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,28 @@ public class ShortenUrlService
private readonly ShortenerDbContext _dbContext;
private readonly AppSettings _appSettings;
private readonly IMemoryCache _memoryCache;

private readonly ILogger<ShortenUrlService> _logger;
private readonly ShortenDiagnostic _shortenDiagnostic;

public ShortenUrlService(
ShortenerDbContext dbContext,
IMemoryCache memoryCache,
IOptions<AppSettings> options,
ShortenDiagnostic shortenDiagnostic)
ShortenDiagnostic shortenDiagnostic,
ILogger<ShortenUrlService> logger)
{
_dbContext = dbContext;
_appSettings = options.Value;
_memoryCache = memoryCache;
_shortenDiagnostic = shortenDiagnostic;
_logger = logger;
}

public async Task<string> GenerateShortenUrlAsync(string destinationUrl, CancellationToken cancellation)
{
var shortenCode = await GenerateCode(destinationUrl);
_logger.LogInformation("Retriving request {destinationUrl}", destinationUrl);


var link = new Link
{
Expand Down
18 changes: 16 additions & 2 deletions src/Shortener/Shortener.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -10,15 +10,29 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MassTransit" Version="8.2.3" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="MongoDB.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-beta.12" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.MassTransit" Version="1.0.0-beta.3" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />

<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.7.0-rc.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.8.0-rc.1" />

<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.0" />

</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Shortener/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"MongoDbSetting": {
"Host": "mongodb://thisisnabi:thisisnabi@localhost:27017",
"Host": "mongodb://sa:thisisnabi@localhost:27017",
"DatabaseName": "thisisnabi_shortener"
},
"BaseUrl": "http://localhost:5236"
Expand Down
18 changes: 17 additions & 1 deletion src/Shortener/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Error",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Model.Validation": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Error",
"Microsoft.EntityFrameworkCore.Query": "Error",
"Steeltoe.Management.Endpoint.Health.HealthEndpoint": "Error",
"System": "Warning",
"System.Net.Http.HttpClient": "Error"
}
}
},
"AllowedHosts": "*",
"ShortCodeLength" : 6
"ShortCodeLength": 6
}

0 comments on commit 488a92f

Please sign in to comment.