Import & migration
Le dossier migrations/ contient des donnees et scripts d'import one-off (PAS des migrations EF) servant a migrer le contenu legacy (Umbraco 6) vers ce CMS. L'import passe par les endpoints de ImportApiController.
Statut : desactive.
ImportApiControllerest entierement exclu du build (#if false) : ces endpoints n'avaient aucune authentification (route publique~/umbraco/api/Import/..., dontDeleteArticle), ce qui exposait de la mutation/suppression de contenu non authentifiee en prod. Pour relancer une migration, retirer les directives#if false/#endifet proteger les endpoints par le headerX-Webhook-Secret(voirMultimediaApiController).
ImportApiController (desactive)
POST CreateArticle: cree un article complet a partir d'unCreateArticleDto(titre, sous-titre, teaser, contenu HTML avec marqueurs de macros, datePublished, tags CSV, image par nom, auteurs CSV, skin, rubrique, categories, localisation JSON, MacroReplacements). Logique :- Resout la racine par skin, cree/trouve le
{skin}ArticlesFolderpuis le sous-dossier de rubrique (map des codes legacy -> labels FR). - Resout l'image par nom (via Examine), les auteurs (firstname+lastname -> UDIs), les categories (JSON CheckBoxList), la rubrique (
sectionJSON). - Si
MacroReplacementsfournis,BuildRteWithBlocksValue()convertit les macros legacy en JSON de blocks TinyMCE (UDIsumb://element/..., BlockList imbriques). - Tag automatique des dessins : si categorie "Dessin" + image,
media.isDrawing = true. - SaveAndPublish immediat.
- Resout la racine par skin, cree/trouve le
POST CreateAuthor: cree un auteur sous le dossier globalauthors.POST DeleteArticle?id=: suppression (cleanup migration/tests).CreateMedia: commente (remplace par import SQL direct).
Dossier migrations/
migrations/
├── doc.md, TODO.md
├── articles/ articles.json (~732 articles legacy), MIGRATION-ARTICLES.md, AUDIT-ARTICLES.md,
│ exportArticlesToJson.sql, import-articles.ps1, import-articles-linfo.ps1, cleanup-broken-articles.ps1
├── authors/ authors.json (~100), exportAuthorToJson.sql, import-author.ps1
└── medias/ medias.json / mediasNew.json, u13_insertMedias.sql, u7_extractMediaToJson.sql, import-media.ps1Volumes (cf. MIGRATION-ARTICLES.md) : 732 articles (l-info 385, icogne 345, accm 2), ~100 auteurs.
Workflow d'import (articles L-Info)
import-articles-linfo.ps1 lit articles.json, met en cache les metadonnees de pickers via SQL sur la base legacy (vmsqla001), resout les macros (WidgetDocument -> filesBlock, WidgetSlider -> galleryBlock, formulaires/DividedContent -> reconstruction manuelle), construit le CreateArticleDto + MacroReplacements, puis POST vers CreateArticle. Si la base legacy est indisponible, les macros tombent en commentaires HTML (reconstruction manuelle).
Medias
Import via SQL direct (u13_insertMedias.sql) dans les tables Umbraco, plutot que via l'API. mediasNew.json fournit le mapping ancien UDI -> nouveau nom de fichier pour resoudre les macros pendant l'import des articles.
Nouveau systeme Icogne — lecture directe de la DB legacy (2026)
Refonte de l'import (skin icogne d'abord) inspiree du FelixImportController d'intranet-apol-pel : on supprime la chaine export JSON + PowerShell et on lit directement la base legacy en ADO.NET cote serveur. Design complet : docs/superpowers/specs/2026-08-04-import-icogne-legacy-db-design.md.
- Source legacy :
umb_accmpd_prod, Umbraco 7.15 (Starter Kit SKV 3.6.0) — pas un V6 (corrigerMIGRATION-ARTICLES.mden consequence). Racine icogne = node 1501 ; typeArticleItem; contenu versionne (cmsDocument+cmsPropertyData.versionId). - Cible : racine icogne node 1233, articles ranges par annee (
icogneArticlesFolder: 2023..2026), typeicogneArticle. Medias icogne sous node 22247. - Reference unique
legacyId(Umbraco.Integer) : compositions uSync jetableslegacyRefContent(attachee aicogneArticle) etlegacyRefMedia(attachee aImage/File). Sert de clef de dedup/idempotence et de pont de resolution des medias. A supprimer apres la migration. - Controller :
LegacyIcogneImportController(#if DEBUG, jamais en Release ; aucune auth, execution locale). Connexion legacy via configConnectionStrings:LegacyImportDb(dansappsettings.local.json, git-ignore). Endpoints GET (skip,take,dryRun) :RetagMedia(a lancer en premier) : tamponnelegacyIdsur les medias cible viamigrations/medias/mediasNew.json(jointurePath==umbracoFile).ImportMissingMedia: recree les medias referencies par les articles icogne mais absents de la cible (le snapshotmediasNew.jsons'arrete au media legacy id 15598 ; les PDF recents 2024→2026 manquent). Reproduit la hierarchie de dossiers legacy (remontee des parents sous la racine media Icogne legacy 1478 → racine cible 22247, dossiers matches par nom/crees) et poseumbracoFile(path legacy) +legacyId. Les fichiers physiques ne sont pas geres (ils vivent sur le serveur media, meme path/media/xxxx/).RunArticles(replaceen plus) : importe les 381 articles icogne, etat de publication conserve (versionpublishedsi elle existe, sinonnewest; publie si publie, sinon brouillon), routing par annee, dedup/replace parlegacyId, conversion du corps. Notifications supprimees (pas de webhook frontend) → recalculermediaIndexapres via~/umbraco/api/Multimedia/Reindex.
- Workflow complet :
RetagMedia→ImportMissingMedia→RunArticles→Multimedia/Reindex+ reconstruire l'index Examine. - Conversion du corps :
WidgetDocument->filesBlock,WidgetSlider->galleryBlock(resolution des medias parlegacyId) ; formulaires laisses en repere HTML<!-- LEGACY_FORM FormGuid=... -->(reconnexion manuelle) ; rubrique/type/geoloc ignores (vides cote icogne). - Nettoyage : manuel (backoffice) ; les re-runs passent par
replace=true. - Pre-requis avant import : importer le schema uSync (creer la propriete
legacyId), sinonSetValue("legacyId", ...)echoue. Apres import : reconstruire l'index Examine (tridatePublished:).
Nouveau systeme L-Info — lecture directe de la DB legacy (2026)
Jumeau du systeme icogne pour la skin l-info (meme moteur, memes garde-fous). Design complet : docs/superpowers/specs/2026-08-04-import-linfo-legacy-db-design.md.
- Source legacy :
umb_accmpd_prod(U7.15). Racine l-info = node 1500 ; typeArticleItem. 397 articles (newest=1), dont 386 publies. Media root legacy L-Info = node 1486. - Cible : racine l-info node 1234 (
linfoSiteRoot) ; conteneur d'articlesArticles= node 32176 (parent des dossiers-rubriques) ; media root L-Info = node 4306 ; auteurs l-info sous node 1238. TypelinfoArticle. - Difference cle vs icogne : rangement par rubrique (pas par annee).
articleMagazineRubric(code EN) ->LinfoRubricMap-> label FR, utilise a la fois comme dossier (linfoArticlesFoldersous 32176, trouve par nom sinon cree) et comme valeursection(Dropdown.Flexible, JSON array). Rubrique vide -> article directement sous 32176, sanssection. - Champs specifiques l-info (absents d'icogne) :
section(rubrique),categories(articleMagazineTypeCSV image/drawing/video/audio -> Genre Photo/Dessin/Vidéo/Audio, CheckBoxList JSON array),noEdition(articleMagazineEditionNumber). Re-tagisDrawing: si Genre contientDessinet image resolue,media.isDrawing=true. - Conversion du corps (delta majeur vs icogne) : le macro dominant l-info est
DividedContent(132/397 articles ; WidgetDocument/WidgetSlider marginaux). ConversionDividedContent(macropicker=<DivContent>) ->dividedContentBlock: les enfantsDivItem-> BlockListdivItemsdedivItemElement(divItemTitle,divItemContentHTML,divItemImageMediaPicker3 resolu parlegacyId).divContentPresentation(int) ->divPresentation(Dropdown, mapping direct int->id : 1 Zones rétractables, 2 Onglets, 3 2 colonnes, 4 3 colonnes, 5 4 colonnes ; 0/inconnu -> vide). Formulaires -> repere HTML. Macro imbrique dansdivItemContent(5 cas, tousWidgetDocument) :divItemContentetant un RTE simple (sans blocs), leWidgetDocumentest converti en lien(s) HTML inline<a href="/media/...">nom</a>(path legacy = path cible) ; tout autre macro imbrique -> commentaire. - Auteurs :
authorsfusionne deux champs legacy —articleMagazineAuthorsTextetarticleMagazineAuthorsMultimedia(159 articles, 2e liste distincte : photographes/videastes) — dedup. Resolution scopee (prefere un auteur sous 1238, fallback global). - Geolocalisation :
articleGeolocations(Archetype legacy,location->position.datum"lat,lng" +lookup+zoom) ->localisation(Our.Umbraco.GMaps, format{address:{full_address,coordinates:{lat,lng},...},mapconfig:{zoom,maptype,centerCoordinates}}). 348/397 articles ont une geoloc reelle ; les coquilles vides sont ignorees. - Legende d'image :
articleImageCaption(217) n'a pas de cible article -> stockee dans ledescriptiondu media image (images non partagees entre articles, pas d'ecrasement d'une description existante). - Non mappe :
articleImportant(28, flag "a la une" — aucun champ cible),articleDisqusDisplay,articleUrlPrediction,articleStorageDisplayWidgetArea(obsoletes/internes). - Controller :
LegacyLinfoImportController(#if DEBUG, aucune auth, execution locale), memes endpoints GET qu'icogne (RetagMediasur 4306,ImportMissingMedia1486->4306,RunArticlesavecskip/take/dryRun/replace). Etat de publication conserve, dedup/replace parlegacyId, notifications supprimees -> recalculermediaIndexvia~/umbraco/api/Multimedia/Reindex. DownloadMediaFiles(rootIddef 4306 /skip/take/dryRun/overwrite) : recupere les fichiers physiques absents en local. Parcourt l'arbre media (4306 par defaut, hors dossiers) et, pour chaque media dont le binaire manque (MediaFileManager.FileSystem.FileExists), telecharge{LegacyImport:MediaBaseUrl|https://www.l-info.ch}{/media/...}et l'ecrit viaMediaFileManager.FileSystem.AddFile. Idempotent (skip si present, saufoverwrite), 404 -> warning. A la difference des autres endpoints d'import, il n'est PAS#if DEBUG: il vit dans son propre controllerMediaDownloadApiControlleret ne touche que le FileSystem (aucun noeud, aucun NuCache). Deux variantes :POST ~/umbraco/api/MediaDownloadApi/DownloadMediaFilesprotege par le secretPublishWebhook:Secret(headerX-Webhook-Secret) -> appelable sur staging/prod apres deploiement.GET ~/umbraco/api/MediaDownloadApi/DownloadMediaFilesDev(#if DEBUG+IsDevelopment, sans secret) pour le local. Ensuite synchroniserwwwroot/mediavers la VM media si on l'a lance en local. Design :docs/superpowers/specs/2026-08-10-import-linfo-media-download-design.md.
- Pre-requis schema : composition
legacyRefContentattachee alinfoArticle(uSync/v9/ContentTypes/linfoarticle.config, Key1e9ac0de-0001-0001-0001-000000000001), a importer avant le run.legacyRefMediacouvre deja tous les media types. - Workflow complet : purge manuelle des 12 dossiers-rubriques (les 378 articles de l'ancien import PowerShell, sans
legacyId) ->RetagMedia->ImportMissingMedia->DownloadMediaFiles(dryRun puis complet) -> synchrowwwroot/media-> VM media ->RunArticles(dryRun+take=5 puis complet) ->RunMultiBands(dryRun+take=5 puis complet) ->Multimedia/Reindex+ reconstruire l'index Examine.
Phase 2 — MultiBands -> blocks (BlockGrid) (2026-08)
Design : docs/superpowers/specs/2026-08-10-import-linfo-multibands-phase2/ (SDD). Le systeme legacy MultiBands (MultiBandsContainer id doctype 630, MultiBandsItem/bande id 631, BandColumn id 639 — ~213 articles concernes) porte le gros du contenu riche l-info dans bandColumnRichText (galeries WidgetSlider, YouTube/iframes, fichiers WidgetDocument, DividedContent imbriques, formulaires). Converti vers le blocks (BlockGrid) de baseArticle.
MultiBandsConverter(src/Web/Services/Import/MultiBandsConverter.cs) : convertisseur pur et unit-teste (aucun acces DB direct — tout est injecte via delegates :contentTypeKeyByAlias,resolveMediaKeys,readPicker,readDivContent,warn).HtmlToBlockGridItems(html)segmente un fragment HTML en items BlockGrid ordonnes : texte ->rteBlock, iframe YouTube ->videoBlock, macroWidgetSlider/WidgetDocument->galleryBlock/filesBlock, macroDividedContent->dividedContentBlock(BlockList imbriqueedivItemElement,divPresentation= colonnes / zones retractables / onglets, contenu recursif viaHtmlToBlockGridItems), macro*Form*-> repere HTML dans uncodeBlock, macro inconnue ->codeBlockcommentaire.TitleBlock(title)emet untitleBlockseul (titre de bande).- Fonds de bande (thèmes) —
MapBandTheme(theme)mappe un thème legacy (multiBandsTheme, à défautbandColumnThemede la 1re colonne) vers une valeurdtl-bg-*:info_sup->backgroundColor dtl-bg-black;cyan->backgroundColor dtl-bg-cyan;brun-light->backgroundTexture dtl-bg-brown-light;pattern->backgroundTexture dtl-bg-brown-texture;brun->backgroundTexture dtl-bg-brown; sinon (gray-light,separate,default, vide) -> aucun fond.WrapBandInLayoutenveloppe alors tous les blocks d'une bande à thème dans un conteneuroneColumnsGridLayout(area « all »e0fa5134-…) porteur du fond ; une bande sans thème reste à plat (inchangé). Aucun nouveau schéma :oneColumnsGridLayoutpossède déjàbackgroundColor(a6a4240d…) /backgroundTexture(be4aabee…), dont les valeurs sont les classes.dtl-bg-*(rendues par le partialoneColumnsGridLayout.cshtmlcôté frontendaccm-website). - Endpoint
RunMultiBands(skip/take/dryRun/replace) dansLegacyLinfoImportController: pour chaque article legacy ayant unMultiBandsContainerenfant, lit ses bandes ordonnees (MultiBandsItem, titremultiBandsTitle) et, pour chaque bande, ses colonnes ordonnees (BandColumn.bandColumnRichText; a defaut, repli surmultiBandsContentde la bande) ; assemble par bande le titre (si present) + blocs de chaque colonne, puis enveloppe la bande dans unoneColumnsGridLayoutà fond si elle porte un thème (MapBandTheme/WrapBandInLayout, cf. ci-dessus), et pose le tout dans un seulblocks(BlockGrid) sur l'article cible (retrouve vialegacyId, cf.RunArticles). N'ecrase pas unblocksdeja rempli saufreplace=true. A executer apresRunArticles(necessite le mappinglegacyId-> nodeId). Notifications supprimees comme les autres endpoints ->Multimedia/Reindexa rejouer ensuite.

