Compare commits

..
2 Commits
Author SHA1 Message Date
Kilian a85cdc6356 Docs pt. 2 2024-07-29 22:07:26 +02:00
Kilian 6a62ae58fc Docs 2024-07-29 22:06:57 +02:00
24 changed files with 690 additions and 10 deletions

No files matched your search

+15
View File
@@ -9,8 +9,23 @@ use Khofmann\Input\Input;
use Khofmann\Response\Response;
use Khofmann\Models\User\User;
/**
* Login route handlers
*/
class Login extends Api
{
/**
* Login POST handler
*
* Log in a user. Required inputs are `email` and `password`.
*
* Returns user and tokens
*
* @throws 400 Missing field
* @throws 401 Invalid credentials (login fails)
* @throws 404 User not found
* @throws 500 Failed to log in user
*/
public function post(): void
{
// Fetch all required inputs.
+12
View File
@@ -7,8 +7,20 @@ use Khofmann\Models\User\User;
use Khofmann\Request\Request;
use Khofmann\Response\Response;
/**
* Logout route handlers
*/
class Logout extends Api
{
/**
* Logout POST handler
*
* Logout a user. User is retrieved using the authentication `token`.
*
* Returns user.
*
* @throws 404 User not found
*/
public function post(): void
{
// Get user auth token.
+42 -1
View File
@@ -11,8 +11,16 @@ use Khofmann\Models\User\User;
use Khofmann\Request\Request;
use Khofmann\Response\Response;
/**
* Posts route handlers
*/
class Posts extends Api
{
/**
* Posts GET handler
*
* Lists posts. Optional parameters are `l` (limit of returned list) and `p` (page, i.e. offset).
*/
public function get()
{
// Fetch and constrain all parameters.
@@ -24,6 +32,15 @@ class Posts extends Api
Response::json(Post::list($page, $limit, $authed));
}
/**
* Posts POST handler
*
* Create a new posts. Required inputs are `content`. Optional parameter is `l` (limit of list for which the returned pages is calculated).
*
* Returns created post and resulting amount of pages for a given limit.
*
* @throws 400 Missing fields
*/
public function post(): void
{
// Fetch all required inputs.
@@ -47,6 +64,19 @@ class Posts extends Api
}
}
/**
* Posts PATCH handler
*
* Update a posts.
*
* Returns updated post.
*
* @param mixed $id ID of post to update
*
* @throws 401 Not authorized (trying to edit a different users post if not admin)
* @throws 404 Post not found
* @throws 500 Failed to update user
*/
public function patch($id): void
{
// Fetch all inputs.
@@ -75,9 +105,20 @@ class Posts extends Api
}
}
/**
* Posts DELETE handler
*
* Delete a post. Optional parameter is `l` (limit of list for which the returned pages is calculated).
*
* Returns deleted post and resulting amount of pages for a given limit.
*
* @param mixed $id ID of posts to delete
*
* @throws 404 Post not found
*/
public function delete($id): void
{
// Fetch ax(0, intval(Input::get("p", 0)));
// Fetch and constrain all parameters.
$limit = constrain(0, 30, intval(Input::get("l", 10)));
// Try delete, 404 if post was not found.
try {
+14
View File
@@ -10,8 +10,22 @@ use Khofmann\Response\Response;
use Khofmann\Models\User\User;
use Khofmann\Request\Request;
/**
* Refresh route handlers
*/
class Refresh extends Api
{
/**
* Refresh POST handler
*
* Refresh a users session. User is retrieved using the authentication `token`.
*
* Returns user and tokens.
*
* @throws 401 Missing field
* @throws 404 User not found
* @throws 500 Failed to refresh tokens
*/
public function post(): void
{
// Fetch all required inputs.
+25
View File
@@ -9,8 +9,22 @@ use Khofmann\Input\Input;
use Khofmann\Response\Response;
use Khofmann\Models\User\User;
/**
* Register route handlers
*/
class Register extends Api
{
/**
* Register POST handler
*
* Register a new user. Required inputs are `username`, `email`, and `password`.
*
* Returns user.
*
* @throws 400 Missing fields
* @throws 400 Duplicate
* @throws 404 Failure to create
*/
public function post(): void
{
// Fetch all required inputs.
@@ -39,6 +53,17 @@ class Register extends Api
}
}
/**
* Register PATCH handler
*
* Confirms a user. Required input is `code`.
*
* Returns user.
*
* @throws 400 Missing field
* @throws 404 User not found
* @throws 404 User already confirmed
*/
public function patch(): void
{
// Fetch all required inputs.
+25
View File
@@ -10,8 +10,23 @@ use Khofmann\Response\Response;
use Khofmann\ApiError\ApiError;
use Khofmann\Request\Request;
/**
* User image route handlers
*/
class Image extends Api
{
/**
* Image POST handler
*
* Set a new user image.
*
* Returns updated user.
*
* @param mixed $id User ID
*
* @throws 404 User not found
* @throws 500 Failed to update user image
*/
public function post($id): void
{
// Fetch all inputs.
@@ -33,6 +48,16 @@ class Image extends Api
}
}
/**
* Image POST handler
*
* Set a new user image. User is retrieved using the authentication `token`.
*
* Returns updated user.
*
* @throws 404 User not found
* @throws 500 Failed to update user image
*/
public function postSelf(): void
{
// Fetch all inputs.
+12
View File
@@ -9,8 +9,20 @@ use Khofmann\Response\Response;
use Khofmann\ApiError\ApiError;
use Khofmann\Input\Input;
/**
* User posts route handlers
*/
class Posts extends Api
{
/**
* Posts GET handler
*
* Lists posts for a user. Optional parameters are `l` (limit of returned list), `p` (page, i.e. offset), `s` (sort order).
*
* Returns list of posts.
*
* @throws 404 User not found
*/
public function get($id): void
{
// Fetch and constrain all parameters.
+57 -1
View File
@@ -10,8 +10,18 @@ use Khofmann\Response\Response;
use Khofmann\ApiError\ApiError;
use Khofmann\Request\Request;
/**
* Users route handlers
*/
class Users extends Api
{
/**
* Users GET handler
*
* Lists users. Optional parameters are `l` (limit of returned list) and `p` (page, i.e. offset).
*
* Returns list of users.
*/
public function list()
{
// Fetch and constrain all parameters.
@@ -22,6 +32,17 @@ class Users extends Api
Response::json(User::list($page, $limit));
}
/**
* User GET handler
*
* Get a single user.
*
* Returns user.
*
* @param mixed $id User ID
*
* @throws 404 User not found
*/
public function get($id): void
{
// Try and get a user, 404 if not found.
@@ -37,6 +58,18 @@ class Users extends Api
}
}
/**
* Users PATCH handler
*
* Update a user.
*
* Returns updated user.
*
* @param mixed $id User ID
*
* @throws 404 User not found
* @throws 500 Failed to update user
*/
public function patch($id): void
{
// Fetch all inputs.
@@ -59,6 +92,16 @@ class Users extends Api
}
}
/**
* Users PATCH handler
*
* Update a user. User is retrieved using the authentication `token`.
*
* Returns updated user.
*
* @throws 404 User not found
* @throws 500 Failed to update user
*/
public function patchSelf(): void
{
// Fetch all inputs.
@@ -82,11 +125,24 @@ class Users extends Api
}
}
/**
* Users DELETE handler
*
* Deletes a user. Optional parameter is `l` (limit of list for which the returned pages is calculated).
*
* Returns deleted user and resulting amount of pages for a given limit.
*
* @param mixed $id User ID
*
* @throws 404 User not found
*/
public function delete($id): void
{
// Fetch and constrain all parameters.
$limit = constrain(0, 30, intval(Input::get("l", 10)));
// Try to delete user, 404 if not found.
try {
Response::json(User::getByID($id)->delete());
Response::json(User::getByID($id)->delete($limit));
} catch (Exception $err) {
switch ($err->getMessage()) {
case "NotFound":
+16 -1
View File
@@ -560,13 +560,21 @@ paths:
schema:
type: integer
format: int14
- in: query
name: l
schema:
type: integer
minimum: 0
maximum: 30
default: 10
description: Number of items per page, influences returned pages count.
responses:
200:
description: Success.
content:
application/json:
schema:
$ref: "#/components/schemas/UserResponse"
$ref: "#/components/schemas/UserDeleteResponse"
404:
description: User not found.
content:
@@ -903,6 +911,13 @@ components:
refreshToken:
type: string
format: uuid4
UserDeleteResponse:
type: object
properties:
pages:
type: number
post:
$ref: "#/components/schemas/UserResponse"
securitySchemes:
BasicAuth:
type: apiKey
+4 -2
View File
@@ -512,11 +512,13 @@ data-styled.g137[id="sc-kvXgyf"]{content:"fBvPoH,"}/*!sc*/
" class="sc-euGpHm sc-exayXG fwfkcU kqJXdD"><p>Delete user with ID.</p>
</div></div><div class="sc-hWiVxH cCfxdS"><div class="sc-jHofgq febKCY"><h5 class="sc-dkjaqt sc-jRHPII gwrByh eGMvsx">Authorizations:</h5><svg class="sc-cBYhjr dJanPw" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-iRFtIJ juVMxG"><span class="sc-iHlJgr lkBZak"><span class="sc-blcnQh fyveIh"><i>BasicAuth</i></span></span></div></div><div><h5 class="sc-dkjaqt gwrByh">path<!-- --> Parameters</h5><table class="sc-dENhDJ ceVHDP"><tbody><tr class="last "><td kind="field" title="id" class="sc-tOkKi sc-epPVmt gbdrVc bUksBx"><span class="sc-hfvVTD hTjFRU"></span><span class="property-name">id</span><div class="sc-xuUkR sc-hrDJJk bJcEcT gLEAmN">required</div></td><td class="sc-fpSrms exGrJC"><div><div><span class="sc-xuUkR sc-cvzDha bJcEcT kDPMlG"></span><span class="sc-xuUkR sc-gKROGD bJcEcT etUsjc">integer</span><span class="sc-xuUkR sc-gKROGD bJcEcT etUsjc"> <!-- -->&lt;<!-- -->int14<!-- -->&gt;<!-- --> </span></div> <div><div html="&lt;p&gt;User ID&lt;/p&gt;
" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"><p>User ID</p>
</div></div></div></td></tr></tbody></table></div><div><h5 class="sc-dkjaqt gwrByh">query<!-- --> Parameters</h5><table class="sc-dENhDJ ceVHDP"><tbody><tr class="last "><td kind="field" title="l" class="sc-tOkKi sc-epPVmt gbdrVc bUksBx"><span class="sc-hfvVTD hTjFRU"></span><span class="property-name">l</span></td><td class="sc-fpSrms exGrJC"><div><div><span class="sc-xuUkR sc-cvzDha bJcEcT kDPMlG"></span><span class="sc-xuUkR sc-gKROGD bJcEcT etUsjc">integer</span><span> <span class="sc-xuUkR sc-fsKlOa bJcEcT hcQlsz"> <!-- -->[ 0 .. 30 ]<!-- --> </span></span></div><div><span class="sc-xuUkR bJcEcT"> <!-- -->Default:<!-- --> </span> <span class="sc-xuUkR sc-cDelgQ bJcEcT iWjUHw">10</span></div> <div><div html="&lt;p&gt;Number of items per page, influences returned pages count.&lt;/p&gt;
" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"><p>Number of items per page, influences returned pages count.</p>
</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eiLgtK dbBFCU">Responses</h3><div><button class="sc-giOWAb gcYfHW"><svg class="sc-cBYhjr fqtTpb" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-catHVh gXPvFO">200<!-- --> </strong><div html="&lt;p&gt;Success.&lt;/p&gt;
" class="sc-euGpHm sc-exayXG fwfkcU kqJXdD sc-dHrNzZ dRdjww"><p>Success.</p>
</div></button></div><div><button class="sc-giOWAb gbPhWg"><svg class="sc-cBYhjr ezFOZv" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-catHVh gXPvFO">404<!-- --> </strong><div html="&lt;p&gt;User not found.&lt;/p&gt;
" class="sc-euGpHm sc-exayXG fwfkcU kqJXdD sc-dHrNzZ dRdjww"><p>User not found.</p>
</div></button></div></div></div><div class="sc-iKTcqh sc-gnpbhQ fTxZsC drGLlX"><div class="sc-dKsqdn dCbPd"><button class="sc-iAlELC gsBSOU"><span type="delete" class="sc-oeqTF ByFMv http-verb delete">delete</span><span class="sc-eowDPD jcAXWA">/users/{id}</span><svg class="sc-cBYhjr iMxoRf" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-ezTrPE bFiOkX"><div class="sc-drnuxz hdRKqQ"><div html="" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"></div><div tabindex="0" role="button"><div class="sc-hDcvty jpmGrk"><span>https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api</span>/users/{id}</div></div></div></div></div><div><h3 class="sc-kkmypM jKNCPF"> <!-- -->Response samples<!-- --> </h3><div class="sc-bSlUec jxQggo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab:R176aq:0" aria-selected="true" aria-disabled="false" aria-controls="panel:R176aq:0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab:R176aq:1" aria-selected="false" aria-disabled="false" aria-controls="panel:R176aq:1" data-rttab="true">404</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel:R176aq:0" aria-labelledby="tab:R176aq:0"><div><div class="sc-WChnI iLXIA-d"><span class="sc-foMnoT dmNpjh">Content type</span><div class="sc-ZubPq KmnOC">application/json</div></div><div class="sc-AmOMz gDrQpr"><div class="sc-eTdEpr dgzSkD"><div class="sc-pFPEP bMXXJy"><button><div class="sc-eHujzY ecCAmX">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-euGpHm fwfkcU sc-fTgapq bDqsIm"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"id"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"username"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"status"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"email"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"image"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"isAdmin"</span>: <span class="token boolean">true</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"memberSince"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"date"</span>: <span class="token string">&quot;2019-08-24T14:15:22Z&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone_type"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone"</span>: <span class="token string">&quot;string&quot;</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"postCount"</span>: <span class="token number">0</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel:R176aq:1" aria-labelledby="tab:R176aq:1"></div></div></div></div></div></div><div id="tag/User/paths/~1users~1{id}~1image/post" data-section-id="tag/User/paths/~1users~1{id}~1image/post" class="sc-dsLQwm kcRA-dj"><div class="sc-la-DxNn dSIRVR"><div class="sc-fQpRED htdgPt"><h2 class="sc-knesRu cbpGTP"><a class="sc-jCbFiK hSvuOo" href="#tag/User/paths/~1users~1{id}~1image/post" aria-label="tag/User/paths/~1users~1{id}~1image/post"></a>Update user image<!-- --> </h2><div class="sc-fpJhiv cQxXyG"><div html="&lt;p&gt;Update user image with ID. &lt;br&gt; Use special ID &lt;code&gt;self&lt;/code&gt; to update logged in user. &lt;br&gt; Requires logged in user to have admin permissions for any ID other than &lt;code&gLine truncated
</div></button></div></div></div><div class="sc-iKTcqh sc-gnpbhQ fTxZsC drGLlX"><div class="sc-dKsqdn dCbPd"><button class="sc-iAlELC gsBSOU"><span type="delete" class="sc-oeqTF ByFMv http-verb delete">delete</span><span class="sc-eowDPD jcAXWA">/users/{id}</span><svg class="sc-cBYhjr iMxoRf" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-ezTrPE bFiOkX"><div class="sc-drnuxz hdRKqQ"><div html="" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"></div><div tabindex="0" role="button"><div class="sc-hDcvty jpmGrk"><span>https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api</span>/users/{id}</div></div></div></div></div><div><h3 class="sc-kkmypM jKNCPF"> <!-- -->Response samples<!-- --> </h3><div class="sc-bSlUec jxQggo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab:R176aq:0" aria-selected="true" aria-disabled="false" aria-controls="panel:R176aq:0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab:R176aq:1" aria-selected="false" aria-disabled="false" aria-controls="panel:R176aq:1" data-rttab="true">404</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel:R176aq:0" aria-labelledby="tab:R176aq:0"><div><div class="sc-WChnI iLXIA-d"><span class="sc-foMnoT dmNpjh">Content type</span><div class="sc-ZubPq KmnOC">application/json</div></div><div class="sc-AmOMz gDrQpr"><div class="sc-eTdEpr dgzSkD"><div class="sc-pFPEP bMXXJy"><button><div class="sc-eHujzY ecCAmX">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-euGpHm fwfkcU sc-fTgapq bDqsIm"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"pages"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"post"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"username"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"status"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"email"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"isAdmin"</span>: <span class="token boolean">true</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"memberSince"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"date"</span>: <span class="token string">&quot;2019-08-24T14:15:22Z&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone_type"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone"</span>: <span class="token string">&quot;string&quot;</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"postCount"</span>: <span class="token number">0</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel:R176aq:1" aria-labelledby="tab:R176aq:1"></div></div></div></div></div></div><div id="tag/User/paths/~1users~1{id}~1image/post" data-section-id="tag/User/paths/~1users~1{id}~1image/post" class="sLine truncated
" class="sc-euGpHm sc-exayXG fwfkcU kqJXdD"><p>Update user image with ID. <br> Use special ID <code>self</code> to update logged in user. <br> Requires logged in user to have admin permissions for any ID other than <code>self</code>.</p>
</div></div><div class="sc-hWiVxH cCfxdS"><div class="sc-jHofgq febKCY"><h5 class="sc-dkjaqt sc-jRHPII gwrByh eGMvsx">Authorizations:</h5><svg class="sc-cBYhjr dJanPw" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-iRFtIJ juVMxG"><span class="sc-iHlJgr lkBZak"><span class="sc-blcnQh fyveIh"><i>BasicAuth</i></span></span><span class="sc-iHlJgr lkBZak"><span class="sc-blcnQh fyveIh"><i>BasicAuth</i></span></span></div></div><div><h5 class="sc-dkjaqt gwrByh">path<!-- --> Parameters</h5><table class="sc-dENhDJ ceVHDP"><tbody><tr class="last "><td kind="field" title="id" class="sc-tOkKi sc-epPVmt gbdrVc bUksBx"><span class="sc-hfvVTD hTjFRU"></span><span class="property-name">id</span><div class="sc-xuUkR sc-hrDJJk bJcEcT gLEAmN">required</div></td><td class="sc-fpSrms exGrJC"><div><div><span class="sc-xuUkR sc-cvzDha bJcEcT kDPMlG"></span><span class="sc-xuUkR sc-gKROGD bJcEcT etUsjc">integer</span><span class="sc-xuUkR sc-gKROGD bJcEcT etUsjc"> <!-- -->&lt;<!-- -->int14<!-- -->&gt;<!-- --> </span></div> <div><div html="&lt;p&gt;User ID&lt;/p&gt;
" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"><p>User ID</p>
@@ -542,7 +544,7 @@ data-styled.g137[id="sc-kvXgyf"]{content:"fBvPoH,"}/*!sc*/
" class="sc-euGpHm sc-exayXG fwfkcU kqJXdD sc-dHrNzZ dRdjww"><p>User not found.</p>
</div></button></div></div></div><div class="sc-iKTcqh sc-gnpbhQ fTxZsC drGLlX"><div class="sc-dKsqdn dCbPd"><button class="sc-iAlELC gsBSOU"><span type="get" class="sc-oeqTF ffmPnn http-verb get">get</span><span class="sc-eowDPD jcAXWA">/users/{id}/posts</span><svg class="sc-cBYhjr iMxoRf" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-ezTrPE bFiOkX"><div class="sc-drnuxz hdRKqQ"><div html="" class="sc-euGpHm sc-exayXG fwfkcU jYGAQp"></div><div tabindex="0" role="button"><div class="sc-hDcvty jpmGrk"><span>https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api</span>/users/{id}/posts</div></div></div></div></div><div><h3 class="sc-kkmypM jKNCPF"> <!-- -->Response samples<!-- --> </h3><div class="sc-bSlUec jxQggo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab:R177aq:0" aria-selected="true" aria-disabled="false" aria-controls="panel:R177aq:0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab:R177aq:1" aria-selected="false" aria-disabled="false" aria-controls="panel:R177aq:1" data-rttab="true">404</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel:R177aq:0" aria-labelledby="tab:R177aq:0"><div><div class="sc-WChnI iLXIA-d"><span class="sc-foMnoT dmNpjh">Content type</span><div class="sc-ZubPq KmnOC">application/json</div></div><div class="sc-AmOMz gDrQpr"><div class="sc-eTdEpr dgzSkD"><div class="sc-pFPEP bMXXJy"><button><div class="sc-eHujzY ecCAmX">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-euGpHm fwfkcU sc-fTgapq bDqsIm"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"pages"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"user"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"username"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"status"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"email"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"isAdmin"</span>: <span class="token boolean">true</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"memberSince"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"date"</span>: <span class="token string">&quot;2019-08-24T14:15:22Z&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone_type"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timezone"</span>: <span class="token string">&quot;string&quot;</span></div></li></ul><span class="token puLine truncated
<script>
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"title":"PHP Course Exam","version":"1.0.0","contact":{"name":"Kilian Kurt Hofmann","email":"khofmann@zedat.fu-berlin.de"},"description":"PHP Course (ABV FU Berlin) 2024 Exam"},"paths":{"/login":{"post":{"summary":"Login","description":"Log in user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Missing fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MissingFieldResponse"},"examples":{"Missing fields":{"value":{"code":"MissingField","fields":["email","password"]}}}}}},"401":{"description":"Invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"},"examples":{"Invalid username or password":{"value":{"code":"Unauthorized","message":"Invalid username or password"}}}}}},"404":{"description":"User not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundResponse"},"examples":{"User not found":{"value":{"code":"NotFound","entity":"user"}}}}}},"500":{"description":"Failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedResponse"},"examples":{"Failed":{"value":{"code":"Failed","message":"Login failed"}}}}}}},"tags":["Login/Logout"]}},"/logout":{"post":{"summary":"Logout","description":"Log out user.","security":[{"BasicAuth":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BooleanResponse"},"examples":{"Success":{"value":true}}}}}},"tags":["Login/Logout"]}},"/posts":{"get":{"summary":"List posts","description":"List all posts, return full user data if authenticated.","security":[{},{"BasicAuth":[]}],"parameters":[{"in":"query","name":"p","schema":{"type":"integer","minimum":0,"default":0},"description":"Current page."},{"in":"query","name":"l","schema":{"type":"integer","minimum":0,"maximum":30,"default":10},"description":"The number of items to return."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostListResponse"},"examples":{"Not authenticated":{"value":{"pages":0,"data":[{"id":0,"user":{"username":"string","image":"string"},"content":"string","postedAt":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"}}]}},"Authenticated":{"value":{"pages":1,"data":[{"id":1,"user":{"id":0,"username":"string","status":0,"email":"string","image":"string","isAdmin":true,"memberSince":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"},"postCount":0},"content":"string","postedAt":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"}}]}}}}}}},"tags":["Post"]},"post":{"summary":"New post","description":"Create a new post.","security":[{"BasicAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreateRequest"}}}},"parameters":[{"in":"query","name":"l","schema":{"type":"integer","minimum":0,"maximum":30,"default":10},"description":"Number of items per page, influences returned pages count."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreateResponse"}}}},"400":{"description":"Missing fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MissingFieldResponse"},"examples":{"Missing fields":{"value":{"code":"MissingField","fields":["content"]}}}}}}},"tags":["Post"]}},"/posts/{id}":{"patch":{"summary":"Update post","description":"Update post with ID. <br> Requires logged in user to have admin permissions for posts not made by them.","security":[{"BasicAuth":[]},{"BasicAuth":["isAdmin"]}],"parameters":[{"name":"id","in":"path","description":"Post ID","required":true,"schema":{"type":"integer","format":"int14"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostUpdateRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResponse"}}}},"401":{"description":"Not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"},"examples":{"Not allowed":{"value":{"code":"Unauthorized","message":"Not allowed"}}}}}},"404":{"description":"Post not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundResponse"},"examples":{"Post not found":{"value":{"code":"NotFound","entity":"post"}}}}}},"500":{"description":"Update failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedUpdateResponse"},"examples":{"Failed":{"value":{"code":"FailedUpdate","fields":["content"],"reasons":["string"]}}}}}}},"tags":["Post"]},"deleLine truncated
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"title":"PHP Course Exam","version":"1.0.0","contact":{"name":"Kilian Kurt Hofmann","email":"khofmann@zedat.fu-berlin.de"},"description":"PHP Course (ABV FU Berlin) 2024 Exam"},"paths":{"/login":{"post":{"summary":"Login","description":"Log in user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Missing fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MissingFieldResponse"},"examples":{"Missing fields":{"value":{"code":"MissingField","fields":["email","password"]}}}}}},"401":{"description":"Invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"},"examples":{"Invalid username or password":{"value":{"code":"Unauthorized","message":"Invalid username or password"}}}}}},"404":{"description":"User not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundResponse"},"examples":{"User not found":{"value":{"code":"NotFound","entity":"user"}}}}}},"500":{"description":"Failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedResponse"},"examples":{"Failed":{"value":{"code":"Failed","message":"Login failed"}}}}}}},"tags":["Login/Logout"]}},"/logout":{"post":{"summary":"Logout","description":"Log out user.","security":[{"BasicAuth":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BooleanResponse"},"examples":{"Success":{"value":true}}}}}},"tags":["Login/Logout"]}},"/posts":{"get":{"summary":"List posts","description":"List all posts, return full user data if authenticated.","security":[{},{"BasicAuth":[]}],"parameters":[{"in":"query","name":"p","schema":{"type":"integer","minimum":0,"default":0},"description":"Current page."},{"in":"query","name":"l","schema":{"type":"integer","minimum":0,"maximum":30,"default":10},"description":"The number of items to return."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostListResponse"},"examples":{"Not authenticated":{"value":{"pages":0,"data":[{"id":0,"user":{"username":"string","image":"string"},"content":"string","postedAt":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"}}]}},"Authenticated":{"value":{"pages":1,"data":[{"id":1,"user":{"id":0,"username":"string","status":0,"email":"string","image":"string","isAdmin":true,"memberSince":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"},"postCount":0},"content":"string","postedAt":{"date":"2019-08-24T14:15:22Z","timezone_type":0,"timezone":"string"}}]}}}}}}},"tags":["Post"]},"post":{"summary":"New post","description":"Create a new post.","security":[{"BasicAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreateRequest"}}}},"parameters":[{"in":"query","name":"l","schema":{"type":"integer","minimum":0,"maximum":30,"default":10},"description":"Number of items per page, influences returned pages count."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCreateResponse"}}}},"400":{"description":"Missing fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MissingFieldResponse"},"examples":{"Missing fields":{"value":{"code":"MissingField","fields":["content"]}}}}}}},"tags":["Post"]}},"/posts/{id}":{"patch":{"summary":"Update post","description":"Update post with ID. <br> Requires logged in user to have admin permissions for posts not made by them.","security":[{"BasicAuth":[]},{"BasicAuth":["isAdmin"]}],"parameters":[{"name":"id","in":"path","description":"Post ID","required":true,"schema":{"type":"integer","format":"int14"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostUpdateRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResponse"}}}},"401":{"description":"Not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedResponse"},"examples":{"Not allowed":{"value":{"code":"Unauthorized","message":"Not allowed"}}}}}},"404":{"description":"Post not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundResponse"},"examples":{"Post not found":{"value":{"code":"NotFound","entity":"post"}}}}}},"500":{"description":"Update failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedUpdateResponse"},"examples":{"Failed":{"value":{"code":"FailedUpdate","fields":["content"],"reasons":["string"]}}}}}}},"tags":["Post"]},"deleLine truncated
var container = document.getElementById('redoc');
Redoc.hydrate(__redoc_state, container);
+5
View File
@@ -4,6 +4,11 @@ namespace Khofmann\Api;
use Khofmann\Response\Response;
/**
* Base class for all API handler classes.
*
* Sets common headers
*/
class Api
{
public function __construct()
+39
View File
@@ -4,6 +4,9 @@ namespace Khofmann\ApiError;
use Exception;
/**
* Facade for common API errors
*/
class ApiError extends Exception
{
private function __construct($message = "", $code = 0)
@@ -11,6 +14,11 @@ class ApiError extends Exception
parent::__construct($message, $code);
}
/**
* Error for missing fields
*
* @param array $fields Array of strings denoting which fields were missing
*/
public static function missingField(array $fields): ApiError
{
return new ApiError(json_encode([
@@ -19,6 +27,11 @@ class ApiError extends Exception
]), 400);
}
/**
* Error for duplicates
*
* @param string entity Entity for which a duplicate exists
*/
public static function duplicate(string $entity): ApiError
{
return new ApiError(json_encode([
@@ -27,6 +40,11 @@ class ApiError extends Exception
]), 400);
}
/**
* Error for missing permissions
*
* @param string message Message specifics
*/
public static function notAllowed(string $message)
{
return new ApiError(json_encode([
@@ -35,6 +53,11 @@ class ApiError extends Exception
]), 401);
}
/**
* Error for missing authentication
*
* @param string $message Message specifics
*/
public static function unauthorized(string $message)
{
return new ApiError(json_encode([
@@ -43,6 +66,11 @@ class ApiError extends Exception
]), 401);
}
/**
* Error for not found
*
* @param string entity Entity for which a duplicate exists
*/
public static function notFound(string $entity)
{
return new ApiError(json_encode([
@@ -51,6 +79,11 @@ class ApiError extends Exception
]), 404);
}
/**
* Generic error
*
* @param string message Message specifics
*/
public static function failed(string $message)
{
return new ApiError(json_encode([
@@ -59,6 +92,12 @@ class ApiError extends Exception
]), 500);
}
/**
* Error for missing fields
*
* @param array $fields Array of strings denoting which fields failed
* @param array $fields Array of strings denoting why the fields failed
*/
public static function failedUpdate(array $fields, array $reasons)
{
return new ApiError(json_encode([
+14
View File
@@ -8,8 +8,20 @@ use Pecee\Http\Request;
use Khofmann\Models\User\User;
use Khofmann\Response\Response;
/**
* Middleware for admin authenticated routes
*/
class AdminAuth implements IMiddleware
{
/**
* Request handler
*
* Returns 401 if `token`is missing, no user is found with the `token`or user is not admin.
*
* Keeps session fresh if request is authenticated.
*
* @param Request $request Incoming request
*/
public function handle(Request $request): void
{
$token = $request->getHeader("token");
@@ -26,7 +38,9 @@ class AdminAuth implements IMiddleware
}
try {
// Get user
$user = User::getByToken($token);
// Check if user is admin
if (!$user->getIsAdmin()) {
Response::response()
->header("Cache-control: no-cache")
+13
View File
@@ -8,8 +8,20 @@ use Pecee\Http\Request;
use Khofmann\Models\User\User;
use Khofmann\Response\Response;
/**
* Middleware for authenticated routes
*/
class Auth implements IMiddleware
{
/**
* Request handler
*
* Returns 401 if `token`is missing, or no user is found with the `token`.
*
* Keeps session fresh if request is authenticated.
*
* @param Request $request Incoming request
*/
public function handle(Request $request): void
{
$token = $request->getHeader("token");
@@ -26,6 +38,7 @@ class Auth implements IMiddleware
}
try {
// Get user
$user = User::getByToken($token);
// Keep fresh
+14 -1
View File
@@ -8,18 +8,31 @@ use Pecee\Http\Request;
use Khofmann\Models\User\User;
use Khofmann\Response\Response;
/**
* Middleware for optional authenticated routes
*/
class OptAuth implements IMiddleware
{
/**
* Request handler
*
* Returns 401 if no user is found with the `token`.
*
* Keeps session fresh if request is authenticated.
*
* @param Request $request Incoming request
*/
public function handle(Request $request): void
{
$token = $request->getHeader("token");
// No token
// No token, since authentication is optional, pass
if ($token === null) {
return;
}
try {
// Get user
$user = User::getByToken($token);
// Keep fresh
+42
View File
@@ -4,29 +4,50 @@ namespace Khofmann\Config;
use Exception;
/**
* Facade for application configuration
*/
class Config
{
// Instances array to ensure singleton pattern
private static array $instances = [];
// Configuration arrays
private array $app;
private array $database;
/**
* Loads configurations into arrays.
*
* Private since singleton pattern.
*/
private function __construct()
{
$this->app = require_once __DIR__ . "/../../config/app.php";
$this->database = require_once __DIR__ . "/../../config/database.php";
}
/**
* Disallow clone
*/
private function __clone()
{
throw new Exception("Cannot clone a singleton.");
}
/**
* Disallow wakeup
*/
private function __wakeup()
{
throw new Exception("Cannot unserialize a singleton.");
}
/**
* Get the instance. Ensures singleton pattern.
*
* Private to ensure only facade methods can be used.
*/
private static function getInstance(): Config
{
$cls = static::class;
@@ -37,36 +58,57 @@ class Config
return self::$instances[$cls];
}
/**
* @return string Application base URI path
*/
public static function getBasePath(): string
{
return Config::getInstance()->app["basePath"];
}
/**
* @return string Application base file system path
*/
public static function getBaseFSPath(): string
{
return Config::getInstance()->app["baseFSPath"];
}
/**
* @return string Application storage URI path
*/
public static function getStoragePath(): string
{
return Config::getInstance()->app["storagePath"];
}
/**
* @return string Application storage file system path
*/
public static function getStorageFSPath(): string
{
return Config::getInstance()->app["storageFSPath"];
}
/**
* @return string Application base URI path
*/
public static function getDatabase(): array
{
return Config::getInstance()->database;
}
/**
* @return string Application base URI path
*/
public static function getTokenExpiry(): string
{
return Config::getInstance()->app["tokenExpiry"];
}
/**
* @return string Application base URI path
*/
public static function getRefreshTokenExpiry(): string
{
return Config::getInstance()->app["refreshTokenExpiry"];
+18
View File
@@ -6,25 +6,43 @@ use PDO;
use Khofmann\Config\Config;
use Exception;
/**
* Facade for database access
*/
class Database extends PDO
{
// Instances array to ensure singleton pattern
private static array $instances = [];
/**
* Private since singleton pattern.
*/
private function __construct(string $dsn, string $username = null, string $password = null, array $options = null)
{
parent::__construct($dsn, $username, $password, $options);
}
/**
* Disallow clone
*/
private function __clone()
{
throw new Exception("Cannot clone a singleton.");
}
/**
* Disallow wakeup
*/
private function __wakeup()
{
throw new Exception("Cannot unserialize a singleton.");
}
/**
* Get the instance. Ensures singleton pattern.
*
* Loads configuration from `Config` facade
*/
public static function getInstance(): Database
{
$cls = static::class;
+13
View File
@@ -2,12 +2,25 @@
namespace Khofmann\GUID;
/**
* Facade for GUID generators
*/
class GUID
{
/**
* Private since facade.
*/
private function __construct()
{
}
/**
* Generate a UUID v4.
*
* @param mixed $data Optional data
*
* @return string UUID v4
*/
public static function v4($data = null): string
{
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
+30
View File
@@ -4,30 +4,60 @@ namespace Khofmann\Input;
use Khofmann\Request\Request;
/**
* Facade for Input (wraps SimpleRouter)
*/
class Input
{
/**
* Private since facade.
*/
private function __construct()
{
}
/**
* Get POST parameter.
*
* @param string $index Parameter name
* @param mixed $defaultValue Default value if parameter was null
*/
public static function post(string $index, $defaultValue = null)
{
$value = Request::request()->getInputHandler()->post($index, $defaultValue);
return !is_object($value) ? $value : $value->getValue();
}
/**
* Get PATCH parameter.
*
* @param string $index Parameter name
* @param mixed $defaultValue Default value if parameter was null
*/
public static function patch(string $index, $defaultValue = null)
{
$value = Request::request()->getInputHandler()->post($index, $defaultValue);
return !is_object($value) ? $value : $value->getValue();
}
/**
* Get query string parameter.
*
* @param string $index Parameter name
* @param mixed $defaultValue Default value if parameter was null
*/
public static function get(string $index, $defaultValue = null)
{
$value = Request::request()->getInputHandler()->get($index, $defaultValue);
return !is_object($value) ? $value : $value->getValue();
}
/**
* Get FILE entry.
*
* @param string $index File name
* @param mixed $defaultValue Default value if parameter was null
*/
public static function file(string $index, $defaultValue = null)
{
$value = Request::request()->getInputHandler()->file($index, $defaultValue);
+64 -1
View File
@@ -2,7 +2,6 @@
namespace Khofmann\Models\Post;
use Api\User\User as UserUser;
use DateTime;
use Exception;
use Khofmann\Models\User\User;
@@ -12,6 +11,11 @@ use Khofmann\Config\Config;
use Khofmann\Database\Database;
use PDO;
/**
* Post database model
*
* Abstracts database access
*/
class Post implements JsonSerializable
{
private int $id;
@@ -37,6 +41,16 @@ class Post implements JsonSerializable
* Statics
*/
/**
* Get a post by ID.
*
* Also get creator (`User` object).
*
* @param int $id Post ID
*
* @throws NotFound Post not found
* @throws NotFound Creator of post was not found
*/
public static function getByID(int $id): Post
{
$db = Database::getInstance();
@@ -59,6 +73,15 @@ class Post implements JsonSerializable
return new Post($data["id"], $user, null, null, $data["beitrag"], $data["zeitstempel"]);
}
/**
* Create a new post.
*
* @param User $user Creator of post
* @param string $content Post content
* @param int $limit Limit for posts list to calculate number of pages
*
* @return array Number of pages after creation (in accordance with `limit`) and created post
*/
public static function create(User $user, string $content, int $limit): array
{
$content = substr(trim($content), 0, 250);
@@ -91,6 +114,15 @@ class Post implements JsonSerializable
];
}
/**
* List of posts
*
* @param int $page Current page (offset)
* @param int $limit Posts per page
* @param bool $authed If `true`, include full `User` object. Defaults to `false`
*
* @return array Number of pages and posts of selected page
*/
public static function list(int $page, int $limit, bool $authed = false): array
{
$db = Database::getInstance();
@@ -128,10 +160,20 @@ class Post implements JsonSerializable
* Members
*/
/**
* Update post
*
* Does nothing if new `content` is empty
*
* @param ?string $content New content
*
* @throws Failed Failed to update content
*/
public function update(?string $content): Post
{
$db = Database::getInstance();
// Make sure we do all changes or none
$db->beginTransaction();
$failed = [];
@@ -156,16 +198,25 @@ class Post implements JsonSerializable
}
}
if (count($failed) > 0) {
// We failed, go back
$db->rollBack();
throw ApiError::failedUpdate($failed, $reason);
}
// Commit the changes
$db->commit();
return Post::getByID($this->id);
}
/**
* Delete post
*
* @param int $limit Limit of list for which the returned pages is calculated.
*
* @return array Returns deleted post and resulting amount of pages for a given limit.
*/
public function delete(int $limit): array
{
$db = Database::getInstance();
@@ -189,21 +240,33 @@ class Post implements JsonSerializable
* Getters
*/
/**
* Get post ID
*/
public function getId(): int
{
return $this->id;
}
/**
* Get post creator
*/
public function getUser(): User
{
return $this->user;
}
/**
* Get post content
*/
public function getContent(): string
{
return $this->content;
}
/**
* Get time when post was created
*/
public function getPostedAt(): DateTime
{
return $this->postedAt;
+163 -3
View File
@@ -11,8 +11,12 @@ use JsonSerializable;
use Khofmann\ApiError\ApiError;
use Khofmann\GUID\GUID;
use Khofmann\Models\Post\Post;
use PDOException;
/**
* User database model
*
* Abstracts database access
*/
class User implements JsonSerializable
{
private int $id;
@@ -36,6 +40,13 @@ class User implements JsonSerializable
$this->postCount = $postCount;
}
/**
* Get a user by their confirmation code.
*
* @param string $code Confirmation code
*
* @throws NotFound User not found
*/
private static function getByConfirmCode(string $code): User
{
$db = Database::getInstance();
@@ -62,6 +73,13 @@ class User implements JsonSerializable
* Statics
*/
/**
* Get a user by ID.
*
* @param int $id User ID
*
* @throws NotFound User not found
*/
public static function getByID(int $id): User
{
$db = Database::getInstance();
@@ -83,6 +101,13 @@ class User implements JsonSerializable
return new User($id, $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
}
/**
* Get a user by their email.
*
* @param string $email User email
*
* @throws NotFound User not found
*/
public static function getByEmail(string $email): User
{
$db = Database::getInstance();
@@ -104,6 +129,13 @@ class User implements JsonSerializable
return new User($data["id"], $data["benutzer"], $data["status"], $email, $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
}
/**
* Get a user by their authentication token.
*
* @param string $token Authentication token
*
* @throws NotFound User not found
*/
public static function getByToken(string $token): User
{
$db = Database::getInstance();
@@ -126,6 +158,18 @@ class User implements JsonSerializable
return new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
}
/**
* Log in a user
*
* @param string $email User email
* @param string $password USer password
*
* @return array Tokens and user
*
* @throws Failed Could not generate tokens
* @throws Invalid Password was wrong
* @throws NotFound User not found (email wrong)
*/
public static function logIn(string $email, string $password): array
{
$db = Database::getInstance();
@@ -191,6 +235,15 @@ class User implements JsonSerializable
}
}
/**
* Create a user
*
* @param string $username Username
* @param string $email User email
* @param string $password User password
*
* @throws Duplicate User with this username or email already exists
*/
public static function create(string $username, string $email, string $password): User
{
$db = Database::getInstance();
@@ -225,6 +278,13 @@ class User implements JsonSerializable
}
}
/**
* Confirm a user
*
* @param string $confirmCode Confirmation code
*
* @throws NotFound User not found (invalid code or already confirmed)
*/
public static function confirm(string $confirmCode): User
{
$db = Database::getInstance();
@@ -243,6 +303,14 @@ class User implements JsonSerializable
return User::getByID($user->getID());
}
/**
* List of users
*
* @param int $page Current page (offset)
* @param int $limit Users per page
*
* @return array Number of pages and posts of selected page
*/
public static function list(int $page, int $limit)
{
$db = Database::getInstance();
@@ -274,6 +342,15 @@ class User implements JsonSerializable
return ["pages" => intdiv($count, $limit + 1), "data" => $list];
}
/**
* Refresh a user session
*
* @param string $token Authentication token
* @param string $refreshToken Refresh token
*
* @throws NotFound User not found (tokens do not exist, refresh token expired)
* @throws Failed Could not regenerate tokens
*/
public static function refresh(string $token, string $refreshToken)
{
$db = Database::getInstance();
@@ -347,10 +424,22 @@ class User implements JsonSerializable
return $stmt->execute();
}
/**
* Update post
*
* Does nothing if new all fields are empty
*
* @param ?string $username New username
* @param ?string $password New password
* @param ?string $email New email
*
* @throws Failed At least one field failed to update
*/
public function update(?string $username, ?string $password, ?string $email): User
{
$db = Database::getInstance();
// Make sure we do all changes or none
$db->beginTransaction();
$failed = [];
@@ -413,20 +502,33 @@ class User implements JsonSerializable
}
if (count($failed) > 0) {
// We failed, go back
$db->rollBack();
throw ApiError::failedUpdate($failed, $reasons);
}
// Commit the changes
$db->commit();
return User::getByID($this->id);
}
/**
* Update post
*
* Does nothing if all fields are empty
*
* @param mixed $image New file upload
* @param ?string $predefined Predefined avatar
*
* @param Failed Image failed to update
*/
public function updateImage($image, ?string $predefined): User
{
$db = Database::getInstance();
// Make sure we do all changes or none
$db->beginTransaction();
$failed = [];
@@ -442,6 +544,7 @@ class User implements JsonSerializable
}
if (!empty($image)) {
// Move file and grab filename
$destinationFilename = sprintf('%s.%s', uniqid(), $image->getExtension());
$image->move(Config::getStorageFSPath() . "profilbilder/$destinationFilename");
@@ -482,25 +585,52 @@ class User implements JsonSerializable
}
if (count($failed) > 0) {
// We failed, go back
$db->rollBack();
throw ApiError::failedUpdate($failed, $reasons);
}
// Commit the changes
$db->commit();
return User::getByID($this->id);
}
public function delete(): User
/**
* Delete user
*
* @param int $limit Limit of list for which the returned pages is calculated.
*
* @return array Returns deleted user and resulting amount of pages for a given limit.
*/
public function delete(int $limit): array
{
$db = Database::getInstance();
$stmt = $db->prepare("DELETE FROM egb_benutzer WHERE id = :ID");
$stmt->bindValue(":ID", $this->id);
return $this;
$stmt = $db->prepare(
"SELECT
COUNT(*)
FROM
egb_benutzer"
);
$stmt->execute();
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $this];
}
/**
* List of posts by user
*
* @param int $page Current page (offset)
* @param int $limit Users per page
* @param string $sort Sort direction
*
* @return array Number of pages and posts of selected page
*/
public function posts(int $page, int $limit, string $sort)
{
$db = Database::getInstance();
@@ -543,6 +673,9 @@ class User implements JsonSerializable
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $list];
}
/**
* Refresh user token expiry
*/
public function keepFresh()
{
try {
@@ -565,41 +698,68 @@ class User implements JsonSerializable
* Getters
*/
/**
* Get user ID
*/
public function getID(): int
{
return $this->id;
}
/**
* Get username
*/
public function getUsername(): string
{
return $this->username;
}
/**
* Get user status
*
* * 0: Not confirmed
* * 1: Confirmed
*/
public function getStatus(): int
{
return $this->status;
}
/**
* Get user email
*/
public function getEmail(): string
{
return $this->email;
}
/**
* Get user image path
*/
public function getImage(): ?string
{
return $this->image;
}
/**
* Get user admin status
*/
public function getIsAdmin(): bool
{
return $this->isAdmin;
}
/**
* Get time of user creation
*/
public function getMemberSince(): DateTime
{
return $this->memberSince;
}
/**
* Get count of posts by user
*/
public function getPostCount(): int
{
return $this->postCount;
+19
View File
@@ -5,22 +5,41 @@ namespace Khofmann\Request;
use Pecee\Http\Request as PRequest;
use Pecee\SimpleRouter\SimpleRouter;
/**
* Facade for Request access (wraps SimpleRouter)
*/
class Request
{
/**
* Private since facade.
*/
private function __construct()
{
}
/**
* Get current request
*/
public static function request(): PRequest
{
return SimpleRouter::request();
}
/**
* Get header value from current request
*
* @param string $name Field name
* @param mixed $defaultValue Default value if header field was null
* @param bool $tryParse When enabled the method will try to find the header from both from client (http) and server-side variants, if the header is not found.
*/
public static function header(string $name, $defaultValue = null, bool $tryParse = true): ?string
{
return Request::request()->getHeader($name, $defaultValue, $tryParse);
}
/**
* Get authentication header field
*/
public static function token()
{
return Request::header("token");
+28
View File
@@ -5,17 +5,33 @@ namespace Khofmann\Response;
use Pecee\SimpleRouter\SimpleRouter;
use Pecee\Http\Response as PResponse;
/**
* Facade for Response creation (wraps SimpleRouter)
*/
class Response
{
/**
* Private since facade.
*/
private function __construct()
{
}
/**
* Get current response
*/
public static function response(): PResponse
{
return SimpleRouter::response();
}
/**
* Create JSON response
*
* @param mixed $value Body of response
* @param int $options Options passed to `json_encode`
* @param int $dept Depth passed to `json_encode`
*/
public static function json($value, int $options = 0, int $dept = 512): void
{
if (is_bool($value)) {
@@ -26,6 +42,12 @@ class Response
SimpleRouter::response()->json($value, $options, $dept);
}
/**
* Create API error response
*
* @param string $value Body of error
* @param int $code HTTP code of error
*/
public static function apiError(string $value, int $code): void
{
Response::response()->header('Content-Type: application/json; charset=utf-8')->httpCode($code);
@@ -33,6 +55,12 @@ class Response
exit(0);
}
/**
* Create redirect response
*
* @param string $url New target
* @param ?int $code HTTP code
*/
public static function redirect(string $url, ?int $code = null): void
{
if ($code !== null) {
+6
View File
@@ -1,5 +1,11 @@
<?php
/**
* Constrain integer to interval `[min,max]`
*
* @param int $min lower bound
* @param int $max upper bound
*/
function constrain(int $min, int $max, $n): int
{
return max(min($max, $n), $min);