Repository navigation
Expand file tree
/
Copy pathMigrationController.cs
More file actions
263 lines (240 loc) · 10.6 KB
/
Copy pathMigrationController.cs
File metadata and controls
263 lines (240 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
using PVAD.WebApi.Services;
using Microsoft.AspNetCore.Mvc;
using PVAD.WebApi.Security;
using PdfTrustMigrationReport = PVAD.Migration.Abstractions.PdfTrustMigrationReport;
namespace PVAD.WebApi.Controllers;
[ApiController]
[Route("api/migration")]
[Route("api/pvad/migration")]
public class MigrationController : ControllerBase
{
private readonly MigrationService _service;
private readonly ReportMigrationService _reportService;
public MigrationController(MigrationService service, ReportMigrationService? reportService = null)
{
_service = service;
_reportService = reportService ?? new ReportMigrationService();
}
/// <summary>Returns metadata for all supported migration frameworks.</summary>
[HttpGet("frameworks")]
[ProducesResponseType(typeof(IEnumerable<object>), 200)]
public IActionResult GetFrameworks()
{
AddCompatibilityHeaders("/api/pvad/v1/migrations/catalog");
var frameworks = _service.GetFrameworks()
.Where(CanReadFramework)
.Select(f => new
{
id = f.Id,
name = f.Name,
status = f.Status,
description = f.Description,
kind = f.Kind,
domain = f.Domain,
migrationKind = f.MigrationKind,
provider = f.Provider,
requiredPermission = string.Equals(f.MigrationKind, "viewer", StringComparison.OrdinalIgnoreCase)
? PvadDesignerMigrationPermissions.ResolveViewerProvider(f.Id)
: PvadDesignerMigrationPermissions.ResolveCodeProvider(f.Id, f.Domain)
});
return Ok(frameworks);
}
/// <summary>Converts source code from a third-party PDF framework to PVAD.Pdf C# code.</summary>
[HttpPost("convert")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult Convert([FromBody] MigrationRequest request)
{
AddCompatibilityHeaders("/api/pvad/v1/migrations/{domain}/code/{provider}/convert");
if (string.IsNullOrWhiteSpace(request.Framework))
return BadRequest(new { error = "Field 'framework' is required." });
if (string.IsNullOrWhiteSpace(request.SourceCode))
return BadRequest(new { error = "Field 'sourceCode' is required." });
if (AuthorizeCodeMigration(request.Framework) is { } permissionError)
return permissionError;
try
{
var result = _service.Convert(request.Framework, request.SourceCode);
return Ok(new
{
pvadCode = result.PvadCode,
pxaCode = result.PvadCode,
summary = new
{
convertedCount = result.Summary.ConvertedCount,
warningCount = result.Summary.WarningCount,
errorCount = result.Summary.ErrorCount,
totalDiagnostics = result.Summary.TotalDiagnostics
},
diagnostics = result.Diagnostics.Select(d => new
{
code = d.Code,
severity = d.Severity,
message = d.Message
}),
pdfTrust = CreatePdfTrustResponse(result.PdfTrust)
});
}
catch (ArgumentException ex)
{
return BadRequest(new { error = ex.Message });
}
}
private static object? CreatePdfTrustResponse(PdfTrustMigrationReport? report)
{
if (report is null)
return null;
return new
{
providerKey = report.ProviderKey,
migrationKind = report.MigrationKind,
overallDisposition = report.OverallDisposition.ToString(),
evidence = report.Evidence.Select(item => new
{
capability = item.Capability.ToString(),
disposition = item.Disposition.ToString(),
sourceOperation = item.SourceOperation,
targetOperation = item.TargetOperation,
diagnosticId = item.DiagnosticId,
documentationPath = item.DocumentationPath,
providerOptions = item.ProviderOptions
}),
manualActions = report.ManualActions.Select(action => new
{
actionId = action.ActionId,
capability = action.Capability.ToString(),
disposition = action.Disposition.ToString(),
sourceOperation = action.SourceOperation,
targetOperation = action.TargetOperation,
documentationPath = action.DocumentationPath
})
};
}
/// <summary>
/// Converts a report-designer layout into a PVAD design (DesignExportDto) the visual designer can
/// open. Auto-detects the format: an RDL/RDLC report (SSRS, Syncfusion — root <c><Report></c> in
/// an RDL namespace), an ActiveReports <c>.rpx</c> section report (root <c><Report></c> with
/// <c><Sections></c>), otherwise a DevExpress XtraReport (a C# Report Designer class or a
/// serialized <c>.repx</c> XML layout). Returns the design plus migration diagnostics.
/// </summary>
[HttpPost("report-to-design")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult ReportToDesign([FromBody] ReportToDesignRequest request)
{
AddCompatibilityHeaders("/api/pvad/v1/migrations/report/designer/auto/convert");
try
{
var detectedProvider = string.IsNullOrWhiteSpace(request.SourceCode)
? "auto"
: ReportMigrationService.DetectProvider(request.SourceCode);
if (AuthorizeReportMigration(detectedProvider) is { } permissionError)
return permissionError;
var result = _reportService.Convert(new ReportMigrationInput(
"auto",
request.SourceCode,
request.Resources,
request.ResourceXml,
request.SourceBase64));
return Ok(new
{
design = result.Design,
diagnostics = result.Diagnostics.Select(d => new
{
code = d.Id,
severity = d.Severity.ToString(),
message = d.Message
})
});
}
catch (ArgumentException ex)
{
return BadRequest(new { error = ex.Message });
}
}
/// <summary>Converts source code and returns a rendered preview as binary — a PDF for PDF-code
/// migrations, or an HTML grid for spreadsheet-code migrations.</summary>
[HttpPost("preview")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult Preview([FromBody] MigrationRequest request)
{
AddCompatibilityHeaders("/api/pvad/v1/migrations/{domain}/code/{provider}/preview");
if (string.IsNullOrWhiteSpace(request.Framework))
return BadRequest(new { error = "Field 'framework' is required." });
if (string.IsNullOrWhiteSpace(request.SourceCode))
return BadRequest(new { error = "Field 'sourceCode' is required." });
if (AuthorizeCodeMigration(request.Framework) is { } permissionError)
return permissionError;
try
{
var bytes = _service.GeneratePreview(request.Framework, request.SourceCode);
return _service.GetKind(request.Framework) == "spreadsheet"
? File(bytes, "text/html; charset=utf-8")
: File(bytes, "application/pdf", "migration-preview.pdf");
}
catch (ArgumentException ex)
{
return BadRequest(new { error = ex.Message });
}
}
private bool CanReadFramework(FrameworkInfo framework)
{
if (!IsDesignerRequest()) return true;
var domainPermission = string.Equals(framework.Domain, "spreadsheet", StringComparison.OrdinalIgnoreCase)
? PvadDesignerPermissions.MigrationSpreadsheetRead
: PvadDesignerPermissions.MigrationPdfRead;
return HasPermission(domainPermission);
}
private IActionResult? AuthorizeCodeMigration(string framework)
{
if (!IsDesignerRequest()) return null;
var domain = _service.GetKind(framework);
var domainPermission = string.Equals(domain, "spreadsheet", StringComparison.OrdinalIgnoreCase)
? PvadDesignerPermissions.MigrationSpreadsheetExecute
: PvadDesignerPermissions.MigrationPdfExecute;
var providerPermission = PvadDesignerMigrationPermissions.ResolveCodeProvider(framework, domain);
if (!HasPermission(domainPermission)) return PermissionDenied(domainPermission);
return providerPermission is null || HasPermission(providerPermission)
? null
: PermissionDenied(providerPermission);
}
private IActionResult? AuthorizeReportMigration(string provider)
{
if (!IsDesignerRequest()) return null;
if (!HasPermission(PvadDesignerPermissions.MigrationReportExecute))
return PermissionDenied(PvadDesignerPermissions.MigrationReportExecute);
var providerPermission = PvadDesignerMigrationPermissions.ResolveReportProvider(provider);
return providerPermission is null || HasPermission(providerPermission)
? null
: PermissionDenied(providerPermission);
}
private IActionResult PermissionDenied(string permission) => StatusCode(
StatusCodes.Status403Forbidden,
new
{
title = "Designer migration permission required",
detail = "Your organization role does not allow this migration provider.",
code = "PXA_DESIGNER_PERMISSION_REQUIRED",
requiredPermission = permission,
});
private bool IsDesignerRequest() => string.Equals(
Request.Headers["X-PVAD-Application"].ToString(),
"designer",
StringComparison.OrdinalIgnoreCase);
private bool HasPermission(string permission) =>
User.HasClaim(PvadClaimTypes.Permission, permission);
private void AddCompatibilityHeaders(string successor)
{
if (ControllerContext.HttpContext is null) return;
Response.Headers["Deprecation"] = "true";
Response.Headers["Sunset"] = "Thu, 30 Sep 2027 23:59:59 GMT";
Response.Headers.Link = $"<{successor}>; rel=\"successor-version\"";
}
}
public sealed record MigrationRequest(string Framework, string SourceCode);
public sealed record ReportToDesignRequest(
string SourceCode,
Dictionary<string, string>? Resources = null,
string? ResourceXml = null,
string? SourceBase64 = null);