fix conflicts
continuous-integration/drone/push Build is failing
Details
@ -0,0 +1,21 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'react',
|
||||||
|
'react-hooks'
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'plugin:react-hooks/recommended'
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
# Welcome on the documentation's description
|
||||||
|
|
||||||
|
## Let's get started with the architecture diagram.
|
||||||
|

|
||||||
|
|
||||||
|
As you can see our entire application is build around three main package.
|
||||||
|
All of them contained in "src" package.
|
||||||
|
The core represent the main code of the web application.
|
||||||
|
It contains all the validation protocol, detailed below, the model of the imposed MVC architecture.
|
||||||
|
It also has a package named "data", it is a package of the structure of all the data we use in our application.
|
||||||
|
Of course there is package containing all the gateways as its name indicates. It is where we use the connection to our database.
|
||||||
|
Allowing to operate on it.
|
||||||
|
|
||||||
|
The App now is more about the web application itself.
|
||||||
|
Having all the controllers of the MVC architecture the use the model, the validation system and the http system in the core.
|
||||||
|
It also calls the twig's views inside of App. Finally, it uses the package Session. This one replace the $_SESSION we all know in PHP.
|
||||||
|
Thanks to this we have a way cleaner use of all session's data.
|
||||||
|
Nevertheless, all the controllers call not only twig views but also react ones.
|
||||||
|
Those are present in the package "front", dispatched in several other packages.
|
||||||
|
Such as assets having all the image and stuff, model containing all the data's structure, style centralizing all css file and eventually components the last package used for the editor.
|
||||||
|
|
||||||
|
Finally, we have the package "Api" that allows to share code and bind all the different third-hand application such as the web admin one.
|
||||||
|
|
||||||
|
## Main data class diagram.
|
||||||
|

|
||||||
|
|
||||||
|
You can see how our data is structured contained in the package "data" as explained right above.
|
||||||
|
There is two clear part.
|
||||||
|
First of all, the Tactic one.
|
||||||
|
We got a nice class named TacticInfo representing as it says the information about a tactic, nothing to discuss more about.
|
||||||
|
It associates an attribute of type "CourtType". This last is just an "evoluated" type of enum with some more features.
|
||||||
|
We had to do it this way because of the language PHP that doesn't implement such a thing as an enum.
|
||||||
|
|
||||||
|
Now, let's discuss a much bigger part of the diagram.
|
||||||
|
In this part we find all the team logic. Actually, a team only have an array of members and a "TeamInfo".
|
||||||
|
The class "TeamInfo" only exists to split the team's information data (name, id etc) from the members.
|
||||||
|
The type Team does only link the information about a team and its members.
|
||||||
|
Talking about them, their class indicate what role they have (either Coach or Player) in the team.
|
||||||
|
Because a member is registered in the app, therefore he is a user of it. Represented by the type of the same name.
|
||||||
|
This class does only contain all the user's basic information.
|
||||||
|
The last class we have is the Account. It could directly be incorporated in User but we decided to split it the same way we did for the team.
|
||||||
|
Then, Account only has a user and a token which is an identifier.
|
||||||
|
|
||||||
|
## Validation's class diagram
|
||||||
|

|
||||||
|
|
||||||
|
We implemented our own validation system, here it is!
|
||||||
|
For the validation methods (for instance those in DefaultValidators) we use lambda to instantiate a Validator.
|
||||||
|
In general, we use the implementation "SimpleFunctionValidator".
|
||||||
|
We reconize the strategy pattern. Indeed, we need a family of algorithms because we have many classes that only differ by the way they validate.
|
||||||
|
Futhermore, you may have notices the ComposedValidator that allows to chain several Validator.
|
||||||
|
We can see that this system uses the composite pattern
|
||||||
|
The other part of the diagram is about the failure a specific field's validation.
|
||||||
|
We have a concrete class to return a something more general. All the successors are just more precise about the failure.
|
||||||
|
|
||||||
|
## Http's class diagram
|
||||||
|

