Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
services
t3o sites
extensions.typo3.org
extensions.typo3.org
Commits
b0ee2fef
Commit
b0ee2fef
authored
Sep 28, 2020
by
Oliver Bartsch
Browse files
Kickstart REST API
parent
dfc64af9
Changes
31
Hide whitespace changes
Inline
Side-by-side
extensions/ter/Classes/Controller/Api/AbstractApiController.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Controller\Api
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Http\Message\ResponseInterface
;
use
Psr\Http\Message\ServerRequestInterface
;
use
Psr\Http\Server\RequestHandlerInterface
;
use
T3o\Ter\Rest\Response\ApiResponseFactory
;
use
T3o\Ter\Rest\RouteArgument\DeepObjectRouteArgumentInterface
;
use
T3o\Ter\Rest\RouteConfiguration
;
use
T3o\Ter\Rest\RouteResultArguments
;
/**
* Receive the request, initialize the response using the ApiResponseFactory,
* validate the route arguments and finally call the operationId of the
* specified request handler.
*
* @see ApiResponseFactory
* @see RouteResultArguments
*/
abstract
class
AbstractApiController
implements
RequestHandlerInterface
{
protected
ApiResponseFactory
$apiResponseFactory
;
protected
RouteConfiguration
$routeConfiguration
;
protected
ServerRequestInterface
$request
;
protected
RouteResultArguments
$routeResultArguments
;
public
function
__construct
(
ApiResponseFactory
$apiResponseFactory
,
RouteConfiguration
$routeConfiguration
)
{
$this
->
apiResponseFactory
=
$apiResponseFactory
;
$this
->
routeConfiguration
=
$routeConfiguration
;
}
public
function
handle
(
ServerRequestInterface
$request
):
ResponseInterface
{
$this
->
request
=
$request
;
if
(
!
(
$request
->
getAttribute
(
'routing'
)
instanceof
RouteResultArguments
))
{
return
$this
->
apiResponseFactory
->
createErrorResponseForRequest
(
$request
,
1600994285
,
'Route arguments are invalid'
);
}
$this
->
routeResultArguments
=
$request
->
getAttribute
(
'routing'
);
$handler
=
$this
->
routeResultArguments
->
getOperationId
();
if
(
!
method_exists
(
$this
,
$handler
))
{
return
$this
->
apiResponseFactory
->
createErrorResponseForRequest
(
$request
,
1601289381
,
'Action couldn\'t be determined'
);
}
$invalidArguments
=
$this
->
validateRouteArguments
(
$this
->
routeResultArguments
->
getRouteArguments
());
if
(
$invalidArguments
!==
[])
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$request
,
[
'status'
=>
400
,
'code'
=>
1601294359
,
'message'
=>
'One ore more arguments are invalid'
,
'invalidArguments'
=>
$invalidArguments
],
400
);
}
return
$this
->
$handler
();
}
protected
function
validateRouteArguments
(
array
$routeArguments
):
array
{
$invalidArguments
=
[];
foreach
(
$routeArguments
as
$routeArgument
)
{
if
(
$routeArgument
instanceof
DeepObjectRouteArgumentInterface
)
{
$subProperties
=
$this
->
validateRouteArguments
(
$routeArgument
->
getProperties
());
if
(
$subProperties
!==
[])
{
$invalidArguments
[
$routeArgument
->
getName
()]
=
$subProperties
;
}
}
if
(
!
$routeArgument
->
isValid
())
{
$invalidArguments
[]
=
$routeArgument
;
}
}
return
$invalidArguments
;
}
}
\ No newline at end of file
extensions/ter/Classes/Controller/Api/ExtensionController.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Controller\Api
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Http\Message\ResponseInterface
;
/**
* Request handler for `/extension` endpoint
*/
final
class
ExtensionController
extends
AbstractApiController
{
public
function
getExtensions
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
checkExtensionKey
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
registerExtensionKey
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
updateExtensionKey
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
deleteExtensionKey
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
getExtensionVersions
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
transferExtensionKey
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
checkExtensionVersion
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
uploadExtenionVersion
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
deleteExtensionVersion
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
updateReviewState
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
}
\ No newline at end of file
extensions/ter/Classes/Controller/Api/OauthController.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Controller\Api
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Http\Message\ResponseInterface
;
/**
* Request handler for `/oauth` endpoint
*/
final
class
OauthController
extends
AbstractApiController
{
public
function
generateAccessToken
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
public
function
invalidateAccessToken
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'handler'
=>
__METHOD__
]);
}
}
\ No newline at end of file
extensions/ter/Classes/Controller/Api/PingController.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Controller\Api
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Http\Message\ResponseInterface
;
/**
* Request handler for `/ping` endpoint
*/
final
class
PingController
extends
AbstractApiController
{
public
function
ping
():
ResponseInterface
{
return
$this
->
apiResponseFactory
->
createResponseForRequest
(
$this
->
request
,
[
'endpoints'
=>
array_keys
(
$this
->
routeConfiguration
->
getSchema
()[
'paths'
]
??
[])]
);
}
}
\ No newline at end of file
extensions/ter/Classes/Middleware/RestRouteDispatcher.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
namespace
T3o\Ter\Middleware
;
use
Psr\Http\Message\ResponseInterface
;
use
Psr\Http\Message\ServerRequestInterface
;
use
Psr\Http\Server\MiddlewareInterface
;
use
Psr\Http\Server\RequestHandlerInterface
;
use
T3o\Ter\Rest\RouteHandler
;
use
T3o\Ter\Rest\RouteConfiguration
;
use
TYPO3\CMS\Core\Utility\GeneralUtility
;
/**
* This middleware forwards the incoming request to the routeHandler
* if the request matches an existing openApi specification.
*
* @see RouteHandler
*/
final
class
RestRouteDispatcher
implements
MiddlewareInterface
{
private
const
API_REQUEST_PATH
=
'/api/'
;
private
const
SCHEMA_PATH
=
'EXT:ter/resources/schema/'
;
protected
RouteHandler
$routeHandler
;
protected
RouteConfiguration
$routeConfiguration
;
public
function
__construct
(
RouteHandler
$routeHandler
,
RouteConfiguration
$routeConfiguration
)
{
$this
->
routeHandler
=
$routeHandler
;
$this
->
routeConfiguration
=
$routeConfiguration
;
}
public
function
process
(
ServerRequestInterface
$request
,
RequestHandlerInterface
$handler
):
ResponseInterface
{
if
(
$this
->
isApiRequest
(
$request
->
getUri
()
->
getPath
()))
{
// @todo Might need need some further TSFE initialization here
return
$this
->
routeHandler
->
handle
(
$request
);
}
return
$handler
->
handle
(
$request
);
}
protected
function
isApiRequest
(
string
$path
):
bool
{
if
(
!
preg_match
(
'/^\/api\/v\d{1}\/.*/'
,
$path
))
{
return
false
;
}
[
$version
]
=
explode
(
'/'
,
str_replace
(
self
::
API_REQUEST_PATH
,
''
,
$path
));
$resource
=
GeneralUtility
::
getFileAbsFileName
(
self
::
SCHEMA_PATH
.
$version
.
'.json'
);
if
(
!
is_readable
(
$resource
))
{
return
false
;
}
$schema
=
json_decode
((
string
)
file_get_contents
(
$resource
),
true
,
512
,
JSON_THROW_ON_ERROR
);
if
(
!
is_array
(
$schema
)
||
$schema
===
[])
{
return
false
;
}
$this
->
routeConfiguration
->
setBase
(
self
::
API_REQUEST_PATH
.
$version
)
->
setSchema
(
$schema
)
->
createRouteCollection
();
return
true
;
}
}
extensions/ter/Classes/Rest/RequestHandlerFactory.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Rest
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Container\ContainerInterface
;
use
Psr\Http\Server\RequestHandlerInterface
;
use
TYPO3\CMS\Core\Routing\RouteResultInterface
;
use
TYPO3\CMS\Core\Utility\GeneralUtility
;
/**
* Create a request handler for the resolved route
*/
final
class
RequestHandlerFactory
{
protected
ContainerInterface
$container
;
public
function
__construct
(
ContainerInterface
$container
)
{
$this
->
container
=
$container
;
}
public
function
createRequestHandlerForRouteResult
(
RouteResultInterface
$routeResult
)
{
if
(
!
$routeResult
->
offsetExists
(
'requestHandler'
))
{
throw
new
\
InvalidArgumentException
(
'No handler to process the request given.'
,
1600995362
);
}
$requestHandlerClassName
=
(
string
)
$routeResult
->
offsetGet
(
'requestHandler'
);
$requestHandler
=
$this
->
container
->
has
(
$requestHandlerClassName
)
?
$this
->
container
->
get
(
$requestHandlerClassName
)
:
GeneralUtility
::
makeInstance
(
$requestHandlerClassName
);
if
(
!
$requestHandler
instanceof
RequestHandlerInterface
)
{
throw
new
\
InvalidArgumentException
(
'No qualified handler to process the request found.'
,
1600994201
);
}
return
$requestHandler
;
}
}
extensions/ter/Classes/Rest/Response/ApiResponseFactory.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Rest\Response
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
Psr\Http\Message\ResponseInterface
;
use
Psr\Http\Message\ServerRequestInterface
;
use
TYPO3\CMS\Core\Configuration\ExtensionConfiguration
;
use
TYPO3\CMS\Core\Http\JsonResponse
;
use
TYPO3\CMS\Core\Utility\GeneralUtility
;
/**
* Create a response object for the accepted type and the given data
*/
final
class
ApiResponseFactory
{
private
const
DEFAULT_RESPONSE_TYPE
=
'application/json'
;
protected
$availableResponseTypes
;
public
function
__construct
(
ExtensionConfiguration
$extensionConfiguration
)
{
$this
->
availableResponseTypes
=
$extensionConfiguration
->
get
(
'ter'
,
'routing/responseTypes'
)
??
[];
}
public
function
createResponseForRequest
(
ServerRequestInterface
$request
,
$data
=
null
,
int
$status
=
200
,
bool
$rateLimit
=
true
):
ResponseInterface
{
$response
=
$this
->
initializeResponse
(
$request
)
->
withStatus
(
$status
);
if
(
$data
!==
null
)
{
$this
->
addDataForResponseType
(
$response
,
$data
);
}
if
(
$rateLimit
)
{
$this
->
addRateLimitHeader
(
$response
);
}
return
$response
;
}
public
function
createErrorResponseForRequest
(
ServerRequestInterface
$request
,
int
$code
,
string
$message
=
''
,
int
$status
=
500
):
ResponseInterface
{
return
$this
->
createResponseForRequest
(
$request
,
[
'status'
=>
$status
,
'code'
=>
$code
,
'message'
=>
$message
],
$status
,
false
);
}
public
function
createResponseForType
(
string
$type
):
ResponseInterface
{
if
(
$type
===
''
)
{
throw
new
\
InvalidArgumentException
(
'Response type cannot be empty'
,
1601025839
);
}
if
(
!
isset
(
$this
->
availableResponseTypes
[
$type
]))
{
throw
new
\
OutOfRangeException
(
sprintf
(
'No Response handler found for %s'
,
$type
),
1601025964
);
}
$className
=
$this
->
availableResponseTypes
[
$type
];
return
GeneralUtility
::
makeInstance
(
$className
);
}
protected
function
initializeResponse
(
ServerRequestInterface
$request
):
ResponseInterface
{
$types
=
$request
->
getHeader
(
'accept'
)
?:
$request
->
getHeader
(
'content-type'
)
?:
[
self
::
DEFAULT_RESPONSE_TYPE
];
$type
=
(
string
)
reset
(
$types
);
return
$this
->
createResponseForType
(
$type
);
}
protected
function
addDataForResponseType
(
ResponseInterface
$response
,
$data
):
void
{
if
(
$response
instanceof
JsonResponse
&&
is_array
(
$data
))
{
$response
->
setPayload
(
$data
);
}
}
protected
function
addRateLimitHeader
(
ResponseInterface
$response
):
void
{
// @todo Implement rate limit functionality.
// @todo Maybe use existing packages like https://github.com/sunspikes/php-ratelimiter
$rateLimitHeader
=
[
'x-ratelimit-limit'
=>
100
,
'x-ratelimit-remaining'
=>
99
,
'x-ratelimit-reset'
=>
time
()
+
86400
];
foreach
(
$rateLimitHeader
as
$header
=>
$value
)
{
$response
->
withHeader
(
$header
,
(
string
)
$value
);
}
}
}
\ No newline at end of file
extensions/ter/Classes/Rest/RouteArgument/AbstractRouteArgument.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Rest\RouteArgument
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use
T3o\Ter\Rest\Validaton\RequiredValidator
;
use
T3o\Ter\Rest\Validaton\ValidationErrorInterface
;
use
TYPO3\CMS\Core\Utility\GeneralUtility
;
/**
* Can be used by specific argument implementations
*/
abstract
class
AbstractRouteArgument
implements
RouteArgumentInterface
,
\
JsonSerializable
{
protected
string
$name
;
protected
array
$configuration
;
protected
array
$validators
=
[];
protected
array
$validationErrors
=
[];
public
function
__construct
(
string
$name
,
array
$configuration
)
{
$this
->
name
=
$name
;
$this
->
configuration
=
$configuration
;
if
((
bool
)(
$this
->
configuration
[
'required'
]
??
false
))
{
$this
->
validators
[
RequiredValidator
::
class
]
=
[];
}
}
public
function
getName
():
string
{
return
$this
->
name
;
}
public
function
getConfiguration
():
array
{
return
$this
->
configuration
;
}
public
function
isValid
():
bool
{
foreach
(
$this
->
validators
as
$class
=>
$options
)
{
GeneralUtility
::
makeInstance
(
$class
,
...
$options
)
->
validate
(
$this
);
}
return
$this
->
validationErrors
===
[];
}
public
function
addValidationError
(
ValidationErrorInterface
$validationError
):
void
{
$this
->
validationErrors
[]
=
$validationError
;
}
public
function
getValidationErrors
():
array
{
return
$this
->
validationErrors
;
}
public
function
jsonSerialize
():
array
{
$validationErrorMessages
=
[];
foreach
(
$this
->
validationErrors
as
$error
)
{
$validationErrorMessages
[]
=
(
string
)
$error
;
}
return
[
'name'
=>
$this
->
getName
(),
'value'
=>
$this
->
getValue
(),
'validationErrors'
=>
$validationErrorMessages
];
}
}
\ No newline at end of file
extensions/ter/Classes/Rest/RouteArgument/DeepObjectRouteArgumentInterface.php
0 → 100644
View file @
b0ee2fef
<?php
declare
(
strict_types
=
1
);
namespace
T3o\Ter\Rest\RouteArgument
;
/*
* This file is part of TYPO3 CMS-extension "ter", created by Oliver Bartsch.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
/**
* Interface to be implemented by deepObject (containing sub properties) route arguments.
*/
interface
DeepObjectRouteArgumentInterface
{