Skip to content

Commit

Permalink
Update frontend models generation to use proper NSwag pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgDangl committed Apr 23, 2024
1 parent eef1e7a commit 168b24c
Show file tree
Hide file tree
Showing 31 changed files with 1,196 additions and 272 deletions.
4 changes: 2 additions & 2 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@
"BuildDocumentation",
"BuildElectronApp",
"BuildFrontend",
"BuildFrontendSwaggerClient",
"BuildRevitPlugin",
"Clean",
"Compile",
"FrontEndRestore",
"GenerateFrontendModels",
"GenerateFrontendVersion",
"PublishGitHubRelease",
"Restore",
Expand All @@ -133,11 +133,11 @@
"BuildDocumentation",
"BuildElectronApp",
"BuildFrontend",
"BuildFrontendSwaggerClient",
"BuildRevitPlugin",
"Clean",
"Compile",
"FrontEndRestore",
"GenerateFrontendModels",
"GenerateFrontendVersion",
"PublishGitHubRelease",
"Restore",
Expand Down
71 changes: 9 additions & 62 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,70 +439,17 @@ await PublishRelease(x => x
.SetToken(GitHubAuthenticationToken));
});

Target GenerateFrontendModels => _ => _
Target BuildFrontendSwaggerClient => _ => _
.DependsOn(Restore)
.Executes(() =>
{
var jsonSchemaSettings = new SystemTextJsonSchemaGeneratorSettings();
jsonSchemaSettings.SerializerOptions = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
};

var document = new OpenApiDocument();

var bcfFileSchema = JsonSchema.FromType<IPA.Bcfier.Models.Bcf.BcfFileWrapper>(jsonSchemaSettings);
document.Definitions.Add(nameof(IPA.Bcfier.Models.Bcf.BcfFileWrapper), bcfFileSchema);
foreach (var typeDef in bcfFileSchema.Definitions)
{
document.Definitions.TryAdd(typeDef.Key, typeDef.Value);
}

var frontendConfigSchema = JsonSchema.FromType<IPA.Bcfier.Models.Config.FrontendConfig>(jsonSchemaSettings);
document.Definitions.Add(nameof(IPA.Bcfier.Models.Config.FrontendConfig), frontendConfigSchema);

var settingsSchema = JsonSchema.FromType<IPA.Bcfier.Models.Settings.Settings>(jsonSchemaSettings);
document.Definitions.Add(nameof(IPA.Bcfier.Models.Settings.Settings), settingsSchema);

var typeScriptClientGeneratorSettings = new TypeScriptClientGeneratorSettings
{
Template = NSwag.CodeGeneration.TypeScript.TypeScriptTemplate.Angular,
RxJsVersion = 7.0m,
};
typeScriptClientGeneratorSettings.TypeScriptGeneratorSettings.TypeStyle = NJsonSchema.CodeGeneration.TypeScript.TypeScriptTypeStyle.Interface;
typeScriptClientGeneratorSettings.TypeScriptGeneratorSettings.EnumStyle = NJsonSchema.CodeGeneration.TypeScript.TypeScriptEnumStyle.Enum;
typeScriptClientGeneratorSettings.TypeScriptGeneratorSettings.TypeScriptVersion = 4.3m;

var typeScriptPropertyNameGenerator = new TypeScriptPropertyNameGenerator();
typeScriptClientGeneratorSettings.TypeScriptGeneratorSettings.PropertyNameGenerator = typeScriptPropertyNameGenerator;

var generator = new TypeScriptClientGenerator(document, typeScriptClientGeneratorSettings);

var typeScriptFile = generator.GenerateFile();

var typeScriptCode = string.Empty;
var lastLineWasEmpty = false;
foreach (var line in Regex.Split(typeScriptFile, "\r\n?|\n"))
{
if (!line.StartsWith("import")
&& !line.StartsWith("export const API_BASE_URL"))
{
if (string.IsNullOrWhiteSpace(line))
{
if (!lastLineWasEmpty)
{
typeScriptCode += line + Environment.NewLine;
}
lastLineWasEmpty = true;
}
else
{
lastLineWasEmpty = false;
typeScriptCode += line + Environment.NewLine;
}
}
}

(SourceDirectory / "ipa-bcfier-ui" / "src" / "generated" / "models.ts").WriteAllText(typeScriptCode);
var nSwagConfigPath = SourceDirectory / "ipa-bcfier-ui" / "src" / "nswag.json";
var nSwagToolPath = NuGetToolPathResolver.GetPackageExecutable("NSwag.MSBuild", "tools/Net80/dotnet-nswag.dll");
DotNetRun(x => x
.SetProcessToolPath(nSwagToolPath)
.SetProcessWorkingDirectory(SourceDirectory / "ipa-bcfier-ui" / "src")
.SetProcessArgumentConfigurator(y => y
.Add($"/Input:\"{nSwagConfigPath}\"")));
});

