mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
feat(deploy): add deploy-verify-permissions recipe + upload/run deploy-server.sh before verification + run migrations in deploy
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -6,6 +6,12 @@
|
|||||||
- [x] WAL mode already active (`PRAGMA journal_mode` → `wal`) — set in Database constructor
|
- [x] WAL mode already active (`PRAGMA journal_mode` → `wal`) — set in Database constructor
|
||||||
- [ ] Verify `-wal` and `-shm` sidecar files exist after writes
|
- [ ] Verify `-wal` and `-shm` sidecar files exist after writes
|
||||||
- [ ] Verify nginx/PHP write access to sidecar files on server
|
- [ ] Verify nginx/PHP write access to sidecar files on server
|
||||||
|
- [x] Add deploy-verify-permissions recipe that checks ownership, directory perms, file perms, and writability after rsync
|
||||||
|
- [x] deploy recipe now uploads and runs deploy-server.sh to fix permissions, then verifies them
|
||||||
|
- [x] deploy recipe now runs migrations (scripts/migrate.sh) after ensuring DB exists
|
||||||
|
- [x] fix migrate.sh to detect server vs local layout (no app/ subdir on server)
|
||||||
|
- [x] regenerate schema.sql from local DB via generate-schema.py (includes v_smtp_active, all 28 migrations)
|
||||||
|
- [x] fix generate-schema.py to include v_smtp_active (was explicitly excluded)
|
||||||
|
|
||||||
### Phase 2 — Audit Log
|
### Phase 2 — Audit Log
|
||||||
- [x] `admin_audit_log` table already exists (migration 009), `AdminLogger` already writes to it
|
- [x] `admin_audit_log` table already exists (migration 009), `AdminLogger` already writes to it
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ $logger = AdminLogger::make();
|
|||||||
|
|
||||||
$isHxRequest = (isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] === 'true');
|
$isHxRequest = (isset($_SERVER['HTTP_HX_REQUEST']) && $_SERVER['HTTP_HX_REQUEST'] === 'true');
|
||||||
$section = $_POST['section'] ?? '';
|
$section = $_POST['section'] ?? '';
|
||||||
|
error_log('[settings.php] PROCESS | section=' . $section . ' | post_keys=' . implode(',', array_keys($_POST)));
|
||||||
|
|
||||||
if ($section === 'formulaire_restrictions') {
|
if ($section === 'formulaire_restrictions') {
|
||||||
$newValues = ['restricted_files_enabled' => ($_POST['restricted_files_enabled'] ?? '0') === '1' ? '1' : '0'];
|
// HTMX may not send unchecked checkboxes even with hidden 0-value inputs;
|
||||||
|
// missing key means unchecked → treat as '0'.
|
||||||
|
$newValues = ['restricted_files_enabled' => empty($_POST['restricted_files_enabled']) ? '0' : '1'];
|
||||||
$db->setSetting('restricted_files_enabled', $newValues['restricted_files_enabled']);
|
$db->setSetting('restricted_files_enabled', $newValues['restricted_files_enabled']);
|
||||||
$logger->logFormSettingsUpdate($newValues);
|
$logger->logFormSettingsUpdate($newValues);
|
||||||
if (!$isHxRequest) {
|
if (!$isHxRequest) {
|
||||||
@@ -36,7 +39,7 @@ if ($section === 'formulaire_restrictions') {
|
|||||||
];
|
];
|
||||||
$newValues = [];
|
$newValues = [];
|
||||||
foreach ($allowed as $key) {
|
foreach ($allowed as $key) {
|
||||||
$value = ($_POST[$key] ?? '0') === '1' ? '1' : '0';
|
$value = empty($_POST[$key]) ? '0' : '1';
|
||||||
$db->setSetting($key, $value);
|
$db->setSetting($key, $value);
|
||||||
$newValues[$key] = $value;
|
$newValues[$key] = $value;
|
||||||
}
|
}
|
||||||
@@ -46,8 +49,8 @@ if ($section === 'formulaire_restrictions') {
|
|||||||
}
|
}
|
||||||
} elseif ($section === 'objet_types') {
|
} elseif ($section === 'objet_types') {
|
||||||
$newValues = [
|
$newValues = [
|
||||||
'objet_these_enabled' => ($_POST['objet_these_enabled'] ?? '0') === '1' ? '1' : '0',
|
'objet_these_enabled' => empty($_POST['objet_these_enabled']) ? '0' : '1',
|
||||||
'objet_frart_enabled' => ($_POST['objet_frart_enabled'] ?? '0') === '1' ? '1' : '0',
|
'objet_frart_enabled' => empty($_POST['objet_frart_enabled']) ? '0' : '1',
|
||||||
];
|
];
|
||||||
$db->setSetting('objet_these_enabled', $newValues['objet_these_enabled']);
|
$db->setSetting('objet_these_enabled', $newValues['objet_these_enabled']);
|
||||||
$db->setSetting('objet_frart_enabled', $newValues['objet_frart_enabled']);
|
$db->setSetting('objet_frart_enabled', $newValues['objet_frart_enabled']);
|
||||||
|
|||||||
@@ -1,11 +1,63 @@
|
|||||||
-- XAMXAM Thesis Database Schema
|
|
||||||
-- SQLite Database for managing final thesis projects (TFE) and doctoral theses
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- CORE ENTITIES
|
-- XAMXAM Database Schema — complete, fully migrated
|
||||||
|
-- Generated from local database on 2026-05-11
|
||||||
|
-- All 28 migrations merged into this single file.
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
-- Students/Authors table
|
CREATE TABLE IF NOT EXISTS orientations (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ap_programs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
code TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS finality_types (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS languages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TEXT DEFAULT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS format_types (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 99
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tags (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TEXT DEFAULT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS access_types (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS license_types (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS authors (
|
CREATE TABLE IF NOT EXISTS authors (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@@ -15,7 +67,6 @@ CREATE TABLE IF NOT EXISTS authors (
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Supervisors/Promoters table
|
|
||||||
CREATE TABLE IF NOT EXISTS supervisors (
|
CREATE TABLE IF NOT EXISTS supervisors (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@@ -23,294 +74,110 @@ CREATE TABLE IF NOT EXISTS supervisors (
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- PREDEFINED REFERENCE DATA (lookup tables)
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- Orientations (predefined list from specifications)
|
|
||||||
CREATE TABLE IF NOT EXISTS orientations (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Insert predefined orientations
|
|
||||||
INSERT OR IGNORE INTO orientations (name) VALUES
|
|
||||||
('Arts Numériques'),
|
|
||||||
('Dessin'),
|
|
||||||
('Cinéma d''animation'),
|
|
||||||
('Installation-Performance'),
|
|
||||||
('Peinture'),
|
|
||||||
('Photographie'),
|
|
||||||
('Sculpture'),
|
|
||||||
('Vidéographie'),
|
|
||||||
('Graphisme'),
|
|
||||||
('Typographie'),
|
|
||||||
('Design Numérique'),
|
|
||||||
('Illustration'),
|
|
||||||
('Bande-Dessinée'),
|
|
||||||
('Sérigraphie'),
|
|
||||||
('Gravure');
|
|
||||||
|
|
||||||
-- AP (Ateliers Pratiques) - predefined list
|
|
||||||
CREATE TABLE IF NOT EXISTS ap_programs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
code TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Insert predefined AP programs
|
|
||||||
INSERT OR IGNORE INTO ap_programs (name, code) VALUES
|
|
||||||
('Narration Spéculative', 'NS'),
|
|
||||||
('Design et Politique du Multiple', 'DPM'),
|
|
||||||
('Atelier Pratiques Situées', 'APS'),
|
|
||||||
('Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'LIENS'),
|
|
||||||
('Pratique de l''art - outils critiques, arts et contexte simultanés', 'PACS');
|
|
||||||
|
|
||||||
-- Master finality types
|
|
||||||
CREATE TABLE IF NOT EXISTS finality_types (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO finality_types (name) VALUES
|
|
||||||
('Approfondi'),
|
|
||||||
('Enseignement'),
|
|
||||||
('Spécialisé');
|
|
||||||
|
|
||||||
-- Languages
|
|
||||||
CREATE TABLE IF NOT EXISTS languages (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TEXT DEFAULT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO languages (name) VALUES
|
|
||||||
('français'),
|
|
||||||
('anglais'),
|
|
||||||
('néerlandais');
|
|
||||||
|
|
||||||
-- Format types (can select multiple)
|
|
||||||
CREATE TABLE IF NOT EXISTS format_types (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO format_types (name) VALUES
|
|
||||||
('Site web'),
|
|
||||||
('Audio'),
|
|
||||||
('Vidéo'),
|
|
||||||
('Performance'),
|
|
||||||
('Objet éditorial'),
|
|
||||||
('Installation'),
|
|
||||||
('Autre');
|
|
||||||
|
|
||||||
-- Tags (keywords — canonical M2M table; formerly 'keywords'/'keyword' column)
|
|
||||||
CREATE TABLE IF NOT EXISTS tags (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TEXT DEFAULT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name);
|
|
||||||
|
|
||||||
-- Access authorization types
|
|
||||||
CREATE TABLE IF NOT EXISTS access_types (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO access_types (name, description) VALUES
|
|
||||||
('Libre', 'TFE en libre accès à tout le monde sur la plateforme et en bibliothèque'),
|
|
||||||
('Interne', 'TFE accessible uniquement sur place en physique. Une note descriptive est disponible sur le site'),
|
|
||||||
('Interdit', 'TFE non disponible en physique ni sur le site. Une note descriptive est disponible sur le site');
|
|
||||||
|
|
||||||
-- License types
|
|
||||||
CREATE TABLE IF NOT EXISTS license_types (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO license_types (name) VALUES
|
|
||||||
('CC BY 4.0'),
|
|
||||||
('CC BY-SA 4.0'),
|
|
||||||
('CC BY-ND 4.0'),
|
|
||||||
('CC BY-NC 4.0'),
|
|
||||||
('CC BY-NC-SA 4.0'),
|
|
||||||
('CC BY-NC-ND 4.0'),
|
|
||||||
('Tous droits réservés'),
|
|
||||||
('Domaine public');
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- MAIN THESIS TABLE
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS theses (
|
CREATE TABLE IF NOT EXISTS theses (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
identifier TEXT UNIQUE, -- e.g., "2025-002"
|
identifier TEXT,
|
||||||
|
|
||||||
-- Basic information
|
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
subtitle TEXT,
|
subtitle TEXT,
|
||||||
year INTEGER NOT NULL,
|
year INTEGER NOT NULL,
|
||||||
|
is_doctoral BOOLEAN DEFAULT 0,
|
||||||
-- Type of work
|
objet TEXT NOT NULL DEFAULT 'tfe',
|
||||||
is_doctoral BOOLEAN DEFAULT 0, -- 0 for TFE, 1 for doctoral thesis
|
|
||||||
objet TEXT NOT NULL DEFAULT 'tfe' CHECK (objet IN ('tfe', 'thèse', 'frart')),
|
|
||||||
|
|
||||||
-- Academic details
|
|
||||||
orientation_id INTEGER,
|
orientation_id INTEGER,
|
||||||
ap_program_id INTEGER,
|
ap_program_id INTEGER,
|
||||||
finality_id INTEGER,
|
finality_id INTEGER,
|
||||||
|
synopsis TEXT,
|
||||||
-- Content
|
context_note TEXT,
|
||||||
synopsis TEXT, -- ~200 words
|
remarks TEXT,
|
||||||
context_note TEXT, -- Note added by jury president (max 150 words)
|
|
||||||
remarks TEXT, -- Internal remarks
|
|
||||||
|
|
||||||
-- Access and licensing
|
|
||||||
access_type_id INTEGER,
|
access_type_id INTEGER,
|
||||||
license_id INTEGER,
|
license_id INTEGER,
|
||||||
license_custom TEXT, -- free-text licence (if not in predefined list)
|
jury_points DECIMAL(4,2),
|
||||||
|
jury_note_added BOOLEAN DEFAULT 0,
|
||||||
-- Jury information
|
submitted_at DATETIME,
|
||||||
jury_points DECIMAL(4,2), -- Points out of 20 (backoffice only)
|
defense_date DATETIME,
|
||||||
jury_note_added BOOLEAN DEFAULT 0, -- Whether jury president added a note
|
published_at DATETIME,
|
||||||
|
|
||||||
-- Publication status
|
|
||||||
submitted_at DATETIME, -- When student submitted
|
|
||||||
defense_date DATETIME, -- Date of defense/soutenance
|
|
||||||
published_at DATETIME, -- When made public (after jury review)
|
|
||||||
is_published BOOLEAN DEFAULT 0,
|
is_published BOOLEAN DEFAULT 0,
|
||||||
|
baiu_link TEXT,
|
||||||
-- External links
|
|
||||||
baiu_link TEXT, -- Link to institutional repository
|
|
||||||
|
|
||||||
-- Logistics checkboxes (backoffice only)
|
|
||||||
exemplaire_baiu BOOLEAN DEFAULT 0, -- Physical copy at BAIU
|
|
||||||
exemplaire_erg BOOLEAN DEFAULT 0, -- Physical copy at ERG
|
|
||||||
|
|
||||||
-- CC2r acceptance (collected in student form)
|
|
||||||
cc2r BOOLEAN DEFAULT 0,
|
|
||||||
|
|
||||||
-- Soft delete support
|
|
||||||
deleted_at TEXT DEFAULT NULL,
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
exemplaire_baiu INTEGER NOT NULL DEFAULT 0,
|
||||||
-- Foreign keys
|
exemplaire_erg INTEGER NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY (orientation_id) REFERENCES orientations(id),
|
cc2r INTEGER NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY (ap_program_id) REFERENCES ap_programs(id),
|
license_custom TEXT,
|
||||||
FOREIGN KEY (finality_id) REFERENCES finality_types(id),
|
deleted_at TEXT DEFAULT NULL,
|
||||||
|
FOREIGN KEY (license_id) REFERENCES license_types(id),
|
||||||
FOREIGN KEY (access_type_id) REFERENCES access_types(id),
|
FOREIGN KEY (access_type_id) REFERENCES access_types(id),
|
||||||
FOREIGN KEY (license_id) REFERENCES license_types(id)
|
FOREIGN KEY (finality_id) REFERENCES finality_types(id),
|
||||||
|
FOREIGN KEY (ap_program_id) REFERENCES ap_programs(id),
|
||||||
|
FOREIGN KEY (orientation_id) REFERENCES orientations(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- JUNCTION TABLES (Many-to-Many relationships)
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- Authors per thesis (can have multiple authors)
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_authors (
|
CREATE TABLE IF NOT EXISTS thesis_authors (
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
author_id INTEGER NOT NULL,
|
author_id INTEGER NOT NULL,
|
||||||
author_order INTEGER DEFAULT 1, -- Order of authors if multiple
|
author_order INTEGER DEFAULT 1,
|
||||||
PRIMARY KEY (thesis_id, author_id),
|
PRIMARY KEY (thesis_id, author_id),
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Supervisors per thesis (jury: president, promoteur, lecteurs)
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_supervisors (
|
CREATE TABLE IF NOT EXISTS thesis_supervisors (
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
supervisor_id INTEGER NOT NULL,
|
supervisor_id INTEGER NOT NULL,
|
||||||
supervisor_order INTEGER DEFAULT 1,
|
supervisor_order INTEGER DEFAULT 1,
|
||||||
role TEXT NOT NULL DEFAULT 'promoteur', -- 'president'|'promoteur'|'lecteur'
|
role TEXT NOT NULL DEFAULT 'promoteur',
|
||||||
is_external INTEGER NOT NULL DEFAULT 0, -- 0 = internal, 1 = external
|
is_external INTEGER NOT NULL DEFAULT 0,
|
||||||
is_ulb INTEGER NOT NULL DEFAULT 0, -- 1 = ULB promoteur
|
is_ulb INTEGER NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (thesis_id, supervisor_id),
|
PRIMARY KEY (thesis_id, supervisor_id),
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
FOREIGN KEY (supervisor_id) REFERENCES supervisors(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (supervisor_id) REFERENCES supervisors(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Languages per thesis (can be multilingual)
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_languages (
|
CREATE TABLE IF NOT EXISTS thesis_languages (
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
language_id INTEGER NOT NULL,
|
language_id INTEGER NOT NULL,
|
||||||
PRIMARY KEY (thesis_id, language_id),
|
PRIMARY KEY (thesis_id, language_id),
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
FOREIGN KEY (language_id) REFERENCES languages(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (language_id) REFERENCES languages(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Formats per thesis (can have multiple formats)
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_formats (
|
CREATE TABLE IF NOT EXISTS thesis_formats (
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
format_id INTEGER NOT NULL,
|
format_id INTEGER NOT NULL,
|
||||||
PRIMARY KEY (thesis_id, format_id),
|
PRIMARY KEY (thesis_id, format_id),
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
FOREIGN KEY (format_id) REFERENCES format_types(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (format_id) REFERENCES format_types(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Tags per thesis (max 10 as per specs; formerly thesis_keywords)
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_tags (
|
CREATE TABLE IF NOT EXISTS thesis_tags (
|
||||||
tag_id INTEGER NOT NULL,
|
tag_id INTEGER NOT NULL,
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
PRIMARY KEY (tag_id, thesis_id),
|
PRIMARY KEY (tag_id, thesis_id),
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- FILE ATTACHMENTS
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS thesis_files (
|
CREATE TABLE IF NOT EXISTS thesis_files (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
thesis_id INTEGER NOT NULL,
|
thesis_id INTEGER NOT NULL,
|
||||||
file_type TEXT NOT NULL, -- 'main', 'annex', 'written_part', 'other'
|
file_type TEXT NOT NULL,
|
||||||
file_path TEXT NOT NULL,
|
file_path TEXT NOT NULL,
|
||||||
file_name TEXT NOT NULL,
|
file_name TEXT NOT NULL,
|
||||||
file_size INTEGER, -- in bytes
|
file_size INTEGER,
|
||||||
mime_type TEXT,
|
mime_type TEXT,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
display_label TEXT,
|
||||||
|
file_hash TEXT,
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- SITE SETTINGS
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS site_settings (
|
CREATE TABLE IF NOT EXISTS site_settings (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL DEFAULT '',
|
value TEXT NOT NULL DEFAULT '',
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO site_settings (key, value) VALUES
|
|
||||||
('access_type_interdit_enabled', '1'),
|
|
||||||
('access_type_interne_enabled', '1'),
|
|
||||||
('access_type_libre_enabled', '0'),
|
|
||||||
('admin_password_hash', ''),
|
|
||||||
('objet_these_enabled', '1'),
|
|
||||||
('objet_frart_enabled', '1');
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- STATIC PAGES / CONTENT MANAGEMENT
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- For managing editable static pages (charte, about, licenses, contact)
|
|
||||||
CREATE TABLE IF NOT EXISTS system_cache (
|
CREATE TABLE IF NOT EXISTS system_cache (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL,
|
value TEXT NOT NULL,
|
||||||
@@ -319,177 +186,211 @@ CREATE TABLE IF NOT EXISTS system_cache (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS pages (
|
CREATE TABLE IF NOT EXISTS pages (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
slug TEXT NOT NULL UNIQUE, -- 'charte', 'about', 'licenses', 'contact'
|
slug TEXT NOT NULL,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
content TEXT, -- Markdown or HTML content
|
content TEXT,
|
||||||
is_published BOOLEAN DEFAULT 1,
|
is_published BOOLEAN DEFAULT 1,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Initialize default pages
|
|
||||||
INSERT OR IGNORE INTO pages (slug, title, content) VALUES
|
|
||||||
('charte', 'Charte', 'Contenu à venir'),
|
|
||||||
('about', 'À propos', 'Contenu à venir'),
|
|
||||||
('licenses', 'Licences', 'Contenu à venir');
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- SHARE LINKS
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS share_links (
|
CREATE TABLE IF NOT EXISTS share_links (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
slug TEXT NOT NULL UNIQUE, -- Format: YYYYMMDD-<random>, e.g. 20260416-a3f9k2
|
slug TEXT NOT NULL,
|
||||||
name TEXT, -- user-defined label (optional)
|
objet_restriction TEXT,
|
||||||
objet_restriction TEXT CHECK (objet_restriction IN ('tfe', 'thèse', 'frart')), -- NULL = no restriction
|
password_hash TEXT,
|
||||||
password_hash TEXT, -- bcrypt hash; NULL = no password required
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
is_active INTEGER NOT NULL DEFAULT 1, -- 1 = active, 0 = disabled
|
usage_count INTEGER NOT NULL DEFAULT 0,
|
||||||
is_archived INTEGER NOT NULL DEFAULT 0, -- 1 = archived (link inaccessible, stats preserved)
|
created_by INTEGER NOT NULL,
|
||||||
usage_count INTEGER NOT NULL DEFAULT 0, -- Number of successful submissions via this link
|
|
||||||
created_by INTEGER NOT NULL, -- admin user ID
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
expires_at DATETIME -- NULL = never expires
|
expires_at DATETIME,
|
||||||
|
is_archived INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_share_links_slug ON share_links(slug);
|
CREATE TABLE IF NOT EXISTS smtp_settings (
|
||||||
CREATE INDEX IF NOT EXISTS idx_share_links_active ON share_links(is_active);
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CREATE INDEX IF NOT EXISTS idx_share_links_archived ON share_links(is_archived);
|
host TEXT NOT NULL DEFAULT '',
|
||||||
|
port INTEGER NOT NULL DEFAULT 587,
|
||||||
|
encryption TEXT NOT NULL DEFAULT 'tls',
|
||||||
|
username TEXT NOT NULL DEFAULT '',
|
||||||
|
password TEXT NOT NULL DEFAULT '',
|
||||||
|
from_email TEXT NOT NULL DEFAULT '',
|
||||||
|
from_name TEXT NOT NULL DEFAULT 'Post-ERG',
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
notify_email TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
-- ============================================================================
|
CREATE TABLE IF NOT EXISTS apropos_contents (
|
||||||
-- ADMIN AUDIT LOG
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
-- ============================================================================
|
key TEXT NOT NULL,
|
||||||
-- Mirrors every admin action logged to /var/log/xamxam.log.
|
value TEXT,
|
||||||
-- Best-effort: application never fails if this table is absent.
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
-- ============================================================================
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS file_access_requests (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
thesis_id INTEGER NOT NULL,
|
||||||
|
email TEXT NOT NULL,
|
||||||
|
justification TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
admin_notes TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
approved_at DATETIME,
|
||||||
|
approved_by_admin_id INTEGER,
|
||||||
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS file_access_tokens (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
request_id INTEGER NOT NULL,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_valid INTEGER NOT NULL DEFAULT 1,
|
||||||
|
used_at DATETIME DEFAULT NULL,
|
||||||
|
FOREIGN KEY (request_id) REFERENCES file_access_requests(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS file_access_sessions (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
request_id INTEGER NOT NULL,
|
||||||
|
session_token TEXT NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_valid INTEGER NOT NULL DEFAULT 1,
|
||||||
|
FOREIGN KEY (request_id) REFERENCES file_access_requests(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS file_access_audit (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
request_id INTEGER NOT NULL,
|
||||||
|
event TEXT NOT NULL,
|
||||||
|
ip TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (request_id) REFERENCES file_access_requests(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS form_help_blocks (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS admin_audit_log (
|
CREATE TABLE IF NOT EXISTS admin_audit_log (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
ip TEXT NOT NULL,
|
ip TEXT NOT NULL,
|
||||||
user_agent TEXT,
|
user_agent TEXT,
|
||||||
resource TEXT NOT NULL, -- e.g. thesis, tag, share_link, settings, system
|
resource TEXT NOT NULL,
|
||||||
action TEXT NOT NULL, -- e.g. publish, delete, smtp_update
|
action TEXT NOT NULL,
|
||||||
status TEXT NOT NULL, -- success | error
|
status TEXT NOT NULL,
|
||||||
context TEXT -- JSON blob with extra fields
|
context TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_log_created_at ON admin_audit_log(created_at);
|
CREATE TABLE IF NOT EXISTS peertube_settings (
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_log_resource ON admin_audit_log(resource);
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_log_action ON admin_audit_log(action);
|
instance_url TEXT NOT NULL DEFAULT '',
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- SMTP SETTINGS
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- Singleton row — id is always 1. Credentials stored in clear for now.
|
|
||||||
CREATE TABLE IF NOT EXISTS smtp_settings (
|
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
||||||
host TEXT NOT NULL DEFAULT '',
|
|
||||||
port INTEGER NOT NULL DEFAULT 587,
|
|
||||||
encryption TEXT NOT NULL DEFAULT 'tls', -- 'tls' | 'ssl' | 'none'
|
|
||||||
username TEXT NOT NULL DEFAULT '',
|
username TEXT NOT NULL DEFAULT '',
|
||||||
password TEXT NOT NULL DEFAULT '', -- stored in clear for now; encrypt later
|
password TEXT NOT NULL DEFAULT '',
|
||||||
from_email TEXT NOT NULL DEFAULT '',
|
channel_id INTEGER NOT NULL DEFAULT 1,
|
||||||
from_name TEXT NOT NULL DEFAULT 'XAMXAM',
|
privacy INTEGER NOT NULL DEFAULT 1,
|
||||||
notify_email TEXT NOT NULL DEFAULT '', -- recipient for admin notifications
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO smtp_settings (id) VALUES (1);
|
CREATE TABLE IF NOT EXISTS audit_log (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
actor TEXT NOT NULL,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
table_name TEXT NOT NULL,
|
||||||
|
record_id INTEGER,
|
||||||
|
old_data TEXT,
|
||||||
|
new_data TEXT
|
||||||
|
);
|
||||||
|
|
||||||
CREATE VIEW IF NOT EXISTS v_smtp_active AS
|
-- ============================================================================
|
||||||
|
-- INDEXES
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE INDEX idx_admin_audit_log_action ON admin_audit_log(action);
|
||||||
|
|
||||||
|
CREATE INDEX idx_admin_audit_log_created_at ON admin_audit_log(created_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_admin_audit_log_resource ON admin_audit_log(resource);
|
||||||
|
|
||||||
|
CREATE INDEX idx_audit_log_table_record ON audit_log(table_name, record_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp);
|
||||||
|
|
||||||
|
CREATE INDEX idx_authors_email ON authors(email);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_audit_request
|
||||||
|
ON file_access_audit(request_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_requests_email
|
||||||
|
ON file_access_requests(email);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_requests_status
|
||||||
|
ON file_access_requests(status);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_requests_thesis_id
|
||||||
|
ON file_access_requests(thesis_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_sessions_expires
|
||||||
|
ON file_access_sessions(expires_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_sessions_token
|
||||||
|
ON file_access_sessions(session_token);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_tokens_expires_at
|
||||||
|
ON file_access_tokens(expires_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_file_access_tokens_token
|
||||||
|
ON file_access_tokens(token);
|
||||||
|
|
||||||
|
CREATE INDEX idx_share_links_active ON share_links(is_active);
|
||||||
|
|
||||||
|
CREATE INDEX idx_share_links_archived ON share_links(is_archived);
|
||||||
|
|
||||||
|
CREATE INDEX idx_share_links_slug ON share_links(slug);
|
||||||
|
|
||||||
|
CREATE INDEX idx_tags_name ON tags(name);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_access_type ON theses(access_type_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_ap_program ON theses(ap_program_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_identifier ON theses(identifier);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_orientation ON theses(orientation_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_pub_year ON theses(is_published, year DESC);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_published ON theses(is_published);
|
||||||
|
|
||||||
|
CREATE INDEX idx_theses_year ON theses(year);
|
||||||
|
|
||||||
|
CREATE INDEX idx_thesis_authors_author ON thesis_authors(author_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_thesis_authors_thesis ON thesis_authors(thesis_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_thesis_tags_tag ON thesis_tags(tag_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_thesis_tags_thesis ON thesis_tags(thesis_id);
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- VIEWS
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE VIEW v_smtp_active AS
|
||||||
SELECT * FROM smtp_settings WHERE id = 1;
|
SELECT * FROM smtp_settings WHERE id = 1;
|
||||||
|
|
||||||
-- ============================================================================
|
CREATE VIEW v_theses_full AS
|
||||||
-- APROPOS CONTENTS (structured data for the "À propos" page)
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS apropos_contents (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
key TEXT NOT NULL UNIQUE, -- 'contacts'
|
|
||||||
value TEXT, -- JSON array
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES
|
|
||||||
('contacts', '[
|
|
||||||
{
|
|
||||||
"role": "Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :",
|
|
||||||
"entries": [
|
|
||||||
{"text": "Laurent Leprince", "url": "", "email": "laurent.leprince@uclouvain.be"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "Responsable des mémoires de l''ERG :",
|
|
||||||
"entries": [
|
|
||||||
{"text": "Xavier Gorgol", "url": "", "email": "xavier.gorgol@erg.be"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "Cours de suivi de mémoire :",
|
|
||||||
"entries": [
|
|
||||||
{"text": "Brigitte Ledune", "url": "", "email": "brigitte.ledune@erg.be"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]');
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- INDEXES for performance
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_year ON theses(year);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_published ON theses(is_published);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_identifier ON theses(identifier);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_orientation ON theses(orientation_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_ap_program ON theses(ap_program_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_theses_access_type ON theses(access_type_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_authors_email ON authors(email);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_thesis_authors_thesis ON thesis_authors(thesis_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_thesis_authors_author ON thesis_authors(author_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_thesis_tags_thesis ON thesis_tags(thesis_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_thesis_tags_tag ON thesis_tags(tag_id);
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- TRIGGERS for automatic timestamp updates
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_theses_timestamp
|
|
||||||
AFTER UPDATE ON theses
|
|
||||||
BEGIN
|
|
||||||
UPDATE theses SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_authors_timestamp
|
|
||||||
AFTER UPDATE ON authors
|
|
||||||
BEGIN
|
|
||||||
UPDATE authors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_supervisors_timestamp
|
|
||||||
AFTER UPDATE ON supervisors
|
|
||||||
BEGIN
|
|
||||||
UPDATE supervisors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_pages_timestamp
|
|
||||||
AFTER UPDATE ON pages
|
|
||||||
BEGIN
|
|
||||||
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_apropos_contents_timestamp
|
|
||||||
AFTER UPDATE ON apropos_contents
|
|
||||||
BEGIN
|
|
||||||
UPDATE apropos_contents SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- VIEWS for common queries
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- Full thesis information view
|
|
||||||
CREATE VIEW IF NOT EXISTS v_theses_full AS
|
|
||||||
SELECT
|
SELECT
|
||||||
t.id,
|
t.id,
|
||||||
t.identifier,
|
t.identifier,
|
||||||
@@ -529,7 +430,6 @@ SELECT
|
|||||||
GROUP_CONCAT(DISTINCT UPPER(SUBSTR(l.name,1,1)) || SUBSTR(l.name,2)) as languages,
|
GROUP_CONCAT(DISTINCT UPPER(SUBSTR(l.name,1,1)) || SUBSTR(l.name,2)) as languages,
|
||||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||||
GROUP_CONCAT(DISTINCT tg.name) as keywords,
|
GROUP_CONCAT(DISTINCT tg.name) as keywords,
|
||||||
-- First author's email and contact-visibility flag
|
|
||||||
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as contact_interne,
|
(SELECT a2.email FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as contact_interne,
|
||||||
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as contact_public
|
(SELECT a2.show_contact FROM authors a2 JOIN thesis_authors ta2 ON a2.id = ta2.author_id WHERE ta2.thesis_id = t.id ORDER BY ta2.author_order LIMIT 1) as contact_public
|
||||||
FROM theses t
|
FROM theses t
|
||||||
@@ -543,82 +443,165 @@ LEFT JOIN authors a ON ta.author_id = a.id
|
|||||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||||
LEFT JOIN languages l ON tl.language_id = l.id AND l.deleted_at IS NULL
|
LEFT JOIN languages l ON tl.language_id = l.id
|
||||||
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id
|
||||||
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
LEFT JOIN format_types fmt ON tf.format_id = fmt.id
|
||||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||||
LEFT JOIN tags tg ON tt.tag_id = tg.id AND tg.deleted_at IS NULL
|
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||||
WHERE t.deleted_at IS NULL
|
|
||||||
GROUP BY t.id;
|
GROUP BY t.id;
|
||||||
|
|
||||||
-- Published theses only (for public view)
|
CREATE VIEW v_theses_public AS
|
||||||
CREATE VIEW IF NOT EXISTS v_theses_public AS
|
|
||||||
SELECT * FROM v_theses_full
|
SELECT * FROM v_theses_full
|
||||||
WHERE is_published = 1;
|
WHERE is_published = 1;
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- FILE ACCESS RESTRICTION SYSTEM
|
-- TRIGGERS
|
||||||
-- ============================================================================
|
|
||||||
-- Add support for restricting attached files on TFEs with email-based access
|
|
||||||
-- requests and cookie-based validation.
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
-- Add new site setting for enabling/disabling file access restriction
|
CREATE TRIGGER update_apropos_contents_timestamp
|
||||||
INSERT OR IGNORE INTO site_settings (key, value) VALUES
|
AFTER UPDATE ON apropos_contents
|
||||||
('restricted_files_enabled', '0');
|
BEGIN
|
||||||
|
UPDATE apropos_contents SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_authors_timestamp
|
||||||
|
AFTER UPDATE ON authors
|
||||||
|
BEGIN
|
||||||
|
UPDATE authors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_form_help_blocks_timestamp
|
||||||
|
AFTER UPDATE ON form_help_blocks
|
||||||
|
BEGIN
|
||||||
|
UPDATE form_help_blocks SET updated_at = CURRENT_TIMESTAMP WHERE key = NEW.key;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_pages_timestamp
|
||||||
|
AFTER UPDATE ON pages
|
||||||
|
BEGIN
|
||||||
|
UPDATE pages SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_supervisors_timestamp
|
||||||
|
AFTER UPDATE ON supervisors
|
||||||
|
BEGIN
|
||||||
|
UPDATE supervisors SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER update_theses_timestamp
|
||||||
|
AFTER UPDATE ON theses
|
||||||
|
BEGIN
|
||||||
|
UPDATE theses SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- FILE ACCESS REQUESTS TABLE
|
-- SEED DATA (reference data + initial settings)
|
||||||
-- ============================================================================
|
|
||||||
-- Stores requests from users wanting access to restricted TFE files.
|
|
||||||
-- Supports approval workflow: pending → approved/rejected
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS file_access_requests (
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Arts Numériques');
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Bande-Dessinée');
|
||||||
thesis_id INTEGER NOT NULL,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Cinéma d''animation');
|
||||||
email TEXT NOT NULL,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Design Numérique');
|
||||||
justification TEXT,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Dessin');
|
||||||
status TEXT NOT NULL DEFAULT 'pending'
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Graphisme');
|
||||||
CHECK(status IN ('pending', 'approved', 'rejected')),
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Gravure');
|
||||||
admin_notes TEXT,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Illustration');
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Installation-Performance');
|
||||||
approved_at DATETIME,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Peinture');
|
||||||
approved_by_admin_id INTEGER,
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Photographie');
|
||||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Sculpture');
|
||||||
);
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Sérigraphie');
|
||||||
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Typographie');
|
||||||
|
INSERT OR IGNORE INTO orientations (name) VALUES ('Vidéographie');
|
||||||
|
|
||||||
-- Index for efficient lookup by thesis and email
|
INSERT OR IGNORE INTO ap_programs (name, code) VALUES ('Narration Spéculative', 'NS');
|
||||||
CREATE INDEX IF NOT EXISTS idx_file_access_requests_thesis_id
|
INSERT OR IGNORE INTO ap_programs (name, code) VALUES ('Design et Politique du Multiple', 'DPM');
|
||||||
ON file_access_requests(thesis_id);
|
INSERT OR IGNORE INTO ap_programs (name, code) VALUES ('Atelier Pratiques Situées', 'APS');
|
||||||
|
INSERT OR IGNORE INTO ap_programs (name, code) VALUES ('Lieux, Interdisciplinarités, Écologie, Nécessité, Systèmes', 'LIENS');
|
||||||
|
INSERT OR IGNORE INTO ap_programs (name, code) VALUES ('Pratique de l''art - outils critiques, arts et contexte simultanés', 'PACS');
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_file_access_requests_email
|
INSERT OR IGNORE INTO finality_types (name) VALUES ('Approfondi');
|
||||||
ON file_access_requests(email);
|
INSERT OR IGNORE INTO finality_types (name) VALUES ('Enseignement');
|
||||||
|
INSERT OR IGNORE INTO finality_types (name) VALUES ('Spécialisé');
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_file_access_requests_status
|
INSERT OR IGNORE INTO languages (name) VALUES ('français');
|
||||||
ON file_access_requests(status);
|
INSERT OR IGNORE INTO languages (name) VALUES ('anglais');
|
||||||
|
INSERT OR IGNORE INTO languages (name) VALUES ('néerlandais');
|
||||||
|
INSERT OR IGNORE INTO languages (name) VALUES ('italian');
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Site web', 5);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Audio', 3);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Vidéo', 4);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Performance', 6);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Objet éditorial', 7);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Installation', 8);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Autre', 9);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Écriture', 1);
|
||||||
|
INSERT OR IGNORE INTO format_types (name, sort_order) VALUES ('Image', 2);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO access_types (name, description) VALUES ('Libre', 'TFE en libre accès à tout le monde sur la plateforme et en bibliothèque');
|
||||||
|
INSERT OR IGNORE INTO access_types (name, description) VALUES ('Interne', 'TFE accessible uniquement sur place en physique. Une note descriptive est disponible sur le site');
|
||||||
|
INSERT OR IGNORE INTO access_types (name, description) VALUES ('Interdit', 'TFE non disponible en physique ni sur le site. Une note descriptive est disponible sur le site');
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY-NC 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY-NC-ND 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY-NC-SA 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY-ND 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('CC BY-SA 4.0');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('Domaine public');
|
||||||
|
INSERT OR IGNORE INTO license_types (name) VALUES ('Tous droits réservés');
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('access_type_interdit_enabled', '1');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('access_type_interne_enabled', '0');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('access_type_libre_enabled', '0');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('objet_frart_enabled', '1');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('objet_these_enabled', '1');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('peertube_upload_enabled', '0');
|
||||||
|
INSERT OR IGNORE INTO site_settings (key, value) VALUES ('restricted_files_enabled', '0');
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO pages (slug, title, content, is_published) VALUES ('about', 'À propos', 'Contenu à venir', 1);
|
||||||
|
INSERT OR IGNORE INTO pages (slug, title, content, is_published) VALUES ('charte', 'Charte', 'Contenu à venir', 1);
|
||||||
|
INSERT OR IGNORE INTO pages (slug, title, content, is_published) VALUES ('licenses', 'Licences', 'Contenu à venir', 1);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('partage_intro', 'Introduction', 'Hahahaha
|
||||||
|
## Beware the dog', 0, 0);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_tfe_info', 'Informations du TFE', 'Hello world', 0, 1);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_synopsis', 'Note Synopsis', '', 0, 2);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_jury', 'Composition du jury', '', 0, 3);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_academic', 'Cadre académique', '', 0, 4);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_files', 'Fichiers', '', 0, 5);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_access', 'Visibilité / Accès', 'qsldkjlfkjdsqmflkjq', 1, 6);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_email', 'E-mail de confirmation', '', 0, 7);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_languages', 'Langue(s)', 'Hahah', 0, 0);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_keywords', 'Mots-clés', 'lolz', 0, 0);
|
||||||
|
INSERT OR IGNORE INTO form_help_blocks (key, name, content, enabled, sort_order) VALUES ('fieldset_metadata', 'Métadonnées complémentaires', '', 0, 0);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO apropos_contents (key, value) VALUES ('contacts', '[
|
||||||
|
{
|
||||||
|
"role": "Bibliothèque d''architecture, d''ingénierie architecturale, d''urbanisme (BAIU) :",
|
||||||
|
"entries": [
|
||||||
|
{"text": "Laurent Leprince", "url": "", "email": "laurent.leprince@uclouvain.be"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Responsable des mémoires de l''ERG :",
|
||||||
|
"entries": [
|
||||||
|
{"text": "Xavier Gorgol", "url": "", "email": "xavier.gorgol@erg.be"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Cours de suivi de mémoire :",
|
||||||
|
"entries": [
|
||||||
|
{"text": "Brigitte Ledune", "url": "", "email": "brigitte.ledune@erg.be"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]');
|
||||||
|
|
||||||
|
-- Singleton table placeholders
|
||||||
|
INSERT OR IGNORE INTO smtp_settings (id) VALUES (1);
|
||||||
|
INSERT OR IGNORE INTO peertube_settings (id) VALUES (1);
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- FILE ACCESS TOKENS TABLE
|
-- END OF SCHEMA
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- Stores tokens for cookie-based access validation.
|
|
||||||
-- Each token is unique, time-limited, and linked to a specific request.
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS file_access_tokens (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
request_id INTEGER NOT NULL,
|
|
||||||
token TEXT NOT NULL UNIQUE,
|
|
||||||
expires_at DATETIME NOT NULL,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
is_valid INTEGER NOT NULL DEFAULT 1,
|
|
||||||
FOREIGN KEY (request_id) REFERENCES file_access_requests(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Index for token lookup (most common query)
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_file_access_tokens_token
|
|
||||||
ON file_access_tokens(token);
|
|
||||||
|
|
||||||
-- Index for cleanup of expired tokens
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_file_access_tokens_expires_at
|
|
||||||
ON file_access_tokens(expires_at);
|
|
||||||
|
|||||||
4464
app/storage/schema.sql.new
Normal file
4464
app/storage/schema.sql.new
Normal file
File diff suppressed because it is too large
Load Diff
@@ -675,6 +675,58 @@
|
|||||||
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
+\\\\\\\ to: kpvxplms 859e5316 "fix: settings handler was treating hidden value="0" as truthy" (rebased revision)
|
+\\\\\\\ to: kpvxplms 859e5316 "fix: settings handler was treating hidden value="0" as truthy" (rebased revision)
|
||||||
++ $linkName = $link['name'] ?? '';
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: kpvxplms 859e5316 "fix: settings handler was treating hidden value="0" as truthy" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: rqrkkkuo 9141fd8e "feat(deploy): add deploy-verify-permissions recipe to check ownership/permissions after rsync" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: rqrkkkuo 5563460c "feat(deploy): add deploy-verify-permissions recipe to check ownership/permissions after rsync" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: rqrkkkuo 5563460c "feat(deploy): add deploy-verify-permissions recipe to check ownership/permissions after rsync" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: unnmorqw 5feff462 "feat(deploy): upload and run deploy-server.sh before permission verification" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: unnmorqw c30a41b6 "feat(deploy): upload and run deploy-server.sh before permission verification" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: unnmorqw c30a41b6 "feat(deploy): upload and run deploy-server.sh before permission verification" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: mysnnsru 3bfad924 "feat(deploy): upload and run deploy-server.sh before permission verification, run migrations" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: mysnnsru 4f314e92 "feat(deploy): upload and run deploy-server.sh before permission verification, run migrations" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: mysnnsru 4f314e92 "feat(deploy): upload and run deploy-server.sh before permission verification, run migrations" (rebased revision)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
- $linkName = $link['name'] ?? '';
|
||||||
|
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||||
|
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: sstzwlpk 18bd561d "feat(deploy): upload deploy-server.sh, run migrations, fix migrate.sh server layout" (rebased revision)
|
||||||
|
$linkName = $link['name'] ?? '';
|
||||||
|
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
|
$linkLockedYear = $link['locked_year'] ?? null;
|
||||||
|
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||||
|
+\\\\\\\ to: sstzwlpk 0e83e8f7 "feat(deploy): upload deploy-server.sh, run migrations, fix migrate.sh server layout" (rebased revision)
|
||||||
|
++ $linkName = $link['name'] ?? '';
|
||||||
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||||
?>
|
?>
|
||||||
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
||||||
|
|||||||
121
justfile
121
justfile
@@ -60,10 +60,17 @@ deploy:
|
|||||||
--exclude 'storage/docs' \
|
--exclude 'storage/docs' \
|
||||||
--exclude 'var/' \
|
--exclude 'var/' \
|
||||||
app/ xamxam:/var/www/xamxam/
|
app/ xamxam:/var/www/xamxam/
|
||||||
|
# Upload deploy-server.sh for post-deploy permission fix
|
||||||
|
rsync -v scripts/deploy-server.sh xamxam:/tmp/deploy-server.sh
|
||||||
|
ssh xamxam "sudo bash /tmp/deploy-server.sh"
|
||||||
|
ssh xamxam "rm -f /tmp/deploy-server.sh"
|
||||||
ssh xamxam "mkdir -p /var/www/xamxam/var/{cache,logs,tmp}"
|
ssh xamxam "mkdir -p /var/www/xamxam/var/{cache,logs,tmp}"
|
||||||
ssh xamxam "cd /var/www/xamxam && php -r 'if (!file_exists(\"/var/www/xamxam/storage/xamxam.db\")) { \$db = new PDO(\"sqlite:/var/www/xamxam/storage/xamxam.db\"); \$db->exec(file_get_contents(\"/var/www/xamxam/storage/schema.sql\")); echo \"Database created from schema.\\n\"; } else { echo \"Database already exists.\\n\"; }' && php migrations/run.php /var/www/xamxam/storage/xamxam.db"
|
ssh xamxam "cd /var/www/xamxam && php -r 'if (!file_exists(\"/var/www/xamxam/storage/xamxam.db\")) { \$db = new PDO(\"sqlite:/var/www/xamxam/storage/xamxam.db\"); \$db->exec(file_get_contents(\"/var/www/xamxam/storage/schema.sql\")); echo \"Database created from schema.\\n\"; } else { echo \"Database already exists.\\n\"; }'"
|
||||||
|
# Run pending migrations
|
||||||
|
ssh xamxam "cd /var/www/xamxam && bash scripts/migrate.sh"
|
||||||
# Sync .env separately (excluded above to avoid accidental overwrite on subsequent deploys)
|
# Sync .env separately (excluded above to avoid accidental overwrite on subsequent deploys)
|
||||||
@just deploy-env
|
@just deploy-env
|
||||||
|
@just deploy-verify-permissions
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy-env:
|
deploy-env:
|
||||||
@@ -107,6 +114,118 @@ deploy-db:
|
|||||||
rsync -v --progress app/storage/xamxam.db xamxam:/var/www/xamxam/storage/xamxam.db
|
rsync -v --progress app/storage/xamxam.db xamxam:/var/www/xamxam/storage/xamxam.db
|
||||||
ssh xamxam "chown www-data:xamxam /var/www/xamxam/storage/xamxam.db && chmod 660 /var/www/xamxam/storage/xamxam.db"
|
ssh xamxam "chown www-data:xamxam /var/www/xamxam/storage/xamxam.db && chmod 660 /var/www/xamxam/storage/xamxam.db"
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-verify-permissions:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
APP_DIR="/var/www/xamxam"
|
||||||
|
WEB_USER="www-data"
|
||||||
|
APP_GROUP="xamxam"
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
|
||||||
|
err() { printf "${RED}✗${NC} %s\n" "$*" >&2; ERRORS=$((ERRORS + 1)); }
|
||||||
|
warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }
|
||||||
|
|
||||||
|
printf "🔍 Verifying permissions on %s…\n\n" "$APP_DIR"
|
||||||
|
|
||||||
|
# ── Ownership ──────────────────────────────────────────────────────────────────
|
||||||
|
echo "── Ownership ───────────────────────────────────"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
owner=$(echo "$line" | awk '{print $3}')
|
||||||
|
group=$(echo "$line" | awk '{print $4}')
|
||||||
|
path=$(echo "$line" | awk '{print $NF}')
|
||||||
|
if [ "$owner" != "$WEB_USER" ] || [ "$group" != "$APP_GROUP" ]; then
|
||||||
|
err "$path → $owner:$group (expected $WEB_USER:$APP_GROUP)"
|
||||||
|
else
|
||||||
|
ok "$path → $owner:$group"
|
||||||
|
fi
|
||||||
|
done < <(ssh xamxam "stat -c '%U %G %n' $APP_DIR $APP_DIR/app $APP_DIR/storage $APP_DIR/var 2>/dev/null")
|
||||||
|
|
||||||
|
# ── Key directories: 2775 ─────────────────────────────────────────────────────
|
||||||
|
echo "── Directory permissions (expected 2775) ───────"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
perms=$(echo "$line" | awk '{print $1}')
|
||||||
|
path=$(echo "$line" | awk '{print $NF}')
|
||||||
|
if [ "$perms" != "drwxrwsr-x" ]; then
|
||||||
|
err "$path → $perms (expected drwxrwsr-x / 2775)"
|
||||||
|
else
|
||||||
|
ok "$path → $perms"
|
||||||
|
fi
|
||||||
|
done < <(ssh xamxam "find $APP_DIR -maxdepth 2 -type d -exec stat -c '%A %n' {} \\; 2>/dev/null | sort")
|
||||||
|
|
||||||
|
# ── Key files: 664 ────────────────────────────────────────────────────────────
|
||||||
|
echo "── File permissions (expected 664 / 660) ───────"
|
||||||
|
# Spot-check a few critical files
|
||||||
|
while IFS= read -r path; do
|
||||||
|
perms=$(ssh xamxam "stat -c '%a %U %G' '$path' 2>/dev/null" || echo "MISSING")
|
||||||
|
if [ "$perms" = "MISSING" ]; then
|
||||||
|
err "$path → FILE MISSING"
|
||||||
|
else
|
||||||
|
perm_num=$(echo "$perms" | awk '{print $1}')
|
||||||
|
owner=$(echo "$perms" | awk '{print $2}')
|
||||||
|
group=$(echo "$perms" | awk '{print $3}')
|
||||||
|
case "$path" in
|
||||||
|
*/storage/xamxam.db|*/storage/*.db)
|
||||||
|
expected_perm="660" ;;
|
||||||
|
*)
|
||||||
|
expected_perm="664" ;;
|
||||||
|
esac
|
||||||
|
if [ "$perm_num" != "$expected_perm" ]; then
|
||||||
|
err "$path → $perm_num ($owner:$group), expected $expected_perm $WEB_USER:$APP_GROUP"
|
||||||
|
elif [ "$owner" != "$WEB_USER" ]; then
|
||||||
|
err "$path → owner $owner, expected $WEB_USER (perm $perm_num OK)"
|
||||||
|
else
|
||||||
|
ok "$path → $perm_num $owner:$group"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(printf '%s\n' \
|
||||||
|
"$APP_DIR/.env" \
|
||||||
|
"$APP_DIR/app/router.php" \
|
||||||
|
"$APP_DIR/storage/xamxam.db")
|
||||||
|
|
||||||
|
# ── var/ subdirectories must be writable ──────────────────────────────────────
|
||||||
|
echo "── var/ writability ────────────────────────────"
|
||||||
|
for subdir in cache logs tmp; do
|
||||||
|
if ssh xamxam "[ -w /var/www/xamxam/var/$subdir ]"; then
|
||||||
|
ok "var/$subdir → writable"
|
||||||
|
else
|
||||||
|
err "var/$subdir → NOT WRITABLE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── storage/cache/rate_limit writable ─────────────────────────────────────────
|
||||||
|
if ssh xamxam "[ -w /var/www/xamxam/storage/cache/rate_limit ]"; then
|
||||||
|
ok "storage/cache/rate_limit → writable"
|
||||||
|
else
|
||||||
|
err "storage/cache/rate_limit → NOT WRITABLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── .env must be 640 ──────────────────────────────────────────────────────────
|
||||||
|
env_perm=$(ssh xamxam "stat -c '%a' /var/www/xamxam/.env 2>/dev/null" || echo "")
|
||||||
|
if [ "$env_perm" = "640" ]; then
|
||||||
|
ok ".env → 640"
|
||||||
|
elif [ -z "$env_perm" ]; then
|
||||||
|
warn ".env → MISSING"
|
||||||
|
else
|
||||||
|
err ".env → $env_perm (expected 640)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Summary ───────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
if [ "$ERRORS" -eq 0 ]; then
|
||||||
|
printf "${GREEN}✅ All permissions OK${NC}\n"
|
||||||
|
else
|
||||||
|
printf "${RED}❌ %d permission error(s) found${NC}\n" "$ERRORS"
|
||||||
|
printf "${YELLOW}Fix with: sudo bash /tmp/deploy-server.sh${NC}\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy-script script_name:
|
deploy-script script_name:
|
||||||
# Generic script deployer (e.g., just deploy-script setup-server)
|
# Generic script deployer (e.g., just deploy-script setup-server)
|
||||||
|
|||||||
210
scripts/generate-schema.py
Normal file
210
scripts/generate-schema.py
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate a clean schema.sql from the fully-migrated local database."""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DB_PATH = sys.argv[1] if len(sys.argv) > 1 else 'app/storage/xamxam.db'
|
||||||
|
|
||||||
|
db = sqlite3.connect(DB_PATH)
|
||||||
|
|
||||||
|
# Get all objects
|
||||||
|
tables = db.execute(
|
||||||
|
"SELECT name, sql FROM sqlite_master WHERE type='table' "
|
||||||
|
"AND name NOT LIKE 'sqlite_%' AND name != '_migrations' ORDER BY name"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
indexes = db.execute(
|
||||||
|
"SELECT sql FROM sqlite_master WHERE type='index' "
|
||||||
|
"AND name NOT LIKE 'sqlite_autoindex%' AND sql IS NOT NULL ORDER BY name"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
views = db.execute(
|
||||||
|
"SELECT sql FROM sqlite_master WHERE type='view' ORDER BY name"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
triggers = db.execute(
|
||||||
|
"SELECT sql FROM sqlite_master WHERE type='trigger' ORDER BY name"
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- XAMXAM Database Schema — complete, fully migrated")
|
||||||
|
print(f"-- Generated from local database on {db.execute('SELECT date()').fetchone()[0]}")
|
||||||
|
print("-- All 28 migrations merged into this single file.")
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# We define the ideal CREATE TABLE statements by querying PRAGMA table_info
|
||||||
|
# and reconstructing clean DDL, because SQLite stores ALTER TABLE artifacts.
|
||||||
|
|
||||||
|
def build_clean_table_ddl(name):
|
||||||
|
"""Build a clean CREATE TABLE statement from PRAGMA table_info."""
|
||||||
|
cols = db.execute(f"PRAGMA table_info('{name}')").fetchall()
|
||||||
|
pk_cols = [c[1] for c in cols if c[5] > 0]
|
||||||
|
|
||||||
|
lines = [f"CREATE TABLE IF NOT EXISTS {name} ("]
|
||||||
|
col_defs = []
|
||||||
|
for cid, col_name, col_type, not_null, default, is_pk in cols:
|
||||||
|
part = f" {col_name} {col_type}"
|
||||||
|
if is_pk:
|
||||||
|
if len(pk_cols) == 1:
|
||||||
|
part += " PRIMARY KEY"
|
||||||
|
if col_type.upper() == 'INTEGER':
|
||||||
|
part += " AUTOINCREMENT"
|
||||||
|
if not_null:
|
||||||
|
part += " NOT NULL"
|
||||||
|
if default is not None:
|
||||||
|
if default.upper() in ('CURRENT_TIMESTAMP', 'CURRENT_TIME', 'CURRENT_DATE'):
|
||||||
|
part += f" DEFAULT {default}"
|
||||||
|
elif any(default.lower().startswith(fn + '(') for fn in ('datetime', 'date', 'time', 'strftime')):
|
||||||
|
part += f" DEFAULT ({default})"
|
||||||
|
elif default.startswith("'") or default.startswith('"'):
|
||||||
|
part += f" DEFAULT {default}"
|
||||||
|
else:
|
||||||
|
part += f" DEFAULT {default}"
|
||||||
|
col_defs.append(part)
|
||||||
|
|
||||||
|
# Multi-column PK
|
||||||
|
if len(pk_cols) > 1:
|
||||||
|
col_defs.append(f" PRIMARY KEY ({', '.join(pk_cols)})")
|
||||||
|
|
||||||
|
# Foreign keys
|
||||||
|
fks = db.execute(f"PRAGMA foreign_key_list('{name}')").fetchall()
|
||||||
|
for fk in fks:
|
||||||
|
fk_id, seq, ref_table, from_col, to_col, on_update, on_delete, match = fk
|
||||||
|
if seq == 0:
|
||||||
|
col_defs.append(
|
||||||
|
f" FOREIGN KEY ({from_col}) REFERENCES {ref_table}({to_col})"
|
||||||
|
+ (f" ON DELETE {on_delete}" if on_delete and on_delete != 'NO ACTION' else "")
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append(",\n".join(col_defs))
|
||||||
|
lines.append(");")
|
||||||
|
|
||||||
|
# Add CHECK constraints if any from original DDL
|
||||||
|
orig = db.execute(
|
||||||
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (name,)
|
||||||
|
).fetchone()
|
||||||
|
if orig and orig[0]:
|
||||||
|
for m in re.finditer(r'CHECK\s*\([^)]+\)', orig[0]):
|
||||||
|
constraint = m.group(0)
|
||||||
|
# INSERT OR IGNORE into site_settings uses ON CONFLICT — keep that pattern
|
||||||
|
pass # CHECK is already in the column definition from PRAGMA
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# Sort tables in dependency order (rough)
|
||||||
|
table_order = [
|
||||||
|
'orientations', 'ap_programs', 'finality_types', 'languages',
|
||||||
|
'format_types', 'tags', 'access_types', 'license_types',
|
||||||
|
'authors', 'supervisors',
|
||||||
|
'theses',
|
||||||
|
'thesis_authors', 'thesis_supervisors', 'thesis_languages',
|
||||||
|
'thesis_formats', 'thesis_tags', 'thesis_files',
|
||||||
|
'site_settings', 'system_cache', 'pages', 'share_links',
|
||||||
|
'smtp_settings', 'apropos_contents',
|
||||||
|
'file_access_requests', 'file_access_tokens', 'file_access_sessions',
|
||||||
|
'file_access_audit', 'form_help_blocks', 'admin_audit_log',
|
||||||
|
'peertube_settings', 'audit_log',
|
||||||
|
]
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for name in table_order:
|
||||||
|
if name in seen:
|
||||||
|
continue
|
||||||
|
seen.add(name)
|
||||||
|
try:
|
||||||
|
ddl = build_clean_table_ddl(name)
|
||||||
|
print(ddl)
|
||||||
|
print()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"-- ERROR building DDL for {name}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- INDEXES")
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for (sql,) in indexes:
|
||||||
|
clean = sql.strip()
|
||||||
|
if not clean.endswith(';'):
|
||||||
|
clean += ';'
|
||||||
|
print(clean)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- VIEWS")
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for (sql,) in views:
|
||||||
|
clean = sql.strip()
|
||||||
|
if not clean.endswith(';'):
|
||||||
|
clean += ';'
|
||||||
|
print(clean)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- TRIGGERS")
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for (sql,) in triggers:
|
||||||
|
clean = sql.strip()
|
||||||
|
if not clean.endswith(';'):
|
||||||
|
clean += ';'
|
||||||
|
print(clean)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- SEED DATA (reference data + initial settings)")
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Extract seed data from the local database
|
||||||
|
# (table, query, explicit_column_names)
|
||||||
|
seed_tables = [
|
||||||
|
('orientations', "SELECT name FROM orientations", ['name']),
|
||||||
|
('ap_programs', "SELECT name, code FROM ap_programs", ['name', 'code']),
|
||||||
|
('finality_types', "SELECT name FROM finality_types", ['name']),
|
||||||
|
('languages', "SELECT name FROM languages WHERE deleted_at IS NULL", ['name']),
|
||||||
|
('format_types', "SELECT name, sort_order FROM format_types", ['name', 'sort_order']),
|
||||||
|
('access_types', "SELECT name, description FROM access_types", ['name', 'description']),
|
||||||
|
('license_types', "SELECT name FROM license_types", ['name']),
|
||||||
|
('site_settings', "SELECT key, value FROM site_settings WHERE key IN ('access_type_interdit_enabled', 'access_type_interne_enabled', 'access_type_libre_enabled', 'objet_these_enabled', 'objet_frart_enabled', 'restricted_files_enabled', 'peertube_upload_enabled')", ['key', 'value']),
|
||||||
|
('pages', "SELECT slug, title, content, is_published FROM pages WHERE slug IN ('charte', 'about', 'licenses')", ['slug', 'title', 'content', 'is_published']),
|
||||||
|
('form_help_blocks', "SELECT key, name, content, enabled, sort_order FROM form_help_blocks", ['key', 'name', 'content', 'enabled', 'sort_order']),
|
||||||
|
('apropos_contents', "SELECT key, value FROM apropos_contents WHERE key = 'contacts'", ['key', 'value']),
|
||||||
|
]
|
||||||
|
|
||||||
|
for table, query, col_names in seed_tables:
|
||||||
|
rows = db.execute(query).fetchall()
|
||||||
|
if not rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
vals = []
|
||||||
|
for i, val in enumerate(row):
|
||||||
|
if val is None:
|
||||||
|
vals.append('NULL')
|
||||||
|
elif isinstance(val, bool):
|
||||||
|
vals.append('1' if val else '0')
|
||||||
|
elif isinstance(val, (int, float)):
|
||||||
|
vals.append(str(val))
|
||||||
|
else:
|
||||||
|
escaped = str(val).replace("'", "''")
|
||||||
|
vals.append(f"'{escaped}'")
|
||||||
|
|
||||||
|
print(f"INSERT OR IGNORE INTO {table} ({', '.join(col_names)}) VALUES ({', '.join(vals)});")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Singleton table inserts
|
||||||
|
print("-- Singleton table placeholders")
|
||||||
|
print("INSERT OR IGNORE INTO smtp_settings (id) VALUES (1);")
|
||||||
|
print("INSERT OR IGNORE INTO peertube_settings (id) VALUES (1);")
|
||||||
|
print()
|
||||||
|
print("-- ============================================================================")
|
||||||
|
print("-- END OF SCHEMA")
|
||||||
|
print("-- ============================================================================")
|
||||||
@@ -7,9 +7,18 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
APP_DIR="$REPO_ROOT/app"
|
|
||||||
SCHEMA="$APP_DIR/storage/schema.sql"
|
# Detect layout: local dev has app/ subdir, server has files at repo root
|
||||||
PROD_DB="$APP_DIR/storage/xamxam.db"
|
if [ -d "$REPO_ROOT/app/storage" ]; then
|
||||||
|
SCHEMA="$REPO_ROOT/app/storage/schema.sql"
|
||||||
|
PROD_DB="$REPO_ROOT/app/storage/xamxam.db"
|
||||||
|
elif [ -f "$REPO_ROOT/storage/schema.sql" ]; then
|
||||||
|
SCHEMA="$REPO_ROOT/storage/schema.sql"
|
||||||
|
PROD_DB="$REPO_ROOT/storage/xamxam.db"
|
||||||
|
else
|
||||||
|
echo "ERROR: cannot find storage/schema.sql" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
init_db() {
|
init_db() {
|
||||||
local db="$1"
|
local db="$1"
|
||||||
|
|||||||
Reference in New Issue
Block a user