feat: P4 backend kvalita - SELECT * fix, overdue konsolidace, Validator
- SELECT * nahrazen explicitnimi sloupci ve 22 PHP souborech (69+ vyskytu) - users-handlers.php: password_hash explicitne vyloucen z dotazu - Overdue detekce presunuta do invoices.php routeru (1x pred dispatch misto 3x v handlerech) - Validator.php: validacni helper s pravidly required, string, int, email, in, numeric - PaginationHelper: PHPStan typy opraveny Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,10 @@ function handleGetAdmin(PDO $pdo): void
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
$sql = "
|
||||
SELECT a.*, CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
SELECT a.id, a.user_id, a.shift_date, a.arrival_time, a.arrival_address,
|
||||
a.break_start, a.break_end, a.departure_time, a.departure_address,
|
||||
a.notes, a.project_id, a.leave_type, a.leave_hours, a.created_at,
|
||||
CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
FROM attendance a
|
||||
JOIN users u ON a.user_id = u.id
|
||||
WHERE a.shift_date BETWEEN ? AND ?
|
||||
@@ -112,7 +115,11 @@ function handleGetWorkFund(PDO $pdo): void
|
||||
$startDate = sprintf('%04d-01-01', $year);
|
||||
$endDate = sprintf('%04d-%02d-%02d', $year, $maxMonth, cal_days_in_month(CAL_GREGORIAN, $maxMonth, $year));
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM attendance WHERE shift_date BETWEEN ? AND ? ORDER BY shift_date');
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
||||
departure_time, notes, project_id, leave_type, leave_hours
|
||||
FROM attendance WHERE shift_date BETWEEN ? AND ? ORDER BY shift_date'
|
||||
);
|
||||
$stmt->execute([$startDate, $endDate]);
|
||||
$allRecords = $stmt->fetchAll();
|
||||
|
||||
@@ -206,7 +213,13 @@ function handleGetWorkFund(PDO $pdo): void
|
||||
function handleGetLocation(PDO $pdo, int $recordId): void
|
||||
{
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT a.*, CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
SELECT a.id, a.user_id, a.shift_date, a.arrival_time,
|
||||
a.arrival_lat, a.arrival_lng, a.arrival_accuracy, a.arrival_address,
|
||||
a.break_start, a.break_end, a.departure_time,
|
||||
a.departure_lat, a.departure_lng, a.departure_accuracy,
|
||||
a.departure_address, a.notes, a.project_id,
|
||||
a.leave_type, a.leave_hours, a.created_at,
|
||||
CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
FROM attendance a
|
||||
JOIN users u ON a.user_id = u.id
|
||||
WHERE a.id = ?
|
||||
@@ -467,7 +480,11 @@ function handleUpdateBalance(PDO $pdo): void
|
||||
|
||||
function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM attendance WHERE id = ?');
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT id, user_id, shift_date, arrival_time, break_start, break_end,
|
||||
departure_time, notes, project_id, leave_type, leave_hours
|
||||
FROM attendance WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([$recordId]);
|
||||
$record = $stmt->fetch();
|
||||
|
||||
@@ -593,7 +610,10 @@ function handleUpdateAttendance(PDO $pdo, int $recordId): void
|
||||
|
||||
function handleDeleteAttendance(PDO $pdo, int $recordId): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM attendance WHERE id = ?');
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT id, user_id, shift_date, leave_type, leave_hours
|
||||
FROM attendance WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([$recordId]);
|
||||
$record = $stmt->fetch();
|
||||
|
||||
@@ -920,7 +940,10 @@ function handleGetPrint(PDO $pdo): void
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
$sql = "
|
||||
SELECT a.*, CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
SELECT a.id, a.user_id, a.shift_date, a.arrival_time, a.arrival_address,
|
||||
a.break_start, a.break_end, a.departure_time, a.departure_address,
|
||||
a.notes, a.project_id, a.leave_type, a.leave_hours, a.created_at,
|
||||
CONCAT(u.first_name, ' ', u.last_name) as user_name
|
||||
FROM attendance a
|
||||
JOIN users u ON a.user_id = u.id
|
||||
WHERE a.shift_date BETWEEN ? AND ?
|
||||
|
||||
@@ -217,7 +217,9 @@ function enrichRecordsWithProjectLogs(PDO $pdo, array &$records): void
|
||||
if (!empty($recordIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($recordIds), '?'));
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT * FROM attendance_project_logs WHERE attendance_id IN ($placeholders) ORDER BY started_at ASC"
|
||||
"SELECT id, attendance_id, project_id, started_at, ended_at, hours, minutes
|
||||
FROM attendance_project_logs
|
||||
WHERE attendance_id IN ($placeholders) ORDER BY started_at ASC"
|
||||
);
|
||||
$stmt->execute($recordIds);
|
||||
foreach ($stmt->fetchAll() as $log) {
|
||||
|
||||
@@ -460,7 +460,9 @@ class AuditLog
|
||||
|
||||
// Get logs
|
||||
$sql = "
|
||||
SELECT *
|
||||
SELECT id, user_id, username, user_ip, action,
|
||||
entity_type, entity_id, description,
|
||||
old_values, new_values, created_at
|
||||
FROM audit_logs
|
||||
$whereClause
|
||||
ORDER BY created_at DESC
|
||||
@@ -503,7 +505,9 @@ class AuditLog
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT *
|
||||
SELECT id, user_id, username, user_ip, action,
|
||||
entity_type, entity_id, description,
|
||||
old_values, new_values, created_at
|
||||
FROM audit_logs
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
@@ -531,7 +535,9 @@ class AuditLog
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT *
|
||||
SELECT id, user_id, username, user_ip, action,
|
||||
entity_type, entity_id, description,
|
||||
old_values, new_values, created_at
|
||||
FROM audit_logs
|
||||
WHERE entity_type = ? AND entity_id = ?
|
||||
ORDER BY created_at DESC
|
||||
|
||||
@@ -245,8 +245,11 @@ class JWTAuth
|
||||
|
||||
// First check if token exists (regardless of expiry)
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT rt.*, u.id as user_id, u.username, u.email, u.first_name, u.last_name,
|
||||
u.is_active, r.name as role_name, r.display_name as role_display_name
|
||||
SELECT rt.id, rt.user_id, rt.token_hash, rt.expires_at,
|
||||
rt.replaced_at, rt.remember_me,
|
||||
u.id as user_id, u.username, u.email,
|
||||
u.first_name, u.last_name, u.is_active,
|
||||
r.name as role_name, r.display_name as role_display_name
|
||||
FROM refresh_tokens rt
|
||||
JOIN users u ON rt.user_id = u.id
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
|
||||
@@ -14,6 +14,7 @@ class PaginationHelper
|
||||
/**
|
||||
* Nacte pagination parametry z GET requestu.
|
||||
*
|
||||
* @param array<string, string> $sortMap
|
||||
* @return array{page: int, per_page: int, sort: string, order: string, search: string}
|
||||
*/
|
||||
public static function parseParams(array $sortMap, string $defaultSort = 'created_at'): array
|
||||
@@ -43,9 +44,10 @@ class PaginationHelper
|
||||
* @param PDO $pdo
|
||||
* @param string $countSql - COUNT(*) dotaz
|
||||
* @param string $dataSql - SELECT dotaz (bez LIMIT/OFFSET)
|
||||
* @param array $params - parametry pro prepared statement
|
||||
* @param array<int, mixed> $params - parametry pro prepared statement
|
||||
* @param array{page: int, per_page: int, sort: string, order: string} $pagination
|
||||
* @return array{items: array, pagination: array}
|
||||
* @return array{items: array<int, array<string, mixed>>,
|
||||
* pagination: array{total: int, page: int, per_page: int, total_pages: int}}
|
||||
*/
|
||||
public static function paginate(
|
||||
PDO $pdo,
|
||||
|
||||
139
api/includes/Validator.php
Normal file
139
api/includes/Validator.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validacni helper pro API vstupy.
|
||||
*
|
||||
* Pouziti:
|
||||
* $v = new Validator($input);
|
||||
* $v->required('name')->string('name', 1, 255);
|
||||
* $v->required('email')->email('email');
|
||||
* $v->int('amount', 0, 1000000);
|
||||
* $v->in('status', ['active', 'inactive']);
|
||||
* if ($v->fails()) errorResponse($v->firstError());
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Validator
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $data;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $errors = [];
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function required(string $field, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
$this->errors[$field] = ($label ?: $field) . ' je povinné pole';
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function string(string $field, int $min = 0, int $max = 0, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return $this;
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
$this->errors[$field] = ($label ?: $field) . ' musí být text';
|
||||
return $this;
|
||||
}
|
||||
$len = mb_strlen($value);
|
||||
if ($min > 0 && $len < $min) {
|
||||
$this->errors[$field] = ($label ?: $field) . " musí mít alespoň {$min} znaků";
|
||||
} elseif ($max > 0 && $len > $max) {
|
||||
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max} znaků";
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function int(string $field, ?int $min = null, ?int $max = null, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return $this;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
$this->errors[$field] = ($label ?: $field) . ' musí být číslo';
|
||||
return $this;
|
||||
}
|
||||
$intVal = (int) $value;
|
||||
if ($min !== null && $intVal < $min) {
|
||||
$this->errors[$field] = ($label ?: $field) . " musí být alespoň {$min}";
|
||||
} elseif ($max !== null && $intVal > $max) {
|
||||
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max}";
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function email(string $field, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return $this;
|
||||
}
|
||||
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->errors[$field] = ($label ?: $field) . ' musí být platný e-mail';
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $allowed
|
||||
*/
|
||||
public function in(string $field, array $allowed, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return $this;
|
||||
}
|
||||
if (!in_array($value, $allowed, true)) {
|
||||
$this->errors[$field] = ($label ?: $field) . ' má neplatnou hodnotu';
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function numeric(string $field, ?float $min = null, ?float $max = null, string $label = ''): self
|
||||
{
|
||||
$value = $this->data[$field] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return $this;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
$this->errors[$field] = ($label ?: $field) . ' musí být číslo';
|
||||
return $this;
|
||||
}
|
||||
$numVal = (float) $value;
|
||||
if ($min !== null && $numVal < $min) {
|
||||
$this->errors[$field] = ($label ?: $field) . " musí být alespoň {$min}";
|
||||
} elseif ($max !== null && $numVal > $max) {
|
||||
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max}";
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fails(): bool
|
||||
{
|
||||
return count($this->errors) > 0;
|
||||
}
|
||||
|
||||
public function firstError(): string
|
||||
{
|
||||
return reset($this->errors) ?: '';
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function errors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user