private bool IsOnBranch(string branchName)
Expand Down
4 changes: 4 additions & 0 deletions build/_build.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<PackageDownload Include="GitVersion.CommandLine" Version="[5.12.0]" />
<PackageReference Include="Nuke.Common" Version="8.0.0" />
<PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.0.7" />
<PackageReference Include="NSwag.MSBuild" Version="14.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="docfx.console" Version="2.59.4">
<ExcludeAssets>build</ExcludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using NSwag;

namespace IPA.Bcfier.App.Configuration
{
public static class BcfierSwaggerConfigurationExtensions
{
public static IServiceCollection AddBcfierSwagger(this IServiceCollection services)
{
services.AddOpenApiDocument(c =>
{
c.Description = "\"Access to the IPA.Bcfier API\" API Specification";
c.Version = FileVersionProvider.NuGetVersion;
c.Title = $"\"Access to the IPA.Bcfier API\" API {FileVersionProvider.NuGetVersion}";
});

return services;
}

public static IApplicationBuilder UseBcfierSwaggerUi(this IApplicationBuilder app)
{
app.UseOpenApi(c =>
{
c.Path = "/swagger/swagger.json";
c.PostProcess = (doc, _) =>
{
// This makes sure that Azure warmup requests that are sent via Http instead of
// Https don't set the document schema to http only
doc.Schemes = new List<OpenApiSchema> { OpenApiSchema.Https };
};
});
app.UseSwaggerUi(settings =>
{
settings.DocumentPath = "/swagger/swagger.json";
settings.Path = "/swagger";
});

return app;
}
}
}
20 changes: 17 additions & 3 deletions src/IPA.Bcfier.App/Controllers/BcfConversionController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using ElectronNET.API;
using Dangl.Data.Shared;
using ElectronNET.API;
using ElectronNET.API.Entities;
using IPA.Bcfier.App.Services;
using IPA.Bcfier.Models.Bcf;
using IPA.Bcfier.Services;
using Microsoft.AspNetCore.Mvc;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
Expand All @@ -19,6 +21,9 @@ public BcfConversionController(ElectronWindowProvider electronWindowProvider)
}

[HttpPost("import")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BcfFileWrapper), (int)HttpStatusCode.OK)]
public async Task<IActionResult> ImportBcfFileAsync()
{
var electronWindow = _electronWindowProvider.BrowserWindow;
Expand Down Expand Up @@ -62,6 +67,9 @@ public async Task<IActionResult> ImportBcfFileAsync()
}

[HttpPost("export")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BcfFileWrapper), (int)HttpStatusCode.OK)]
public async Task<IActionResult> ExportBcfFileAsync([FromBody] BcfFile bcfFile)
{
var bcfFileResult = new BcfExportService().ExportBcfFile(bcfFile);
Expand Down Expand Up @@ -96,13 +104,16 @@ public async Task<IActionResult> ExportBcfFileAsync([FromBody] BcfFile bcfFile)

using var fs = System.IO.File.Create(fileSaveSelectResult);
await bcfFileResult.CopyToAsync(fs);
return Ok(new
return Ok(new BcfFileWrapper
{
FileName = fileSaveSelectResult
FileName = fileSaveSelectResult,
BcfFile = bcfFile
});
}

[HttpPost("save")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> SaveBcfFileAsync([FromBody] BcfFileWrapper bcfFileWrapper)
{
var bcfFileResult = new BcfExportService().ExportBcfFile(bcfFileWrapper.BcfFile);
Expand All @@ -117,6 +128,9 @@ public async Task<IActionResult> SaveBcfFileAsync([FromBody] BcfFileWrapper bcfF
}

[HttpPost("merge")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BcfFile), (int)HttpStatusCode.OK)]
public async Task<IActionResult> MergeBcfFilesAsync()
{
var electronWindow = _electronWindowProvider.BrowserWindow;
Expand Down
2 changes: 2 additions & 0 deletions src/IPA.Bcfier.App/Controllers/DocumentationController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ElectronNET.API;
using Microsoft.AspNetCore.Mvc;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
Expand All @@ -8,6 +9,7 @@ namespace IPA.Bcfier.App.Controllers
public class DocumentationController : ControllerBase
{
[HttpPost("")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> OpenDocumentation()
{
await Electron.Shell.OpenExternalAsync("https://docs.dangl-it.com/Projects/IPA.BCFier");
Expand Down
25 changes: 25 additions & 0 deletions src/IPA.Bcfier.App/Controllers/FrontendConfigController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using IPA.Bcfier.App.Configuration;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
Expand Down Expand Up @@ -35,6 +36,30 @@ public IActionResult GetFrontendConfigAsJavaScript([FromQuery] string? timestamp
return GetContentResultForFrontendConfig();
}

[HttpGet("json")]
[ProducesResponseType(typeof(FrontendConfig), (int)HttpStatusCode.OK)]
public IActionResult GetFrontendConfig([FromQuery] string? timestamp)
{
if (!string.IsNullOrWhiteSpace(timestamp))
{
HttpContext.Response
.GetTypedHeaders()
.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(365)
};
}


if (_frontendConfig == null)
{
InitializeFrontendConfig();
}

return Ok(_frontendConfig);
}

private ContentResult GetContentResultForFrontendConfig()
{
if (_frontendConfig == null)
Expand Down
3 changes: 3 additions & 0 deletions src/IPA.Bcfier.App/Controllers/SettingsController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using IPA.Bcfier.Models.Settings;
using IPA.Bcfier.Services;
using Microsoft.AspNetCore.Mvc;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
Expand All @@ -16,13 +17,15 @@ public SettingsController(SettingsService settingsService)
}

[HttpGet("")]
[ProducesResponseType(typeof(Settings), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetSettingsAsync()
{
var settings = await _settingsService.LoadSettingsAsync();
return Ok(settings);
}

[HttpPut("")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> SaveSettingsAsync([FromBody] Settings settings)
{
await _settingsService.SaveSettingsAsync(settings);
Expand Down
6 changes: 6 additions & 0 deletions src/IPA.Bcfier.App/Controllers/ViewpointsController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Dangl.Data.Shared;
using IPA.Bcfier.Ipc;
using IPA.Bcfier.Models.Bcf;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
Expand All @@ -10,6 +12,8 @@ namespace IPA.Bcfier.App.Controllers
public class ViewpointsController : ControllerBase
{
[HttpPost("visualization")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> ShowViewpointAsync([FromBody] BcfViewpoint viewpoint)
{
using var ipcHandler = new IpcHandler(thisAppName: "BcfierApp", otherAppName: "Revit");
Expand Down Expand Up @@ -46,6 +50,8 @@ await ipcHandler.SendMessageAsync(JsonConvert.SerializeObject(new IpcMessage
}

[HttpPost("")]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BcfViewpoint), (int)HttpStatusCode.OK)]
public async Task<IActionResult> CreateViewpointAsync()
{
using var ipcHandler = new IpcHandler(thisAppName: "BcfierApp", otherAppName: "Revit");
Expand Down
1 change: 1 addition & 0 deletions src/IPA.Bcfier.App/IPA.Bcfier.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageReference Include="ElectronNET.API" Version="23.6.2" />
<PackageReference Include="Dangl.Data.Shared.AspNetCore" Version="2.5.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.3" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions src/IPA.Bcfier.App/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ public void ConfigureServices(IServiceCollection services)
{
options.SuppressModelStateInvalidFilter = true;
});

services.AddBcfierSwagger();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseRouting();

app.UseBcfierSwaggerUi();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
Expand Down
2 changes: 1 addition & 1 deletion src/ipa-bcfier-ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BcfFile, BcfFileWrapper } from '../generated/models';
import { BcfFile, BcfFileWrapper } from './generated-client/generated-client';
import { Component, OnDestroy, ViewChild } from '@angular/core';
import { MatTabGroup, MatTabsModule } from '@angular/material/tabs';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
MatDialogRef,
} from '@angular/material/dialog';

import { BcfViewpoint } from '../../../generated/models';
import { BcfViewpoint } from '../../generated-client/generated-client';
import { Component } from '@angular/core';
import { DropzoneCdkModule } from '@ngx-dropzone/cdk';
import { DropzoneMaterialModule } from '@ngx-dropzone/material';
Expand Down
Loading

0 comments on commit 168b24c

Please sign in to comment.