refactor: encapsulate thesis creation SQL in Database::createThesis()

Move the raw identifier-generation query and the INSERT INTO theses /
INSERT INTO thesis_authors statements out of formulaire.php into two new
Database methods:

  generateThesisIdentifier(int $year): string
    – counts existing theses for the year inside the open transaction so
      concurrent workers cannot produce duplicate YYYY-NNN identifiers.

  createThesis(array $data): int
    – generates the identifier, INSERTs the thesis row, links the author
      via thesis_authors (author_order=1), returns the new thesis ID.

  getThesisIdentifier(int $id): string
    – fetches the stored identifier for a thesis ID; used by formulaire.php
      to reconstruct the upload path (storage/theses/YYYY/YYYY-NNN/).

formulaire.php now calls $db->createThesis([…]) + $db->getThesisIdentifier()
and no longer holds any raw PDO queries for the core thesis insert.
The $pdo local variable (previously $db->getPDO()) is removed entirely.

All four test suites (Unit, RateLimit, Integration, Security) pass.
This commit is contained in:
Pontoporeia
2026-03-28 13:52:43 +01:00
parent 2ec5a7f38f
commit 61ac3c002d
5 changed files with 95 additions and 41 deletions

View File

@@ -1101,6 +1101,84 @@ class Database {
return (int)$this->pdo->query("SELECT COUNT(*) FROM theses")->fetchColumn();
}
/**
* Generate a unique YYYY-NNN identifier for a new thesis in the given year.
* Counts existing theses for that year (published or not) to determine the next sequence
* number. Must be called inside the same transaction that performs the INSERT so that
* concurrent requests cannot produce duplicate identifiers.
*/
public function generateThesisIdentifier(int $year): string {
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM theses WHERE year = ?");
$stmt->execute([$year]);
$count = (int)$stmt->fetchColumn() + 1;
return sprintf("%d-%03d", $year, $count);
}
/**
* Insert a new thesis row, link its author, and return the new thesis ID.
*
* Expected keys in $data:
* year (int), orientation_id (int), ap_program_id (int), finality_id (int),
* title (string), subtitle (?string), synopsis (string),
* file_size_info (?string), baiu_link (?string), license_id (?int),
* author_id (int)
*
* The identifier is generated automatically from $data['year'].
* Must be called inside an open transaction.
*
* @return int The new thesis ID.
*/
public function createThesis(array $data): int {
$identifier = $this->generateThesisIdentifier((int)$data['year']);
$stmt = $this->pdo->prepare("
INSERT INTO theses (
identifier, title, subtitle, year,
orientation_id, ap_program_id, finality_id,
synopsis, file_size_info,
baiu_link, license_id,
submitted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
");
$stmt->execute([
$identifier,
$data['title'],
!empty($data['subtitle']) ? $data['subtitle'] : null,
(int)$data['year'],
(int)$data['orientation_id'],
(int)$data['ap_program_id'],
(int)$data['finality_id'],
$data['synopsis'],
!empty($data['file_size_info']) ? $data['file_size_info'] : null,
!empty($data['baiu_link']) ? $data['baiu_link'] : null,
isset($data['license_id']) ? $data['license_id'] : null,
]);
$thesisId = (int)$this->pdo->lastInsertId();
// Link author — always author_order = 1 for single-author submissions.
$stmt = $this->pdo->prepare(
"INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, 1)"
);
$stmt->execute([$thesisId, (int)$data['author_id']]);
return $thesisId;
}
/**
* Return the stored identifier string (e.g. "2024-003") for an existing thesis.
*/
public function getThesisIdentifier(int $thesisId): string {
$stmt = $this->pdo->prepare("SELECT identifier FROM theses WHERE id = ?");
$stmt->execute([$thesisId]);
$row = $stmt->fetch();
if (!$row) {
throw new \RuntimeException("Thesis #$thesisId not found");
}
return (string)$row['identifier'];
}
/**
* Insert a thesis file record
*/

View File

@@ -1 +1 @@
[1774702033]
[1774702347]