From 859d1ee4b26da2cd65e2ee7bd739b1158bed1a2b Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 7 Sep 2026 14:26:37 +0100 Subject: [PATCH 1/5] add an adapter for the purpose of authorisation --- .../OneClickLCA_Tests/AuthAdapterMock.cs | 42 +++++++++++++ .../CalculationsResultsApi_Tests.cs | 4 +- .../OneClickLCA_Tests.csproj | 3 + OneClickLCA_Adapter/AdapterActions/Pull.cs | 42 ++++++++----- OneClickLCA_Adapter/Objects/UserAPIDetails.cs | 16 +++++ OneClickLCA_Adapter/OneClickLCAAdapter.cs | 63 ++++++++++++++++++- .../OneClickLCA_Adapter.csproj | 11 +++- .../Query/GetUserAPIDetails.cs | 56 +++++++++++++++++ .../Requests/CalculationResultsApiRequest.cs | 6 -- .../Requests/DictionaryDataApiRequest.cs | 6 -- .../Requests/MaterialsCarbonDataApiRequest.cs | 6 -- .../Requests/ProjectsDataApiRequest.cs | 6 -- 12 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 .ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs create mode 100644 OneClickLCA_Adapter/Objects/UserAPIDetails.cs create mode 100644 OneClickLCA_Adapter/Query/GetUserAPIDetails.cs diff --git a/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs b/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs new file mode 100644 index 0000000..07bb92c --- /dev/null +++ b/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs @@ -0,0 +1,42 @@ +using BH.Adapter; +using BH.oM.Adapter; +using BH.oM.Adapters.SQL; +using BH.oM.Base; +using BH.oM.Data.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OneClickLCA_Tests +{ + /// + /// This is a mock BHoMAdapter used for testing that the main adapter flow is working correctly, + /// it does not return an actually valid API key, nor does it need to. + ///
+ /// This adapter is solely for testing. + ///
+ internal class AuthAdapterMock: BHoMAdapter, IBHoMAdapter + { + /// + /// mock pull method that directly returns a "valid API key" + /// + public override IEnumerable Pull(IRequest request, PullType pullType = PullType.AdapterDefault, ActionConfig actionConfig = null) + { + TableRequest tRequest = request as TableRequest; + + if (tRequest == null) + { + return null; + } + + CustomObject rtn = new CustomObject(); + + rtn.CustomData["ClientId"] = "ValidClientId"; + rtn.CustomData["ClientSecret"] = "ValidAPIKey"; + + return new List() { rtn }; + } + } +} diff --git a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs index b5a64ae..06130da 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs @@ -30,7 +30,7 @@ public void OneTimeSetUp() else Assert.Inconclusive("Token file was not found in settings, so these tests have been marked as inconclusive."); - adapter = new OneClickLCAAdapter(); + adapter = new OneClickLCAAdapter(new AuthAdapterMock()); } [SetUp] @@ -39,8 +39,6 @@ public void Setup() { CalculationResultsApiRequest request = new CalculationResultsApiRequest { - ClientId = clientId, - ClientSecret = clientSecret, DesignId = designId, ToolId = toolId, ShowAllCategoriesForTool = false diff --git a/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj b/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj index 055ab89..13e2307 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj +++ b/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj @@ -61,6 +61,9 @@ $(ProgramData)\BHoM\Assemblies\LifeCycleAssessment_oM.dll false + + ..\..\..\..\..\ProgramData\BHoM\Assemblies\SQL_oM.dll + diff --git a/OneClickLCA_Adapter/AdapterActions/Pull.cs b/OneClickLCA_Adapter/AdapterActions/Pull.cs index 40bf710..7edddeb 100644 --- a/OneClickLCA_Adapter/AdapterActions/Pull.cs +++ b/OneClickLCA_Adapter/AdapterActions/Pull.cs @@ -214,13 +214,15 @@ private Dictionary GetMetaData(IExcelDataReader reader) private IEnumerable Pull(MaterialsCarbonDataApiRequest request) { - if (string.IsNullOrEmpty(request.ClientId) || string.IsNullOrEmpty(request.ClientSecret)) + UserAPIDetails details = Query.GetUserAPIDetails(m_userAdapter); + + if (details == null) { - BH.Engine.Base.Compute.RecordError("Client ID and Client Secret are required for the OneClick LCA Carbon Data API."); + //GetUserAPIDetails already specifies why the user is invalid. return new List(); } - string token = AcquireToken(request.ClientId, request.ClientSecret); + string token = AcquireToken(details.ClientId, details.ClientSecret); if (token == null) return new List(); @@ -373,13 +375,15 @@ private MaterialsCarbonDataSearchResponse SearchResources(string token, Material private IEnumerable Pull(ProjectsDataApiRequest request) { - if (string.IsNullOrEmpty(request.ClientId) || string.IsNullOrEmpty(request.ClientSecret)) + UserAPIDetails details = Query.GetUserAPIDetails(m_userAdapter); + + if (details == null) { - BH.Engine.Base.Compute.RecordError("Client ID and Client Secret are required for the OneClick LCA Calculation Results API."); + //GetUserAPIDetails already specifies why the user is invalid. return new List(); } - string token = AcquireToken(request.ClientId, request.ClientSecret); + string token = AcquireToken(details.ClientId, details.ClientSecret); if (token == null) return new List(); @@ -454,18 +458,21 @@ private IEnumerable Pull(ProjectsDataApiRequest request) private IEnumerable Pull(DictionaryDataApiRequest request) { - if (string.IsNullOrEmpty(request.ClientId) || string.IsNullOrEmpty(request.ClientSecret)) + if (string.IsNullOrEmpty(request.DesignId)) { - BH.Engine.Base.Compute.RecordError("Client ID and Client Secret are required for the OneClick LCA Calculation Results API."); + BH.Engine.Base.Compute.RecordError("DesignId is required for GetDictionaryDataRequest."); return new List(); } - if (string.IsNullOrEmpty(request.DesignId)) + + UserAPIDetails details = Query.GetUserAPIDetails(m_userAdapter); + + if (details == null) { - BH.Engine.Base.Compute.RecordError("DesignId is required for GetDictionaryDataRequest."); + //GetUserAPIDetails already specifies why the user is invalid. return new List(); } - string token = AcquireToken(request.ClientId, request.ClientSecret); + string token = AcquireToken(details.ClientId, details.ClientSecret); if (token == null) return new List(); @@ -502,18 +509,21 @@ private IEnumerable Pull(DictionaryDataApiRequest request) private IEnumerable Pull(CalculationResultsApiRequest request) { - if (string.IsNullOrEmpty(request.ClientId) || string.IsNullOrEmpty(request.ClientSecret)) + if (string.IsNullOrEmpty(request.DesignId) || string.IsNullOrEmpty(request.ToolId)) { - BH.Engine.Base.Compute.RecordError("Client ID and Client Secret are required for the OneClick LCA Calculation Results API."); + BH.Engine.Base.Compute.RecordError("DesignId and ToolId are required for CalculationResultsApiRequest."); return new List(); } - if (string.IsNullOrEmpty(request.DesignId) || string.IsNullOrEmpty(request.ToolId)) + + UserAPIDetails details = Query.GetUserAPIDetails(m_userAdapter); + + if (details == null) { - BH.Engine.Base.Compute.RecordError("DesignId and ToolId are required for CalculationResultsApiRequest."); + //GetUserAPIDetails already specifies why the user is invalid. return new List(); } - string token = AcquireToken(request.ClientId, request.ClientSecret); + string token = AcquireToken(details.ClientId, details.ClientSecret); if (token == null) return new List(); diff --git a/OneClickLCA_Adapter/Objects/UserAPIDetails.cs b/OneClickLCA_Adapter/Objects/UserAPIDetails.cs new file mode 100644 index 0000000..e2b0a6b --- /dev/null +++ b/OneClickLCA_Adapter/Objects/UserAPIDetails.cs @@ -0,0 +1,16 @@ +using BH.oM.Base; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BH.Adapter.OneClickLCA.Objects +{ + public class UserAPIDetails: IObject + { + public virtual string UserId { get; set; } + + public virtual string ClientId { get; set; } + + public virtual string ClientSecret { get; set; } + } +} diff --git a/OneClickLCA_Adapter/OneClickLCAAdapter.cs b/OneClickLCA_Adapter/OneClickLCAAdapter.cs index b235f71..e9cd659 100644 --- a/OneClickLCA_Adapter/OneClickLCAAdapter.cs +++ b/OneClickLCA_Adapter/OneClickLCAAdapter.cs @@ -21,6 +21,8 @@ */ using BH.Adapter; +using BH.Adapter.SQL; +using BH.oM.Adapter; using BH.oM.Base.Attributes; using System.ComponentModel; using System.Net; @@ -39,13 +41,72 @@ public OneClickLCAAdapter() { m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + SetUpUserAdapter("SQL-BHOM01", "OneClickLCA_Production"); + } + + /***************************************************/ + + [Description("Adapter for OneClickLCA. Supports reading OneClick LCA report files (Excel) and querying the OneClick LCA Materials Carbon Data API.")] + [Input("server", "The server name to connect to the authentication database.")] + [Input("database", "The database name to connect to the authentication database.")] + [Output("The created OneClickLCA adapter.")] + public OneClickLCAAdapter(string server, string database) + { + m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + + SetUpUserAdapter(server, database); + } + + /***************************************************/ + + [Description("Adapter for OneClickLCA. Supports reading OneClick LCA report files (Excel) and querying the OneClick LCA Materials Carbon Data API.")] + [Input("connectionString", "The connection string to connect to the authentication database.")] + [Output("The created OneClickLCA adapter.")] + public OneClickLCAAdapter(string connectionString) + { + m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + + SetUpUserAdapter(connectionString); + } + + /***************************************************/ + + [Description("Adapter for OneClickLCA using a custom user adapter. If you are unsure of how to use this, try using the `OneClickLCAAdapter` constructor with no inputs.")] + [Input("userAdapter", "The IBHoMAdapter that connects to a valid authentication method.")] + [Output("The created OneClickLCA adapter.")] + public OneClickLCAAdapter(IBHoMAdapter userAdapter) + { + m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + m_userAdapter = userAdapter; + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private void SetUpUserAdapter(string connectionString) + { + SqlAdapter userAdapter = new SqlAdapter(connectionString); + m_userAdapter = userAdapter; + } + + private void SetUpUserAdapter(string server, string database) + { + SqlAdapter userAdapter = new SqlAdapter(server, database); + m_userAdapter = userAdapter; } /***************************************************/ /**** Private Fields ****/ /***************************************************/ - //TODO: set up database connection to check user name against allowed user list in database. + //using `IBHoMAdapter` interface to allow testing with a stub adapter that always returns the API key. + //the important thing is that a `TableRequest` sent with `Pull` with the correct table and filter should return at least one user. + //this is a form of Dependency Injection (https://en.wikipedia.org/wiki/Dependency_injection) + IBHoMAdapter m_userAdapter; /***************************************************/ } diff --git a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj index 11a4bd0..44e62eb 100644 --- a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj +++ b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -97,6 +97,15 @@ false false + + ..\..\..\ProgramData\BHoM\Assemblies\SQL_Adapter.dll + + + ..\..\..\ProgramData\BHoM\Assemblies\SQL_Engine.dll + + + ..\..\..\ProgramData\BHoM\Assemblies\SQL_oM.dll + diff --git a/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs new file mode 100644 index 0000000..8ff3217 --- /dev/null +++ b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs @@ -0,0 +1,56 @@ +using BH.Adapter.OneClickLCA.Objects; +using BH.Engine.Base; +using BH.oM.Adapter; +using BH.oM.Adapters.SQL; +using BH.oM.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BH.Adapter.OneClickLCA +{ + public static partial class Query + { + //TODO: return both clientID and clientSecret(API key) + public static UserAPIDetails GetUserAPIDetails(this IBHoMAdapter adapter, string userId = null, bool defaultUseEnvironmentName = true) + { + if (adapter == null) + { + BH.Engine.Base.Compute.RecordError("The user authentication adapter was null when requesting the users API key. Please provide a valid adapter when creating the OneClickLCAAdapter."); + return null; + } + + if (string.IsNullOrEmpty(userId) && !defaultUseEnvironmentName) + { + BH.Engine.Base.Compute.RecordError("The given user ID/name was null or empty, which is invalid for getting the API key. Please provide a valid user ID."); + return null; + } + + //TODO: sanitise the user ID!!! + TableRequest request = new TableRequest() + { + Table = "dbo.GetAPIKeyByUser", + Filter = $"User = '{userId ?? System.Environment.UserName}'" + }; + + List pulled = adapter.Pull(request).ToList(); + + CustomObject first = pulled.OfType().FirstOrDefault(); + + if (first == null) + { + BH.Engine.Base.Compute.RecordError($"The user {userId} is not authorised to pull from the One Click LCA API.") + return null; + } + + //assume that the resulting dictionary is correct if there were no errors and the return was not empty. + return new UserAPIDetails() + { + UserId = userId, + ClientId = (string)first.CustomData["ClientId"], + ClientSecret = (string)first.CustomData["ClientSecret"] + } + } + } +} diff --git a/OneClickLCA_oM/Requests/CalculationResultsApiRequest.cs b/OneClickLCA_oM/Requests/CalculationResultsApiRequest.cs index e407c8b..6ae9c0f 100644 --- a/OneClickLCA_oM/Requests/CalculationResultsApiRequest.cs +++ b/OneClickLCA_oM/Requests/CalculationResultsApiRequest.cs @@ -29,12 +29,6 @@ namespace BH.oM.Adapters.OneClickLCA [Description("Request calculation results for a design and tool from the OneClick LCA Calculation Results API (GET /calculation-results). For projects use GetProjectsRequest; for dictionary data use GetDictionaryDataRequest.")] public class CalculationResultsApiRequest : BHoMObject, IRequest { - [Description("OAuth2 client identifier used for client credentials authentication.")] - public virtual string ClientId { get; set; } = ""; - - [Description("OAuth2 client secret used for client credentials authentication.")] - public virtual string ClientSecret { get; set; } = ""; - [Description("Unique identifier for the design (24-character alphanumeric).")] public virtual string DesignId { get; set; } = ""; diff --git a/OneClickLCA_oM/Requests/DictionaryDataApiRequest.cs b/OneClickLCA_oM/Requests/DictionaryDataApiRequest.cs index 499fb07..9d5544a 100644 --- a/OneClickLCA_oM/Requests/DictionaryDataApiRequest.cs +++ b/OneClickLCA_oM/Requests/DictionaryDataApiRequest.cs @@ -29,12 +29,6 @@ namespace BH.oM.Adapters.OneClickLCA [Description("Request dictionary data (tool, category, and rule names) for a design from the OneClick LCA Calculation Results API (GET /calculation-results/dictionary).")] public class DictionaryDataApiRequest : BHoMObject, IRequest { - [Description("OAuth2 client identifier used for client credentials authentication.")] - public virtual string ClientId { get; set; } = ""; - - [Description("OAuth2 client secret used for client credentials authentication.")] - public virtual string ClientSecret { get; set; } = ""; - [Description("Unique identifier for the design (24-character alphanumeric).")] public virtual string DesignId { get; set; } = ""; } diff --git a/OneClickLCA_oM/Requests/MaterialsCarbonDataApiRequest.cs b/OneClickLCA_oM/Requests/MaterialsCarbonDataApiRequest.cs index cf723f4..96fc6ca 100644 --- a/OneClickLCA_oM/Requests/MaterialsCarbonDataApiRequest.cs +++ b/OneClickLCA_oM/Requests/MaterialsCarbonDataApiRequest.cs @@ -34,12 +34,6 @@ public class MaterialsCarbonDataApiRequest : BHoMObject, IRequest /**** Properties ****/ /***************************************************/ - [Description("OAuth2 client identifier used for client credentials authentication.")] - public virtual string ClientId { get; set; } = ""; - - [Description("OAuth2 client secret used for client credentials authentication.")] - public virtual string ClientSecret { get; set; } = ""; - [Description("The query text to search for. Use * to return all resources matching the filter, or provide a specific term such as a material name or resource ID. Defaults to * (all resources).")] public virtual string SearchQuery { get; set; } = "*"; diff --git a/OneClickLCA_oM/Requests/ProjectsDataApiRequest.cs b/OneClickLCA_oM/Requests/ProjectsDataApiRequest.cs index 87db997..9345620 100644 --- a/OneClickLCA_oM/Requests/ProjectsDataApiRequest.cs +++ b/OneClickLCA_oM/Requests/ProjectsDataApiRequest.cs @@ -29,12 +29,6 @@ namespace BH.oM.Adapters.OneClickLCA [Description("Request the list of projects with associated designs from the OneClick LCA Calculation Results API (GET /projects). Pull returns a single ProjectDataApiResponse whose Projects list aggregates pages up to MaxResults.")] public class ProjectsDataApiRequest : BHoMObject, IRequest { - [Description("OAuth2 client identifier used for client credentials authentication.")] - public virtual string ClientId { get; set; } = ""; - - [Description("OAuth2 client secret used for client credentials authentication.")] - public virtual string ClientSecret { get; set; } = ""; - [Description("Page number. Defaults to 1.")] public virtual int Page { get; set; } = 1; From 52ad93fc326871d0920cbacf28218a5faba2af1e Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 8 Sep 2026 16:12:37 +0100 Subject: [PATCH 2/5] potentially this should be separate PR, change to use new HTTPAdapter, also allowing using a mock api adapter for testing. --- .../OneClickLCA_Tests/ApiAdapterMock.cs | 43 ++++++++++ .../CalculationsResultsApi_Tests.cs | 12 +-- .../MaterialsCarbonDataApi_Tests.cs | 21 ++--- OneClickLCA_Adapter/AdapterActions/Pull.cs | 71 +++++----------- OneClickLCA_Adapter/OneClickLCAAdapter.cs | 80 ++++++++++++++++++- .../OneClickLCA_Adapter.csproj | 3 + .../Query/GetUserAPIDetails.cs | 4 +- 7 files changed, 154 insertions(+), 80 deletions(-) create mode 100644 .ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs diff --git a/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs b/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs new file mode 100644 index 0000000..4a323e5 --- /dev/null +++ b/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs @@ -0,0 +1,43 @@ +using BH.Adapter; +using BH.oM.Adapter; +using BH.oM.Adapters.HTTP; +using BH.oM.Data.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OneClickLCA_Tests +{ + internal class ApiAdapterMock: BHoMAdapter, IBHoMAdapter + { + public override IEnumerable Pull(IRequest request, PullType pullType = PullType.AdapterDefault, ActionConfig actionConfig = null) + { + GetRequest getRequest = (GetRequest)request; + + if (getRequest.BaseUrl.Contains("materials-carbon-data")) + { + return new List() { + "{\"found\":1,\"hits\":[{\"document\":{\"_id\":\"testId\",\"nameEN\":\"Test material\",\"unitForData\":\"kg\"," + + "\"impacts\":{\"A1-A3\":{\"impactGWP100_kgCO2e_total\":1.5}},\"epdNumber\":\"EPD-1\"},\"highlight\":{},\"highlights\":[]}]," + + "\"page\":1,\"facet_counts\":[],\"request_params\":{\"collection_name\":\"c\",\"first_q\":\"*\",\"per_page\":10,\"q\":\"*\"}," + + "\"search_cutoff\":false,\"search_time_ms\":1}" }; + } + else if (getRequest.BaseUrl.Contains("projects")) + { + return new List() { "expectedProjectsResponse" }; + } + else if (getRequest.BaseUrl.Contains("calculation-results")) + { + if (getRequest.BaseUrl.Contains("dictionary")) + { + return new List() { "expectedDictionaryResponse" }; + } + return new List() { "expectedCalculationResultsResponse" }; + } + + throw new NotImplementedException($"The requested uri was not expected: {getRequest.BaseUrl}"); + } + } +} \ No newline at end of file diff --git a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs index 06130da..3244f32 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs @@ -9,9 +9,6 @@ namespace OneClickLCA_Tests { public class CalculationsResultsApi_Tests { - private const string clientId = "service_acc_buro_happold"; - private string clientSecret = ""; - // These can be set to whatever designId & toolId you want to fetch private const string designId = "69b1576c2b13bd02eee02e3c"; private const string toolId = "lcaRicsV2"; @@ -23,14 +20,7 @@ public class CalculationsResultsApi_Tests [Description("One time set up for the adapter and client secret key.")] public void OneTimeSetUp() { - //TODO: change to pull from database based on current user instead of token file. - string tokenFile = "$ProgramData/BHoM/Settings/OneClickLCA.token"; - if (File.Exists(tokenFile)) - clientSecret = File.ReadAllText(tokenFile); - else - Assert.Inconclusive("Token file was not found in settings, so these tests have been marked as inconclusive."); - - adapter = new OneClickLCAAdapter(new AuthAdapterMock()); + adapter = new OneClickLCAAdapter(new AuthAdapterMock(), new ApiAdapterMock()); } [SetUp] diff --git a/.ci/unit-tests/OneClickLCA_Tests/MaterialsCarbonDataApi_Tests.cs b/.ci/unit-tests/OneClickLCA_Tests/MaterialsCarbonDataApi_Tests.cs index 6798104..d160a32 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/MaterialsCarbonDataApi_Tests.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/MaterialsCarbonDataApi_Tests.cs @@ -16,27 +16,28 @@ public class MaterialsCarbonDataApi_Tests "\"page\":1,\"facet_counts\":[],\"request_params\":{\"collection_name\":\"c\",\"first_q\":\"*\",\"per_page\":10,\"q\":\"*\"}," + "\"search_cutoff\":false,\"search_time_ms\":1}"; - private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + private OneClickLCAAdapter adapter; + + [OneTimeSetUp] + [Description("One time set up for the adapter and client secret key.")] + public void OneTimeSetUp() { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; + adapter = new OneClickLCAAdapter(new AuthAdapterMock(), new ApiAdapterMock()); + } [Test] [Description("Test that a materials-carbon document extracted from a search response can be converted to an EPD using the public converter.")] public void MaterialsCarbonDocumentJson_Converts_ToEpd() { - using JsonDocument doc = JsonDocument.Parse(MinimalSearchJson); - JsonElement documentElement = doc.RootElement.GetProperty("hits")[0].GetProperty("document"); + IEnumerable objs = adapter.Pull(new MaterialsCarbonDataApiRequest()); - string documentJson = documentElement.GetRawText(); - - var epd = BH.Adapter.OneClickLCA.Convert.ToEnvironmentalProductDeclaration(documentJson); + Assert.That(objs.ToList(), Has.Count.EqualTo(1)); + EnvironmentalProductDeclaration? epd = objs.Single() as EnvironmentalProductDeclaration; Assert.That(epd, Is.Not.Null); Assert.That(epd!.Name, Is.EqualTo("Test material")); Assert.That(epd.EnvironmentalMetrics, Is.Not.Null); - Assert.That(epd.EnvironmentalMetrics.Count, Is.GreaterThan(0)); + Assert.That(epd.EnvironmentalMetrics, Is.Not.Empty); } [Description("Test that the public converter returns null for invalid input and produces expected EPD for valid document JSON.")] diff --git a/OneClickLCA_Adapter/AdapterActions/Pull.cs b/OneClickLCA_Adapter/AdapterActions/Pull.cs index 7edddeb..bf4d04a 100644 --- a/OneClickLCA_Adapter/AdapterActions/Pull.cs +++ b/OneClickLCA_Adapter/AdapterActions/Pull.cs @@ -22,12 +22,14 @@ using BH.Adapter; using BH.Adapter.Excel; +using BH.Adapter.HTTP; using BH.Adapter.OneClickLCA.Objects; using BH.Engine.Adapter; using BH.Engine.Adapters.OneClickLCA; using BH.Engine.Base; using BH.oM.Adapter; using BH.oM.Adapters.Excel; +using BH.oM.Adapters.HTTP; using BH.oM.Adapters.OneClickLCA; using BH.oM.Base; using BH.oM.Data.Requests; @@ -237,51 +239,6 @@ private IEnumerable Pull(MaterialsCarbonDataApiRequest request) /***************************************************/ - private string AcquireToken(string clientId, string clientSecret) - { - const string tokenUrl = "https://id.oneclicklcaapp.com/realms/oneclicklca/protocol/openid-connect/token"; - - try - { - using (HttpClient client = new HttpClient()) - { - // Prepare client credentials form - FormUrlEncodedContent body = new FormUrlEncodedContent(new Dictionary - { - { "grant_type", "client_credentials" }, - { "client_id", clientId }, - { "client_secret", clientSecret } - }); - - HttpResponseMessage response = client.PostAsync(tokenUrl, body).Result; - - if (!response.IsSuccessStatusCode) - { - BH.Engine.Base.Compute.RecordError($"Failed to acquire OneClick LCA access token. Response: {(int)response.StatusCode} {response.ReasonPhrase}"); - return null; - } - - string json = response.Content.ReadAsStringAsync().Result; - - using (JsonDocument doc = JsonDocument.Parse(json)) - { - if (doc.RootElement.TryGetProperty("access_token", out JsonElement tokenElement)) - return tokenElement.GetString(); - } - - BH.Engine.Base.Compute.RecordError("Failed to extract access token from the OneClick LCA authentication response."); - return null; - } - } - catch (Exception e) - { - BH.Engine.Base.Compute.RecordError(e, "Failed to acquire OneClick LCA access token."); - return null; - } - } - - /***************************************************/ - private MaterialsCarbonDataSearchResponse SearchResources(string token, MaterialsCarbonDataApiRequest request) { const string searchUrl = "https://oneclicklcaapp.com/api/materials-carbon-data/resource/_search"; @@ -315,12 +272,14 @@ private MaterialsCarbonDataSearchResponse SearchResources(string token, Material if (!string.IsNullOrEmpty(request.SortBy)) parameters["sort_by"] = request.SortBy; - string responseJson = BH.Engine.Adapters.HTTP.Compute.MakeRequest(new BH.oM.Adapters.HTTP.GetRequest + IEnumerable objs = m_apiAdapter.Pull(new BH.oM.Adapters.HTTP.GetRequest { BaseUrl = searchUrl, Headers = new Dictionary { { "Authorization", $"Bearer {token}" } }, Parameters = parameters - }); + }, actionConfig: new HttpPullConfig() { ForceDeserialiseAsBHoM = false }); + + string responseJson = objs.FirstOrDefault()?.ToString(); if (responseJson == null) break; @@ -398,12 +357,14 @@ private IEnumerable Pull(ProjectsDataApiRequest request) if (!string.IsNullOrEmpty(request.LastUpdatedAfter)) parameters["lastUpdatedAfter"] = request.LastUpdatedAfter; - string responseJson = BH.Engine.Adapters.HTTP.Compute.MakeRequest(new BH.oM.Adapters.HTTP.GetRequest + IEnumerable objs = m_apiAdapter.Pull(new BH.oM.Adapters.HTTP.GetRequest { BaseUrl = $"{CalculationResultsApiBase}/projects", Headers = new Dictionary { { "Authorization", $"Bearer {token}" } }, Parameters = parameters - }); + }, actionConfig: new HttpPullConfig() { ForceDeserialiseAsBHoM = false }); + + string responseJson = objs.FirstOrDefault()?.ToString(); if (string.IsNullOrEmpty(responseJson)) break; @@ -476,12 +437,14 @@ private IEnumerable Pull(DictionaryDataApiRequest request) if (token == null) return new List(); - string responseJson = BH.Engine.Adapters.HTTP.Compute.MakeRequest(new BH.oM.Adapters.HTTP.GetRequest + IEnumerable objs = m_apiAdapter.Pull(new BH.oM.Adapters.HTTP.GetRequest { BaseUrl = $"{CalculationResultsApiBase}/calculation-results/dictionary", Headers = new Dictionary { { "Authorization", $"Bearer {token}" } }, Parameters = new Dictionary { { "designId", request.DesignId } } - }); + }, actionConfig: new HttpPullConfig() { ForceDeserialiseAsBHoM = false }); + + string responseJson = objs.FirstOrDefault()?.ToString(); if (string.IsNullOrEmpty(responseJson)) return new List(); @@ -527,7 +490,7 @@ private IEnumerable Pull(CalculationResultsApiRequest request) if (token == null) return new List(); - string calculationJson = BH.Engine.Adapters.HTTP.Compute.MakeRequest(new BH.oM.Adapters.HTTP.GetRequest + IEnumerable objects1 = m_apiAdapter.Pull(new BH.oM.Adapters.HTTP.GetRequest { BaseUrl = $"{CalculationResultsApiBase}/calculation-results", Headers = new Dictionary { { "Authorization", $"Bearer {token}" } }, @@ -537,7 +500,9 @@ private IEnumerable Pull(CalculationResultsApiRequest request) { "toolId", request.ToolId }, { "showAllCategoriesForTool", request.ShowAllCategoriesForTool } } - }); + }, actionConfig: new HttpPullConfig() { ForceDeserialiseAsBHoM = false }); + + string calculationJson = objects1.FirstOrDefault().ToString(); if (string.IsNullOrEmpty(calculationJson)) { diff --git a/OneClickLCA_Adapter/OneClickLCAAdapter.cs b/OneClickLCA_Adapter/OneClickLCAAdapter.cs index e9cd659..f8f1b2b 100644 --- a/OneClickLCA_Adapter/OneClickLCAAdapter.cs +++ b/OneClickLCA_Adapter/OneClickLCAAdapter.cs @@ -21,26 +21,38 @@ */ using BH.Adapter; +using BH.Adapter.HTTP; using BH.Adapter.SQL; using BH.oM.Adapter; using BH.oM.Base.Attributes; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Net; +using System.Net.Http; +using System.Text.Json; namespace BH.Adapter.OneClickLCA { public partial class OneClickLCAAdapter : BHoMAdapter { + private TimeSpan DefaultTimeout = TimeSpan.FromMinutes(2); + /***************************************************/ /**** Constructors ****/ /***************************************************/ [Description("Adapter for OneClickLCA. Supports reading OneClick LCA report files (Excel) and querying the OneClick LCA Materials Carbon Data API.")] [Output("The created OneClickLCA adapter.")] - public OneClickLCAAdapter() + public OneClickLCAAdapter(HttpClient apiClient = null) { m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + + if (apiClient is null) + apiClient = new HttpClient() { Timeout = DefaultTimeout }; + m_apiAdapter = new HTTPAdapter(apiClient); + SetUpUserAdapter("SQL-BHOM01", "OneClickLCA_Production"); } @@ -50,11 +62,15 @@ public OneClickLCAAdapter() [Input("server", "The server name to connect to the authentication database.")] [Input("database", "The database name to connect to the authentication database.")] [Output("The created OneClickLCA adapter.")] - public OneClickLCAAdapter(string server, string database) + public OneClickLCAAdapter(string server, string database, HttpClient apiClient = null) { m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + if (apiClient is null) + apiClient = new HttpClient() { Timeout = DefaultTimeout }; + m_apiAdapter = new HTTPAdapter(apiClient); + SetUpUserAdapter(server, database); } @@ -63,11 +79,15 @@ public OneClickLCAAdapter(string server, string database) [Description("Adapter for OneClickLCA. Supports reading OneClick LCA report files (Excel) and querying the OneClick LCA Materials Carbon Data API.")] [Input("connectionString", "The connection string to connect to the authentication database.")] [Output("The created OneClickLCA adapter.")] - public OneClickLCAAdapter(string connectionString) + public OneClickLCAAdapter(string connectionString, HttpClient apiClient = null) { m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + if (apiClient is null) + apiClient = new HttpClient() { Timeout = DefaultTimeout }; + m_apiAdapter = new HTTPAdapter(apiClient); + SetUpUserAdapter(connectionString); } @@ -76,11 +96,13 @@ public OneClickLCAAdapter(string connectionString) [Description("Adapter for OneClickLCA using a custom user adapter. If you are unsure of how to use this, try using the `OneClickLCAAdapter` constructor with no inputs.")] [Input("userAdapter", "The IBHoMAdapter that connects to a valid authentication method.")] [Output("The created OneClickLCA adapter.")] - public OneClickLCAAdapter(IBHoMAdapter userAdapter) + public OneClickLCAAdapter(IBHoMAdapter userAdapter, IBHoMAdapter apiAdapter) { m_AdapterSettings.DefaultPushType = oM.Adapter.PushType.CreateNonExisting; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + m_userAdapter = userAdapter; + m_apiAdapter = apiAdapter; } /***************************************************/ @@ -93,12 +115,60 @@ private void SetUpUserAdapter(string connectionString) m_userAdapter = userAdapter; } + /***************************************************/ + private void SetUpUserAdapter(string server, string database) { SqlAdapter userAdapter = new SqlAdapter(server, database); m_userAdapter = userAdapter; } + /***************************************************/ + + private string AcquireToken(string clientId, string clientSecret) + { + const string tokenUrl = "https://id.oneclicklcaapp.com/realms/oneclicklca/protocol/openid-connect/token"; + + if (!(m_apiAdapter is HTTPAdapter adapter)) + return ""; + + try + { + // Prepare client credentials form + FormUrlEncodedContent body = new FormUrlEncodedContent(new Dictionary + { + { "grant_type", "client_credentials" }, + { "client_id", clientId }, + { "client_secret", clientSecret } + }); + + using (HttpResponseMessage response = adapter.HttpClient.PostAsync(tokenUrl, body).Result) + { + if (!response.IsSuccessStatusCode) + { + BH.Engine.Base.Compute.RecordError($"Failed to acquire OneClick LCA access token. Response: {(int)response.StatusCode} {response.ReasonPhrase}"); + return null; + } + + string json = response.Content.ReadAsStringAsync().Result; + + using (JsonDocument doc = JsonDocument.Parse(json)) + { + if (doc.RootElement.TryGetProperty("access_token", out JsonElement tokenElement)) + return tokenElement.GetString(); + } + + BH.Engine.Base.Compute.RecordError("Failed to extract access token from the OneClick LCA authentication response."); + return null; + } + } + catch (Exception e) + { + BH.Engine.Base.Compute.RecordError(e, "Failed to acquire OneClick LCA access token."); + return null; + } + } + /***************************************************/ /**** Private Fields ****/ /***************************************************/ @@ -107,6 +177,8 @@ private void SetUpUserAdapter(string server, string database) //the important thing is that a `TableRequest` sent with `Pull` with the correct table and filter should return at least one user. //this is a form of Dependency Injection (https://en.wikipedia.org/wiki/Dependency_injection) IBHoMAdapter m_userAdapter; + //ditto but with requests to one click api + IBHoMAdapter m_apiAdapter; /***************************************************/ } diff --git a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj index 44e62eb..ac3c1af 100644 --- a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj +++ b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj @@ -72,6 +72,9 @@ false false + + ..\..\..\ProgramData\BHoM\Assemblies\HTTP_Adapter.dll + $(ProgramData)\BHoM\Assemblies\LifeCycleAssessment_Engine.dll false diff --git a/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs index 8ff3217..a4eea39 100644 --- a/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs +++ b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs @@ -40,7 +40,7 @@ public static UserAPIDetails GetUserAPIDetails(this IBHoMAdapter adapter, string if (first == null) { - BH.Engine.Base.Compute.RecordError($"The user {userId} is not authorised to pull from the One Click LCA API.") + BH.Engine.Base.Compute.RecordError($"The user {userId} is not authorised to pull from the One Click LCA API."); return null; } @@ -50,7 +50,7 @@ public static UserAPIDetails GetUserAPIDetails(this IBHoMAdapter adapter, string UserId = userId, ClientId = (string)first.CustomData["ClientId"], ClientSecret = (string)first.CustomData["ClientSecret"] - } + }; } } } From 3e6bd0b65de93e164549ee6db9ba80d9c6931796 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 14 Sep 2026 11:46:22 +0100 Subject: [PATCH 3/5] update api mock, and getting user api details simplified --- .../OneClickLCA_Tests/ApiAdapterMock.cs | 14 +++++++++----- OneClickLCA_Adapter/Query/GetUserAPIDetails.cs | 18 +++++------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs b/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs index 4a323e5..e0ca420 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/ApiAdapterMock.cs @@ -18,18 +18,22 @@ public override IEnumerable Pull(IRequest request, PullType pullType = P if (getRequest.BaseUrl.Contains("materials-carbon-data")) { - return new List() { - "{\"found\":1,\"hits\":[{\"document\":{\"_id\":\"testId\",\"nameEN\":\"Test material\",\"unitForData\":\"kg\"," + - "\"impacts\":{\"A1-A3\":{\"impactGWP100_kgCO2e_total\":1.5}},\"epdNumber\":\"EPD-1\"},\"highlight\":{},\"highlights\":[]}]," + - "\"page\":1,\"facet_counts\":[],\"request_params\":{\"collection_name\":\"c\",\"first_q\":\"*\",\"per_page\":10,\"q\":\"*\"}," + - "\"search_cutoff\":false,\"search_time_ms\":1}" }; + return new List() + { + "{\"found\":1,\"hits\":[{\"document\":{\"_id\":\"testId\",\"nameEN\":\"Test material\",\"unitForData\":\"kg\"," + + "\"impacts\":{\"A1-A3\":{\"impactGWP100_kgCO2e_total\":1.5}},\"epdNumber\":\"EPD-1\"},\"highlight\":{},\"highlights\":[]}]," + + "\"page\":1,\"facet_counts\":[],\"request_params\":{\"collection_name\":\"c\",\"first_q\":\"*\",\"per_page\":10,\"q\":\"*\"}," + + "\"search_cutoff\":false,\"search_time_ms\":1}" + }; } else if (getRequest.BaseUrl.Contains("projects")) { + //TODO: get json of an expected project response return new List() { "expectedProjectsResponse" }; } else if (getRequest.BaseUrl.Contains("calculation-results")) { + //TODO: get jsons for dictionary and calculation results responses if (getRequest.BaseUrl.Contains("dictionary")) { return new List() { "expectedDictionaryResponse" }; diff --git a/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs index a4eea39..8ced1b4 100644 --- a/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs +++ b/OneClickLCA_Adapter/Query/GetUserAPIDetails.cs @@ -30,27 +30,19 @@ public static UserAPIDetails GetUserAPIDetails(this IBHoMAdapter adapter, string //TODO: sanitise the user ID!!! TableRequest request = new TableRequest() { - Table = "dbo.GetAPIKeyByUser", - Filter = $"User = '{userId ?? System.Environment.UserName}'" + Table = $"dbo.GetAPIKeyByUser('{userId ?? System.Environment.UserName}')", + DataType = typeof(UserAPIDetails) }; - List pulled = adapter.Pull(request).ToList(); + UserAPIDetails details = adapter.Pull(request).OfType().FirstOrDefault(); - CustomObject first = pulled.OfType().FirstOrDefault(); - - if (first == null) + if (details == null) { BH.Engine.Base.Compute.RecordError($"The user {userId} is not authorised to pull from the One Click LCA API."); return null; } - //assume that the resulting dictionary is correct if there were no errors and the return was not empty. - return new UserAPIDetails() - { - UserId = userId, - ClientId = (string)first.CustomData["ClientId"], - ClientSecret = (string)first.CustomData["ClientSecret"] - }; + return details; } } } From 1cd1748ca0f1e10fd2e04e2baca9365612004a78 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 14 Sep 2026 12:11:29 +0100 Subject: [PATCH 4/5] fix mock after specifying return type --- .ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs | 8 ++------ .ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs b/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs index 07bb92c..3f88f99 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/AuthAdapterMock.cs @@ -1,4 +1,5 @@ using BH.Adapter; +using BH.Adapter.OneClickLCA.Objects; using BH.oM.Adapter; using BH.oM.Adapters.SQL; using BH.oM.Base; @@ -27,14 +28,9 @@ public override IEnumerable Pull(IRequest request, PullType pullType = P TableRequest tRequest = request as TableRequest; if (tRequest == null) - { return null; - } - CustomObject rtn = new CustomObject(); - - rtn.CustomData["ClientId"] = "ValidClientId"; - rtn.CustomData["ClientSecret"] = "ValidAPIKey"; + UserAPIDetails rtn = new UserAPIDetails() { ClientId = "ValidClientId", ClientSecret = "ValidAPIKey" }; return new List() { rtn }; } diff --git a/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj b/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj index 13e2307..52ab9b1 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj +++ b/.ci/unit-tests/OneClickLCA_Tests/OneClickLCA_Tests.csproj @@ -62,7 +62,8 @@ false - ..\..\..\..\..\ProgramData\BHoM\Assemblies\SQL_oM.dll + $(ProgramData)\BHoM\Assemblies\SQL_oM.dll + false From dc846e2b9c7582af701ef6c9f1a8297547a1c11a Mon Sep 17 00:00:00 2001 From: Karolina Prusicka Date: Thu, 15 Oct 2026 11:19:14 +0100 Subject: [PATCH 5/5] Adding security check --- .../OneClickLCA_Tests/CalculationsResultsApi_Tests.cs | 2 +- OneClickLCA_Adapter/Convert/FromOneClickLCA/FromAPI.cs | 4 ++-- OneClickLCA_Adapter/OneClickLCA_Adapter.csproj | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs index 3244f32..a3162fb 100644 --- a/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs +++ b/.ci/unit-tests/OneClickLCA_Tests/CalculationsResultsApi_Tests.cs @@ -35,7 +35,7 @@ public void Setup() }; IEnumerable pullResult = adapter.Pull(request); - calculationResults = pullResult?.FirstOrDefault() as IEnumerable ?? Enumerable.Empty(); + calculationResults = pullResult?.FirstOrDefault() as IEnumerable ?? Enumerable.Empty().ToList(); } [Test] diff --git a/OneClickLCA_Adapter/Convert/FromOneClickLCA/FromAPI.cs b/OneClickLCA_Adapter/Convert/FromOneClickLCA/FromAPI.cs index 40d326b..9e8d9de 100644 --- a/OneClickLCA_Adapter/Convert/FromOneClickLCA/FromAPI.cs +++ b/OneClickLCA_Adapter/Convert/FromOneClickLCA/FromAPI.cs @@ -92,8 +92,8 @@ public static Design FromAPI(this DesignRecord record) Baseline = record.Baseline, RibaStage = record.RibaStage, LastUpdated = record.LastUpdated, - CreatedBy = record.CreatedBy.FromAPI(), - LastUpdatedBy = record.LastUpdatedBy.FromAPI(), + CreatedBy = record.CreatedBy?.FromAPI() ?? new BasicUserDetails(), + LastUpdatedBy = record.LastUpdatedBy?.FromAPI() ?? new BasicUserDetails(), }; } diff --git a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj index ac3c1af..a0eae20 100644 --- a/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj +++ b/OneClickLCA_Adapter/OneClickLCA_Adapter.csproj @@ -73,7 +73,7 @@ false - ..\..\..\ProgramData\BHoM\Assemblies\HTTP_Adapter.dll + $(ProgramData)\BHoM\Assemblies\HTTP_Adapter.dll $(ProgramData)\BHoM\Assemblies\LifeCycleAssessment_Engine.dll @@ -101,13 +101,13 @@ false - ..\..\..\ProgramData\BHoM\Assemblies\SQL_Adapter.dll + $(ProgramData)\BHoM\Assemblies\SQL_Adapter.dll - ..\..\..\ProgramData\BHoM\Assemblies\SQL_Engine.dll + $(ProgramData)\BHoM\Assemblies\SQL_Engine.dll - ..\..\..\ProgramData\BHoM\Assemblies\SQL_oM.dll + $(ProgramData)\BHoM\Assemblies\SQL_oM.dll