|
||||||
|
It were we centralize what the app can render, and what the api can receive.
|
||||||
|
Then, we got the "basic" response (HttpResponse) that just render a HttpCodes.
|
||||||
|
We have two successors for now. ViewHttpResponse render not only a code but also a view, either react or twig ones.
|
||||||
|
Finally, we have the JsonHttpResponse that renders, as it's name says, some Json.
|
||||||
|
|
||||||
|
## Session's class diagram
|
||||||
|

|
||||||
|
|
||||||
|
It encapsulates the PHP's array "$_SESSION". With two interfaces that dictate how a session should be handled, and same for a mutable one.
|
||||||
|
## Model View Controller
|
||||||
|
All class diagram, separated by their range of action, of the imposed MVC architecture.
|
||||||
|
All of them have a controller that validates entries with the validation system and check the permission the user has,and whether or not actually do the action.
|
||||||
|
These controllers are composed by a Model that handle the pure data and is the point of contact between these and the gateways.
|
||||||
|
Speaking of which, Gateways are composing Models. They use the connection class to access the database and send their query.
|
||||||
|
|
||||||
|
### Team
|
||||||
|

|
||||||
|
|
||||||
|
### Editor
|
||||||
|

|
||||||
|
|
||||||
|
### Authentification
|
||||||
|

