Анимации, профиль, медиа
This commit is contained in:
+1
-1
@@ -95,7 +95,7 @@ internal sealed class GetSharedMediaQueryHandler : IQueryHandler<GetSharedMediaQ
|
||||
|
||||
if (filterType == "media")
|
||||
{
|
||||
return mediaType == "image" || mediaType == "video";
|
||||
return (mediaType == "image" || mediaType == "video") && !isGif;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Contracts.Settings.Application.Abstractions;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.Klipy.Application.Klipy.Commands;
|
||||
|
||||
public record MarkGifSharedCommand(string GifId, string Query = "") : ICommand;
|
||||
|
||||
internal sealed class MarkGifSharedCommandHandler : ICommandHandler<MarkGifSharedCommand>
|
||||
{
|
||||
private readonly IKlipySettings _settings;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
public MarkGifSharedCommandHandler(IKlipySettings settings, IHttpClientFactory httpClientFactory, IUserContext userContext)
|
||||
{
|
||||
_settings = settings;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_userContext = userContext;
|
||||
}
|
||||
|
||||
public async Task<Result> Handle(MarkGifSharedCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var conf = _settings.Current;
|
||||
if (!conf.Enabled || string.IsNullOrEmpty(conf.ApiKey))
|
||||
return Result.Failure(new Error(Errors.KlipyNotConfigured, "Klipy is not configured"));
|
||||
|
||||
var customerId = _userContext.IsAuthenticated ? _userContext.UserId.ToString().ToLowerInvariant() : Knot.Shared.Kernel.Constants.Klipy.DefaultCustomerId;
|
||||
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
|
||||
// Correct URL according to docs: POST https://api.klipy.com/api/v1/{app_key}/gifs/share/{gif_id}
|
||||
var baseUrl = "https://api.klipy.com/api/v1";
|
||||
var url = $"{baseUrl}/{conf.ApiKey}/gifs/share/{request.GifId}";
|
||||
|
||||
// According to docs, q and customer_id must be in the JSON body
|
||||
var body = new
|
||||
{
|
||||
customer_id = customerId,
|
||||
q = request.Query ?? "" // Must be empty string if not from search
|
||||
};
|
||||
|
||||
using var content = JsonContent.Create(body);
|
||||
var response = await client.PostAsync(url, content, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
var urlCo = url.Replace("api.klipy.com", "api.klipy.co");
|
||||
using var contentCo = JsonContent.Create(body);
|
||||
response = await client.PostAsync(urlCo, contentCo, cancellationToken);
|
||||
}
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
return Result.Failure(new Error(Errors.KlipyApiError, $"Klipy Share API Error: {response.StatusCode} {errorBody}"));
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Contracts.Settings.Application.Abstractions;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.Klipy.Application.Klipy.Queries;
|
||||
|
||||
public record GetGifCategoriesQuery(string CountryCode = "RU") : IQuery<JsonElement?>;
|
||||
|
||||
internal sealed class GetGifCategoriesQueryHandler : IQueryHandler<GetGifCategoriesQuery, JsonElement?>
|
||||
{
|
||||
private readonly IKlipySettings _settings;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
public GetGifCategoriesQueryHandler(IKlipySettings settings, IMemoryCache cache, IHttpClientFactory httpClientFactory, IUserContext userContext)
|
||||
{
|
||||
_settings = settings;
|
||||
_cache = cache;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_userContext = userContext;
|
||||
}
|
||||
|
||||
public async Task<Result<JsonElement?>> Handle(GetGifCategoriesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var conf = _settings.Current;
|
||||
if (!conf.Enabled || string.IsNullOrEmpty(conf.ApiKey))
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.KlipyNotConfigured, "Klipy is not configured"));
|
||||
|
||||
var locale = string.IsNullOrEmpty(request.CountryCode) ? "ru_RU" : (request.CountryCode.Length == 2 ? $"{request.CountryCode.ToLowerInvariant()}_{request.CountryCode.ToUpperInvariant()}" : request.CountryCode);
|
||||
var customerId = _userContext.IsAuthenticated ? _userContext.UserId.ToString().ToLowerInvariant() : Knot.Shared.Kernel.Constants.Klipy.DefaultCustomerId;
|
||||
|
||||
var cacheKeyCat = $"klipy_categories_{conf.ApiKey}_{locale}";
|
||||
if (_cache.TryGetValue(cacheKeyCat, out JsonElement cachedResult))
|
||||
return Result.Success<JsonElement?>(cachedResult);
|
||||
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
|
||||
// Correct URL according to docs: GET https://api.klipy.com/api/v1/{app_key}/gifs/categories?locale={locale}&customer_id={customer_id}
|
||||
var baseUrl = "https://api.klipy.com/api/v1";
|
||||
var url = $"{baseUrl}/{conf.ApiKey}/gifs/categories?locale={locale}&customer_id={customerId}";
|
||||
|
||||
var response = await client.GetAsync(url, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
var urlCo = url.Replace("api.klipy.com", "api.klipy.co");
|
||||
response = await client.GetAsync(urlCo, cancellationToken);
|
||||
}
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.KlipyApiError, $"Klipy Categories API Error: {response.StatusCode} {errorBody}"));
|
||||
}
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: cancellationToken);
|
||||
_cache.Set(cacheKeyCat, result, TimeSpan.FromMinutes(Knot.Shared.Kernel.Constants.Klipy.CategoriesCacheMinutes));
|
||||
|
||||
return Result.Success<JsonElement?>(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Contracts.Settings.Application.Abstractions;
|
||||
using Knot.Shared.Kernel.Constants;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
|
||||
namespace Knot.Modules.Klipy.Application.Klipy.Queries;
|
||||
|
||||
public record GetRecentGifsQuery(int Page = 1) : IQuery<JsonElement?>;
|
||||
|
||||
internal sealed class GetRecentGifsQueryHandler : IQueryHandler<GetRecentGifsQuery, JsonElement?>
|
||||
{
|
||||
private readonly IKlipySettings _settings;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
public GetRecentGifsQueryHandler(IKlipySettings settings, IMemoryCache cache, IHttpClientFactory httpClientFactory, IUserContext userContext)
|
||||
{
|
||||
_settings = settings;
|
||||
_cache = cache;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_userContext = userContext;
|
||||
}
|
||||
|
||||
public async Task<Result<JsonElement?>> Handle(GetRecentGifsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var conf = _settings.Current;
|
||||
if (!conf.Enabled || string.IsNullOrEmpty(conf.ApiKey))
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.KlipyNotConfigured, "Klipy is not configured"));
|
||||
|
||||
if (!_userContext.IsAuthenticated)
|
||||
return Result.Failure<JsonElement?>(new Error("AuthError", "User not authenticated"));
|
||||
|
||||
var customerId = _userContext.UserId.ToString().ToLowerInvariant();
|
||||
|
||||
// Disable cache for history to ensure it updates immediately
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
|
||||
// Correct URL according to docs: GET https://api.klipy.com/api/v1/{app_key}/gifs/recent/{customer_id}?page={page}&per_page={per_page}
|
||||
var baseUrl = "https://api.klipy.com/api/v1";
|
||||
var url = $"{baseUrl}/{conf.ApiKey}/gifs/recent/{customerId}?page={request.Page}&per_page=24";
|
||||
|
||||
var response = await client.GetAsync(url, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
var urlCo = url.Replace("api.klipy.com", "api.klipy.co");
|
||||
response = await client.GetAsync(urlCo, cancellationToken);
|
||||
}
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.KlipyApiError, $"Klipy Recent API Error: {response.StatusCode} {errorBody}"));
|
||||
}
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: cancellationToken);
|
||||
|
||||
return Result.Success<JsonElement?>(result);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ using MediatR;
|
||||
|
||||
namespace Knot.Modules.Klipy.Application.Klipy.Queries;
|
||||
|
||||
public record GetTrendingGifsQuery : IQuery<JsonElement?>;
|
||||
public record GetTrendingGifsQuery(int Page = 1) : IQuery<JsonElement?>;
|
||||
|
||||
internal sealed class GetTrendingGifsQueryHandler : IQueryHandler<GetTrendingGifsQuery, JsonElement?>
|
||||
{
|
||||
@@ -34,21 +34,21 @@ internal sealed class GetTrendingGifsQueryHandler : IQueryHandler<GetTrendingGif
|
||||
if (!conf.Enabled || string.IsNullOrEmpty(conf.ApiKey))
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.KlipyNotConfigured, "Klipy is not configured"));
|
||||
|
||||
var cacheKeyTrending = $"klipy_trending_{conf.ApiKey}";
|
||||
var cacheKeyTrending = $"klipy_trending_{conf.ApiKey}_{request.Page}";
|
||||
if (_cache.TryGetValue(cacheKeyTrending, out JsonElement cachedResult))
|
||||
return Result.Success<JsonElement?>(cachedResult);
|
||||
|
||||
var customerId = Knot.Shared.Kernel.Constants.Klipy.DefaultCustomerId;
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
|
||||
var urlCo = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCo, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceTrending, "", customerId);
|
||||
var urlCo = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCo, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceTrending, request.Page, 30, "", customerId);
|
||||
|
||||
var response = await client.GetAsync(urlCo, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
var urlCom = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCom, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceTrending, "", customerId);
|
||||
var urlCom = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCom, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceTrending, request.Page, 30, "", customerId);
|
||||
response = await client.GetAsync(urlCom, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using MediatR;
|
||||
|
||||
namespace Knot.Modules.Klipy.Application.Klipy.Queries;
|
||||
|
||||
public record SearchGifsQuery(string Query) : IQuery<JsonElement?>;
|
||||
public record SearchGifsQuery(string Query, int Page = 1) : IQuery<JsonElement?>;
|
||||
|
||||
internal sealed class SearchGifsQueryHandler : IQueryHandler<SearchGifsQuery, JsonElement?>
|
||||
{
|
||||
@@ -37,7 +37,7 @@ internal sealed class SearchGifsQueryHandler : IQueryHandler<SearchGifsQuery, Js
|
||||
if (string.IsNullOrWhiteSpace(request.Query))
|
||||
return Result.Failure<JsonElement?>(new Error(Errors.InvalidQuery, "Invalid query parameter"));
|
||||
|
||||
var cacheKey = $"klipy_search_{conf.ApiKey}_{request.Query.ToLowerInvariant()}";
|
||||
var cacheKey = $"klipy_search_{conf.ApiKey}_{request.Query.ToLowerInvariant()}_{request.Page}";
|
||||
if (_cache.TryGetValue(cacheKey, out JsonElement cachedResult))
|
||||
return Result.Success<JsonElement?>(cachedResult);
|
||||
|
||||
@@ -45,14 +45,14 @@ internal sealed class SearchGifsQueryHandler : IQueryHandler<SearchGifsQuery, Js
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
|
||||
var queryParam = $"q={Uri.EscapeDataString(request.Query)}";
|
||||
var urlCo = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCo, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceSearch, queryParam, customerId);
|
||||
var urlCo = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCo, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceSearch, request.Page, 30, queryParam, customerId);
|
||||
|
||||
var response = await client.GetAsync(urlCo, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
var urlCom = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCom, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceSearch, queryParam, customerId);
|
||||
var urlCom = string.Format(Knot.Shared.Kernel.Constants.Klipy.ApiUrlCom, conf.ApiKey, Knot.Shared.Kernel.Constants.Klipy.ResourceSearch, request.Page, 30, queryParam, customerId);
|
||||
response = await client.GetAsync(urlCom, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,34 @@ public static class KlipyEndpoints
|
||||
{
|
||||
var group = app.MapGroup(Routes.ApiKlipy).RequireAuthorization();
|
||||
|
||||
group.MapGet("/trending", async (ISender sender, CancellationToken ct) =>
|
||||
group.MapGet("/trending", async ([FromQuery] int page, ISender sender, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.GetTrendingGifsQuery(), ct);
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.GetTrendingGifsQuery(page > 0 ? page : 1), ct);
|
||||
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
|
||||
});
|
||||
|
||||
group.MapGet("/search", async ([FromQuery] string q, ISender sender, CancellationToken ct) =>
|
||||
group.MapGet("/search", async ([FromQuery] string q, [FromQuery] int page, ISender sender, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.SearchGifsQuery(q), ct);
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.SearchGifsQuery(q, page > 0 ? page : 1), ct);
|
||||
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
|
||||
});
|
||||
|
||||
group.MapGet("/recent", async ([FromQuery] int page, ISender sender, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.GetRecentGifsQuery(page > 0 ? page : 1), ct);
|
||||
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
|
||||
});
|
||||
|
||||
group.MapGet("/categories", async ([FromQuery] string? countryCode, ISender sender, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Queries.GetGifCategoriesQuery(countryCode ?? "RU"), ct);
|
||||
return result.IsSuccess ? Results.Ok(result.Value) : Results.BadRequest(new { error = result.Error.Description });
|
||||
});
|
||||
|
||||
group.MapPost("/{gifId}/share", async (string gifId, [FromQuery] string? q, ISender sender, CancellationToken ct) =>
|
||||
{
|
||||
var result = await sender.Send(new Knot.Modules.Klipy.Application.Klipy.Commands.MarkGifSharedCommand(gifId, q ?? ""), ct);
|
||||
return result.IsSuccess ? Results.Ok() : Results.BadRequest(new { error = result.Error.Description });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,27 @@ namespace Knot.Shared.Kernel.Constants;
|
||||
|
||||
public static class Klipy
|
||||
{
|
||||
public const string ApiUrlCo = "https://api.klipy.co/api/v1/{0}/{1}?page=1&per_page=30&{2}&customer_id={3}";
|
||||
public const string ApiUrlCom = "https://api.klipy.com/api/v1/{0}/{1}?page=1&per_page=30&{2}&customer_id={3}";
|
||||
|
||||
public const string ResourceTrending = "gifs/trending";
|
||||
public const string ResourceSearch = "gifs/search";
|
||||
public const string ResourceRecent = "gifs/recent";
|
||||
public const string ResourceCategories = "gifs/categories";
|
||||
public const string ResourceShare = "gifs/{0}/share";
|
||||
|
||||
public const string ApiUrlCo = "https://api.klipy.co/api/v1/{0}/{1}?page={2}&per_page={3}&{4}&customer_id={5}";
|
||||
public const string ApiUrlCom = "https://api.klipy.com/api/v1/{0}/{1}?page={2}&per_page={3}&{4}&customer_id={5}";
|
||||
|
||||
// Categories use country_code
|
||||
public const string ApiUrlCategoriesCo = "https://api.klipy.co/api/v1/{0}/{1}?country_code={2}&customer_id={3}";
|
||||
public const string ApiUrlCategoriesCom = "https://api.klipy.com/api/v1/{0}/{1}?country_code={2}&customer_id={3}";
|
||||
|
||||
// Share is POST
|
||||
public const string ApiUrlShareCo = "https://api.klipy.co/api/v1/{0}/gifs/{1}/share?q={2}&customer_id={3}";
|
||||
public const string ApiUrlShareCom = "https://api.klipy.com/api/v1/{0}/gifs/{1}/share?q={2}&customer_id={3}";
|
||||
|
||||
public const int TrendingCacheMinutes = 60;
|
||||
public const int SearchCacheMinutes = 15;
|
||||
public const int CategoriesCacheMinutes = 1440; // 24 hours
|
||||
public const int RecentCacheMinutes = 5; // Frequent changes
|
||||
|
||||
public const string DefaultCustomerId = "anonymous";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user