Skip to content

Commit cc78894

Browse files
Moves updating Graph DB to the GraphSelectGuidancePlugin. Closes #481 (#482)
Co-authored-by: Garry Trinder <garry@trinder365.co.uk>
1 parent fce0622 commit cc78894

File tree

6 files changed

+216
-206
lines changed

6 files changed

+216
-206
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Data.Sqlite;
5+
using Microsoft.OpenApi.Models;
6+
using Microsoft.OpenApi.Readers;
7+
8+
namespace Microsoft.DevProxy.Abstractions;
9+
10+
public static class MSGraphDbUtils
11+
{
12+
private static readonly Dictionary<string, OpenApiDocument> _openApiDocuments = new();
13+
private static readonly string[] graphVersions = ["v1.0", "beta"];
14+
15+
private static string GetGraphOpenApiYamlFileName(string version) => $"graph-{version.Replace(".", "_")}-openapi.yaml";
16+
17+
// v1 refers to v1 of the db schema, not the graph version
18+
public static string MSGraphDbFilePath => Path.Combine(ProxyUtils.AppFolder!, "msgraph-openapi-v1.db");
19+
private static SqliteConnection? _msGraphDbConnection;
20+
public static SqliteConnection MSGraphDbConnection
21+
{
22+
get
23+
{
24+
if (_msGraphDbConnection is null)
25+
{
26+
_msGraphDbConnection = new SqliteConnection($"Data Source={MSGraphDbFilePath}");
27+
_msGraphDbConnection.Open();
28+
}
29+
30+
return _msGraphDbConnection;
31+
}
32+
}
33+
34+
public static async Task<int> GenerateMSGraphDb(ILogger logger, bool skipIfUpdatedToday = false)
35+
{
36+
var appFolder = ProxyUtils.AppFolder;
37+
if (string.IsNullOrEmpty(appFolder))
38+
{
39+
logger.LogError("App folder not found");
40+
return 1;
41+
}
42+
43+
try
44+
{
45+
var dbFileInfo = new FileInfo(MSGraphDbFilePath);
46+
var modifiedToday = dbFileInfo.Exists && dbFileInfo.LastWriteTime.Date == DateTime.Now.Date;
47+
if (modifiedToday && skipIfUpdatedToday)
48+
{
49+
logger.LogInfo("Microsoft Graph database already updated today");
50+
return 1;
51+
}
52+
53+
await UpdateOpenAPIGraphFilesIfNecessary(appFolder, logger);
54+
await LoadOpenAPIFiles(appFolder, logger);
55+
if (_openApiDocuments.Count < 1)
56+
{
57+
logger.LogDebug("No OpenAPI files found or couldn't load them");
58+
return 1;
59+
}
60+
61+
var dbConnection = MSGraphDbConnection;
62+
CreateDb(dbConnection, logger);
63+
FillData(dbConnection, logger);
64+
65+
logger.LogInfo("Microsoft Graph database successfully updated");
66+
67+
return 0;
68+
}
69+
catch (Exception ex)
70+
{
71+
logger.LogError(ex.Message);
72+
return 1;
73+
}
74+
75+
}
76+
77+
private static void CreateDb(SqliteConnection dbConnection, ILogger logger)
78+
{
79+
logger.LogInfo("Creating database...");
80+
81+
logger.LogDebug("Dropping endpoints table...");
82+
var dropTable = dbConnection.CreateCommand();
83+
dropTable.CommandText = "DROP TABLE IF EXISTS endpoints";
84+
dropTable.ExecuteNonQuery();
85+
86+
logger.LogDebug("Creating endpoints table...");
87+
var createTable = dbConnection.CreateCommand();
88+
// when you change the schema, increase the db version number in ProxyUtils
89+
createTable.CommandText = "CREATE TABLE IF NOT EXISTS endpoints (path TEXT, graphVersion TEXT, hasSelect BOOLEAN)";
90+
createTable.ExecuteNonQuery();
91+
92+
logger.LogDebug("Creating index on endpoints and version...");
93+
// Add an index on the path and graphVersion columns
94+
var createIndex = dbConnection.CreateCommand();
95+
createIndex.CommandText = "CREATE INDEX IF NOT EXISTS idx_endpoints_path_version ON endpoints (path, graphVersion)";
96+
createIndex.ExecuteNonQuery();
97+
}
98+
99+
private static void FillData(SqliteConnection dbConnection, ILogger logger)
100+
{
101+
logger.LogInfo("Filling database...");
102+
103+
var i = 0;
104+
105+
foreach (var openApiDocument in _openApiDocuments)
106+
{
107+
var graphVersion = openApiDocument.Key;
108+
var document = openApiDocument.Value;
109+
110+
logger.LogDebug($"Filling database for {graphVersion}...");
111+
112+
var insertEndpoint = dbConnection.CreateCommand();
113+
insertEndpoint.CommandText = "INSERT INTO endpoints (path, graphVersion, hasSelect) VALUES (@path, @graphVersion, @hasSelect)";
114+
insertEndpoint.Parameters.Add(new SqliteParameter("@path", null));
115+
insertEndpoint.Parameters.Add(new SqliteParameter("@graphVersion", null));
116+
insertEndpoint.Parameters.Add(new SqliteParameter("@hasSelect", null));
117+
118+
foreach (var path in document.Paths)
119+
{
120+
logger.LogDebug($"Endpoint {graphVersion}{path.Key}...");
121+
122+
// Get the GET operation for this path
123+
var getOperation = path.Value.Operations.FirstOrDefault(o => o.Key == OperationType.Get).Value;
124+
if (getOperation == null)
125+
{
126+
logger.LogDebug($"No GET operation found for {graphVersion}{path.Key}");
127+
continue;
128+
}
129+
130+
// Check if the GET operation has a $select parameter
131+
var hasSelect = getOperation.Parameters.Any(p => p.Name == "$select");
132+
133+
logger.LogDebug($"Inserting endpoint {graphVersion}{path.Key} with hasSelect={hasSelect}...");
134+
insertEndpoint.Parameters["@path"].Value = path.Key;
135+
insertEndpoint.Parameters["@graphVersion"].Value = graphVersion;
136+
insertEndpoint.Parameters["@hasSelect"].Value = hasSelect;
137+
insertEndpoint.ExecuteNonQuery();
138+
i++;
139+
}
140+
}
141+
142+
logger.LogInfo($"Inserted {i} endpoints in the database");
143+
}
144+
145+
private static async Task UpdateOpenAPIGraphFilesIfNecessary(string folder, ILogger logger)
146+
{
147+
logger.LogInfo("Checking for updated OpenAPI files...");
148+
149+
foreach (var version in graphVersions)
150+
{
151+
try
152+
{
153+
var file = new FileInfo(Path.Combine(folder, GetGraphOpenApiYamlFileName(version)));
154+
logger.LogDebug($"Checking for updated OpenAPI file {file}...");
155+
if (file.Exists && file.LastWriteTime.Date == DateTime.Now.Date)
156+
{
157+
logger.LogInfo($"File {file} already updated today");
158+
continue;
159+
}
160+
161+
var url = $"https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/{version}/openapi.yaml";
162+
logger.LogInfo($"Downloading OpenAPI file from {url}...");
163+
164+
var client = new HttpClient();
165+
var response = await client.GetStringAsync(url);
166+
File.WriteAllText(file.FullName, response);
167+
168+
logger.LogDebug($"Downloaded OpenAPI file from {url} to {file}");
169+
}
170+
catch (Exception ex)
171+
{
172+
logger.LogError(ex.Message);
173+
}
174+
}
175+
}
176+
177+
private static async Task LoadOpenAPIFiles(string folder, ILogger logger)
178+
{
179+
logger.LogInfo("Loading OpenAPI files...");
180+
181+
foreach (var version in graphVersions)
182+
{
183+
var filePath = Path.Combine(folder, GetGraphOpenApiYamlFileName(version));
184+
var file = new FileInfo(filePath);
185+
logger.LogDebug($"Loading OpenAPI file for {filePath}...");
186+
187+
if (!file.Exists)
188+
{
189+
logger.LogDebug($"File {filePath} does not exist");
190+
continue;
191+
}
192+
193+
try
194+
{
195+
var openApiDocument = await new OpenApiStreamReader().ReadAsync(file.OpenRead());
196+
_openApiDocuments[version] = openApiDocument.OpenApiDocument;
197+
198+
logger.LogDebug($"Added OpenAPI file {filePath} for {version}");
199+
}
200+
catch (Exception ex)
201+
{
202+
logger.LogError($"Error loading OpenAPI file {filePath}: {ex.Message}");
203+
}
204+
}
205+
}
206+
}

