Index links between texts & articles
This commit is contained in:
parent
da419fce5b
commit
b846817fbb
15 changed files with 1014 additions and 849 deletions
1152
package-lock.json
generated
1152
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -60,6 +60,7 @@
|
|||
"@types/he": "^1.1.2",
|
||||
"@types/jsdom": "^21.1.3",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/object-hash": "^3.0.6",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
||||
"@typescript-eslint/parser": "^7.6.0",
|
||||
|
@ -74,6 +75,7 @@
|
|||
"iconify-icon": "^2.0.0",
|
||||
"metslesliens": "git+https://git.en-root.org/eraviart/metslesliens.git",
|
||||
"morgan": "^1.10.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"papaparse": "^5.3.2",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-load-config": "^5.0.2",
|
||||
|
|
|
@ -11,9 +11,11 @@ import type {
|
|||
TexteVersion,
|
||||
Versions,
|
||||
} from "$lib/legal"
|
||||
import type { ArticleLienDb, TexteVersionLienDb } from "./legal/shared"
|
||||
|
||||
export interface Aggregate {
|
||||
article?: { [id: string]: Article | JorfArticle | LegiArticle }
|
||||
article_lien?: ArticleLienDb[]
|
||||
dossier_legislatif?: { [id: string]: DossierLegislatif }
|
||||
id?: string
|
||||
idcc?: { [id: string]: Idcc }
|
||||
|
@ -21,6 +23,7 @@ export interface Aggregate {
|
|||
jo?: { [id: string]: Jo }
|
||||
section_ta?: { [id: string]: SectionTa }
|
||||
texte_version?: { [id: string]: TexteVersion }
|
||||
texte_version_lien?: TexteVersionLienDb[]
|
||||
textekali?: { [id: string]: Textekali }
|
||||
textelr?: { [id: string]: Textelr }
|
||||
versions?: { [eli: string]: Versions }
|
||||
|
|
|
@ -305,18 +305,7 @@ export interface JorfTexteVersion {
|
|||
DATE_DEBUT: string
|
||||
DATE_FIN: string
|
||||
LIENS?: {
|
||||
LIEN: Array<{
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: JorfTexteVersionLienNature
|
||||
"@num"?: string
|
||||
"@nortexte"?: string
|
||||
"@numtexte"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": JorfTexteVersionLienType
|
||||
}>
|
||||
LIEN: Array<JorfTexteVersionLien>
|
||||
}
|
||||
MCS_TXT?: {
|
||||
MC: string[]
|
||||
|
@ -347,6 +336,19 @@ export interface JorfTexteVersion {
|
|||
}
|
||||
}
|
||||
|
||||
export interface JorfTexteVersionLien {
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: JorfTexteVersionLienNature
|
||||
"@nortexte"?: string
|
||||
"@num"?: string
|
||||
"@numtexte"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": JorfTexteVersionLienType
|
||||
}
|
||||
|
||||
export type JorfTexteVersionLienNature =
|
||||
(typeof allJorfTexteVersionLienNatures)[number]
|
||||
|
||||
|
|
|
@ -26,17 +26,7 @@ export interface LegiArticle {
|
|||
}
|
||||
}
|
||||
LIENS?: {
|
||||
LIEN: Array<{
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: LegiArticleLienNature
|
||||
"@nortexte"?: string
|
||||
"@num"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": LegiArticleLienType
|
||||
}>
|
||||
LIEN: Array<LegiArticleLien>
|
||||
}
|
||||
META: {
|
||||
META_COMMUN: {
|
||||
|
@ -76,6 +66,18 @@ export interface LegiArticle {
|
|||
|
||||
export type LegiArticleEtat = (typeof allLegiArticleEtats)[number]
|
||||
|
||||
export interface LegiArticleLien {
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: LegiArticleLienNature
|
||||
"@nortexte"?: string
|
||||
"@num"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": LegiArticleLienType
|
||||
}
|
||||
|
||||
export type LegiArticleLienArticleOrigine =
|
||||
(typeof allLegiArticleLienArticleOrigines)[number]
|
||||
|
||||
|
@ -286,18 +288,7 @@ export interface LegiTexteVersion {
|
|||
DATE_FIN?: string
|
||||
ETAT?: LegiTexteVersionEtat
|
||||
LIENS?: {
|
||||
LIEN: Array<{
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: LegiTexteVersionLienNature
|
||||
"@num"?: string
|
||||
"@nortexte"?: string
|
||||
"@numtexte"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": LegiTexteVersionLienType
|
||||
}>
|
||||
LIEN: Array<LegiTexteVersionLien>
|
||||
}
|
||||
MCS_TXT?: {
|
||||
MC: string[]
|
||||
|
@ -327,6 +318,19 @@ export interface LegiTexteVersion {
|
|||
|
||||
export type LegiTexteVersionEtat = (typeof allLegiTexteVersionEtats)[number]
|
||||
|
||||
export interface LegiTexteVersionLien {
|
||||
"#text"?: string
|
||||
"@cidtexte"?: string
|
||||
"@datesignatexte"?: string
|
||||
"@id"?: string
|
||||
"@naturetexte"?: LegiTexteVersionLienNature
|
||||
"@num"?: string
|
||||
"@nortexte"?: string
|
||||
"@numtexte"?: string
|
||||
"@sens": Sens
|
||||
"@typelien": LegiTexteVersionLienType
|
||||
}
|
||||
|
||||
export type LegiTexteVersionLienNature =
|
||||
(typeof allLegiTexteVersionLienNatures)[number]
|
||||
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
export type Sens = (typeof allSens)[number]
|
||||
|
||||
export const allSens = ["cible", "source"] as const
|
||||
|
||||
export interface ArticleLienDb {
|
||||
article_id: string
|
||||
cible: boolean
|
||||
id: string
|
||||
typelien: string
|
||||
}
|
||||
|
||||
export interface TexteVersionLienDb {
|
||||
texte_version_id: string
|
||||
cible: boolean
|
||||
id: string
|
||||
typelien: string
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { iterArrayOrSingleton } from "@tricoteuses/explorer-tools"
|
||||
import objectHash from "object-hash"
|
||||
|
||||
import type { Aggregate, Follow } from "$lib/aggregates"
|
||||
import {
|
||||
|
@ -12,14 +13,17 @@ import {
|
|||
type Textelr,
|
||||
type TexteVersion,
|
||||
} from "$lib/legal"
|
||||
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
|
||||
import { db } from "$lib/server/databases"
|
||||
|
||||
export class Aggregator {
|
||||
article: { [id: string]: Article | JorfArticle | LegiArticle } = {}
|
||||
follow: Set<Follow>
|
||||
article_lien: { [hash: string]: ArticleLienDb } = {}
|
||||
requestedTypeAndIds: Set<TypeAndId> = new Set()
|
||||
section_ta: { [id: string]: SectionTa } = {}
|
||||
texte_version: { [id: string]: TexteVersion } = {}
|
||||
texte_version_lien: { [hash: string]: TexteVersionLienDb } = {}
|
||||
textekali: { [id: string]: Textekali } = {}
|
||||
textelr: { [id: string]: Textelr } = {}
|
||||
visitedTypeAndIds: Set<TypeAndId> = new Set()
|
||||
|
@ -31,6 +35,7 @@ export class Aggregator {
|
|||
addArticle(article: Article | JorfArticle | LegiArticle): void {
|
||||
const id = article.META.META_COMMUN.ID
|
||||
this.article[id] = article
|
||||
this.visitedTypeAndIds.add(["article", id])
|
||||
|
||||
if (this.follow.has("LIENS.LIEN[@sens=cible,@typelien=CREATION].@id")) {
|
||||
for (const lien of iterArrayOrSingleton(
|
||||
|
@ -47,9 +52,17 @@ export class Aggregator {
|
|||
}
|
||||
}
|
||||
|
||||
addArticleLienDb(lien: ArticleLienDb): void {
|
||||
this.article_lien[objectHash(lien)] = lien
|
||||
|
||||
this.requestTypeAndId(["article", lien.article_id])
|
||||
this.requestId(lien.id)
|
||||
}
|
||||
|
||||
addSectionTa(sectionTa: SectionTa): void {
|
||||
const id = sectionTa.ID
|
||||
this.section_ta[id] = sectionTa
|
||||
this.visitedTypeAndIds.add(["section_ta", id])
|
||||
|
||||
if (this.follow.has("STRUCTURE_TA.LIEN_ART.@id")) {
|
||||
for (const lien of iterArrayOrSingleton(
|
||||
|
@ -71,6 +84,7 @@ export class Aggregator {
|
|||
addTextekali(textekali: Textekali): void {
|
||||
const id = textekali.META.META_COMMUN.ID
|
||||
this.textekali[id] = textekali
|
||||
this.visitedTypeAndIds.add(["textekali", id])
|
||||
|
||||
if (this.follow.has("STRUCT.LIEN_ART.@id")) {
|
||||
for (const lien of iterArrayOrSingleton(textekali.STRUCT?.LIEN_ART)) {
|
||||
|
@ -90,6 +104,7 @@ export class Aggregator {
|
|||
addTextelr(textelr: Textelr): void {
|
||||
const id = textelr.META.META_COMMUN.ID
|
||||
this.textelr[id] = textelr
|
||||
this.visitedTypeAndIds.add(["textelr", id])
|
||||
|
||||
if (this.follow.has("STRUCT.LIEN_ART.@id")) {
|
||||
for (const lien of iterArrayOrSingleton(textelr.STRUCT?.LIEN_ART)) {
|
||||
|
@ -109,6 +124,7 @@ export class Aggregator {
|
|||
addTexteVersion(texteVersion: TexteVersion): void {
|
||||
const id = texteVersion.META.META_COMMUN.ID
|
||||
this.texte_version[id] = texteVersion
|
||||
this.visitedTypeAndIds.add(["texte_version", id])
|
||||
|
||||
if (this.follow.has("TEXTEKALI")) {
|
||||
this.requestTypeAndId(["textekali", id])
|
||||
|
@ -118,10 +134,11 @@ export class Aggregator {
|
|||
}
|
||||
}
|
||||
|
||||
addToVisitedTypeAndIds(typeAndIds: TypeAndId[]) {
|
||||
for (const typeAndId of typeAndIds) {
|
||||
this.visitedTypeAndIds.add(typeAndId)
|
||||
}
|
||||
addTexteVersionLienDb(lien: TexteVersionLienDb): void {
|
||||
this.texte_version_lien[objectHash(lien)] = lien
|
||||
|
||||
this.requestTypeAndId(["texte_version", lien.texte_version_id])
|
||||
this.requestId(lien.id)
|
||||
}
|
||||
|
||||
deleteFromRequestedTypeAndIds(typeAndIds: TypeAndId[]) {
|
||||
|
@ -144,7 +161,6 @@ export class Aggregator {
|
|||
`) {
|
||||
this.addArticle(data)
|
||||
}
|
||||
this.addToVisitedTypeAndIds(articleTypeAndIds)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +176,21 @@ export class Aggregator {
|
|||
`) {
|
||||
this.addSectionTa(data)
|
||||
}
|
||||
this.addToVisitedTypeAndIds(sectionTaTypeAndIds)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const texteVersionTypeAndIds = [...this.requestedTypeAndIds].filter(
|
||||
([type]) => type === "texte_version",
|
||||
)
|
||||
if (texteVersionTypeAndIds.length > 0) {
|
||||
this.deleteFromRequestedTypeAndIds(texteVersionTypeAndIds)
|
||||
for (const { data } of await db<{ id: string; data: TexteVersion }[]>`
|
||||
SELECT id, data FROM texte_version
|
||||
WHERE id IN ${db(texteVersionTypeAndIds.map(([, id]) => id))}
|
||||
`) {
|
||||
this.addTexteVersion(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +206,6 @@ export class Aggregator {
|
|||
`) {
|
||||
this.addTextekali(data)
|
||||
}
|
||||
this.addToVisitedTypeAndIds(textekaliTypeAndIds)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +221,6 @@ export class Aggregator {
|
|||
`) {
|
||||
this.addTextelr(data)
|
||||
}
|
||||
this.addToVisitedTypeAndIds(textelrTypeAndIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,12 +244,18 @@ export class Aggregator {
|
|||
if (Object.keys(this.article).length > 0) {
|
||||
json.article = this.article
|
||||
}
|
||||
if (Object.keys(this.article_lien).length > 0) {
|
||||
json.article_lien = Object.values(this.article_lien)
|
||||
}
|
||||
if (Object.keys(this.section_ta).length > 0) {
|
||||
json.section_ta = this.section_ta
|
||||
}
|
||||
if (Object.keys(this.texte_version).length > 0) {
|
||||
json.texte_version = this.texte_version
|
||||
}
|
||||
if (Object.keys(this.texte_version_lien).length > 0) {
|
||||
json.texte_version_lien = Object.values(this.texte_version_lien)
|
||||
}
|
||||
if (Object.keys(this.textekali).length > 0) {
|
||||
json.textekali = this.textekali
|
||||
}
|
||||
|
|
|
@ -111,12 +111,23 @@ export async function configureDatabase() {
|
|||
// await `
|
||||
// CREATE TABLE IF NOT EXISTS article_autocompletions (
|
||||
// autocompletion text NOT NULL,
|
||||
// id bigint NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
// id char(20) NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
// weight int NOT NULL,
|
||||
// PRIMARY KEY (id, autocompletion)
|
||||
// )
|
||||
// `
|
||||
|
||||
// Table: article_lien
|
||||
await db`
|
||||
CREATE TABLE IF NOT EXISTS article_lien (
|
||||
article_id char(20) NOT NULL REFERENCES article(id) ON DELETE CASCADE,
|
||||
cible boolean,
|
||||
id char(20) NOT NULL,
|
||||
typelien text NOT NULL,
|
||||
PRIMARY KEY (article_id, cible, typelien, id)
|
||||
)
|
||||
`
|
||||
|
||||
// Table: dossier_legislatif
|
||||
await db`
|
||||
CREATE TABLE IF NOT EXISTS dossier_legislatif (
|
||||
|
@ -180,6 +191,17 @@ export async function configureDatabase() {
|
|||
)
|
||||
`
|
||||
|
||||
// Table: texte_version_lien
|
||||
await db`
|
||||
CREATE TABLE IF NOT EXISTS texte_version_lien (
|
||||
texte_version_id char(20) NOT NULL REFERENCES texte_version(id) ON DELETE CASCADE,
|
||||
cible boolean,
|
||||
id char(20) NOT NULL,
|
||||
typelien text NOT NULL,
|
||||
PRIMARY KEY (texte_version_id, cible, typelien, id)
|
||||
)
|
||||
`
|
||||
|
||||
// Table: textekali
|
||||
await db`
|
||||
CREATE TABLE IF NOT EXISTS textekali (
|
||||
|
@ -274,6 +296,17 @@ export async function configureDatabase() {
|
|||
|
||||
// Add indexes once every table and column exists.
|
||||
|
||||
// await db`
|
||||
// CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx
|
||||
// ON article_autocompletions
|
||||
// USING GIST (autocompletion gist_trgm_ops)
|
||||
// `
|
||||
|
||||
await db`
|
||||
CREATE INDEX IF NOT EXISTS article_lien_reverse_key
|
||||
ON article_lien (id, cible, typelien)
|
||||
`
|
||||
|
||||
await db`
|
||||
CREATE INDEX IF NOT EXISTS dossier_legislatif_jorf_texte_principal_id_key
|
||||
ON dossier_legislatif (jorf_texte_principal_id)
|
||||
|
@ -289,11 +322,10 @@ export async function configureDatabase() {
|
|||
ON texte_version (nature_et_num)
|
||||
`
|
||||
|
||||
// await db`
|
||||
// CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx
|
||||
// ON article_autocompletions
|
||||
// USING GIST (autocompletion gist_trgm_ops)
|
||||
// `
|
||||
await db`
|
||||
CREATE INDEX IF NOT EXISTS texte_version_lien_reverse_key
|
||||
ON texte_version_lien (id, cible, typelien)
|
||||
`
|
||||
|
||||
// Add comments once every table and column exists.
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const db = postgres({
|
|||
port: config.db.port,
|
||||
user: config.db.user,
|
||||
})
|
||||
export const versionNumber = 8
|
||||
export const versionNumber = 9
|
||||
|
||||
/// Check that assemblee database exists and is up to date.
|
||||
export async function checkAssembleeDb(): Promise<void> {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { Aggregator } from "$lib/server/aggregates"
|
|||
import { db } from "$lib/server/databases"
|
||||
|
||||
import type { RequestHandler } from "./$types"
|
||||
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
|
||||
|
||||
function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
|
||||
if (query == null) {
|
||||
|
@ -36,18 +37,20 @@ function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
|
|||
const remainingKeys = new Set(Object.keys(data))
|
||||
|
||||
auditFollowQuery(audit, data, errors, remainingKeys)
|
||||
audit.attribute(
|
||||
data,
|
||||
"latest",
|
||||
true,
|
||||
errors,
|
||||
remainingKeys,
|
||||
auditSingleton(
|
||||
auditTrimString,
|
||||
auditStringToBoolean,
|
||||
auditSetNullish(false),
|
||||
),
|
||||
)
|
||||
for (const key of ["latest", "liens_entrants"]) {
|
||||
audit.attribute(
|
||||
data,
|
||||
key,
|
||||
true,
|
||||
errors,
|
||||
remainingKeys,
|
||||
auditSingleton(
|
||||
auditTrimString,
|
||||
auditStringToBoolean,
|
||||
auditSetNullish(false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
|
||||
}
|
||||
|
@ -58,6 +61,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
{
|
||||
follow: Set<Follow>
|
||||
latest: boolean
|
||||
liens_entrants: boolean
|
||||
},
|
||||
unknown,
|
||||
]
|
||||
|
@ -71,7 +75,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
)
|
||||
error(400, JSON.stringify(queryError, null, 2))
|
||||
}
|
||||
const { follow, latest } = query
|
||||
const { follow, latest, liens_entrants } = query
|
||||
|
||||
let article = (
|
||||
await db<{ data: JorfArticle | LegiArticle }[]>`
|
||||
|
@ -106,6 +110,24 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
|
||||
const aggregator = new Aggregator(follow)
|
||||
aggregator.addArticle(article)
|
||||
|
||||
if (liens_entrants) {
|
||||
for (const lien of await db<ArticleLienDb[]>`
|
||||
SELECT *
|
||||
FROM article_lien
|
||||
WHERE id = ${id}
|
||||
`) {
|
||||
aggregator.addArticleLienDb(lien)
|
||||
}
|
||||
for (const lien of await db<TexteVersionLienDb[]>`
|
||||
SELECT *
|
||||
FROM texte_version_lien
|
||||
WHERE id = ${id}
|
||||
`) {
|
||||
aggregator.addTexteVersionLienDb(lien)
|
||||
}
|
||||
}
|
||||
|
||||
await aggregator.getAll()
|
||||
|
||||
return new Response(
|
||||
|
@ -114,6 +136,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
...aggregator.toJson(),
|
||||
follow: [...follow],
|
||||
id,
|
||||
liens_entrants,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
|
||||
import {
|
||||
type Audit,
|
||||
auditSetNullish,
|
||||
cleanAudit,
|
||||
auditSingleton,
|
||||
auditTrimString,
|
||||
auditStringToBoolean,
|
||||
} from "@auditors/core"
|
||||
import { error } from "@sveltejs/kit"
|
||||
|
||||
import { auditFollowQuery } from "$lib/auditors/queries"
|
||||
|
||||
import type { Follow } from "$lib/aggregates"
|
||||
import type { TexteVersion } from "$lib/legal"
|
||||
import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared"
|
||||
import { Aggregator } from "$lib/server/aggregates"
|
||||
import { db } from "$lib/server/databases"
|
||||
|
||||
|
@ -30,14 +38,28 @@ function auditQuery(audit: Audit, query: URLSearchParams): [unknown, unknown] {
|
|||
const remainingKeys = new Set(Object.keys(data))
|
||||
|
||||
auditFollowQuery(audit, data, errors, remainingKeys)
|
||||
audit.attribute(
|
||||
data,
|
||||
"liens_entrants",
|
||||
true,
|
||||
errors,
|
||||
remainingKeys,
|
||||
auditSingleton(
|
||||
auditTrimString,
|
||||
auditStringToBoolean,
|
||||
auditSetNullish(false),
|
||||
),
|
||||
)
|
||||
|
||||
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
const id = params.id
|
||||
const [query, queryError] = auditQuery(cleanAudit, url.searchParams) as [
|
||||
{
|
||||
follow: Set<Follow>
|
||||
liens_entrants: boolean
|
||||
},
|
||||
unknown,
|
||||
]
|
||||
|
@ -51,19 +73,37 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
)
|
||||
error(400, JSON.stringify(queryError, null, 2))
|
||||
}
|
||||
const { follow } = query
|
||||
const { follow, liens_entrants } = query
|
||||
const texteVersion = (
|
||||
await db<{ data: TexteVersion }[]>`
|
||||
SELECT data FROM texte_version
|
||||
WHERE ID = ${params.id}
|
||||
WHERE ID = ${id}
|
||||
`
|
||||
).map(({ data }) => data)[0]
|
||||
if (texteVersion === undefined) {
|
||||
error(404, `TEXTE_VERSION ${params.id} non trouvé`)
|
||||
error(404, `TEXTE_VERSION ${id} non trouvé`)
|
||||
}
|
||||
|
||||
const aggregator = new Aggregator(follow)
|
||||
aggregator.addTexteVersion(texteVersion)
|
||||
|
||||
if (liens_entrants) {
|
||||
for (const lien of await db<ArticleLienDb[]>`
|
||||
SELECT *
|
||||
FROM article_lien
|
||||
WHERE id = ${id}
|
||||
`) {
|
||||
aggregator.addArticleLienDb(lien)
|
||||
}
|
||||
for (const lien of await db<TexteVersionLienDb[]>`
|
||||
SELECT *
|
||||
FROM texte_version_lien
|
||||
WHERE id = ${id}
|
||||
`) {
|
||||
aggregator.addTexteVersionLienDb(lien)
|
||||
}
|
||||
}
|
||||
|
||||
await aggregator.getAll()
|
||||
|
||||
return new Response(
|
||||
|
@ -71,7 +111,8 @@ export const GET: RequestHandler = async ({ params, url }) => {
|
|||
{
|
||||
...aggregator.toJson(),
|
||||
follow: [...follow],
|
||||
id: params.id,
|
||||
id,
|
||||
liens_entrants,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
|
|
|
@ -57,7 +57,10 @@ export const load: PageLoad = async ({ fetch, url }) => {
|
|||
follow = new Set(["LIENS.LIEN[@sens=cible,@typelien=CREATION].@id"])
|
||||
}
|
||||
|
||||
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
|
||||
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, {
|
||||
follow,
|
||||
liens_entrants: true,
|
||||
})
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: { Accept: "application/json" },
|
||||
})
|
||||
|
|
|
@ -64,7 +64,10 @@ export const load: PageLoad = async ({ fetch, url }) => {
|
|||
])
|
||||
}
|
||||
|
||||
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
|
||||
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, {
|
||||
follow,
|
||||
liens_entrants: true,
|
||||
})
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: { Accept: "application/json" },
|
||||
})
|
||||
|
|
180
src/scripts/index_liens.ts
Normal file
180
src/scripts/index_liens.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import objectHash from "object-hash"
|
||||
import sade from "sade"
|
||||
|
||||
import { db } from "$lib/server/databases"
|
||||
import type {
|
||||
LegiArticleLien,
|
||||
LegiArticleLienType,
|
||||
LegiTexteVersionLien,
|
||||
LegiTexteVersionLienType,
|
||||
} from "$lib/legal/legi"
|
||||
import type {
|
||||
JorfTexteVersionLien,
|
||||
JorfTexteVersionLienType,
|
||||
} from "$lib/legal/jorf"
|
||||
|
||||
async function indexLiens(): Promise<void> {
|
||||
console.log("Indexing articles links…")
|
||||
const articlesAvecLiensCursor = db<
|
||||
{
|
||||
id: string
|
||||
liens: Array<LegiArticleLien>
|
||||
}[]
|
||||
>`
|
||||
SELECT
|
||||
id,
|
||||
data -> 'LIENS' -> 'LIEN' AS liens
|
||||
FROM article
|
||||
WHERE
|
||||
id LIKE 'LEGIART%'
|
||||
AND data -> 'LIENS' -> 'LIEN' IS NOT NULL
|
||||
`.cursor(100)
|
||||
for await (const rows of articlesAvecLiensCursor) {
|
||||
for (const { id, liens } of rows) {
|
||||
const existingLienByHash = new Map(
|
||||
(
|
||||
await db<
|
||||
{
|
||||
cible: boolean
|
||||
id: string
|
||||
typelien: LegiArticleLienType
|
||||
}[]
|
||||
>`
|
||||
SELECT id, cible, typelien
|
||||
FROM article_lien
|
||||
WHERE article_id = ${id}
|
||||
`
|
||||
).map((lien) => [objectHash(lien), lien]),
|
||||
)
|
||||
|
||||
for (const lien of liens) {
|
||||
if (lien["@id"] === undefined) {
|
||||
continue
|
||||
}
|
||||
existingLienByHash.delete(
|
||||
objectHash({
|
||||
cible: lien["@sens"] === "cible",
|
||||
id: lien["@id"],
|
||||
typelien: lien["@typelien"],
|
||||
}),
|
||||
)
|
||||
await db`
|
||||
INSERT INTO article_lien (
|
||||
article_id,
|
||||
cible,
|
||||
id,
|
||||
typelien
|
||||
) VALUES (
|
||||
${id},
|
||||
${lien["@sens"] === "cible"},
|
||||
${lien["@id"]},
|
||||
${lien["@typelien"]}
|
||||
)
|
||||
ON CONFLICT
|
||||
DO NOTHING
|
||||
`
|
||||
}
|
||||
|
||||
for (const {
|
||||
cible,
|
||||
id: linkedId,
|
||||
typelien,
|
||||
} of existingLienByHash.values()) {
|
||||
await db`
|
||||
DELETE FROM article_lien
|
||||
WHERE
|
||||
article_id = ${id}
|
||||
AND cible = ${cible}
|
||||
AND id = ${linkedId}
|
||||
AND typelien = ${typelien}
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Indexing textes versions links…")
|
||||
const textesVersionsAvecLiensCursor = db<
|
||||
{
|
||||
id: string
|
||||
liens: Array<JorfTexteVersionLien | LegiTexteVersionLien>
|
||||
}[]
|
||||
>`
|
||||
SELECT
|
||||
id,
|
||||
data -> 'META' -> 'META_SPEC' -> 'META_TEXTE_VERSION' -> 'LIENS' -> 'LIEN' AS liens
|
||||
FROM texte_version
|
||||
WHERE
|
||||
(id LIKE 'JORFTEXT%' OR id LIKE 'LEGITEXT%')
|
||||
AND data -> 'META' -> 'META_SPEC' -> 'META_TEXTE_VERSION' -> 'LIENS' -> 'LIEN' IS NOT NULL
|
||||
`.cursor(100)
|
||||
for await (const rows of textesVersionsAvecLiensCursor) {
|
||||
for (const { id, liens } of rows) {
|
||||
const existingLienByHash = new Map(
|
||||
(
|
||||
await db<
|
||||
{
|
||||
cible: boolean
|
||||
id: string
|
||||
typelien: JorfTexteVersionLienType | LegiTexteVersionLienType
|
||||
}[]
|
||||
>`
|
||||
SELECT id, cible, typelien
|
||||
FROM texte_version_lien
|
||||
WHERE texte_version_id = ${id}
|
||||
`
|
||||
).map((lien) => [objectHash(lien), lien]),
|
||||
)
|
||||
|
||||
for (const lien of liens) {
|
||||
if (lien["@id"] === undefined) {
|
||||
continue
|
||||
}
|
||||
existingLienByHash.delete(
|
||||
objectHash({
|
||||
cible: lien["@sens"] === "cible",
|
||||
id: lien["@id"],
|
||||
typelien: lien["@typelien"],
|
||||
}),
|
||||
)
|
||||
await db`
|
||||
INSERT INTO texte_version_lien (
|
||||
texte_version_id,
|
||||
cible,
|
||||
id,
|
||||
typelien
|
||||
) VALUES (
|
||||
${id},
|
||||
${lien["@sens"] === "cible"},
|
||||
${lien["@id"]},
|
||||
${lien["@typelien"]}
|
||||
)
|
||||
ON CONFLICT
|
||||
DO NOTHING
|
||||
`
|
||||
}
|
||||
|
||||
for (const {
|
||||
cible,
|
||||
id: linkedId,
|
||||
typelien,
|
||||
} of existingLienByHash.values()) {
|
||||
await db`
|
||||
DELETE FROM texte_version_lien
|
||||
WHERE
|
||||
texte_version_id_id = ${id}
|
||||
AND cible = ${cible}
|
||||
AND id = ${linkedId}
|
||||
AND typelien = ${typelien}
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sade("index_liens", true)
|
||||
.describe("Index links of LEGI articles and LEGI & JORF textes versions")
|
||||
.action(async () => {
|
||||
await indexLiens()
|
||||
process.exit(0)
|
||||
})
|
||||
.parse(process.argv)
|
230
src/scripts/test_dole.ts
Normal file
230
src/scripts/test_dole.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
import assert from "assert"
|
||||
import fs from "fs-extra"
|
||||
import path from "path"
|
||||
import sade from "sade"
|
||||
|
||||
import type {
|
||||
DossierLegislatif,
|
||||
JorfTexteVersion,
|
||||
JorfTextelr,
|
||||
} from "$lib/legal"
|
||||
import { parseDossierLegislatif } from "$lib/parsers"
|
||||
import { db } from "$lib/server/databases"
|
||||
import { walkDir } from "$lib/server/file_systems"
|
||||
|
||||
async function testDole(dilaDir: string): Promise<void> {
|
||||
const dataDir = path.join(dilaDir, "dole")
|
||||
assert(await fs.pathExists(dataDir))
|
||||
iterXmlFiles: for (const relativeSplitPath of walkDir(dataDir)) {
|
||||
const relativePath = path.join(...relativeSplitPath)
|
||||
const filePath = path.join(dataDir, relativePath)
|
||||
if (!filePath.endsWith(".xml")) {
|
||||
console.info(`Skipping non XML file at ${filePath}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const xmlString: string = await fs.readFile(filePath, {
|
||||
encoding: "utf8",
|
||||
})
|
||||
const dossierLegislatif = parseDossierLegislatif(filePath, xmlString)
|
||||
if (dossierLegislatif === undefined) {
|
||||
break iterXmlFiles
|
||||
}
|
||||
|
||||
const metaDossierLegislatif = dossierLegislatif.META.META_DOSSIER_LEGISLATIF
|
||||
const textelrs: JorfTextelr[] = []
|
||||
const texteVersions: JorfTexteVersion[] = []
|
||||
for (const idTexteName of [
|
||||
"ID_TEXTE_1",
|
||||
"ID_TEXTE_2",
|
||||
"ID_TEXTE_3",
|
||||
"ID_TEXTE_4",
|
||||
"ID_TEXTE_5",
|
||||
] as Array<keyof DossierLegislatif["META"]["META_DOSSIER_LEGISLATIF"]>) {
|
||||
const idTexte = metaDossierLegislatif[idTexteName] as string | undefined
|
||||
if (idTexte === undefined) {
|
||||
continue
|
||||
}
|
||||
assert(idTexte.startsWith("JORFTEXT"))
|
||||
|
||||
const textelr = (
|
||||
await db<{ data: JorfTextelr }[]>`
|
||||
SELECT data
|
||||
FROM textelr
|
||||
WHERE id = ${idTexte}
|
||||
`
|
||||
).map(({ data }) => data)[0]
|
||||
if (textelr === undefined) {
|
||||
console.warn(
|
||||
`In dossier législatif ${dossierLegislatif.META.META_COMMUN.ID}, field META.META_DOSSIER_LEGISLATIF.${idTexteName} has value ${idTexte} that is not a valid JORF reference of a textelr`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
const texteVersion = (
|
||||
await db<{ data: JorfTexteVersion }[]>`
|
||||
SELECT data
|
||||
FROM texte_version
|
||||
WHERE id = ${idTexte}
|
||||
`
|
||||
).map(({ data }) => data)[0]
|
||||
if (texteVersion === undefined) {
|
||||
console.warn(
|
||||
`In dossier législatif ${dossierLegislatif.META.META_COMMUN.ID}, field META.META_DOSSIER_LEGISLATIF.${idTexteName} has value ${idTexte} that is not a valid JORF reference of a texte_version`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
textelrs.push(textelr)
|
||||
texteVersions.push(texteVersion)
|
||||
}
|
||||
const natures = textelrs.map((textelr) => textelr.META.META_COMMUN.NATURE)
|
||||
for (const expectedNatures of [
|
||||
[],
|
||||
["DECISION", "DECISION"],
|
||||
["DIRECTIVE_EURO"],
|
||||
["LOI"],
|
||||
[
|
||||
"LOI",
|
||||
undefined, // Décision
|
||||
],
|
||||
[
|
||||
"LOI",
|
||||
undefined, // Décision
|
||||
"LOI",
|
||||
],
|
||||
["LOI", "AVIS"],
|
||||
["LOI", "DECISION"],
|
||||
["LOI", "DECISION", "ACCORD"],
|
||||
["LOI", "DECISION", "AVIS", "LOI"],
|
||||
["LOI", "DECISION", "DECISION"],
|
||||
["LOI", "DECISION", "DECISION", "LOI"],
|
||||
["LOI", "DECISION", "DIRECTIVE_EURO"],
|
||||
["LOI", "DECISION", "LOI"],
|
||||
["LOI", "DECISION", "LOI", "DECISION"],
|
||||
["LOI", "DECISION", "LOI", "LOI"],
|
||||
["LOI", "DECISION", "RAPPORT", "ORDONNANCE"],
|
||||
["LOI", "DECRET"],
|
||||
["LOI", "DIRECTIVE_EURO"],
|
||||
["LOI", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
|
||||
["LOI", "LOI"],
|
||||
["LOI", "LOI", "DECISION"],
|
||||
["LOI", "LOI", "DECISION", "DECISION"],
|
||||
["LOI", "LOI", "DECISION", "DIRECTIVE_EURO"],
|
||||
["LOI", "LOI", "LOI"],
|
||||
["LOI", "RAPPORT", "LOI"],
|
||||
["LOI_CONSTIT"],
|
||||
["LOI_ORGANIQUE"],
|
||||
["LOI_ORGANIQUE", "DECISION"],
|
||||
["LOI_ORGANIQUE", "LOI_ORGANIQUE"],
|
||||
["LOI_ORGANIQUE", "LOI_ORGANIQUE", "DECISION"],
|
||||
["ORDONNANCE"],
|
||||
// [
|
||||
// "ORDONNANCE",
|
||||
// undefined, // Rapport au Président de la République relatif à l'ordonnance
|
||||
// ],
|
||||
// [
|
||||
// "ORDONNANCE",
|
||||
// undefined, // Rapport au Président de la République relatif à l'ordonnance
|
||||
// "LOI",
|
||||
// ],
|
||||
// // ["ORDONNANCE", "LOI"],
|
||||
// ["ORDONNANCE", "LOI", "DECISION"],
|
||||
// ["ORDONNANCE", "ORDONNANCE", "LOI"],
|
||||
// ["ORDONNANCE", "ORDONNANCE", "ORDONNANCE", "LOI"],
|
||||
// ["ORDONNANCE", "ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
|
||||
// ["ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
|
||||
["ORDONNANCE", "ORDONNANCE", "RAPPORT"],
|
||||
// ["ORDONNANCE", "ORDONNANCE", "RAPPORT", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT"],
|
||||
["ORDONNANCE", "RAPPORT", "DECISION"],
|
||||
// ["ORDONNANCE", "RAPPORT", "DECISION", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "DECRET"],
|
||||
// ["ORDONNANCE", "RAPPORT", "DECRET", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "DECRET", "ORDONNANCE"],
|
||||
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO"],
|
||||
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
|
||||
[
|
||||
"ORDONNANCE",
|
||||
"RAPPORT",
|
||||
"DIRECTIVE_EURO",
|
||||
"DIRECTIVE_EURO",
|
||||
"DIRECTIVE_EURO",
|
||||
],
|
||||
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "DIRECTIVE_EURO", "LOI"],
|
||||
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI"],
|
||||
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI", "DIRECTIVE_EURO"],
|
||||
// ["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "LOI", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "DIRECTIVE_EURO", "ORDONNANCE"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI", "DECISION"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI", "DIRECTIVE_EURO"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI", "DIRECTIVE_EURO", "DIRECTIVE_EURO"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI", "LOI"],
|
||||
// ["ORDONNANCE", "RAPPORT", "LOI", "LOI", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "ORDONNANCE"],
|
||||
[
|
||||
"ORDONNANCE",
|
||||
"RAPPORT",
|
||||
"ORDONNANCE",
|
||||
undefined, // Rapport au Président de la République relatif à l'ordonnance
|
||||
],
|
||||
// [
|
||||
// "ORDONNANCE",
|
||||
// "RAPPORT",
|
||||
// "ORDONNANCE",
|
||||
// undefined, // Rapport au Président de la République relatif à l'ordonnance
|
||||
// "LOI",
|
||||
// ],
|
||||
[
|
||||
"ORDONNANCE",
|
||||
"RAPPORT",
|
||||
"ORDONNANCE",
|
||||
"DIRECTIVE_EURO",
|
||||
"DIRECTIVE_EURO",
|
||||
],
|
||||
// ["ORDONNANCE", "RAPPORT", "ORDONNANCE", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE"],
|
||||
// ["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE", "LOI"],
|
||||
["ORDONNANCE", "RAPPORT", "ORDONNANCE", "ORDONNANCE", "ORDONNANCE"],
|
||||
["ORDONNANCE", "RAPPORT", "RAPPORT"],
|
||||
// ["ORDONNANCE", "RAPPORT", "RAPPORT", "LOI"],
|
||||
["RAPPORT"],
|
||||
// ["RAPPORT", "ORDONNANCE", "LOI"],
|
||||
]) {
|
||||
if (
|
||||
natures.length === expectedNatures.length &&
|
||||
natures.every((nature, index) => nature === expectedNatures[index])
|
||||
) {
|
||||
continue iterXmlFiles
|
||||
}
|
||||
}
|
||||
// console.log(
|
||||
// "Dossier",
|
||||
// dossierLegislatif.META.META_COMMUN.ID,
|
||||
// dossierLegislatif.META.META_DOSSIER_LEGISLATIF.TITRE,
|
||||
// )
|
||||
for (const [index, textelr] of textelrs.entries()) {
|
||||
const texteVersion = texteVersions[index]
|
||||
if (textelr.META.META_COMMUN.NATURE === "LOI") {
|
||||
console.log(
|
||||
"Dossier",
|
||||
dossierLegislatif.META.META_COMMUN.ID,
|
||||
dossierLegislatif.META.META_DOSSIER_LEGISLATIF.TITRE,
|
||||
)
|
||||
console.log(
|
||||
" ",
|
||||
textelr.META.META_COMMUN.ID,
|
||||
textelr.META.META_COMMUN.NATURE,
|
||||
texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sade("test_dole <dilaDir>", true)
|
||||
.describe("Test Dila's DOLE database")
|
||||
.action(async (dilaDir) => {
|
||||
await testDole(dilaDir)
|
||||
process.exit(0)
|
||||
})
|
||||
.parse(process.argv)
|
Loading…
Add table
Reference in a new issue