|
||||||
|
|
@ -1,3 +1,3 @@
|
|||||||
# The wiki also exists
|
* [Description.md](Description.md)
|
||||||
|
* [Conception.md](Conception.md)
|
||||||
Some of our explanation are contained in the [wiki](https://codefirst.iut.uca.fr/git/IQBall/Application-Web/wiki)
|
* [how-to-dev.md](how-to-dev.md)
|
@ -0,0 +1,60 @@
|
|||||||
|
@startuml
|
||||||
|
'https://plantuml.com/component-diagram
|
||||||
|
|
||||||
|
package front{
|
||||||
|
package assets
|
||||||
|
package components
|
||||||
|
package model
|
||||||
|
package style
|
||||||
|
package views
|
||||||
|
}
|
||||||
|
|
||||||
|
database sql{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package src {
|
||||||
|
|
||||||
|
package "Api"{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package "App" {
|
||||||
|
package Controller
|
||||||
|
package Session
|
||||||
|
package Views
|
||||||
|
}
|
||||||
|
|
||||||
|
package Core{
|
||||||
|
package Data
|
||||||
|
package Gateway
|
||||||
|
package Http
|
||||||
|
package Model
|
||||||
|
package Validation
|
||||||
|
[Connection]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[sql] -- [Connection]
|
||||||
|
|
||||||
|
[views] -- [style]
|
||||||
|
[views] -- [components]
|
||||||
|
[views] -- [assets]
|
||||||
|
[views] -- [model]
|
||||||
|
|
||||||
|
[Gateway] -- [Connection]
|
||||||
|
|
||||||
|
[Validation] -- [Controller]
|
||||||
|
[Controller] -- [Session]
|
||||||
|
[Controller] -- [Http]
|
||||||
|
[Controller] -- [Views]
|
||||||
|
[Controller] -- [views]
|
||||||
|
[Controller] -- [Model]
|
||||||
|
[Model] -- [Gateway]
|
||||||
|
|
||||||
|
[Api] -- [Validation]
|
||||||
|
[Api] -- [Model]
|
||||||
|
[Api] -- [Http]
|
||||||
|
|
||||||
|
@enduml
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,44 @@
|
|||||||
|
@startuml
|
||||||
|
class EditorController {
|
||||||
|
+__construct (model : TacticModel)
|
||||||
|
+ openEditorFor(tactic:TacticInfo): ViewHttpResponse
|
||||||
|
+ createNew(): ViewHttpResponse
|
||||||
|
+ openTestEditor(courtType:CourtType): ViewHttpResponse
|
||||||
|
+ createNewOfKind(type:CourtType, session:SessionHandle): ViewHttpResponse
|
||||||
|
+ openEditor(id:int, session:SessionHandle): ViewHttpResponse
|
||||||
|
}
|
||||||
|
EditorController *-- "- model" TacticModel
|
||||||
|
|
||||||
|
class TacticModel {
|
||||||
|
+ TACTIC_DEFAULT_NAME:int {static}{frozen}
|
||||||
|
+ __construct(tactics : TacticInfoGateway)
|
||||||
|
+ makeNew(name:string, ownerId:int, type:CourtType): TacticInfo
|
||||||
|
+ makeNewDefault(ownerId:int, type:CourtType): ?TacticInfo
|
||||||
|
+ get(id:int): ?TacticInfo
|
||||||
|
+ getLast(nb:int, ownerId:int): array
|
||||||
|
+ getAll(ownerId:int): ?array
|
||||||
|
+ updateName(id:int, name:string, authId:int): array
|
||||||
|
+ updateContent(id:int, json:string): ?ValidationFail
|
||||||
|
}
|
||||||
|
|
||||||
|
TacticModel *-- "- tactics" TacticInfoGateway
|
||||||
|
|
||||||
|
class TacticInfoGateway{
|
||||||
|
+ __construct(con : Connexion)
|
||||||
|
+ get(id:int): ?TacticInfo
|
||||||
|
+ getLast(nb:int, ownerId:int): ?array
|
||||||
|
+ getAll(ownerId:int): ?array
|
||||||
|
+ insert(name:string, owner:int, type:CourtType): int
|
||||||
|
+ updateName(id:int, name:string): bool
|
||||||
|
+ updateContent(id:int, json:string): bool
|
||||||
|
}
|
||||||
|
|
||||||
|
TacticInfoGateway *--"- con" Connexion
|
||||||
|
|
||||||
|
class TacticValidator{
|
||||||
|
+ validateAccess(tacticId:int, tactic:?TacticInfo, ownerId:int): ?ValidationFail {static}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorController ..> TacticValidator
|
||||||
|
|
||||||
|
@enduml
|
@ -1,63 +1,87 @@
|
|||||||
@startuml
|
@startuml
|
||||||
class Team {
|
|
||||||
- name: string
|
|
||||||
- picture: Url
|
|
||||||
- members: array<int, MemberRole>
|
|
||||||
|
|
||||||
+ __construct(name : string, picture : string, mainColor : Colo, secondColor : Color)
|
|
||||||
+ getName(): string
|
|
||||||
+ getPicture(): Url
|
|
||||||
+ getMainColor(): Color
|
|
||||||
+ getSecondColor(): Color
|
|
||||||
+ listMembers(): array<Member>
|
|
||||||
}
|
|
||||||
|
|
||||||
Team --> "- mainColor" Color
|
|
||||||
Team --> "- secondColor" Color
|
|
||||||
|
|
||||||
class Color {
|
|
||||||
- value: string
|
|
||||||
- __construct(value : string)
|
|
||||||
+ getValue(): string
|
|
||||||
+ from(value: string): Color
|
|
||||||
+ tryFrom(value : string) : ?Color
|
|
||||||
}
|
|
||||||
|
|
||||||
class TeamGateway{
|
class TeamGateway{
|
||||||
--
|
|
||||||
+ __construct(con : Connexion)
|
+ __construct(con : Connexion)
|
||||||
+ insert(name : string ,picture : string, mainColor : Color, secondColor : Color)
|
+ insert(name : string ,picture : string, mainColor : Color, secondColor : Color)
|
||||||
+ listByName(name : string): array
|
+ listByName(name : string): array
|
||||||
|
+ getTeamById(id:int): ?TeamInfo
|
||||||
|
+ getTeamIdByName(name:string): ?int
|
||||||
|
+ deleteTeam(idTeam:int): void
|
||||||
|
+ editTeam(idTeam:int, newName:string, newPicture:string, newMainColor:string, newSecondColor:string)
|
||||||
|
+ getAll(user:int): array
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamGateway *--"- con" Connexion
|
TeamGateway *--"- con" Connexion
|
||||||
TeamGateway ..> Color
|
|
||||||
|
|
||||||
|
class MemberGateway{
|
||||||
|
|
||||||
|
+ __construct(con : Connexion)
|
||||||
|
+ insert(idTeam:int, userId:int, role:string): void
|
||||||
|
+ getMembersOfTeam(teamId:int): array
|
||||||
|
+ remove(idTeam:int, idMember:int): void
|
||||||
|
+ isCoach(email:string, idTeam:int): bool
|
||||||
|
+ isMemberOfTeam(idTeam:int, idCurrentUser:int): bool
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberGateway *--"- con" Connexion
|
||||||
|
|
||||||
|
class AccountGateway{
|
||||||
|
+ __construct(con : Connexion)
|
||||||
|
+ insertAccount(name:string, email:string, token:string, hash:string, profilePicture:string): int
|
||||||
|
+ getRowsFromMail(email:string): ?array
|
||||||
|
+ getHash(email:string): ?string
|
||||||
|
+ exists(email:string): bool
|
||||||
|
+ getAccountFromMail(email:string): ?Account
|
||||||
|
+ getAccountFromToken(token:string): ?Account
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountGateway *--"- con" Connexion
|
||||||
|
|
||||||
class TeamModel{
|
class TeamModel{
|
||||||
---
|
|
||||||
+ __construct(gateway : TeamGateway)
|
+ __construct(gateway : TeamGateway)
|
||||||
+ createTeam(name : string,picture : string, mainColorValue : int, secondColorValue : int, errors : array)
|
+ createTeam(name : string,picture : string, mainColorValue : int, secondColorValue : int, errors : array)
|
||||||
|
+ addMember(mail:string, teamId:int, role:string): int
|
||||||
+ listByName(name : string ,errors : array) : ?array
|
+ listByName(name : string ,errors : array) : ?array
|
||||||
+ displayTeam(id : int): Team
|
+ getTeam(idTeam:int, idCurrentUser:int): ?Team
|
||||||
|
+ deleteMember(idMember:int, teamId:int): int
|
||||||
|
+ deleteTeam(email:string, idTeam:int): int
|
||||||
|
+ isCoach(idTeam:int, email:string): bool
|
||||||
|
+ editTeam(idTeam:int, newName:string, newPicture:string, newMainColor:string, newSecondColor:string)
|
||||||
|
+ getAll(user:int): array
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamModel *--"- gateway" TeamGateway
|
TeamModel *--"- members" MemberGateway
|
||||||
TeamModel ..> Team
|
TeamModel *--"- teams" TeamGateway
|
||||||
TeamModel ..> Color
|
TeamModel *--"- teams" AccountGateway
|
||||||
|
|
||||||
|
|
||||||
class TeamController{
|
class TeamController{
|
||||||
- twig : Environement
|
+ __construct( model : TeamModel)
|
||||||
--
|
+ displayCreateTeam(session:SessionHandle): ViewHttpResponse
|
||||||
+ __construct( model : TeamModel, twig : Environement)
|
+ displayDeleteMember(session:SessionHandle): ViewHttpResponse
|
||||||
+ displaySubmitTeam() : HttpResponse
|
+ submitTeam(request:array, session:SessionHandle): HttpResponse
|
||||||
+ submitTeam(request : array) : HttpResponse
|
+ displayListTeamByName(session:SessionHandle): ViewHttpResponse
|
||||||
+ displayListTeamByName(): HttpResponse
|
+ listTeamByName(request:array, session:SessionHandle): HttpResponse
|
||||||
+ listTeamByName(request : array) : HttpResponse
|
+ deleteTeamById(id:int, session:SessionHandle): HttpResponse
|
||||||
+ displayTeam(id : int): HttpResponse
|
+ displayTeam(id:int, session:SessionHandle): ViewHttpResponse
|
||||||
|
+ displayAddMember(idTeam:int, session:SessionHandle): ViewHttpResponse
|
||||||
|
+ addMember(idTeam:int, request:array, session:SessionHandle): HttpResponse
|
||||||
|
+ deleteMember(idTeam:int, idMember:int, session:SessionHandle): HttpResponse
|
||||||
|
+ displayEditTeam(idTeam:int, session:SessionHandle): ViewHttpResponse
|
||||||
|
+ editTeam(idTeam:int, request:array, session:SessionHandle): HttpResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamController *--"- model" TeamModel
|
TeamController *--"- model" TeamModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Connexion { }
|
class Connexion { }
|
||||||
|
|
||||||
@enduml
|
@enduml
|
@ -0,0 +1,27 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
interface SessionHandle{
|
||||||
|
+ getInitialTarget(): ?string {abstract}
|
||||||
|
+ getAccount(): ?Account {abstract}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MutableSessionHandle{
|
||||||
|
+ setInitialTarget(url:?string): void
|
||||||
|
+ setAccount(account:Account): void
|
||||||
|
+ destroy(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhpSessionHandle{
|
||||||
|
+ init(): self {static}
|
||||||
|
+ getAccount(): ?Account
|
||||||
|
+ getInitialTarget(): ?string
|
||||||
|
+ setAccount(account:Account): void
|
||||||
|
+ setInitialTarget(url:?string): void
|
||||||
|
+ destroy(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PhpSessionHandle ..|> MutableSessionHandle
|
||||||
|
MutableSessionHandle ..|> SessionHandle
|
||||||
|
|
||||||
|
@enduml
|
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
export OUTPUT=$1
|
||||||
|
export BASE=$2
|
||||||
|
|
||||||
|
rm -rf $OUTPUT/*
|
||||||
|
|
||||||
|
echo "VITE_API_ENDPOINT=$BASE/api" >> .env.PROD
|
||||||
|
echo "VITE_BASE=$BASE" >> .env.PROD
|
||||||
|
|
||||||
|
ci/build_react.msh
|
||||||
|
|
||||||
|
mkdir -p $OUTPUT/profiles/
|
||||||
|
|
||||||
|
sed -E 's/\/\*PROFILE_FILE\*\/\s*".*"/"profiles\/prod-config-profile.php"/' config.php > $OUTPUT/config.php
|
||||||
|
sed -E "s/const BASE_PATH = .*;/const BASE_PATH = \"$(sed s/\\//\\\\\\//g <<< "$BASE")\";/" profiles/prod-config-profile.php > $OUTPUT/profiles/prod-config-profile.php
|
||||||
|
|
||||||
|
cp -r vendor sql src public $OUTPUT
|
@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine on
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^.*$ ./index.php [NC,L,QSA]
|
Before Width: | Height: | Size: 747 B |
@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine on
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^.*$ ./index.php [NC,L,QSA]
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Api;
|
||||||
|
|
||||||
|
use IQBall\Core\Control;
|
||||||
|
use IQBall\Core\ControlSchemaErrorResponseFactory;
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpRequest;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
use IQBall\Core\Validation\Validator;
|
||||||
|
|
||||||
|
class APIControl {
|
||||||
|
private static function errorFactory(): ControlSchemaErrorResponseFactory {
|
||||||
|
return new class () implements ControlSchemaErrorResponseFactory {
|
||||||
|
public function apply(array $failures): HttpResponse {
|
||||||
|
return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs given callback, if the request's payload json validates the given schema.
|
||||||
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static function runChecked(array $schema, callable $run): HttpResponse {
|
||||||
|
return Control::runChecked($schema, $run, self::errorFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs given callback, if the given request data array validates the given schema.
|
||||||
|
* @param array<string, mixed> $data the request's data array.
|
||||||
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
||||||
|
return Control::runCheckedFrom($data, $schema, $run, self::errorFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Api\Controller;
|
||||||
|
|
||||||
|
use IQBall\Api\APIControl;
|
||||||
|
use IQBall\Core\Data\Account;
|
||||||
|
use IQBall\Core\Gateway\AccountGateway;
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpRequest;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
use IQBall\Core\Model\AuthModel;
|
||||||
|
use IQBall\Core\Validation\DefaultValidators;
|
||||||
|
use IQBall\Core\Validation\ValidationFail;
|
||||||
|
|
||||||
|
class APIAccountsController {
|
||||||
|
private AccountGateway $accounts;
|
||||||
|
private AuthModel $authModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AuthModel $model
|
||||||
|
* @param AccountGateway $accounts
|
||||||
|
*/
|
||||||
|
public function __construct(AuthModel $model, AccountGateway $accounts) {
|
||||||
|
$this->accounts = $accounts;
|
||||||
|
$this->authModel = $model;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $request
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public function listUsers(array $request): HttpResponse {
|
||||||
|
return APIControl::runCheckedFrom($request, [
|
||||||
|
|
||||||
|
'start' => [DefaultValidators::isUnsignedInteger()],
|
||||||
|
'n' => [DefaultValidators::isIntInRange(0, 250)],
|
||||||
|
'search' => [DefaultValidators::lenBetween(0, 256)],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$accounts = $this->accounts->searchAccounts(intval($req['start']), intval($req['n']), $req["search"]);
|
||||||
|
$users = array_map(fn(Account $acc) => $acc->getUser(), $accounts);
|
||||||
|
return new JsonHttpResponse([
|
||||||
|
"users" => $users,
|
||||||
|
"totalCount" => $this->accounts->totalCount(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $userId
|
||||||
|
* @return HttpResponse given user information.
|
||||||
|
*/
|
||||||
|
public function getUser(int $userId): HttpResponse {
|
||||||
|
$acc = $this->accounts->getAccount($userId);
|
||||||
|
|
||||||
|
if ($acc == null) {
|
||||||
|
return new JsonHttpResponse([ValidationFail::notFound("User not found")], HttpCodes::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonHttpResponse($acc->getUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addUser(): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
"username" => [DefaultValidators::name()],
|
||||||
|
"email" => [DefaultValidators::email()],
|
||||||
|
"password" => [DefaultValidators::password()],
|
||||||
|
"isAdmin" => [DefaultValidators::bool()],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$model = new AuthModel($this->accounts);
|
||||||
|
|
||||||
|
$account = $model->register($req["username"], $req["password"], $req["email"]);
|
||||||
|
if ($account == null) {
|
||||||
|
return new JsonHttpResponse([new ValidationFail("already exists", "An account with provided email ")], HttpCodes::FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonHttpResponse([
|
||||||
|
"id" => $account->getUser()->getId(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeUsers(): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
"identifiers" => [DefaultValidators::array(), DefaultValidators::forall(DefaultValidators::isUnsignedInteger())],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$this->accounts->removeAccounts($req["identifiers"]);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateUser(int $id): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
"email" => [DefaultValidators::email()],
|
||||||
|
"username" => [DefaultValidators::name()],
|
||||||
|
"isAdmin" => [DefaultValidators::bool()],
|
||||||
|
], function (HttpRequest $req) use ($id) {
|
||||||
|
$mailAccount = $this->accounts->getAccount($id);
|
||||||
|
if ($mailAccount->getUser()->getId() != $id) {
|
||||||
|
return new JsonHttpResponse([new ValidationFail("email exists", "The provided mail address already exists for another account.")], HttpCodes::FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->authModel->update($id, $req["email"], $req["username"], $req["isAdmin"]);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Api\Controller;
|
||||||
|
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
|
||||||
|
class APIServerController {
|
||||||
|
private string $basePath;
|
||||||
|
private \PDO $pdo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $basePath
|
||||||
|
* @param \PDO $pdo
|
||||||
|
*/
|
||||||
|
public function __construct(string $basePath, \PDO $pdo) {
|
||||||
|
$this->basePath = $basePath;
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function countLines(string $table): int {
|
||||||
|
$stmnt = $this->pdo->prepare("SELECT count(*) FROM $table");
|
||||||
|
$stmnt->execute();
|
||||||
|
$res = $stmnt->fetch(\PDO::FETCH_BOTH);
|
||||||
|
return $res[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HttpResponse some (useless) information about the server
|
||||||
|
*/
|
||||||
|
public function getServerInfo(): HttpResponse {
|
||||||
|
|
||||||
|
return new JsonHttpResponse([
|
||||||
|
'base_path' => $this->basePath,
|
||||||
|
'date' => (int) gettimeofday(true) * 1000,
|
||||||
|
'database' => [
|
||||||
|
'accounts' => $this->countLines("Account") . " line(s)",
|
||||||
|
'tactics' => $this->countLines("Tactic") . " line(s)",
|
||||||
|
'teams' => $this->countLines("Team") . " line(s)",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Api\Controller;
|
||||||
|
|
||||||
|
use IQBall\Api\APIControl;
|
||||||
|
use IQBall\Core\Data\Account;
|
||||||
|
use IQBall\Core\Data\Team;
|
||||||
|
use IQBall\Core\Data\TeamInfo;
|
||||||
|
use IQBall\Core\Gateway\TeamGateway;
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpRequest;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
|
use IQBall\Core\Model\TeamModel;
|
||||||
|
use IQBall\Core\Validation\DefaultValidators;
|
||||||
|
|
||||||
|
class APITeamController {
|
||||||
|
private TeamModel $teamModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TeamModel $teamModel
|
||||||
|
*/
|
||||||
|
public function __construct(TeamModel $teamModel) {
|
||||||
|
$this->teamModel = $teamModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $req_params
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public function listTeams(array $req_params): HttpResponse {
|
||||||
|
return APIControl::runCheckedFrom($req_params, [
|
||||||
|
'start' => [DefaultValidators::isUnsignedInteger()],
|
||||||
|
'n' => [DefaultValidators::isUnsignedInteger()],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$teams = $this->teamModel->listAll(intval($req['start']), intval($req['n']));
|
||||||
|
return new JsonHttpResponse([
|
||||||
|
"totalCount" => $this->teamModel->countTeam(),
|
||||||
|
"teams" => $teams,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addTeam(): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
'name' => [DefaultValidators::name()],
|
||||||
|
'picture' => [DefaultValidators::isURL()],
|
||||||
|
'mainColor' => [DefaultValidators::hexColor()],
|
||||||
|
'secondaryColor' => [DefaultValidators::hexColor()],
|
||||||
|
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$this->teamModel->createTeam($req['name'], $req['picture'], $req['mainColor'], $req['secondaryColor']);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteTeamSelected(): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
'teams' => [],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$this->teamModel->deleteTeamSelected($req['teams']);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateTeam(int $id): HttpResponse {
|
||||||
|
return APIControl::runChecked([
|
||||||
|
'name' => [DefaultValidators::name()],
|
||||||
|
'picture' => [DefaultValidators::isURL()],
|
||||||
|
'mainColor' => [DefaultValidators::hexColor()],
|
||||||
|
'secondaryColor' => [DefaultValidators::hexColor()],
|
||||||
|
], function (HttpRequest $req) {
|
||||||
|
$this->teamModel->editTeam($req['id'], $req['name'], $req['picture'], $req['mainColor'], $req['secondaryColor']);
|
||||||
|
return HttpResponse::fromCode(HttpCodes::OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\App;
|
||||||
|
|
||||||
|
use IQBall\Core\Control;
|
||||||
|
use IQBall\Core\ControlSchemaErrorResponseFactory;
|
||||||
|
use IQBall\Core\Http\HttpCodes;
|
||||||
|
use IQBall\Core\Http\HttpRequest;
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Validation\Validator;
|
||||||
|
|
||||||
|
class AppControl {
|
||||||
|
private static function errorFactory(): ControlSchemaErrorResponseFactory {
|
||||||
|
return new class () implements ControlSchemaErrorResponseFactory {
|
||||||
|
public function apply(array $failures): HttpResponse {
|
||||||
|
return ViewHttpResponse::twig("error.html.twig", ['failures' => $failures], HttpCodes::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs given callback, if the request's payload json validates the given schema.
|
||||||
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static function runChecked(array $schema, callable $run): HttpResponse {
|
||||||
|
return Control::runChecked($schema, $run, self::errorFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs given callback, if the given request data array validates the given schema.
|
||||||
|
* @param array<string, mixed> $data the request's data array.
|
||||||
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
||||||
|
return Control::runCheckedFrom($data, $schema, $run, self::errorFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,46 +1,51 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace IQBall\App;
|
namespace IQBall\Core;
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
use IQBall\Core\Http\HttpCodes;
|
||||||
use IQBall\Core\Http\HttpRequest;
|
use IQBall\Core\Http\HttpRequest;
|
||||||
use IQBall\Core\Http\HttpResponse;
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Http\JsonHttpResponse;
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
use IQBall\Core\Validation\ValidationFail;
|
||||||
use IQBall\Core\Validation\Validator;
|
use IQBall\Core\Validation\Validator;
|
||||||
|
|
||||||
class Control {
|
class Control {
|
||||||
/**
|
/**
|
||||||
* Runs given callback, if the request's json validates the given schema.
|
* Runs given callback, if the request's payload json validates the given schema.
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => Validators` which represents the request object schema
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
* THe callback must accept an HttpRequest, and return an HttpResponse object.
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @param ControlSchemaErrorResponseFactory $errorFactory an error factory to use if the request does not validate the required schema
|
||||||
* @return HttpResponse
|
* @return HttpResponse
|
||||||
*/
|
*/
|
||||||
public static function runChecked(array $schema, callable $run): HttpResponse {
|
public static function runChecked(array $schema, callable $run, ControlSchemaErrorResponseFactory $errorFactory): HttpResponse {
|
||||||
$request_body = file_get_contents('php://input');
|
$request_body = file_get_contents('php://input');
|
||||||
$payload_obj = json_decode($request_body);
|
$payload_obj = json_decode($request_body);
|
||||||
if (!$payload_obj instanceof \stdClass) {
|
if (!$payload_obj instanceof \stdClass) {
|
||||||
$fail = new ValidationFail("bad-payload", "request body is not a valid json object");
|
$fail = new ValidationFail("bad-payload", "request body is not a valid json object");
|
||||||
return ViewHttpResponse::twig("error.html.twig", ["failures" => [$fail]], HttpCodes::BAD_REQUEST);
|
return $errorFactory->apply([$fail]);
|
||||||
|
|
||||||
}
|
}
|
||||||
$payload = get_object_vars($payload_obj);
|
$payload = get_object_vars($payload_obj);
|
||||||
return self::runCheckedFrom($payload, $schema, $run);
|
return self::runCheckedFrom($payload, $schema, $run, $errorFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs given callback, if the given request data array validates the given schema.
|
* Runs given callback, if the given request data array validates the given schema.
|
||||||
* @param array<string, mixed> $data the request's data array.
|
* @param array<string, mixed> $data the request's data array.
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => Validators` which represents the request object schema
|
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
||||||
* THe callback must accept an HttpRequest, and return an HttpResponse object.
|
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
||||||
|
* @param ControlSchemaErrorResponseFactory $errorFactory an error factory to use if the request does not validate the required schema
|
||||||
* @return HttpResponse
|
* @return HttpResponse
|
||||||
*/
|
*/
|
||||||
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
public static function runCheckedFrom(array $data, array $schema, callable $run, ControlSchemaErrorResponseFactory $errorFactory): HttpResponse {
|
||||||
$fails = [];
|
$fails = [];
|
||||||
$request = HttpRequest::from($data, $fails, $schema);
|
$request = HttpRequest::from($data, $fails, $schema);
|
||||||
|
|
||||||
if (!empty($fails)) {
|
if (!empty($fails)) {
|
||||||
return ViewHttpResponse::twig("error.html.twig", ['failures' => $fails], HttpCodes::BAD_REQUEST);
|
return $errorFactory->apply($fails);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return call_user_func_array($run, [$request]);
|
return call_user_func_array($run, [$request]);
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQBall\Core;
|
||||||
|
|
||||||
|
use IQBall\Core\Http\HttpResponse;
|
||||||
|
use IQBall\Core\Validation\ValidationFail;
|
||||||
|
|
||||||
|
interface ControlSchemaErrorResponseFactory {
|
||||||
|
/**
|
||||||
|
* @param ValidationFail[] $failures
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public function apply(array $failures): HttpResponse;
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* relative path of the public directory from the server's document root.
|
|
||||||
*/
|
|
||||||
function get_public_path(string $public_dir): string {
|
|
||||||
// find the server path of the index.php file
|
|
||||||
$basePath = substr($public_dir, strlen($_SERVER['DOCUMENT_ROOT']));
|
|
||||||
|
|
||||||
$basePathLen = strlen($basePath);
|
|
||||||
if ($basePathLen == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
$c = $basePath[$basePathLen - 1];
|
|
||||||
|
|
||||||
if ($c == "/" || $c == "\\") {
|
|
||||||
$basePath = substr($basePath, 0, $basePathLen - 1);
|
|
||||||
}
|
|
||||||
return $basePath;
|
|
||||||
}
|
|