dev-proxy-abstractions/ProxyUtils.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,6 @@ public static class ProxyUtils
2626
private static readonly Regex deprecationRegex = new Regex("^[a-z]+_v2$", RegexOptions.IgnoreCase);
2727
private static readonly Regex functionCallRegex = new Regex(@"^[a-z]+\(.*\)$", RegexOptions.IgnoreCase);
2828

29-
// v1 refers to v1 of the db schema, not the graph version
30-
public static string MsGraphDbFilePath => Path.Combine(AppFolder!, "msgraph-openapi-v1.db");
31-
private static SqliteConnection? _msGraphDbConnection;
32-
public static SqliteConnection MsGraphDbConnection
33-
{
34-
get
35-
{
36-
if (_msGraphDbConnection is null)
37-
{
38-
_msGraphDbConnection = new SqliteConnection($"Data Source={MsGraphDbFilePath}");
39-
_msGraphDbConnection.Open();
40-
}
41-
42-
return _msGraphDbConnection;
43-
}
44-
}
45-
4629
// doesn't end with a path separator
4730
public static string? AppFolder => Path.GetDirectoryName(AppContext.BaseDirectory);
4831

dev-proxy-abstractions/dev-proxy-abstractions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
1515
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
1616
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
17+
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.11" />
1718
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
1819
<PackageReference Include="Titanium.Web.Proxy" Version="3.2.0" />
1920
</ItemGroup>

dev-proxy-plugins/Guidance/GraphSelectGuidancePlugin.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public override void Register(IPluginEvents pluginEvents,
1919
base.Register(pluginEvents, context, urlsToWatch, configSection);
2020

2121
pluginEvents.AfterResponse += AfterResponse;
22+
23+
// for background db refresh, let's use a separate logger
24+
// that only logs warnings and errors
25+
var _logger2 = (ILogger)context.Logger.Clone();
26+
_logger2.LogLevel = LogLevel.Warn;
27+
// let's not await so that it doesn't block the proxy startup
28+
_ = MSGraphDbUtils.GenerateMSGraphDb(_logger2, true);
2229
}
2330

2431
private Task AfterResponse(object? sender, ProxyResponseArgs e)
@@ -61,7 +68,7 @@ private bool EndpointSupportsSelect(string graphVersion, string relativeUrl)
6168

6269
try
6370
{
64-
var dbConnection = ProxyUtils.MsGraphDbConnection;
71+
var dbConnection = MSGraphDbUtils.MSGraphDbConnection;
6572
// lookup information from the database
6673
var selectEndpoint = dbConnection.CreateCommand();
6774
selectEndpoint.CommandText = "SELECT hasSelect FROM endpoints WHERE path = @path AND graphVersion = @graphVersion";

0 commit comments

Comments
 (0)