validate user inputs
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
29685562bb
commit
f8e8e642d3
@ -1,29 +1,41 @@
|
|||||||
import React from "react";
|
import React, {CSSProperties, useState} from "react";
|
||||||
import "../style/editor.css";
|
import "../style/editor.css";
|
||||||
import TitleInput from "../components/TitleInput";
|
import TitleInput from "../components/TitleInput";
|
||||||
import {API} from "../Constants";
|
import {API} from "../Constants";
|
||||||
|
|
||||||
|
const ERROR_STYLE: CSSProperties = {
|
||||||
|
borderColor: "red"
|
||||||
|
}
|
||||||
|
|
||||||
export default function Editor({id, name}: { id: number, name: string }) {
|
export default function Editor({id, name}: { id: number, name: string }) {
|
||||||
|
|
||||||
|
const [style, setStyle] = useState<CSSProperties>({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="topbar">
|
<div id="topbar">
|
||||||
<div>LEFT</div>
|
<div>LEFT</div>
|
||||||
<TitleInput default_value={name} on_validated={name => update_tactic_name(id, name)}/>
|
<TitleInput style={style} default_value={name} on_validated={new_name => {
|
||||||
|
fetch(`${API}/tactic/${id}/edit/name`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: new_name,
|
||||||
|
})
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
setStyle({})
|
||||||
|
} else {
|
||||||
|
setStyle(ERROR_STYLE)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}/>
|
||||||
<div>RIGHT</div>
|
<div>RIGHT</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_tactic_name(id: number, new_name: string) {
|
|
||||||
//FIXME avoid absolute path as they would not work on staging server
|
|
||||||
fetch(`${API}/tactic/${id}/edit/name`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: new_name
|
|
||||||
})
|
|
||||||
}).then(response => {
|
|
||||||
if (!response.ok)
|
|
||||||
alert("could not update tactic name!")
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,43 +1,77 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Api;
|
namespace App\Api;
|
||||||
use App\Gateway\TacticInfoGateway;
|
|
||||||
|
use App\Model\TacticModel;
|
||||||
|
|
||||||
|
use App\HttpCodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API endpoint related to tactics
|
* API endpoint related to tactics
|
||||||
*/
|
*/
|
||||||
class TacticEndpoint {
|
class TacticEndpoint {
|
||||||
private TacticInfoGateway $tactics;
|
private TacticModel $model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TacticInfoGateway $tactics
|
* @param TacticModel $model
|
||||||
*/
|
*/
|
||||||
public function __construct(TacticInfoGateway $tactics) {
|
public function __construct(TacticModel $model) {
|
||||||
$this->tactics = $tactics;
|
$this->model = $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function updateName(int $tactic_id) {
|
public function updateName(int $tactic_id): void {
|
||||||
$request_body = file_get_contents('php://input');
|
$request_body = file_get_contents('php://input');
|
||||||
$data = json_decode($request_body);
|
$data = json_decode($request_body);
|
||||||
|
|
||||||
$new_name = $data->name;
|
if (!isset($data->name)) {
|
||||||
|
http_response_code(HttpCodes::BAD_REQUEST);
|
||||||
|
echo "missing 'name'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->tactics->update($tactic_id, $new_name);
|
$fails = [];
|
||||||
|
|
||||||
|
$this->model->updateName($fails, $tactic_id, $data->name);
|
||||||
|
if (!empty($fails)) {
|
||||||
|
http_response_code(HttpCodes::PRECONDITION_FAILED);
|
||||||
|
echo json_encode($fails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newTactic() {
|
public function newTactic(): void {
|
||||||
$request_body = file_get_contents('php://input');
|
$request_body = file_get_contents('php://input');
|
||||||
$data = json_decode($request_body);
|
$data = json_decode($request_body);
|
||||||
|
|
||||||
$initial_name = $data->name;
|
$initial_name = $data->name;
|
||||||
$id = $this->tactics->insert($initial_name)->getId();
|
|
||||||
|
|
||||||
|
if (!isset($data->name)) {
|
||||||
|
http_response_code(HttpCodes::BAD_REQUEST);
|
||||||
|
echo "missing 'name'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fails = [];
|
||||||
|
$tactic = $this->model->makeNew($fails, $initial_name);
|
||||||
|
|
||||||
|
if (!empty($fails)) {
|
||||||
|
http_response_code(HttpCodes::PRECONDITION_FAILED);
|
||||||
|
echo json_encode($fails);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $tactic->getId();
|
||||||
echo "{id: $id}";
|
echo "{id: $id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTacticInfo(int $id) {
|
public function getTacticInfo(int $id): void {
|
||||||
$tactic_info = $this->tactics->get($id);
|
$tactic_info = $this->model->get($id);
|
||||||
|
|
||||||
|
if ($tactic_info == null) {
|
||||||
|
http_response_code(HttpCodes::NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode($tactic_info);
|
echo json_encode($tactic_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to define constants of used http codes
|
||||||
|
*/
|
||||||
|
class HttpCodes {
|
||||||
|
public const OK = 200;
|
||||||
|
public const BAD_REQUEST = 400;
|
||||||
|
public const NOT_FOUND = 404;
|
||||||
|
public const PRECONDITION_FAILED = 412;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error that concerns a field, with a bound message name
|
||||||
|
*/
|
||||||
|
class FieldValidationFail extends ValidationFail {
|
||||||
|
private string $fieldName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function __construct(string $fieldName, string $message) {
|
||||||
|
parent::__construct("field", $message);
|
||||||
|
$this->fieldName = $fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFieldName(): string {
|
||||||
|
return $this->fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function invalidChars(string $fieldName): FieldValidationFail {
|
||||||
|
return new FieldValidationFail($fieldName, "field contains illegal chars");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function empty(string $fieldName): FieldValidationFail {
|
||||||
|
return new FieldValidationFail($fieldName, "field is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return ["field" => $this->fieldName, "message" => $this->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple validator that takes a predicate and an error factory
|
||||||
|
*/
|
||||||
|
class SimpleFunctionValidator implements Validator {
|
||||||
|
|
||||||
|
private $predicate;
|
||||||
|
private $error_factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $predicate a function predicate with signature: `(string) => bool`, to validate the given string
|
||||||
|
* @param callable $error_factory a factory function with signature `(string) => Error)` to emit error when the predicate fails
|
||||||
|
*/
|
||||||
|
public function __construct(callable $predicate, callable $error_factory) {
|
||||||
|
$this->predicate = $predicate;
|
||||||
|
$this->error_factory = $error_factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(string $name, $val): ?ValidationFail {
|
||||||
|
if (!call_user_func_array($this->predicate, [$val])) {
|
||||||
|
return call_user_func_array($this->error_factory, [$name]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for validation
|
||||||
|
*/
|
||||||
|
class Validation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a value from validators, appending failures in the given errors array.
|
||||||
|
* @param mixed $val the value to validate
|
||||||
|
* @param string $val_name the name of the value
|
||||||
|
* @param array $errors array to push when a validator fails
|
||||||
|
* @param Validator ...$validators given validators
|
||||||
|
* @return bool true if any of the given validators did fail
|
||||||
|
*/
|
||||||
|
public static function validate($val, string $val_name, array &$errors, Validator...$validators): bool {
|
||||||
|
$had_errors = false;
|
||||||
|
foreach ($validators as $validator) {
|
||||||
|
$error = $validator->validate($val_name, $val);
|
||||||
|
if ($error != null) {
|
||||||
|
$errors[] = $error;
|
||||||
|
$had_errors = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $had_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
class ValidationFail implements \JsonSerializable {
|
||||||
|
private string $kind;
|
||||||
|
|
||||||
|
private string $message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
* @param string $kind
|
||||||
|
*/
|
||||||
|
protected function __construct(string $kind, string $message) {
|
||||||
|
$this->message = $message;
|
||||||
|
$this->kind = $kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage(): string {
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKind(): string {
|
||||||
|
return $this->kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return ["error" => $this->kind, "message" => $this->message];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function notFound(string $message): ValidationFail {
|
||||||
|
return new ValidationFail("not found", $message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
interface Validator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validates a variable string
|
||||||
|
* @param string $name the name of the tested value
|
||||||
|
* @param mixed $val the value to validate
|
||||||
|
* @return ValidationFail|null the error if the validator did fail, or null if it succeeded
|
||||||
|
*/
|
||||||
|
public function validate(string $name, $val): ?ValidationFail;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Validation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of standard validators
|
||||||
|
*/
|
||||||
|
class Validators {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Validator a validator that validates strings that does not contain invalid chars such as `<` and `>`
|
||||||
|
*/
|
||||||
|
public static function noInvalidChars(): Validator {
|
||||||
|
return new SimpleFunctionValidator(
|
||||||
|
fn($str) => !filter_var($str, FILTER_VALIDATE_REGEXP, ['options' => ["regexp" => "/[<>]/"]]),
|
||||||
|
fn(string $name) => FieldValidationFail::invalidChars($name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Validator a validator that validates non-empty strings
|
||||||
|
*/
|
||||||
|
public static function nonEmpty(): Validator {
|
||||||
|
return new SimpleFunctionValidator(
|
||||||
|
fn($str) => !empty($str),
|
||||||
|
fn(string $name) => FieldValidationFail::empty($name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue