Skip to content
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
16 changes: 16 additions & 0 deletions TickAPI/TickAPI/Common/Redis/Abstractions/IRedisService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace TickAPI.Common.Redis.Abstractions;

public interface IRedisService
{
public Task<string?> GetStringAsync(string key);
public Task<bool> SetStringAsync(string key, string value, TimeSpan? expiry = null);
public Task<bool> DeleteKeyAsync(string key);
public Task<T?> GetObjectAsync<T>(string key);
public Task<bool> SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null);
public Task<bool> KeyExistsAsync(string key);
public Task<bool> KeyExpireAsync(string key, TimeSpan expiry);
public Task<long> IncrementValueAsync(string key, long value = 1);
public Task<long> DecrementValueAsync(string key, long value = 1);
public Task<long?> GetLongValueAsync(string key);
public Task<bool> SetLongValueAsync(string key, long value, TimeSpan? expiry = null);
}
104 changes: 104 additions & 0 deletions TickAPI/TickAPI/Common/Redis/Services/RedisService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Text.Json;
using StackExchange.Redis;
using TickAPI.Common.Redis.Abstractions;

namespace TickAPI.Common.Redis.Services;

public class RedisService : IRedisService
{
private readonly IDatabase _database;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

public RedisService(IConnectionMultiplexer connectionMultiplexer)
{
_database = connectionMultiplexer.GetDatabase();
}

public async Task<string?> GetStringAsync(string key)
{
return await RetryAsync(async () =>
{
var value = await _database.StringGetAsync(key);
return value.HasValue ? value.ToString() : null;
});
}

public async Task<bool> SetStringAsync(string key, string value, TimeSpan? expiry = null)
{
return await RetryAsync(async () => await _database.StringSetAsync(key, value, expiry));
}

public async Task<bool> DeleteKeyAsync(string key)
{
return await RetryAsync(async () => await _database.KeyDeleteAsync(key));
}

public async Task<T?> GetObjectAsync<T>(string key)
{
var json = await GetStringAsync(key);
if (string.IsNullOrEmpty(json))
{
return default;
}
return JsonSerializer.Deserialize<T>(json, JsonOptions);
}

public async Task<bool> SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null)
{
var json = JsonSerializer.Serialize(value, JsonOptions);

return await SetStringAsync(key, json, expiry);
}

public async Task<bool> KeyExistsAsync(string key)
{
return await RetryAsync(async () => await _database.KeyExistsAsync(key));
}

public async Task<bool> KeyExpireAsync(string key, TimeSpan expiry)
{
return await RetryAsync(async () => await _database.KeyExpireAsync(key, expiry));
}

public async Task<long> IncrementValueAsync(string key, long value = 1)
{
return await RetryAsync(async () => await _database.StringIncrementAsync(key, value));
}

public async Task<long> DecrementValueAsync(string key, long value = 1)
{
return await RetryAsync(async () => await _database.StringDecrementAsync(key, value));
}

public async Task<long?> GetLongValueAsync(string key)
{
var value = await RetryAsync(() => _database.StringGetAsync(key));
if (value.HasValue && long.TryParse(value, out var result))
{
return result;
}
return null;
}

public async Task<bool> SetLongValueAsync(string key, long value, TimeSpan? expiry = null)
{
return await RetryAsync(async () => await _database.StringSetAsync(key, value.ToString(), expiry));
}

private static async Task<T> RetryAsync<T>(Func<Task<T>> action, int retryCount = 3, int millisecondsDelay = 100)
{
var attempt = 0;
while (true)
{
try
{
return await action();
}
catch (Exception ex) when (ex is RedisConnectionException or RedisTimeoutException && attempt < retryCount)
{
attempt++;
await Task.Delay(millisecondsDelay);
}
}
}
}
8 changes: 8 additions & 0 deletions TickAPI/TickAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using StackExchange.Redis;
using TickAPI.Admins.Abstractions;
using TickAPI.Admins.Repositories;
using TickAPI.Admins.Services;
Expand Down Expand Up @@ -35,6 +36,8 @@
using TickAPI.Categories.Services;
using TickAPI.Common.Claims.Abstractions;
using TickAPI.Common.Claims.Services;
using TickAPI.Common.Redis.Abstractions;
using TickAPI.Common.Redis.Services;

// Builder constants
const string allowClientPolicyName = "AllowClient";
Expand All @@ -52,8 +55,8 @@
})
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Authentication:Google:ClientId"];

Check warning on line 58 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];

Check warning on line 59 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
})
.AddJwtBearer(options =>
{
Expand All @@ -65,7 +68,7 @@
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Authentication:Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Authentication:Jwt:SecurityKey"]))

Check warning on line 71 in TickAPI/TickAPI/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 's' in 'byte[] Encoding.GetBytes(string s)'.
};
});

Expand Down Expand Up @@ -116,6 +119,7 @@
builder.Services.AddScoped<IPaginationService, PaginationService>();
builder.Services.AddScoped<IDateTimeService, DateTimeService>();
builder.Services.AddScoped<IClaimsService, ClaimsService>();
builder.Services.AddScoped<IRedisService, RedisService>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
Expand Down Expand Up @@ -154,6 +158,10 @@
errorNumbersToAdd: null));
});

builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("ResellioRedisCache"))
);

// Create CORS policy
builder.Services.AddCors(options =>
{
Expand Down
1 change: 1 addition & 0 deletions TickAPI/TickAPI/TickAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
<PackageReference Include="StackExchange.Redis" Version="2.8.31" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.3.1" />
</ItemGroup>

Expand Down
3 changes: 2 additions & 1 deletion TickAPI/TickAPI/appsettings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"https://another-site.com"
],
"ConnectionStrings": {
"ResellioDatabase": "Server=tcp:localhost,1433;Initial Catalog=resellioDB;Persist Security Info=False;User ID=sa;Password=Rese11io;TrustServerCertificate=True;Encrypt=False"
"ResellioDatabase": "Server=tcp:localhost,1433;Initial Catalog=resellioDB;Persist Security Info=False;User ID=sa;Password=Rese11io;TrustServerCertificate=True;Encrypt=False",
"ResellioRedisCache": "localhost:6379"
},
"Authentication": {
"Google": {
Expand Down
Loading