Skip to content

Commit c79d2df

Browse files
committed
Initial commit
Signed-off-by: kvmw <mshamsi@broadcom.com>
0 parents  commit c79d2df

File tree

13 files changed

+554
-0
lines changed

13 files changed

+554
-0
lines changed

.editorconfig

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
root = true
2+
3+
[*.cs]
4+
# Indentation
5+
indent_style = space
6+
indent_size = 4
7+
8+
# New lines
9+
end_of_line = lf
10+
insert_final_newline = true
11+
charset = utf-8
12+
13+
# C# formatting
14+
csharp_new_line_before_open_brace = all
15+
csharp_new_line_before_else = true
16+
csharp_new_line_before_catch = true
17+
csharp_new_line_before_finally = true
18+
csharp_new_line_before_members_in_object_initializers = true
19+
csharp_new_line_before_members_in_anonymous_types = true
20+
21+
# Naming conventions
22+
dotnet_naming_rule.interface_should_be_prefixed_with_i.severity = warning
23+
dotnet_naming_rule.interface_should_be_prefixed_with_i.symbols = interface
24+
dotnet_naming_rule.interface_should_be_prefixed_with_i.style = prefix_interface_with_i
25+
26+
dotnet_naming_symbols.interface.applicable_kinds = interface
27+
dotnet_naming_style.prefix_interface_with_i.required_prefix = I
28+
29+
# Code style
30+
dotnet_style_qualification_for_field = false:suggestion
31+
dotnet_style_qualification_for_property = false:suggestion
32+
dotnet_style_qualification_for_method = false:suggestion
33+
dotnet_style_qualification_for_event = false:suggestion
34+
35+
# Expression-level preferences
36+
dotnet_style_object_initializer = true:suggestion
37+
dotnet_style_collection_initializer = true:suggestion
38+
dotnet_style_explicit_tuple_names = true:suggestion
39+
dotnet_style_null_propagation = true:suggestion
40+
dotnet_style_coalesce_expression = true:suggestion
41+
42+
[*.{json,yml,yaml}]
43+
indent_style = space
44+
indent_size = 2

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: ['main']
6+
pull_request:
7+
branches: ['main']
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup .NET
18+
uses: actions/setup-dotnet@v4
19+
with:
20+
dotnet-version: '8.0.x'
21+
22+
- name: Restore dependencies
23+
run: dotnet restore
24+
25+
- name: Check code formatting
26+
run: dotnet format --verify-no-changes
27+
28+
- name: Build
29+
run: dotnet build --no-restore --configuration Release
30+
31+
- name: Test
32+
run: dotnet test --no-build --verbosity normal
33+

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin/
2+
obj/

Cook.csproj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Description>cook project</Description>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<NoWarn>SA1402;SA1600;SA0001</NoWarn>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<SteeltoeVersion>4.0.*-*</SteeltoeVersion>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.*" />
17+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="Steeltoe.Configuration.ConfigServer" Version="$(SteeltoeVersion)" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
26+
<PrivateAssets>all</PrivateAssets>
27+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
28+
</PackageReference>
29+
</ItemGroup>
30+
31+
</Project>

Cook.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Solution>
2+
<Project Path="cook.csproj" />
3+
</Solution>

IResourceService.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// <copyright file="IResourceService.cs" company="PlaceholderCompany">
2+
// Copyright (c) PlaceholderCompany. All rights reserved.
3+
// </copyright>
4+
5+
namespace Cook;
6+
7+
using System.Net.Http.Headers;
8+
using System.Text.Json;
9+
using System.Text.Json.Serialization;
10+
11+
public interface IResourceService
12+
{
13+
Task<string> GetContentAsync(string name);
14+
}
15+
16+
public class ResourceService : IResourceService
17+
{
18+
private readonly IConfiguration configuration;
19+
private readonly HttpClient httpClient;
20+
private OAuth2TokenResponse? cachedToken;
21+
22+
public ResourceService(IConfiguration configuration, HttpClient httpClient)
23+
{
24+
this.configuration = configuration;
25+
this.httpClient = httpClient;
26+
}
27+
28+
/// <inheritdoc/>
29+
public async Task<string> GetContentAsync(string name)
30+
{
31+
try
32+
{
33+
var configServerUri = this.configuration["spring:cloud:config:uri"];
34+
35+
var appName = this.configuration["spring:application:name"] ?? "application";
36+
var profile = this.configuration["spring:cloud:config:env"] ?? "default";
37+
var label = this.configuration["spring:cloud:config:label"] ?? "main";
38+
39+
var accessToken = await this.GetOrRefreshAccessTokenAsync();
40+
41+
var request = new HttpRequestMessage(HttpMethod.Get, $"{configServerUri}/{appName}/{profile}/{label}/{name}");
42+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
43+
44+
var response = await this.httpClient.SendAsync(request);
45+
if (response.IsSuccessStatusCode)
46+
{
47+
return await response.Content.ReadAsStringAsync();
48+
}
49+
else
50+
{
51+
throw new HttpRequestException($"Failed to fetch resource: {response.StatusCode} - {response.ReasonPhrase}");
52+
}
53+
}
54+
catch (Exception ex)
55+
{
56+
throw new InvalidOperationException($"Error fetching resource: {ex.Message}", ex);
57+
}
58+
}
59+
60+
private async Task<string> GetOrRefreshAccessTokenAsync()
61+
{
62+
if (this.cachedToken != null && DateTime.UtcNow < this.cachedToken.ExpirationTime)
63+
{
64+
return this.cachedToken.AccessToken;
65+
}
66+
67+
var clientId = this.configuration["spring:cloud:config:clientId"];
68+
var clientSecret = this.configuration["spring:cloud:config:clientSecret"];
69+
var accessTokenUri = this.configuration["spring:cloud:config:accessTokenUri"];
70+
71+
if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret) || string.IsNullOrEmpty(accessTokenUri))
72+
{
73+
throw new InvalidOperationException("OAuth2 credentials not configured");
74+
}
75+
76+
var tokenRequest = new FormUrlEncodedContent(new[]
77+
{
78+
new KeyValuePair<string, string>("grant_type", "client_credentials"),
79+
new KeyValuePair<string, string>("client_id", clientId),
80+
new KeyValuePair<string, string>("client_secret", clientSecret),
81+
});
82+
83+
var tokenResponse = await this.httpClient.PostAsync(accessTokenUri, tokenRequest);
84+
if (!tokenResponse.IsSuccessStatusCode)
85+
{
86+
throw new HttpRequestException($"Failed to get access token: {tokenResponse.StatusCode} - {tokenResponse.ReasonPhrase}");
87+
}
88+
89+
this.cachedToken = this.ExtractTokenData(await tokenResponse.Content.ReadAsStringAsync());
90+
91+
return this.cachedToken.AccessToken;
92+
}
93+
94+
private OAuth2TokenResponse ExtractTokenData(string tokenResponse)
95+
{
96+
return JsonSerializer.Deserialize<OAuth2TokenResponse>(tokenResponse) ?? new OAuth2TokenResponse();
97+
}
98+
}
99+
100+
public class OAuth2TokenResponse
101+
{
102+
[JsonPropertyName("access_token")]
103+
public string AccessToken { get; set; } = string.Empty;
104+
105+
[JsonPropertyName("expires_in")]
106+
public int ExpiresIn { get; set; } = 3600;
107+
108+
public DateTime ExpirationTime => DateTime.UtcNow.AddSeconds(this.ExpiresIn - 10); // Subtract 10 seconds for safety
109+
}

0 commit comments

Comments
 (0)