mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
refactor: rename keywords→tags M2M (migration 001)
- migration 001_rename_keywords_to_tags.sql: CREATE tags/thesis_tags from keywords/thesis_keywords, copy data, drop old tables, rebuild indexes and views - schema.sql: tags table, thesis_tags junction, updated indexes and v_theses_full/v_theses_public - Database.php: findOrCreateTag(), getUsedTags() with proper JOIN; backwards-compat aliases; buildSearchConditions uses EXISTS subquery on thesis_tags+tags with vp. alias throughout - admin/actions/formulaire.php: INSERT OR IGNORE INTO thesis_tags - admin/edit.php: DELETE FROM thesis_tags + findOrCreateTag - search.php: $kw['name'] (was $kw['keyword']) - fixtures/CreateTestDatabase.php: tags/thesis_tags table names
This commit is contained in:
@@ -56,7 +56,7 @@ try {
|
||||
}
|
||||
echo "Inserted " . count($supervisors) . " sample supervisors\n";
|
||||
|
||||
// Insert sample keywords
|
||||
// Insert sample tags (formerly keywords)
|
||||
$sampleKeywords = [
|
||||
'spéculation', 'narration', 'urbanisme', 'patrimoine', 'intime',
|
||||
'collectivité', 'film', 'cinéma', 'sociologie', 'anthropologie',
|
||||
@@ -65,10 +65,10 @@ try {
|
||||
];
|
||||
|
||||
foreach ($sampleKeywords as $keyword) {
|
||||
$stmt = $pdo->prepare("INSERT INTO keywords (keyword) VALUES (:keyword)");
|
||||
$stmt->execute(['keyword' => $keyword]);
|
||||
$stmt = $pdo->prepare("INSERT INTO tags (name) VALUES (:name)");
|
||||
$stmt->execute(['name' => $keyword]);
|
||||
}
|
||||
echo "Inserted " . count($sampleKeywords) . " sample keywords\n";
|
||||
echo "Inserted " . count($sampleKeywords) . " sample tags\n";
|
||||
|
||||
// Insert sample theses
|
||||
$theses = [
|
||||
@@ -192,30 +192,30 @@ try {
|
||||
}
|
||||
echo "Linked supervisors to theses\n";
|
||||
|
||||
// Link keywords to theses
|
||||
// Link tags to theses (thesis_tags junction)
|
||||
$thesisKeywords = [
|
||||
['thesis_id' => 1, 'keyword_id' => 3], // urbanisme
|
||||
['thesis_id' => 1, 'keyword_id' => 2], // narration
|
||||
['thesis_id' => 1, 'keyword_id' => 6], // collectivité
|
||||
['thesis_id' => 2, 'keyword_id' => 16], // technologies
|
||||
['thesis_id' => 2, 'keyword_id' => 18], // performance
|
||||
['thesis_id' => 2, 'keyword_id' => 20], // art numérique
|
||||
['thesis_id' => 3, 'keyword_id' => 14], // poésie
|
||||
['thesis_id' => 3, 'keyword_id' => 11], // éphémérité
|
||||
['thesis_id' => 3, 'keyword_id' => 5], // intime
|
||||
['thesis_id' => 4, 'keyword_id' => 15], // écologie
|
||||
['thesis_id' => 4, 'keyword_id' => 17], // design
|
||||
['thesis_id' => 5, 'keyword_id' => 1], // spéculation
|
||||
['thesis_id' => 5, 'keyword_id' => 4], // patrimoine
|
||||
['thesis_id' => 6, 'keyword_id' => 18], // performance
|
||||
['thesis_id' => 6, 'keyword_id' => 9], // sociologie
|
||||
['thesis_id' => 1, 'tag_id' => 3], // urbanisme
|
||||
['thesis_id' => 1, 'tag_id' => 2], // narration
|
||||
['thesis_id' => 1, 'tag_id' => 6], // collectivité
|
||||
['thesis_id' => 2, 'tag_id' => 16], // technologies
|
||||
['thesis_id' => 2, 'tag_id' => 18], // performance
|
||||
['thesis_id' => 2, 'tag_id' => 20], // art numérique
|
||||
['thesis_id' => 3, 'tag_id' => 14], // poésie
|
||||
['thesis_id' => 3, 'tag_id' => 11], // éphémérité
|
||||
['thesis_id' => 3, 'tag_id' => 5], // intime
|
||||
['thesis_id' => 4, 'tag_id' => 15], // écologie
|
||||
['thesis_id' => 4, 'tag_id' => 17], // design
|
||||
['thesis_id' => 5, 'tag_id' => 1], // spéculation
|
||||
['thesis_id' => 5, 'tag_id' => 4], // patrimoine
|
||||
['thesis_id' => 6, 'tag_id' => 18], // performance
|
||||
['thesis_id' => 6, 'tag_id' => 9], // sociologie
|
||||
];
|
||||
|
||||
foreach ($thesisKeywords as $link) {
|
||||
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (:thesis_id, :keyword_id)");
|
||||
$stmt = $pdo->prepare("INSERT OR IGNORE INTO thesis_tags (tag_id, thesis_id) VALUES (:tag_id, :thesis_id)");
|
||||
$stmt->execute($link);
|
||||
}
|
||||
echo "Linked keywords to theses\n";
|
||||
echo "Linked tags to theses\n";
|
||||
|
||||
// Link languages to theses (all in French)
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
|
||||
106
storage/migrations/001_rename_keywords_to_tags.sql
Normal file
106
storage/migrations/001_rename_keywords_to_tags.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
-- Migration 001: Rename keywords→tags, thesis_keywords→thesis_tags, keyword column→name
|
||||
-- SQLite does not support ALTER TABLE RENAME COLUMN before 3.25, so we recreate tables.
|
||||
-- This migration is safe to run after 004 and 005 (no dependency ordering required
|
||||
-- since SQLite processes this in one transaction).
|
||||
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. Create new tags table
|
||||
CREATE TABLE tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 2. Copy data from keywords → tags (keyword column → name)
|
||||
INSERT INTO tags (id, name, created_at)
|
||||
SELECT id, keyword, created_at FROM keywords;
|
||||
|
||||
-- 3. Create new junction table thesis_tags
|
||||
CREATE TABLE thesis_tags (
|
||||
tag_id INTEGER NOT NULL,
|
||||
thesis_id INTEGER NOT NULL,
|
||||
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
|
||||
);
|
||||
|
||||
-- 4. Copy junction data (keyword_id → tag_id)
|
||||
INSERT INTO thesis_tags (tag_id, thesis_id)
|
||||
SELECT keyword_id, thesis_id FROM thesis_keywords;
|
||||
|
||||
-- 5. Drop old tables
|
||||
DROP TABLE thesis_keywords;
|
||||
DROP TABLE keywords;
|
||||
|
||||
-- 6. Recreate indexes with canonical names
|
||||
CREATE INDEX idx_tags_name ON tags(name);
|
||||
CREATE INDEX idx_thesis_tags_thesis ON thesis_tags(thesis_id);
|
||||
CREATE INDEX idx_thesis_tags_tag ON thesis_tags(tag_id);
|
||||
|
||||
-- 7. Rebuild views to reference new tables
|
||||
DROP VIEW IF EXISTS v_theses_full;
|
||||
|
||||
CREATE VIEW v_theses_full AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.identifier,
|
||||
t.title,
|
||||
t.subtitle,
|
||||
t.year,
|
||||
t.is_doctoral,
|
||||
o.name as orientation,
|
||||
ap.name as ap_program,
|
||||
ft.name as finality_type,
|
||||
t.synopsis,
|
||||
t.context_note,
|
||||
t.duration_minutes,
|
||||
t.duration_pages,
|
||||
t.file_size_info,
|
||||
at.name as access_type,
|
||||
lt.name as license_type,
|
||||
t.license_id,
|
||||
t.jury_points,
|
||||
t.submitted_at,
|
||||
t.defense_date,
|
||||
t.published_at,
|
||||
t.is_published,
|
||||
t.baiu_link,
|
||||
t.banner_path,
|
||||
GROUP_CONCAT(DISTINCT a.name) as authors,
|
||||
GROUP_CONCAT(DISTINCT s.name) as supervisors,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'president' THEN s.name END) as jury_president,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'promoteur' THEN s.name END) as jury_promoteurs,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
LEFT JOIN finality_types ft ON t.finality_id = ft.id
|
||||
LEFT JOIN access_types at ON t.access_type_id = at.id
|
||||
LEFT JOIN license_types lt ON t.license_id = lt.id
|
||||
LEFT JOIN thesis_authors ta ON t.id = ta.thesis_id
|
||||
LEFT JOIN authors a ON ta.author_id = a.id
|
||||
LEFT JOIN thesis_supervisors ts ON t.id = ts.thesis_id
|
||||
LEFT JOIN supervisors s ON ts.supervisor_id = s.id
|
||||
LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.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 thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
DROP VIEW IF EXISTS v_theses_public;
|
||||
|
||||
CREATE VIEW v_theses_public AS
|
||||
SELECT * FROM v_theses_full
|
||||
WHERE is_published = 1;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
@@ -105,13 +105,15 @@ INSERT OR IGNORE INTO format_types (name) VALUES
|
||||
('Installation'),
|
||||
('Autre');
|
||||
|
||||
-- Keywords (expandable list)
|
||||
CREATE TABLE IF NOT EXISTS keywords (
|
||||
-- Tags (keywords — canonical M2M table; formerly 'keywords'/'keyword' column)
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
keyword TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
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,
|
||||
@@ -250,13 +252,13 @@ CREATE TABLE IF NOT EXISTS thesis_formats (
|
||||
FOREIGN KEY (format_id) REFERENCES format_types(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Keywords per thesis (max 10 as per specs)
|
||||
CREATE TABLE IF NOT EXISTS thesis_keywords (
|
||||
-- Tags per thesis (max 10 as per specs; formerly thesis_keywords)
|
||||
CREATE TABLE IF NOT EXISTS thesis_tags (
|
||||
tag_id INTEGER NOT NULL,
|
||||
thesis_id INTEGER NOT NULL,
|
||||
keyword_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (thesis_id, keyword_id),
|
||||
PRIMARY KEY (tag_id, thesis_id),
|
||||
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (keyword_id) REFERENCES keywords(id) ON DELETE CASCADE
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
@@ -311,8 +313,8 @@ 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_keywords_thesis ON thesis_keywords(thesis_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_thesis_keywords_keyword ON thesis_keywords(keyword_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
|
||||
@@ -380,7 +382,7 @@ SELECT
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs,
|
||||
GROUP_CONCAT(DISTINCT l.name) as languages,
|
||||
GROUP_CONCAT(DISTINCT fmt.name) as formats,
|
||||
GROUP_CONCAT(DISTINCT k.keyword) as keywords
|
||||
GROUP_CONCAT(DISTINCT tg.name) as keywords
|
||||
FROM theses t
|
||||
LEFT JOIN orientations o ON t.orientation_id = o.id
|
||||
LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id
|
||||
@@ -395,8 +397,8 @@ LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id
|
||||
LEFT JOIN languages l ON tl.language_id = l.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 thesis_keywords tk ON t.id = tk.thesis_id
|
||||
LEFT JOIN keywords k ON tk.keyword_id = k.id
|
||||
LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id
|
||||
LEFT JOIN tags tg ON tt.tag_id = tg.id
|
||||
GROUP BY t.id;
|
||||
|
||||
-- Published theses only (for public view)
|
||||
|
||||
BIN
storage/test.db
BIN
storage/test.db
Binary file not shown.
Reference in New Issue
Block a user