mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
+ Added support for API versioning in Swagger UI
+ Added Swagger API Versioning Sample (swagger_api_versioning_primer)
This commit is contained in:
parent
b5ddf9fe6a
commit
347c5fb2fd
10
README.md
10
README.md
@ -555,9 +555,9 @@ The current beta release is named 3.2.2-nitrogen. If you want to stay on the-edg
|
||||
|
||||
|
||||
|
||||
- ⚡New! Added new default parameter to `TMVCActiveRecord.RemoveDefaultConnection` and `TMVCActiveRecord.RemoveConnection` to avoid exceptions in case of not initialized connection.
|
||||
- ⚡ New! Added new default parameter to `TMVCActiveRecord.RemoveDefaultConnection` and `TMVCActiveRecord.RemoveConnection` to avoid exceptions in case of not initialized connection.
|
||||
|
||||
- ⚡New! Added the new `MVCOwned` attribute which allows to auto-create nested objects in the deserialization phase. This will not change the current behavior, you ned to explocitly define a property (or a field) as `MVCOwned` to allows the serialization to create or destroy object for you.
|
||||
- ⚡ New! Added the new `MVCOwned` attribute which allows to auto-create nested objects in the deserialization phase. This will not change the current behavior, you ned to explocitly define a property (or a field) as `MVCOwned` to allows the serialization to create or destroy object for you.
|
||||
|
||||
- ✅ Improved! `Context.Data` property is now created on-demand using a lazy loading approach (expect an overall speed improvement).
|
||||
|
||||
@ -565,6 +565,8 @@ The current beta release is named 3.2.2-nitrogen. If you want to stay on the-edg
|
||||
|
||||
- ✅ Improved `MVCAREntitiesGenerator` project - now it can better handle border cases, field names which collide with Delphi keywords and a big number of tables.
|
||||
|
||||
- ✅ Improved error handling for JSON-RPC APIs (Thanks to [David Moorhouse](https://github.com/fastbike)). More info [here](https://github.com/danieleteti/delphimvcframework/issues/538).
|
||||
|
||||
- ⚡ New! Added `ActiveRecordConnectionRegistry.AddDefaultConnection(const aConnetionDefName: String)`. The connection definition **must** be known by FireDAC. This method simplifies the most common scenario shown below.
|
||||
|
||||
```delphi
|
||||
@ -576,9 +578,9 @@ The current beta release is named 3.2.2-nitrogen. If you want to stay on the-edg
|
||||
end;
|
||||
```
|
||||
|
||||
- ⚡New! Added `ToJSONObject` and `ToJSONArray` to the `IMVCRESTResponse`. These methods automatically parse the response body and return a `TJSONObject` or a `TJSONArray` respectively. These methods work as a factory - the client code need to handle returned istances. Is the body is not compatible with the request (a.k.a. is not a JSONObject in case of `ToJSONObject`, or is not a JSONArray in case of `ToJSONArray`) an exception is raised.
|
||||
- ⚡ New! Added `ToJSONObject` and `ToJSONArray` to the `IMVCRESTResponse`. These methods automatically parse the response body and return a `TJSONObject` or a `TJSONArray` respectively. These methods work as a factory - the client code need to handle returned istances. Is the body is not compatible with the request (a.k.a. is not a JSONObject in case of `ToJSONObject`, or is not a JSONArray in case of `ToJSONArray`) an exception is raised.
|
||||
|
||||
- ⚡New! Added `TMVCJWTBlackListMiddleware` to allow black-listing and (a sort of) logout for a JWT based authentication. This middleware **must** be registered **after** the `TMVCJWTAuthenticationMiddleware`.
|
||||
- ⚡ New! Added `TMVCJWTBlackListMiddleware` to allow black-listing and (a sort of) logout for a JWT based authentication. This middleware **must** be registered **after** the `TMVCJWTAuthenticationMiddleware`.
|
||||
|
||||
> This middleware provides 2 events named: `OnAcceptToken` (invoked when a request contains a token - need to returns true/false if the token is still accepted by the server or not) and `OnNewJWTToBlackList` (invoked when a client ask to blacklist its current token). There is a new sample available which shows the funtionalities: `samples\middleware_jwtblacklist`.
|
||||
|
||||
|
75
samples/swagger_api_versioning_primer/EntitiesU.pas
Normal file
75
samples/swagger_api_versioning_primer/EntitiesU.pas
Normal file
@ -0,0 +1,75 @@
|
||||
unit EntitiesU;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
MVCFramework.Serializer.Commons,
|
||||
System.Generics.Collections,
|
||||
MVCFramework.Swagger.Commons;
|
||||
|
||||
type
|
||||
|
||||
[MVCNameCase(ncLowerCase)]
|
||||
TCustomer = class
|
||||
private
|
||||
fID: Integer;
|
||||
fCustomerName: String;
|
||||
fCountry: String;
|
||||
fContactName: String;
|
||||
public
|
||||
function IsValid: Boolean;
|
||||
[MVCSwagJSONSchemaField(stInteger, 'id', 'Customer ID', True)]
|
||||
property ID: Integer read fID write fID;
|
||||
[MVCSwagJSONSchemaField(stString, 'customername', 'The registered name of the customer', True)]
|
||||
property CustomerName: String read fCustomerName write fCustomerName;
|
||||
[MVCSwagJSONSchemaField(stString, 'contactname', 'Fullname of the customer''s contact', False)]
|
||||
property ContactName: String read fContactName write fContactName;
|
||||
[MVCSwagJSONSchemaField(stString, 'country', 'The country where the company is registered', True)]
|
||||
property Country: String read fCountry write fCountry;
|
||||
end;
|
||||
|
||||
[MVCNameCase(ncLowerCase)]
|
||||
TCustomers = class(TObjectList<TCustomer>)
|
||||
end;
|
||||
|
||||
function GetCustomer(const ID: Integer): TCustomer;
|
||||
function GetCustomers: TCustomers;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Math, System.SysUtils;
|
||||
|
||||
const
|
||||
CONTACT_NAMES: array [0 .. 2] of string = ('Daniele Teti', 'Peter Parker', 'Bruce Banner');
|
||||
CONTRIES: array [0 .. 2] of string = ('ITALY', 'USA', 'United Kingdom');
|
||||
CUSTOMER_NAMES: array [0 .. 2] of string = ('bit Time Professionals s.r.l.', 'Spidey Ltd.', 'Green Power Corp.');
|
||||
|
||||
function GetCustomer(const ID: Integer): TCustomer;
|
||||
begin
|
||||
Result := TCustomer.Create;
|
||||
Result.ID := ID;
|
||||
Result.CustomerName := CUSTOMER_NAMES[Random(3)];
|
||||
Result.Country := CONTRIES[Random(3)];
|
||||
Result.ContactName := CONTACT_NAMES[Random(3)];
|
||||
end;
|
||||
|
||||
function GetCustomers: TCustomers;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := TCustomers.Create(True);
|
||||
for I := 1 to 3 do
|
||||
begin
|
||||
Result.Add(GetCustomer(I));
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TCustomer }
|
||||
|
||||
function TCustomer.IsValid: Boolean;
|
||||
begin
|
||||
Result := not(CustomerName.IsEmpty or Country.IsEmpty);
|
||||
end;
|
||||
|
||||
end.
|
126
samples/swagger_api_versioning_primer/MyControllerU.pas
Normal file
126
samples/swagger_api_versioning_primer/MyControllerU.pas
Normal file
@ -0,0 +1,126 @@
|
||||
unit MyControllerU;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
MVCFramework, MVCFramework.Commons, MVCFramework.DataSet.Utils, MVCFramework.Swagger.Commons, EntitiesU;
|
||||
|
||||
type
|
||||
[MVCPath('/api/v1')]
|
||||
TMyControllerV1 = class(TMVCController)
|
||||
public
|
||||
[MVCSwagSummary('Customers', 'Get all customers', 'getCustomers')]
|
||||
[MVCSwagResponses(200, 'Customers', TCustomer, True)]
|
||||
[MVCPath('/customers')]
|
||||
[MVCHTTPMethod([httpGET])]
|
||||
procedure GetCustomers; virtual;
|
||||
|
||||
[MVCSwagSummary('Customers', 'Get a customer', 'getCustomerById')]
|
||||
[MVCSwagParam(plPath, 'id', 'Customer ID', ptInteger, True)]
|
||||
[MVCSwagResponses(200, 'Customer', TCustomer)]
|
||||
[MVCSwagResponses(404, 'Customer not found', TMVCErrorResponse)]
|
||||
[MVCPath('/customers/($id)')]
|
||||
[MVCHTTPMethod([httpGET])]
|
||||
procedure GetCustomer(id: Integer);
|
||||
|
||||
[MVCSwagSummary('Customers', 'Create a customer', 'createCustomers')]
|
||||
[MVCSwagParam(plBody, 'Customer', 'Customer JSON Object', TCustomer)]
|
||||
[MVCSwagResponses(201, 'Customer created')]
|
||||
[MVCSwagResponses(HTTP_STATUS.BadRequest, 'Invalid request', TMVCErrorResponse)]
|
||||
[MVCPath('/customers')]
|
||||
[MVCHTTPMethod([httpPOST])]
|
||||
procedure CreateCustomer;
|
||||
|
||||
[MVCSwagSummary('Customers', 'Update a customer', 'updateCustomer')]
|
||||
[MVCSwagParam(plBody, 'Customer', 'Customer JSON Object', TCustomer, ptNotDefined, True)]
|
||||
[MVCSwagParam(plPath, 'id', 'Customer ID to update', ptInteger, True)]
|
||||
[MVCSwagResponses(200, 'Customer updated')]
|
||||
[MVCSwagResponses(HTTP_STATUS.BadRequest, 'Invalid request', TMVCErrorResponse)]
|
||||
[MVCPath('/customers/($id)')]
|
||||
[MVCHTTPMethod([httpPUT])]
|
||||
procedure UpdateCustomer(id: Integer);
|
||||
|
||||
[MVCSwagSummary('Customers', 'Delete a customer', 'deleteCustomer')]
|
||||
[MVCPath('/customers/($id)')]
|
||||
[MVCHTTPMethod([httpDELETE])]
|
||||
procedure DeleteCustomer(id: Integer);
|
||||
|
||||
end;
|
||||
|
||||
[MVCPath('/api/v2')]
|
||||
TMyControllerV2 = class(TMyControllerV1)
|
||||
public
|
||||
[MVCSwagSummary('Customers', 'Get all customers with extended criteria', 'getCustomersWithCriteria')]
|
||||
[MVCSwagResponses(200, 'Customers', TCustomer, True)]
|
||||
[MVCPath('/customers')]
|
||||
[MVCHTTPMethod([httpGET])]
|
||||
procedure GetCustomers; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils, MVCFramework.Logger, System.StrUtils, JsonDataObjects;
|
||||
|
||||
procedure TMyControllerV1.GetCustomers;
|
||||
begin
|
||||
Render<TCustomer>(EntitiesU.GetCustomers);
|
||||
end;
|
||||
|
||||
procedure TMyControllerV1.GetCustomer(id: Integer);
|
||||
begin
|
||||
if id = 42 then
|
||||
begin
|
||||
raise EMVCException.Create(HTTP_STATUS.NotFound, 'Customer not found');
|
||||
end;
|
||||
Render(EntitiesU.GetCustomer(id));
|
||||
end;
|
||||
|
||||
procedure TMyControllerV1.CreateCustomer;
|
||||
var
|
||||
lCustomer: TCustomer;
|
||||
begin
|
||||
lCustomer := Self.Context.Request.BodyAs<TCustomer>;
|
||||
try
|
||||
if not lCustomer.IsValid then
|
||||
begin
|
||||
raise EMVCException.Create(HTTP_STATUS.BadRequest, 'Customer not valid');
|
||||
end;
|
||||
// do something smart with lCustomer...
|
||||
Render201Created();
|
||||
finally
|
||||
lCustomer.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMyControllerV1.UpdateCustomer(id: Integer);
|
||||
var
|
||||
lCustomer: TCustomer;
|
||||
begin
|
||||
lCustomer := Self.Context.Request.BodyAs<TCustomer>;
|
||||
try
|
||||
lCustomer.ID := id; //dont be confident of the user!
|
||||
if not lCustomer.IsValid then
|
||||
begin
|
||||
raise EMVCException.Create(HTTP_STATUS.BadRequest, 'Customer not valid');
|
||||
end;
|
||||
// do something smart with lCustomer...
|
||||
Render(lCustomer, False);
|
||||
finally
|
||||
lCustomer.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMyControllerV1.DeleteCustomer(id: Integer);
|
||||
begin
|
||||
ResponseStatus(200);
|
||||
end;
|
||||
|
||||
{ TMyControllerV2 }
|
||||
|
||||
procedure TMyControllerV2.GetCustomers;
|
||||
begin
|
||||
GetCustomers;
|
||||
end;
|
||||
|
||||
end.
|
99
samples/swagger_api_versioning_primer/SwaggerPrimer.dpr
Normal file
99
samples/swagger_api_versioning_primer/SwaggerPrimer.dpr
Normal file
@ -0,0 +1,99 @@
|
||||
program SwaggerPrimer;
|
||||
|
||||
{$APPTYPE CONSOLE}
|
||||
|
||||
uses
|
||||
{$IFDEF MSWINDOWS}
|
||||
WinAPI.ShellAPI,
|
||||
WinAPI.Windows,
|
||||
{$ENDIF}
|
||||
System.SysUtils,
|
||||
MVCFramework.Logger,
|
||||
MVCFramework.Commons,
|
||||
MVCFramework.REPLCommandsHandlerU,
|
||||
Web.ReqMulti,
|
||||
Web.WebReq,
|
||||
Web.WebBroker,
|
||||
IdContext,
|
||||
IdHTTPWebBrokerBridge,
|
||||
MyControllerU in 'MyControllerU.pas',
|
||||
WebModuleU in 'WebModuleU.pas' {MyWebModule: TWebModule} ,
|
||||
EntitiesU in 'EntitiesU.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
procedure RunServer(APort: Integer);
|
||||
var
|
||||
LServer: TIdHTTPWebBrokerBridge;
|
||||
LCustomHandler: TMVCCustomREPLCommandsHandler;
|
||||
LCmd: string;
|
||||
begin
|
||||
Writeln('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION);
|
||||
LCmd := 'start';
|
||||
if ParamCount >= 1 then
|
||||
LCmd := ParamStr(1);
|
||||
|
||||
LCustomHandler :=
|
||||
function(const Value: String; const Server: TIdHTTPWebBrokerBridge; out Handled: Boolean)
|
||||
: THandleCommandResult
|
||||
begin
|
||||
Handled := False;
|
||||
Result := THandleCommandResult.Unknown;
|
||||
end;
|
||||
|
||||
LServer := TIdHTTPWebBrokerBridge.Create(nil);
|
||||
try
|
||||
LServer.OnParseAuthentication := TMVCParseAuthentication.OnParseAuthentication;
|
||||
LServer.DefaultPort := APort;
|
||||
LServer.MaxConnections := 0;
|
||||
LServer.ListenQueue := 200;
|
||||
|
||||
Writeln('Write "quit" or "exit" to shutdown the server');
|
||||
repeat
|
||||
if LCmd.IsEmpty then
|
||||
begin
|
||||
Write('-> ');
|
||||
ReadLn(LCmd)
|
||||
end;
|
||||
try
|
||||
case HandleCommand(LCmd.ToLower, LServer, LCustomHandler) of
|
||||
THandleCommandResult.Continue:
|
||||
begin
|
||||
Continue;
|
||||
end;
|
||||
THandleCommandResult.Break:
|
||||
begin
|
||||
Break;
|
||||
end;
|
||||
THandleCommandResult.Unknown:
|
||||
begin
|
||||
REPLEmit('Unknown command: ' + LCmd);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
LCmd := '';
|
||||
end;
|
||||
until False;
|
||||
|
||||
finally
|
||||
LServer.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
ReportMemoryLeaksOnShutdown := True;
|
||||
IsMultiThread := True;
|
||||
try
|
||||
if WebRequestHandler <> nil then
|
||||
WebRequestHandler.WebModuleClass := WebModuleClass;
|
||||
WebRequestHandlerProc.MaxConnections := 1024;
|
||||
{$IFDEF MSWINDOWS}
|
||||
ShellExecute(0, PChar('open'), PChar('http://localhost:8080/swagger'), nil, nil, sw_show);
|
||||
{$ENDIF}
|
||||
RunServer(8080);
|
||||
except
|
||||
on E: Exception do
|
||||
Writeln(E.ClassName, ': ', E.Message);
|
||||
end;
|
||||
|
||||
end.
|
1184
samples/swagger_api_versioning_primer/SwaggerPrimer.dproj
Normal file
1184
samples/swagger_api_versioning_primer/SwaggerPrimer.dproj
Normal file
File diff suppressed because it is too large
Load Diff
7
samples/swagger_api_versioning_primer/WebModuleU.dfm
Normal file
7
samples/swagger_api_versioning_primer/WebModuleU.dfm
Normal file
@ -0,0 +1,7 @@
|
||||
object MyWebModule: TMyWebModule
|
||||
OnCreate = WebModuleCreate
|
||||
OnDestroy = WebModuleDestroy
|
||||
Actions = <>
|
||||
Height = 230
|
||||
Width = 415
|
||||
end
|
101
samples/swagger_api_versioning_primer/WebModuleU.pas
Normal file
101
samples/swagger_api_versioning_primer/WebModuleU.pas
Normal file
@ -0,0 +1,101 @@
|
||||
unit WebModuleU;
|
||||
|
||||
interface
|
||||
|
||||
uses System.SysUtils,
|
||||
System.Classes,
|
||||
Web.HTTPApp,
|
||||
MVCFramework, MVCFramework.Swagger.Commons;
|
||||
|
||||
type
|
||||
TMyWebModule = class(TWebModule)
|
||||
procedure WebModuleCreate(Sender: TObject);
|
||||
procedure WebModuleDestroy(Sender: TObject);
|
||||
private
|
||||
FMVC: TMVCEngine;
|
||||
function GetSwagInfoV1: TMVCSwaggerInfo;
|
||||
function GetSwagInfoV2: TMVCSwaggerInfo;
|
||||
public
|
||||
{ Public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
WebModuleClass: TComponentClass = TMyWebModule;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
uses MyControllerU, System.IOUtils, MVCFramework.Commons, MVCFramework.Middleware.Compression,
|
||||
MVCFramework.Middleware.Swagger, MVCFramework.Middleware.CORS,
|
||||
MVCFramework.Middleware.StaticFiles;
|
||||
|
||||
function TMyWebModule.GetSwagInfoV1: TMVCSwaggerInfo;
|
||||
begin
|
||||
Result.Title := 'DMVCFramework Swagger Sample (Version1)';
|
||||
Result.Version := 'v1';
|
||||
Result.Description := 'SwaggerAPI Versioning V1' + DMVCFRAMEWORK_VERSION;
|
||||
Result.ContactName := 'Daniele Teti';
|
||||
Result.ContactEmail := 'd.teti@bittime.it';
|
||||
Result.ContactUrl := 'http://www.danieleteti.it';
|
||||
Result.LicenseName := 'Apache v2';
|
||||
Result.LicenseUrl := 'https://www.apache.org/licenses/LICENSE-2.0';
|
||||
end;
|
||||
|
||||
function TMyWebModule.GetSwagInfoV2: TMVCSwaggerInfo;
|
||||
begin
|
||||
Result.Title := 'DMVCFramework Swagger Sample (Version2)';
|
||||
Result.Version := 'v2';
|
||||
Result.Description := 'SwaggerAPI Versioning V1' + DMVCFRAMEWORK_VERSION;
|
||||
Result.ContactName := 'Daniele Teti';
|
||||
Result.ContactEmail := 'd.teti@bittime.it';
|
||||
Result.ContactUrl := 'http://www.danieleteti.it';
|
||||
Result.LicenseName := 'Apache v2';
|
||||
Result.LicenseUrl := 'https://www.apache.org/licenses/LICENSE-2.0';
|
||||
end;
|
||||
|
||||
procedure TMyWebModule.WebModuleCreate(Sender: TObject);
|
||||
begin
|
||||
FMVC := TMVCEngine.Create(Self,
|
||||
procedure(Config: TMVCConfig)
|
||||
begin
|
||||
// session timeout (0 means session cookie)
|
||||
Config[TMVCConfigKey.SessionTimeout] := '0';
|
||||
// default content-type
|
||||
Config[TMVCConfigKey.DefaultContentType] := TMVCConstants.DEFAULT_CONTENT_TYPE;
|
||||
// default content charset
|
||||
Config[TMVCConfigKey.DefaultContentCharset] := TMVCConstants.DEFAULT_CONTENT_CHARSET;
|
||||
// unhandled actions are permitted?
|
||||
Config[TMVCConfigKey.AllowUnhandledAction] := 'false';
|
||||
// default view file extension
|
||||
Config[TMVCConfigKey.DefaultViewFileExtension] := 'html';
|
||||
// view path
|
||||
Config[TMVCConfigKey.ViewPath] := 'templates';
|
||||
// Max Record Count for automatic Entities CRUD
|
||||
Config[TMVCConfigKey.MaxEntitiesRecordCount] := '20';
|
||||
// Enable Server Signature in response
|
||||
Config[TMVCConfigKey.ExposeServerSignature] := 'true';
|
||||
// Max request size in bytes
|
||||
Config[TMVCConfigKey.MaxRequestSize] := IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE);
|
||||
end);
|
||||
FMVC.AddController(TMyControllerV1);
|
||||
FMVC.AddController(TMyControllerV2);
|
||||
|
||||
FMVC.AddMiddleware(TMVCCORSMiddleware.Create);
|
||||
FMVC.AddMiddleware(TMVCStaticFilesMiddleware.Create(
|
||||
'/swagger', { StaticFilesPath }
|
||||
'.\www', { DocumentRoot }
|
||||
'index.html' { IndexDocument }
|
||||
));
|
||||
FMVC.AddMiddleware(TMVCSwaggerMiddleware.Create(FMVC, GetSwagInfoV1, '/api/swagger-v1.json',
|
||||
'Method for authentication using JSON Web Token (JWT)', False, '','','/api/v1'));
|
||||
FMVC.AddMiddleware(TMVCSwaggerMiddleware.Create(FMVC, GetSwagInfoV2, '/api/swagger-v2.json',
|
||||
'Method for authentication using JSON Web Token (JWT)', False, '','','/api/v2'));
|
||||
end;
|
||||
|
||||
procedure TMyWebModule.WebModuleDestroy(Sender: TObject);
|
||||
begin
|
||||
FMVC.Free;
|
||||
end;
|
||||
|
||||
end.
|
BIN
samples/swagger_api_versioning_primer/bin/www/favicon-16x16.png
Normal file
BIN
samples/swagger_api_versioning_primer/bin/www/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
samples/swagger_api_versioning_primer/bin/www/favicon-32x32.png
Normal file
BIN
samples/swagger_api_versioning_primer/bin/www/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
65
samples/swagger_api_versioning_primer/bin/www/index.html
Normal file
65
samples/swagger_api_versioning_primer/bin/www/index.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
//url: "/api/swagger.json",
|
||||
urls: [
|
||||
{url:"/api/swagger-v1.json",name:"V1"},
|
||||
{url:"/api/swagger-v2.json",name:"V2"}
|
||||
],
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl,
|
||||
SwaggerUIBundle.plugins.Topbar
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,75 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -50,6 +50,7 @@ type
|
||||
fEnableBasicAuthentication: Boolean;
|
||||
fHost: string;
|
||||
fBasePath: string;
|
||||
fPathFilter: string;
|
||||
procedure DocumentApiInfo(const ASwagDoc: TSwagDoc);
|
||||
procedure DocumentApiSettings(AContext: TWebContext; ASwagDoc: TSwagDoc);
|
||||
procedure DocumentApiAuthentication(const ASwagDoc: TSwagDoc);
|
||||
@ -59,7 +60,9 @@ type
|
||||
public
|
||||
constructor Create(const AEngine: TMVCEngine; const ASwaggerInfo: TMVCSwaggerInfo;
|
||||
const ASwaggerDocumentationURL: string = '/swagger.json'; const AJWTDescription: string = JWT_DEFAULT_DESCRIPTION;
|
||||
const AEnableBasicAuthentication: Boolean = False; const AHost: string = ''; const ABasePath: string = '');
|
||||
const AEnableBasicAuthentication: Boolean = False;
|
||||
const AHost: string = ''; const ABasePath: string = '';
|
||||
const APathFilter: String = '');
|
||||
destructor Destroy; override;
|
||||
procedure OnBeforeRouting(AContext: TWebContext; var AHandled: Boolean);
|
||||
procedure OnBeforeControllerAction(AContext: TWebContext; const AControllerQualifiedClassName: string;
|
||||
@ -93,7 +96,8 @@ uses
|
||||
|
||||
constructor TMVCSwaggerMiddleware.Create(const AEngine: TMVCEngine; const ASwaggerInfo: TMVCSwaggerInfo;
|
||||
const ASwaggerDocumentationURL, AJWTDescription: string; const AEnableBasicAuthentication: Boolean;
|
||||
const AHost, ABasePath: string);
|
||||
const AHost, ABasePath: string;
|
||||
const APathFilter: String);
|
||||
begin
|
||||
inherited Create;
|
||||
fSwagDocURL := ASwaggerDocumentationURL;
|
||||
@ -103,6 +107,7 @@ begin
|
||||
fEnableBasicAuthentication := AEnableBasicAuthentication;
|
||||
fHost := AHost;
|
||||
fBasePath := ABasePath;
|
||||
fPathFilter := APathFilter;
|
||||
end;
|
||||
|
||||
destructor TMVCSwaggerMiddleware.Destroy;
|
||||
@ -156,8 +161,8 @@ begin
|
||||
end;
|
||||
if lAttr is MVCPathAttribute then
|
||||
begin
|
||||
lPathAttributeFound := True;
|
||||
lControllerPath := MVCPathAttribute(lAttr).Path;
|
||||
lPathAttributeFound := fPathFilter.IsEmpty or lControllerPath.StartsWith(fPathFilter);
|
||||
end;
|
||||
if lAttr is MVCSWAGDefaultModel then
|
||||
begin
|
||||
|
@ -308,7 +308,7 @@ const
|
||||
'}';
|
||||
JWT_DEFAULT_DESCRIPTION = 'For accessing the API a valid JWT token must be passed in all the queries ' +
|
||||
'in the ''Authorization'' header.' + sLineBreak + sLineBreak +
|
||||
'A valid JWT token is generated by the API and retourned as answer of a call ' + 'to the route defined ' +
|
||||
'A valid JWT token is generated by the API and returned as answer of a call ' + 'to the route defined ' +
|
||||
'in the JWT middleware giving a valid username and password.' + sLineBreak + sLineBreak +
|
||||
'The following syntax must be used in the ''Authorization'' header :' + sLineBreak + sLineBreak +
|
||||
' Bearer xxxxxx.yyyyyyy.zzzzzz' + sLineBreak;
|
||||
|
Loading…
Reference in New Issue
Block a user