Skip to content

Commit

Permalink
add : level enum 정의
Browse files Browse the repository at this point in the history
add : 필요 deserialize 모델 정의

refactoring : user data 가져오는 로직 분리
  • Loading branch information
chanos-dev committed Jun 8, 2023
1 parent db7dbb8 commit 303e598
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 81 deletions.
9 changes: 8 additions & 1 deletion src/dotnetdev-badge/dotnetdev-badge.web/Common/Defines.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
namespace DotNetDevBadgeWeb.Common
{
internal enum ETheme
public enum ELevel
{
Bronze,
Silver,
Gold,
}

public enum ETheme
{
Light,
Dark,
Expand Down
9 changes: 4 additions & 5 deletions src/dotnetdev-badge/dotnetdev-badge.web/Common/Palette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ static Palette()

public static ColorSet GetColorSet(ETheme theme) => _colorSets[theme];

public static string GetTrustColor(string trust) => trust switch
public static string GetTrustColor(ELevel level) => level switch
{
"1" => "CD7F32",
"2" => "CD7F32",
"3" => "C0C0C0",
"4" => "E7C300",
ELevel.Bronze => "CD7F32",
ELevel.Silver => "C0C0C0",
ELevel.Gold => "E7C300",
_ => "CD7F32",
};
}
Expand Down
84 changes: 13 additions & 71 deletions src/dotnetdev-badge/dotnetdev-badge.web/Core/BadgeCreatorV1.cs
Original file line number Diff line number Diff line change
@@ -1,52 +1,24 @@
using DotNetDevBadgeWeb.Common;
using Newtonsoft.Json.Linq;
using DotNetDevBadgeWeb.Interfaces;
using DotNetDevBadgeWeb.Model;

namespace DotNetDevBadgeWeb.Core
{
internal class BadgeCreatorV1 : IBadgeV1
{
private const int AVATAR_SIZE = 128;
private const string UNKOWN_IMG_PATH = "Assets/unknown.png";
private const string BASE_URL = "https://forum.dotnetdev.kr";
private const string BADGE_URL = "https://forum.dotnetdev.kr/user-badges/{0}.json?grouped=true";
private const string SUMMARY_URL = "https://forum.dotnetdev.kr/u/{0}/summary.json";
{
private readonly IProvider _forumProvider;

private IHttpClientFactory _httpClientFactory;

public BadgeCreatorV1(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

private async Task<string> GetResponseStringAsync(Uri uri, CancellationToken token)
{
using HttpClient client = _httpClientFactory.CreateClient();

using HttpResponseMessage response = await client.GetAsync(uri, token);

return await response.Content.ReadAsStringAsync(token);
}

private async Task<byte[]> GetResponseBytesAsync(Uri uri, CancellationToken token)
public BadgeCreatorV1(IProvider forumProvider)
{
using HttpClient client = _httpClientFactory.CreateClient();

using HttpResponseMessage response = await client.GetAsync(uri, token);

return await response.Content.ReadAsByteArrayAsync(token);
_forumProvider = forumProvider;
}

public async Task<string> GetSmallBadge(string id, ETheme theme, CancellationToken token)
{
Uri uri = new(string.Format(SUMMARY_URL, id));
string data = await GetResponseStringAsync(uri, token);
JObject json = JObject.Parse(data);

string heart = json["user_summary"]?["likes_received"]?.ToString() ?? "0";
string trustLv = json["users"]?.Where(t => t["id"]?.ToString() != "-1").FirstOrDefault()?["trust_level"]?.ToString() ?? string.Empty;
(UserSummary summary, User user) = await _forumProvider.GetUserInfoAsync(id, token);

ColorSet colorSet = Palette.GetColorSet(theme);
string trustColor = Palette.GetTrustColor(trustLv);
string trustColor = Palette.GetTrustColor(user.Level);

string svg = $@"
<svg width=""110"" height=""20"" viewBox=""0 0 110 20"" fill=""none"" xmlns=""http://www.w3.org/2000/svg""
Expand All @@ -72,7 +44,7 @@ public async Task<string> GetSmallBadge(string id, ETheme theme, CancellationTok
d=""M37.0711 4.79317C37.5874 4.7052 38.1168 4.73422 38.6204 4.87808C39.124 5.02195 39.5888 5.27699 39.9807 5.62442L40.0023 5.64367L40.0222 5.62617C40.3962 5.29792 40.8359 5.05321 41.312 4.90836C41.7881 4.76352 42.2896 4.72186 42.7831 4.78617L42.9266 4.80717C43.5486 4.91456 44.13 5.18817 44.6092 5.59902C45.0884 6.00987 45.4476 6.54267 45.6487 7.14099C45.8498 7.73931 45.8853 8.38088 45.7516 8.99776C45.6178 9.61464 45.3198 10.1839 44.8889 10.6452L44.7839 10.7531L44.7559 10.777L40.4101 15.0814C40.3098 15.1807 40.1769 15.2402 40.0361 15.249C39.8953 15.2578 39.756 15.2153 39.6442 15.1292L39.5893 15.0814L35.2184 10.7519C34.7554 10.3014 34.4261 9.73148 34.267 9.10532C34.1079 8.47917 34.1252 7.82119 34.317 7.20427C34.5088 6.58734 34.8676 6.03555 35.3537 5.60999C35.8398 5.18443 36.4342 4.90172 37.0711 4.79317Z""
fill=""#FA6C8D"" />
</g>
<text class=""text"" x=""49"" y=""14.5"" >{heart}</text>
<text class=""text"" x=""49"" y=""14.5"" >{summary.LikesReceived}</text>
<rect x=""88"" y=""1"" width=""18"" height=""18"" fill=""url(#pattern0)"" />
<defs>
<pattern id=""pattern0"" patternContentUnits=""objectBoundingBox"" width=""1"" height=""1"">
Expand All @@ -88,41 +60,11 @@ public async Task<string> GetSmallBadge(string id, ETheme theme, CancellationTok

public async Task<string> GetMediumBadge(string id, ETheme theme, CancellationToken token)
{
Uri badgeUri = new(string.Format(BADGE_URL, id));
string badgeData = await GetResponseStringAsync(badgeUri, token);
JObject badgeJson = JObject.Parse(badgeData);

var badges = badgeJson["badges"]?.GroupBy(badge => badge["badge_type_id"]?.ToString() ?? string.Empty).Select(g => new
{
Type = g.Key,
Count = g.Count(),
}).ToDictionary(kv => kv.Type, kv => kv.Count);

int gold = default;
int silver = default;
int bronze = default;

if (badges is not null)
{
gold = badges.ContainsKey("1") ? badges["1"] : 0;
silver = badges.ContainsKey("2") ? badges["2"] : 0;
bronze = badges.ContainsKey("3") ? badges["3"] : 0;
}

Uri summaryUri = new(string.Format(SUMMARY_URL, id));
string summaryData = await GetResponseStringAsync(summaryUri, token);
JObject summaryJson = JObject.Parse(summaryData);

string avatarEndpoint = summaryJson["users"]?.Where(t => t["id"]?.ToString() != "-1").FirstOrDefault()?["avatar_template"]?.ToString() ?? string.Empty;
Uri avatarUri = new(string.Concat(BASE_URL, avatarEndpoint.Replace("{size}", AVATAR_SIZE.ToString())));

byte[] avatar = string.IsNullOrEmpty(avatarEndpoint) ? await File.ReadAllBytesAsync(UNKOWN_IMG_PATH, token) : await GetResponseBytesAsync(avatarUri, token);

string heart = summaryJson["user_summary"]?["likes_received"]?.ToString() ?? "0";
string trustLv = summaryJson["users"]?.Where(t => t["id"]?.ToString() != "-1").FirstOrDefault()?["trust_level"]?.ToString() ?? string.Empty;
(byte[] avatar, UserSummary summary, User user) = await _forumProvider.GetUserInfoWithAvatarAsync(id, token);
(int gold, int silver, int bronze) = await _forumProvider.GetBadgeCountAsync(id, token);

ColorSet colorSet = Palette.GetColorSet(theme);
string trustColor = Palette.GetTrustColor(trustLv);
string trustColor = Palette.GetTrustColor(user.Level);

string svg = $@"
<svg width=""193"" height=""60"" viewBox=""0 0 193 60"" fill=""none"" xmlns=""http://www.w3.org/2000/svg"" xmlns:xlink=""http://www.w3.org/1999/xlink"">
Expand Down Expand Up @@ -156,7 +98,7 @@ @keyframes fadein {{
</g>
<g id=""heart_group"" class=""anime"" style=""animation-delay: 400ms"">
<text id=""heart_text"" class=""text"" x=""75.5"" y=""33.5"">{heart}</text>
<text id=""heart_text"" class=""text"" x=""75.5"" y=""33.5"">{summary.LikesReceived}</text>
<path id=""heart_shape""
d=""M64.4895 25.537C64.932 25.4616 65.3858 25.4865 65.8175 25.6098C66.2491 25.7331 66.6476 25.9517 66.9835 26.2495L67.002 26.266L67.019 26.251C67.3396 25.9697 67.7165 25.7599 68.1246 25.6357C68.5327 25.5116 68.9625 25.4759 69.3855 25.531L69.5085 25.549C70.0417 25.6411 70.54 25.8756 70.9507 26.2277C71.3615 26.5799 71.6693 27.0366 71.8417 27.5494C72.0141 28.0623 72.0446 28.6122 71.93 29.1409C71.8153 29.6697 71.5598 30.1576 71.1905 30.553L71.1005 30.6455L71.0765 30.666L67.3515 34.3555C67.2655 34.4406 67.1517 34.4916 67.0309 34.4992C66.9102 34.5067 66.7909 34.4702 66.695 34.3965L66.648 34.3555L62.9015 30.6445C62.5046 30.2583 62.2224 29.7698 62.086 29.2331C61.9496 28.6964 61.9645 28.1325 62.1289 27.6037C62.2933 27.0749 62.6008 26.6019 63.0175 26.2371C63.4341 25.8724 63.9436 25.6301 64.4895 25.537Z""
fill=""#FA6C8D"" />
Expand Down
92 changes: 92 additions & 0 deletions src/dotnetdev-badge/dotnetdev-badge.web/Core/ForumDataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using DotNetDevBadgeWeb.Interfaces;
using DotNetDevBadgeWeb.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DotNetDevBadgeWeb.Core
{
internal class ForumDataProvider : IProvider
{
private const string UNKOWN_IMG_PATH = "Assets/unknown.png";

private const string BASE_URL = "https://forum.dotnetdev.kr";
private const string BADGE_URL = "https://forum.dotnetdev.kr/user-badges/{0}.json?grouped=true";
private const string SUMMARY_URL = "https://forum.dotnetdev.kr/u/{0}/summary.json";

private readonly IHttpClientFactory _httpClientFactory;

public ForumDataProvider(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

private async Task<string> GetResponseStringAsync(Uri uri, CancellationToken token)
{
using HttpClient client = _httpClientFactory.CreateClient();

using HttpResponseMessage response = await client.GetAsync(uri, token);

return await response.Content.ReadAsStringAsync(token);
}

private async Task<byte[]> GetResponseBytesAsync(Uri uri, CancellationToken token)
{
using HttpClient client = _httpClientFactory.CreateClient();

using HttpResponseMessage response = await client.GetAsync(uri, token);

return await response.Content.ReadAsByteArrayAsync(token);
}

public async Task<(UserSummary summary, User user)> GetUserInfoAsync(string id, CancellationToken token)
{
Uri summaryUri = new(string.Format(SUMMARY_URL, id));
string summaryData = await GetResponseStringAsync(summaryUri, token);
JObject summaryJson = JObject.Parse(summaryData);

UserSummary userSummary = JsonConvert.DeserializeObject<UserSummary>(summaryJson["user_summary"]?.ToString() ?? string.Empty) ?? new();
List<User>? users = JsonConvert.DeserializeObject<List<User>>(summaryJson["users"]?.ToString() ?? string.Empty);

User user = users?.Where(user => user.Id != -1).FirstOrDefault() ?? new();

return (userSummary, user);
}

public async Task<(byte[] avatar, UserSummary summary, User user)> GetUserInfoWithAvatarAsync(string id, CancellationToken token)
{
(UserSummary summary, User user) = await GetUserInfoAsync(id, token);

if (string.IsNullOrEmpty(user.AvatarEndPoint))
return (await File.ReadAllBytesAsync(UNKOWN_IMG_PATH, token), summary, user);

Uri avatarUri = new(string.Concat(BASE_URL, user.AvatarEndPoint));
return (await GetResponseBytesAsync(avatarUri, token), summary, user);
}

public async Task<(int gold, int silver, int bronze)> GetBadgeCountAsync(string id, CancellationToken token)
{
Uri badgeUri = new(string.Format(BADGE_URL, id));
string badgeData = await GetResponseStringAsync(badgeUri, token);
JObject badgeJson = JObject.Parse(badgeData);

var badges = badgeJson["badges"]?.GroupBy(badge => badge["badge_type_id"]?.ToString() ?? string.Empty).Select(g => new
{
Type = g.Key,
Count = g.Count(),
}).ToDictionary(kv => kv.Type, kv => kv.Count);

int gold = default;
int silver = default;
int bronze = default;

if (badges is not null)
{
gold = badges.ContainsKey("1") ? badges["1"] : 0;
silver = badges.ContainsKey("2") ? badges["2"] : 0;
bronze = badges.ContainsKey("3") ? badges["3"] : 0;
}

return (gold, silver, bronze);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using DotNetDevBadgeWeb.Common;
using DotNetDevBadgeWeb.Core;
using DotNetDevBadgeWeb.Extensions;
using DotNetDevBadgeWeb.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace DotNetDevBadgeWeb.Endpoints.BadgeEndPoints
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using DotNetDevBadgeWeb.Common;

namespace DotNetDevBadgeWeb.Core
namespace DotNetDevBadgeWeb.Interfaces
{
internal interface IBadge
public interface IBadge
{
Task<string> GetSmallBadge(string id, ETheme theme, CancellationToken token);
Task<string> GetMediumBadge(string id, ETheme theme, CancellationToken token);
}

internal interface IBadgeV1 : IBadge
public interface IBadgeV1 : IBadge
{

}
Expand Down
11 changes: 11 additions & 0 deletions src/dotnetdev-badge/dotnetdev-badge.web/Interfaces/IProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using DotNetDevBadgeWeb.Model;

namespace DotNetDevBadgeWeb.Interfaces
{
public interface IProvider
{
Task<(UserSummary summary, User user)> GetUserInfoAsync(string id, CancellationToken token);
Task<(byte[] avatar, UserSummary summary, User user)> GetUserInfoWithAvatarAsync(string id, CancellationToken token);
Task<(int gold, int silver, int bronze)> GetBadgeCountAsync(string id, CancellationToken token);
}
}
44 changes: 44 additions & 0 deletions src/dotnetdev-badge/dotnetdev-badge.web/Model/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using DotNetDevBadgeWeb.Common;
using Newtonsoft.Json;

namespace DotNetDevBadgeWeb.Model
{
public class User
{
private const int AVATAR_SIZE = 128;


[JsonProperty("id")]
public int Id { get; set; }

[JsonProperty("username")]
public string Username { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("avatar_template")]
public string AvatarTemplate { get; set; }

[JsonProperty("flair_name")]
public object FlairName { get; set; }

[JsonProperty("trust_level")]
public int TrustLevel { get; set; }

[JsonProperty("admin")]
public bool? Admin { get; set; }

[JsonProperty("moderator")]
public bool? Moderator { get; set; }

public ELevel Level => TrustLevel switch
{
3 => ELevel.Silver,
4 => ELevel.Gold,
_ => ELevel.Bronze,
};

public string AvatarEndPoint => AvatarTemplate?.Replace("{size}", AVATAR_SIZE.ToString()) ?? string.Empty;
}
}
43 changes: 43 additions & 0 deletions src/dotnetdev-badge/dotnetdev-badge.web/Model/UserSummary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Newtonsoft.Json;

namespace DotNetDevBadgeWeb.Model
{
public class UserSummary
{
[JsonProperty("likes_given")]
public int LikesGiven { get; set; }

[JsonProperty("likes_received")]
public int LikesReceived { get; set; }

[JsonProperty("topics_entered")]
public int TopicsEntered { get; set; }

[JsonProperty("posts_read_count")]
public int PostsReadCount { get; set; }

[JsonProperty("days_visited")]
public int DaysVisited { get; set; }

[JsonProperty("topic_count")]
public int TopicCount { get; set; }

[JsonProperty("post_count")]
public int PostCount { get; set; }

[JsonProperty("time_read")]
public int TimeRead { get; set; }

[JsonProperty("recent_time_read")]
public int RecentTimeRead { get; set; }

[JsonProperty("bookmark_count")]
public int BookmarkCount { get; set; }

[JsonProperty("can_see_summary_stats")]
public bool CanSeeSummaryStats { get; set; }

[JsonProperty("solved_count")]
public int SolvedCount { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/dotnetdev-badge/dotnetdev-badge.web/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using DotNetDevBadgeWeb.Core;
using DotNetDevBadgeWeb.Endpoints.BadgeEndPoints;
using DotNetDevBadgeWeb.Interfaces;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddSingleton<IBadgeV1, BadgeCreatorV1>();
builder.Services.AddSingleton<IProvider, ForumDataProvider>();
builder.Services.AddResponseCaching();

builder.WebHost.UseUrls("http://0.0.0.0:5000");
Expand Down

0 comments on commit 303e598

Please sign in to comment.