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
+ }
0 commit comments