Add JORF data.

This commit is contained in:
Emmanuel 2022-08-13 12:18:09 +02:00
parent 7602ac2852
commit 6842108f49
28 changed files with 1334 additions and 443 deletions

99
src/lib/auditors/data.ts Normal file
View file

@ -0,0 +1,99 @@
import {
type Audit,
auditChain,
auditRequire,
auditString,
auditTest,
auditOptions,
} from "@auditors/core"
export const auditId = auditChain(
auditString,
auditTest((id) => id.length === 20, "Invalid length for ID"),
)
export function auditVersions(
audit: Audit,
dataUnknown: unknown,
): [unknown, unknown] {
if (dataUnknown == null) {
return [dataUnknown, null]
}
if (typeof dataUnknown !== "object") {
return audit.unexpectedType(dataUnknown, "object")
}
const data = { ...dataUnknown }
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
audit.attribute(
data,
"VERSION",
true,
errors,
remainingKeys,
auditVersionsVersion,
auditRequire,
)
return audit.reduceRemaining(data, errors, remainingKeys)
}
export function auditVersionsVersion(
audit: Audit,
dataUnknown: unknown,
): [unknown, unknown] {
if (dataUnknown == null) {
return [dataUnknown, null]
}
if (typeof dataUnknown !== "object") {
return audit.unexpectedType(dataUnknown, "object")
}
const data = { ...dataUnknown }
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
audit.attribute(
data,
"@id",
true,
errors,
remainingKeys,
auditId,
auditRequire,
)
audit.attribute(
data,
"@fin",
true,
errors,
remainingKeys,
auditString,
auditOptions(["2999-01-01"]),
auditRequire,
)
audit.attribute(
data,
"@etat",
true,
errors,
remainingKeys,
auditString,
auditOptions([""]),
auditRequire,
)
audit.attribute(
data,
"@debut",
true,
errors,
remainingKeys,
auditString,
auditOptions(["2999-01-01"]),
auditRequire,
)
return audit.reduceRemaining(data, errors, remainingKeys)
}

View file

@ -11,22 +11,11 @@ export interface Article {
} }
} }
} }
CONTEXTE: { CONTEXTE: Contexte
TEXTE: {
TITRE_TXT: TitreTexte | TitreTexte[]
}
}
VERSIONS: { VERSIONS: {
VERSION: Array<{ VERSION: Array<{
"@etat": Etat "@etat": Etat
LIEN_ART: { LIEN_ART: LienArt
"@id": string
"@fin": string
"@num": string
"@etat": Etat
"@debut": string
"@origine": "LEGI"
}
}> }>
} }
BLOC_TEXTUEL: { BLOC_TEXTUEL: {
@ -34,36 +23,65 @@ export interface Article {
} }
} }
export type EliId = string export interface Contexte {
TEXTE: {
export interface EliVersions {} TITRE_TXT: TitreTxt | TitreTxt[]
}
}
export type Etat = "MODIFIE" | "VIGUEUR" export type Etat = "MODIFIE" | "VIGUEUR"
export type LegiObject =
| Article
| EliId
| EliVersions
| Section
| Struct
| TexteVersion
export type LegiObjectType = export interface Jo {
META: {
META_SPEC: {
META_CONTENEUR: {
NUM: string
TITRE: string
DATE_PUBLI: string
}
}
META_COMMUN: MetaCommun
}
}
export type LegalObject =
| Article
| Jo
| SectionTa
| Textelr
| TexteVersion
| Versions
export type LegalObjectType =
| "article" | "article"
| "eli_id" | "id"
| "eli_versions" | "jo"
| "section" | "section_ta"
| "struct" | "texte_version"
| "texte" | "textelr"
| "versions"
export interface LienArt {
"@id": string
"@fin": string
"@num": string
"@etat": Etat
"@debut": string
"@origine": "LEGI"
}
export interface MetaCommun { export interface MetaCommun {
ID: string ID: string
} }
export interface Section { export interface SectionTa {
ID: string ID: string
CONTEXTE: Contexte
TITRE_TA: string
STRUCTURE_TA: { LIEN_ART?: LienArt | LienArt[] }
} }
export interface Struct { export interface Textelr {
META: { META: {
META_COMMUN: MetaCommun META_COMMUN: MetaCommun
} }
@ -71,58 +89,102 @@ export interface Struct {
export interface TexteVersion { export interface TexteVersion {
META: { META: {
META_SPEC: {
META_TEXTE_VERSION: {
TITRE: string
TITREFULL: string
}
}
META_COMMUN: MetaCommun META_COMMUN: MetaCommun
} }
} }
export interface TitreTexte { export interface TitreTxt {
"@fin": string
"@debut": string
"@id_txt": string
"@c_titre_court": string
"#text": string "#text": string
} }
export function assertNeverLegiObjectType(type: never): never { export interface Versions {
throw `Unexpected type for LEGI object: ${type}` VERSION: {
"@id": string
"@fin": "2999-01-01"
"@etat": ""
"@debut": "2999-01-01"
}
} }
export function pathnameFromLegiObject( export interface XmlHeader {
type: LegiObjectType, "@encoding": "UTF-8"
object: LegiObject, "@version": "1.0"
}
export function assertNeverLegalObjectType(type: never): never {
throw `Unexpected type for legal object: ${type}`
}
export function bestItemForDate<T extends { "@debut": string; "@fin": string }>(
items: T | T[],
date: string,
): T | undefined {
if (!Array.isArray(items)) {
// Singleton
return items
}
for (const item of items) {
if (item["@debut"] <= date && date <= item["@fin"]) {
return item
}
}
return items[0]
}
export function pathnameFromLegalObject(
type: LegalObjectType,
object: LegalObject,
): string { ): string {
switch (type) { switch (type) {
case "article": case "article":
return `/articles/${(object as Article).META.META_COMMUN.ID}` return `/article/${(object as Article).META.META_COMMUN.ID}`
case "eli_id": case "id":
return `/eli/ids/TODO` return `/eli/ids/TODO`
case "eli_versions": case "jo":
return `/eli/ids/TODO` return `/jo/${(object as Jo).META.META_COMMUN.ID}`
case "section": case "section_ta":
return `/sections/${(object as Section).ID}` return `/section_ta/${(object as unknown as SectionTa).ID}`
case "struct": case "texte_version":
return `/structs/${(object as Struct).META.META_COMMUN.ID}` return `/texte_version/${(object as TexteVersion).META.META_COMMUN.ID}`
case "texte": case "textelr":
return `/textes/${(object as TexteVersion).META.META_COMMUN.ID}` return `/textelr/${(object as Textelr).META.META_COMMUN.ID}`
case "versions":
return `/eli/versions/TODO`
default: default:
assertNeverLegiObjectType(type) assertNeverLegalObjectType(type)
} }
} }
export function pathnameFromLegiObjectId( export function pathnameFromLegalObjectId(
type: LegiObjectType, type: LegalObjectType,
id: string, id: string,
): string { ): string {
switch (type) { switch (type) {
case "article": case "article":
return `/articles/${id}` return `/article/${id}`
case "eli_id": case "id":
return `/eli/ids/TODO` return `/eli/ids/TODO`
case "eli_versions": case "jo":
return `/eli/ids/TODO` return `/jo/${id}`
case "section": case "section_ta":
return `/sections/${id}` return `/section_ta/${id}`
case "struct": case "texte_version":
return `/structs/${id}` return `/texte_version/${id}`
case "texte": case "textelr":
return `/textes/${id}` return `/textelr/${id}`
case "versions":
return `/eli/versions/TODO`
default: default:
assertNeverLegiObjectType(type) assertNeverLegalObjectType(type)
} }
} }

View file

@ -0,0 +1,369 @@
import { auditChain, auditRequire, strictAudit } from "@auditors/core"
import assert from "assert"
import { XMLParser } from "fast-xml-parser"
import fs from "fs-extra"
import he from "he"
import path from "path"
import type { JSONValue } from "postgres"
import sade from "sade"
import { auditId, auditVersions } from "$lib/auditors/data"
import type {
Article,
Jo,
SectionTa,
Textelr,
TexteVersion,
Versions,
XmlHeader,
} from "$lib/data"
import { db } from "$lib/server/database"
import { walkDir } from "$lib/server/file_systems"
const xmlParser = new XMLParser({
attributeNamePrefix: "@",
ignoreAttributes: false,
stopNodes: ["ARTICLE.BLOC_TEXTUEL.CONTENU", "ARTICLE.SM.CONTENU"],
tagValueProcessor: (_tagName, tagValue) => he.decode(tagValue),
})
async function importJorf({ resume }: { resume?: string } = {}): Promise<void> {
let skip = resume !== undefined
const deleteRemainingIds = !skip
const articleRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM article
WHERE id LIKE 'JORF%'
`
).map(({ id }) => id),
)
const idRemainingElis = new Set(
(
await db<{ eli: string }[]>`
SELECT eli
FROM id
`
).map(({ eli }) => eli),
)
const joRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM jo
`
).map(({ id }) => id),
)
const sectionTaRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM section_ta
WHERE id LIKE 'JORF%'
`
).map(({ id }) => id),
)
const textelrRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM textelr
WHERE id LIKE 'JORF%'
`
).map(({ id }) => id),
)
const texteVersionRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM texte_version
WHERE id LIKE 'JORF%'
`
).map(({ id }) => id),
)
const versionsRemainingElis = new Set(
(
await db<{ eli: string }[]>`
SELECT eli
FROM versions
`
).map(({ eli }) => eli),
)
const dataDir = path.join("..", "dila-data", "jorf")
assert(await fs.pathExists(dataDir))
iterXmlFiles: for (const relativeSplitPath of walkDir(dataDir)) {
const relativePath = path.join(...relativeSplitPath)
if (skip) {
if (relativePath.startsWith(resume!)) {
skip = false
console.log(`Resuming at file ${relativePath}...`)
} else {
continue
}
}
const filePath = path.join(dataDir, relativePath)
if (!filePath.endsWith(".xml")) {
console.info(`Skipping non XML file at ${filePath}`)
continue
}
try {
const xmlString: string = await fs.readFile(filePath, {
encoding: "utf8",
})
const xmlData = xmlParser.parse(xmlString)
for (const [key, element] of Object.entries(xmlData) as [
string,
(
| Article
| Jo
| SectionTa
| Textelr
| TexteVersion
| Versions
| XmlHeader
),
][]) {
switch (key) {
case "?xml": {
const xmlHeader = element as XmlHeader
assert.strictEqual(xmlHeader["@encoding"], "UTF-8", filePath)
assert.strictEqual(xmlHeader["@version"], "1.0", filePath)
break
}
case "ARTICLE": {
const article = element as Article
await db`
INSERT INTO article (
id,
data
) VALUES (
${article.META.META_COMMUN.ID},
${db.json(article as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(article as unknown as JSONValue)}
`
articleRemainingIds.delete(article.META.META_COMMUN.ID)
break
}
case "ID": {
assert.strictEqual(relativeSplitPath[0], "global")
assert.strictEqual(relativeSplitPath[1], "eli")
const eli = relativeSplitPath.slice(2, -1).join("/")
const [id, idError] = auditChain(auditId, auditRequire)(
strictAudit,
element,
)
assert.strictEqual(
idError,
null,
`Unexpected format for ID:\n${JSON.stringify(
id,
null,
2,
)}\nError:\n${JSON.stringify(idError, null, 2)}`,
)
assert
await db`
INSERT INTO id (
eli,
id
) VALUES (
${eli},
${id}
)
ON CONFLICT (eli)
DO UPDATE SET
id = ${id}
`
idRemainingElis.delete(eli)
break
}
case "JO": {
const jo = element as Jo
await db`
INSERT INTO jo (
id,
data
) VALUES (
${jo.META.META_COMMUN.ID},
${db.json(jo as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(jo as unknown as JSONValue)}
`
joRemainingIds.delete(jo.META.META_COMMUN.ID)
break
}
case "SECTION_TA": {
const section = element as SectionTa
await db`
INSERT INTO section_ta (
id,
data
) VALUES (
${section.ID},
${db.json(section as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(section as unknown as JSONValue)}
`
sectionTaRemainingIds.delete(section.ID)
break
}
case "TEXTE_VERSION": {
const version = element as TexteVersion
await db`
INSERT INTO texte_version (
id,
data
) VALUES (
${version.META.META_COMMUN.ID},
${db.json(version as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(version as unknown as JSONValue)}
`
texteVersionRemainingIds.delete(version.META.META_COMMUN.ID)
break
}
case "TEXTELR": {
const textelr = element as Textelr
await db`
INSERT INTO textelr (
id,
data
) VALUES (
${textelr.META.META_COMMUN.ID},
${db.json(textelr as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(textelr as unknown as JSONValue)}
`
textelrRemainingIds.delete(textelr.META.META_COMMUN.ID)
break
}
case "VERSIONS": {
assert.strictEqual(relativeSplitPath[0], "global")
assert.strictEqual(relativeSplitPath[1], "eli")
const eli = relativeSplitPath.slice(2, -1).join("/")
const [versions, versionsError] = auditChain(
auditVersions,
auditRequire,
)(strictAudit, element)
assert.strictEqual(
versionsError,
null,
`Unexpected format for VERSIONS:\n${JSON.stringify(
versions,
null,
2,
)}\nError:\n${JSON.stringify(versionsError, null, 2)}`,
)
const id = versions.VERSION["@id"]
await db`
INSERT INTO versions (
eli,
id,
data
) VALUES (
${eli},
${id},
${db.json(versions as unknown as JSONValue)}
)
ON CONFLICT (eli)
DO UPDATE SET
id = ${id},
data = ${db.json(versions as unknown as JSONValue)}
`
versionsRemainingElis.delete(id)
break
}
default: {
console.warn(
`Unexpected root element "${key}" in XML file: ${filePath}`,
)
break iterXmlFiles
}
}
}
} catch (e) {
console.error("An error occurred while parsing XML file", filePath)
throw e
}
}
if (deleteRemainingIds) {
for (const id of articleRemainingIds) {
console.log(`Deleting ARTICLE ${id}`)
await db`
DELETE FROM article
WHERE id = ${id}
`
}
for (const eli of idRemainingElis) {
console.log(`Deleting ID ${eli}`)
await db`
DELETE FROM id
WHERE eli = ${eli}
`
}
for (const id of joRemainingIds) {
console.log(`Deleting JO ${id}`)
await db`
DELETE FROM jo
WHERE id = ${id}
`
}
for (const id of sectionTaRemainingIds) {
console.log(`Deleting SECTION_TA ${id}`)
await db`
DELETE FROM section_ta
WHERE id = ${id}
`
}
for (const id of textelrRemainingIds) {
console.log(`Deleting TEXTELR ${id}`)
await db`
DELETE FROM textelr
WHERE id = ${id}
`
}
for (const id of texteVersionRemainingIds) {
console.log(`Deleting TEXTE_VERSION ${id}`)
await db`
DELETE FROM texte_version
WHERE id = ${id}
`
}
for (const eli of versionsRemainingElis) {
console.log(`Deleting VERSIONS ${eli}`)
await db`
DELETE FROM versions
WHERE eli = ${eli}
`
}
}
}
sade("import_jorf", true)
.describe("Import Dila's JORF database")
.option("-r", "--resume", "Resume import at given relative file path")
.example(
"--resume global/eli/accord/2002/5/5/MESS0221690X/jo/article_1/versions.xml",
)
.action(async (options) => {
await importJorf(options)
process.exit(0)
})
.parse(process.argv)

View file

@ -1,3 +1,4 @@
import { auditChain, auditRequire, strictAudit } from "@auditors/core"
import assert from "assert" import assert from "assert"
import { XMLParser } from "fast-xml-parser" import { XMLParser } from "fast-xml-parser"
import fs from "fs-extra" import fs from "fs-extra"
@ -6,22 +7,18 @@ import path from "path"
import type { JSONValue } from "postgres" import type { JSONValue } from "postgres"
import sade from "sade" import sade from "sade"
import { auditId, auditVersions } from "$lib/auditors/data"
import type { import type {
Article, Article,
EliId, SectionTa,
EliVersions, Textelr,
Section,
Struct,
TexteVersion, TexteVersion,
Versions,
XmlHeader,
} from "$lib/data" } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
import { walkDir } from "$lib/server/file_systems" import { walkDir } from "$lib/server/file_systems"
interface XmlHeader {
"@encoding": "UTF-8"
"@version": "1.0"
}
const xmlParser = new XMLParser({ const xmlParser = new XMLParser({
attributeNamePrefix: "@", attributeNamePrefix: "@",
ignoreAttributes: false, ignoreAttributes: false,
@ -42,53 +39,57 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
let skip = resume !== undefined let skip = resume !== undefined
const deleteRemainingIds = !skip const deleteRemainingIds = !skip
const articlesRemainingIds = new Set( const articleRemainingIds = new Set(
( (
await db<{ id: string }[]>` await db<{ id: string }[]>`
SELECT id SELECT id
FROM articles FROM article
WHERE id LIKE 'LEGI%'
` `
).map(({ id }) => id), ).map(({ id }) => id),
) )
const eliIdsRemainingIds = new Set( const idRemainingElis = new Set(
(
await db<{ eli: string }[]>`
SELECT eli
FROM id
`
).map(({ eli }) => eli),
)
const sectionTaRemainingIds = new Set(
( (
await db<{ id: string }[]>` await db<{ id: string }[]>`
SELECT id SELECT id
FROM eli_ids FROM section_ta
WHERE id LIKE 'LEGI%'
` `
).map(({ id }) => id), ).map(({ id }) => id),
) )
const eliVersionsRemainingIds = new Set( const textelrRemainingIds = new Set(
( (
await db<{ id: string }[]>` await db<{ id: string }[]>`
SELECT id SELECT id
FROM eli_versions FROM textelr
WHERE id LIKE 'LEGI%'
` `
).map(({ id }) => id), ).map(({ id }) => id),
) )
const sectionsRemainingIds = new Set( const texteVersionRemainingElis = new Set(
( (
await db<{ id: string }[]>` await db<{ id: string }[]>`
SELECT id SELECT id
FROM sections FROM texte_version
WHERE id LIKE 'LEGI%'
` `
).map(({ id }) => id), ).map(({ id }) => id),
) )
const structsRemainingIds = new Set( const versionsRemainingElis = new Set(
( (
await db<{ id: string }[]>` await db<{ eli: string }[]>`
SELECT id SELECT eli
FROM structs FROM versions
` `
).map(({ id }) => id), ).map(({ eli }) => eli),
)
const textesRemainingIds = new Set(
(
await db<{ id: string }[]>`
SELECT id
FROM textes_versions
`
).map(({ id }) => id),
) )
const dataDir = path.join("..", "dila-data", "legi") const dataDir = path.join("..", "dila-data", "legi")
@ -109,32 +110,27 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
console.info(`Skipping non XML file at ${filePath}`) console.info(`Skipping non XML file at ${filePath}`)
continue continue
} }
try {
const xmlString: string = await fs.readFile(filePath, { const xmlString: string = await fs.readFile(filePath, {
encoding: "utf8", encoding: "utf8",
}) })
const xmlData = xmlParser.parse(xmlString) const xmlData = xmlParser.parse(xmlString)
for (const [key, element] of Object.entries(xmlData) as [ for (const [key, element] of Object.entries(xmlData) as [
string, string,
( Article | SectionTa | Textelr | TexteVersion | Versions | XmlHeader,
| Article
| EliId
| EliVersions
| Section
| Struct
| TexteVersion
| XmlHeader
),
][]) { ][]) {
switch (key) { switch (key) {
case "?xml": case "?xml": {
const xmlHeader = element as XmlHeader const xmlHeader = element as XmlHeader
assert.strictEqual(xmlHeader["@encoding"], "UTF-8", filePath) assert.strictEqual(xmlHeader["@encoding"], "UTF-8", filePath)
assert.strictEqual(xmlHeader["@version"], "1.0", filePath) assert.strictEqual(xmlHeader["@version"], "1.0", filePath)
break break
}
case "ARTICLE": { case "ARTICLE": {
const article = element as Article const article = element as Article
await db` await db`
INSERT INTO articles ( INSERT INTO article (
id, id,
data data
) VALUES ( ) VALUES (
@ -145,33 +141,46 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
DO UPDATE SET DO UPDATE SET
data = ${db.json(article as unknown as JSONValue)} data = ${db.json(article as unknown as JSONValue)}
` `
articlesRemainingIds.delete(article.META.META_COMMUN.ID) articleRemainingIds.delete(article.META.META_COMMUN.ID)
break break
} }
case "ID": { case "ID": {
assert.strictEqual(relativeSplitPath[0], "global") assert.strictEqual(relativeSplitPath[0], "global")
assert.strictEqual(relativeSplitPath[1], "eli") assert.strictEqual(relativeSplitPath[1], "eli")
const id = relativeSplitPath.slice(2, -1).join("/") const eli = relativeSplitPath.slice(2, -1).join("/")
const eliId = element as EliId const [id, idError] = auditChain(auditId, auditRequire)(
await db` strictAudit,
INSERT INTO eli_ids ( element,
id,
data
) VALUES (
${id},
${db.json(eliId as unknown as JSONValue)}
) )
ON CONFLICT (id) assert.strictEqual(
idError,
null,
`Unexpected format for ID:\n${JSON.stringify(
id,
null,
2,
)}\nError:\n${JSON.stringify(idError, null, 2)}`,
)
assert
await db`
INSERT INTO id (
eli,
id
) VALUES (
${eli},
${id}
)
ON CONFLICT (eli)
DO UPDATE SET DO UPDATE SET
data = ${db.json(eliId as unknown as JSONValue)} id = ${id}
` `
eliIdsRemainingIds.delete(id) idRemainingElis.delete(eli)
break break
} }
case "SECTION_TA": { case "SECTION_TA": {
const section = element as Section const section = element as SectionTa
await db` await db`
INSERT INTO sections ( INSERT INTO section_ta (
id, id,
data data
) VALUES ( ) VALUES (
@ -182,31 +191,13 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
DO UPDATE SET DO UPDATE SET
data = ${db.json(section as unknown as JSONValue)} data = ${db.json(section as unknown as JSONValue)}
` `
sectionsRemainingIds.delete(section.ID) sectionTaRemainingIds.delete(section.ID)
break
}
case "TEXTELR": {
const struct = element as Struct
await db`
INSERT INTO structs (
id,
data
) VALUES (
${struct.META.META_COMMUN.ID},
${db.json(struct as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(struct as unknown as JSONValue)}
`
structsRemainingIds.delete(struct.META.META_COMMUN.ID)
break break
} }
case "TEXTE_VERSION": { case "TEXTE_VERSION": {
const version = element as TexteVersion const version = element as TexteVersion
console.log("TEXTE_VERSION:", version.META.META_COMMUN.ID)
await db` await db`
INSERT INTO textes_versions ( INSERT INTO texte_version (
id, id,
data data
) VALUES ( ) VALUES (
@ -217,27 +208,60 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
DO UPDATE SET DO UPDATE SET
data = ${db.json(version as unknown as JSONValue)} data = ${db.json(version as unknown as JSONValue)}
` `
textesRemainingIds.delete(version.META.META_COMMUN.ID) texteVersionRemainingElis.delete(version.META.META_COMMUN.ID)
break
}
case "TEXTELR": {
const textelr = element as Textelr
await db`
INSERT INTO textelr (
id,
data
) VALUES (
${textelr.META.META_COMMUN.ID},
${db.json(textelr as unknown as JSONValue)}
)
ON CONFLICT (id)
DO UPDATE SET
data = ${db.json(textelr as unknown as JSONValue)}
`
textelrRemainingIds.delete(textelr.META.META_COMMUN.ID)
break break
} }
case "VERSIONS": { case "VERSIONS": {
assert.strictEqual(relativeSplitPath[0], "global") assert.strictEqual(relativeSplitPath[0], "global")
assert.strictEqual(relativeSplitPath[1], "eli") assert.strictEqual(relativeSplitPath[1], "eli")
const id = relativeSplitPath.slice(2, -1).join("/") const eli = relativeSplitPath.slice(2, -1).join("/")
const eliVersion = element as EliVersions const [versions, versionsError] = auditChain(
auditVersions,
auditRequire,
)(strictAudit, element)
assert.strictEqual(
versionsError,
null,
`Unexpected format for VERSIONS:\n${JSON.stringify(
versions,
null,
2,
)}\nError:\n${JSON.stringify(versionsError, null, 2)}`,
)
const id = versions.VERSION["@id"]
await db` await db`
INSERT INTO eli_versions ( INSERT INTO versions (
eli,
id, id,
data data
) VALUES ( ) VALUES (
${eli},
${id}, ${id},
${db.json(eliVersion as unknown as JSONValue)} ${db.json(versions as unknown as JSONValue)}
) )
ON CONFLICT (id) ON CONFLICT (eli)
DO UPDATE SET DO UPDATE SET
data = ${db.json(eliVersion as unknown as JSONValue)} id = ${id},
data = ${db.json(versions as unknown as JSONValue)}
` `
eliVersionsRemainingIds.delete(id) versionsRemainingElis.delete(id)
break break
} }
default: { default: {
@ -248,51 +272,53 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise<void> {
} }
} }
} }
// console.log(filePath) } catch (e) {
// console.log(JSON.stringify(xmlData, null, 2)) console.error("An error occurred while parsing XML file", filePath)
throw e
}
} }
if (deleteRemainingIds) { if (deleteRemainingIds) {
for (const id of articlesRemainingIds) { for (const id of articleRemainingIds) {
console.log(`Deleting article ${id}`) console.log(`Deleting ARTICLE ${id}`)
await db` await db`
DELETE FROM articles DELETE FROM article
WHERE id = ${id} WHERE id = ${id}
` `
} }
for (const id of eliIdsRemainingIds) { for (const eli of idRemainingElis) {
console.log(`Deleting ELI ID ${id}`) console.log(`Deleting ID ${eli}`)
await db` await db`
DELETE FROM eli_ids DELETE FROM id
WHERE eli = ${eli}
`
}
for (const id of sectionTaRemainingIds) {
console.log(`Deleting SECTION_TA ${id}`)
await db`
DELETE FROM section_ta
WHERE id = ${id} WHERE id = ${id}
` `
} }
for (const id of eliVersionsRemainingIds) { for (const id of textelrRemainingIds) {
console.log(`Deleting ELI versions ${id}`) console.log(`Deleting TEXTELR ${id}`)
await db` await db`
DELETE FROM eli_versions DELETE FROM textelr
WHERE id = ${id} WHERE id = ${id}
` `
} }
for (const id of sectionsRemainingIds) { for (const id of texteVersionRemainingElis) {
console.log(`Deleting section ${id}`) console.log(`Deleting TEXTE_VERSION ${id}`)
await db` await db`
DELETE FROM sections DELETE FROM texte_version
WHERE id = ${id} WHERE id = ${id}
` `
} }
for (const id of structsRemainingIds) { for (const eli of versionsRemainingElis) {
console.log(`Deleting struct ${id}`) console.log(`Deleting VERSIONS ${eli}`)
await db` await db`
DELETE FROM structs DELETE FROM versions
WHERE id = ${id} WHERE eli = ${eli}
`
}
for (const id of textesRemainingIds) {
console.log(`Deleting texte version ${id}`)
await db`
DELETE FROM textes_versions
WHERE id = ${id}
` `
} }
} }

View file

@ -1,5 +1,4 @@
import assert from "assert" import assert from "assert"
import dedent from "dedent-js"
import { db, type Version, versionNumber } from "$lib/server/database" import { db, type Version, versionNumber } from "$lib/server/database"
@ -34,55 +33,72 @@ export async function configureDatabase() {
// Apply patches that must be executed before every table is created. // Apply patches that must be executed before every table is created.
if (version.number < 2) {
await db`ALTER TABLE IF EXISTS articles RENAME TO article`
await db`DROP TABLE IF EXISTS eli_ids`
await db`DROP TABLE IF EXISTS eli_versions`
await db`ALTER TABLE IF EXISTS sections RENAME TO section_ta`
await db`ALTER TABLE IF EXISTS structs RENAME TO textelr`
await db`ALTER TABLE IF EXISTS textes RENAME TO texte_version`
}
// Types // Types
// Tables // Tables
// Table: articles // Table: article
await db` await db`
CREATE TABLE IF NOT EXISTS articles ( CREATE TABLE IF NOT EXISTS article (
id char(20) PRIMARY KEY, id char(20) PRIMARY KEY,
data jsonb NOT NULL data jsonb NOT NULL
) )
` `
// // Table: articles_autocompletions // // Table: article_autocompletions
// await ` // await `
// CREATE TABLE IF NOT EXISTS articles_autocompletions ( // CREATE TABLE IF NOT EXISTS article_autocompletions (
// autocompletion text NOT NULL, // autocompletion text NOT NULL,
// id bigint NOT NULL REFERENCES articles(id) ON DELETE CASCADE, // id bigint NOT NULL REFERENCES article(id) ON DELETE CASCADE,
// weight int NOT NULL, // weight int NOT NULL,
// PRIMARY KEY (id, autocompletion) // PRIMARY KEY (id, autocompletion)
// ) // )
// ` // `
// Table: eli_ids // Table: id
await db` await db`
CREATE TABLE IF NOT EXISTS eli_ids ( CREATE TABLE IF NOT EXISTS id (
id text PRIMARY KEY, eli text PRIMARY KEY,
data jsonb NOT NULL id char(20) NOT NULL
) )
` `
// Table: eli_versions // Table: jo
await db` await db`
CREATE TABLE IF NOT EXISTS eli_versions ( CREATE TABLE IF NOT EXISTS jo (
id text PRIMARY KEY,
data jsonb NOT NULL
)
`
// Table: sections
await db`
CREATE TABLE IF NOT EXISTS sections (
id char(20) PRIMARY KEY, id char(20) PRIMARY KEY,
data jsonb NOT NULL data jsonb NOT NULL
) )
` `
// Table: structs // Table: section_ta
await db` await db`
CREATE TABLE IF NOT EXISTS structs ( CREATE TABLE IF NOT EXISTS section_ta (
id char(20) PRIMARY KEY,
data jsonb NOT NULL
)
`
// Table: texte_version
await db`
CREATE TABLE IF NOT EXISTS texte_version (
id char(20) PRIMARY KEY,
data jsonb NOT NULL
)
`
// Table: textelr
await db`
CREATE TABLE IF NOT EXISTS textelr (
id char(20) PRIMARY KEY, id char(20) PRIMARY KEY,
data jsonb NOT NULL data jsonb NOT NULL
) )
@ -90,8 +106,9 @@ export async function configureDatabase() {
// Table: versions // Table: versions
await db` await db`
CREATE TABLE IF NOT EXISTS textes_versions ( CREATE TABLE IF NOT EXISTS versions (
id char(20) PRIMARY KEY, eli text PRIMARY KEY,
id char(20) NOT NULL,
data jsonb NOT NULL data jsonb NOT NULL
) )
` `
@ -101,8 +118,8 @@ export async function configureDatabase() {
// Add indexes once every table and column exists. // Add indexes once every table and column exists.
// await db` // await db`
// CREATE INDEX IF NOT EXISTS articles_autocompletions_trigrams_idx // CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx
// ON articles_autocompletions // ON article_autocompletions
// USING GIST (autocompletion gist_trgm_ops) // USING GIST (autocompletion gist_trgm_ops)
// ` // `

View file

@ -14,7 +14,7 @@ export const db = postgres({
port: config.db.port, port: config.db.port,
user: config.db.user, user: config.db.user,
}) })
export const versionNumber = 1 export const versionNumber = 2
/// Check that database exists and is up to date. /// Check that database exists and is up to date.
export async function checkDb(): Promise<void> { export async function checkDb(): Promise<void> {

View file

@ -1,35 +1,46 @@
import arrowRight from "@iconify-icons/codicon/arrow-small-right"
import type { Access, Summarizer, Summary } from "augmented-data-viewer" import type { Access, Summarizer, Summary } from "augmented-data-viewer"
import { import {
type Article, type Article,
assertNeverLegiObjectType, assertNeverLegalObjectType,
type LegiObject, bestItemForDate,
type LegiObjectType, type Jo,
pathnameFromLegiObject, type LegalObject,
pathnameFromLegiObjectId, type LegalObjectType,
type Section, pathnameFromLegalObject,
type Struct, pathnameFromLegalObjectId,
type SectionTa,
type Textelr,
type TexteVersion, type TexteVersion,
type LienArt,
} from "$lib/data" } from "$lib/data"
export const summarizeArticleProperties: Summarizer = (access, value) => { export const summarizeArticleProperties: Summarizer = (access, value) => {
if (access?.key === "article") { if (access?.key === "article" && typeof value !== "number") {
return summarizeLegiObject(access, "article", value) return summarizeLegalObject(access, "article", value)
} }
if (access?.access?.key === "articles") { if (typeof access?.key === "number" && access?.access?.key === "article") {
return summarizeLegiObjectToLink(access, "article", value) return summarizeLegalObjectToLink(access, "article", value)
} }
if (access?.key === "@id" && access?.access?.key === "LIEN_ART") { if (
return { access?.key === "@id" &&
content: value as string, (access?.access?.key === "LIEN_ART" ||
href: pathnameFromLegiObjectId("article", value as string), (typeof access?.access?.key === "number" &&
type: "link", access?.access?.access?.key === "LIEN_ART"))
} ) {
return summarizeLienArtId(access.access, access.parent)
} }
if (access?.key === "CONTENU") { if (access?.key === "CONTENU") {
return { content: value as string, type: "html" } return { content: value as string, type: "html" }
} }
if (access?.key === "LIEN_ART" && !Array.isArray(value)) {
return summarizeLienArt(access, value)
}
if (typeof access?.key === "number" && access?.access?.key === "LIEN_ART") {
return summarizeLienArt(access, value)
}
if (access?.access?.key === "VERSION") { if (access?.access?.key === "VERSION") {
const version = value as Article["VERSIONS"]["VERSION"][0] const version = value as Article["VERSIONS"]["VERSION"][0]
const lienArt = version.LIEN_ART const lienArt = version.LIEN_ART
@ -60,16 +71,27 @@ export const summarizeArticleProperties: Summarizer = (access, value) => {
], ],
type: "concatenation", type: "concatenation",
}, },
href: pathnameFromLegiObjectId("article", lienArt["@id"]), href: pathnameFromLegalObjectId("article", lienArt["@id"]),
type: "link", type: "link",
} }
} }
return undefined return undefined
} }
export function summarizeLegiObject( export const summarizeJoProperties: Summarizer = (access, value) => {
if (access?.key === "jo" && typeof value !== "number") {
return summarizeLegalObject(access, "jo", value)
}
if (typeof access?.key === "number" && access?.access?.key === "jo") {
return summarizeLegalObjectToLink(access, "jo", value)
}
return undefined
}
export function summarizeLegalObject(
access: Access | undefined, access: Access | undefined,
type: LegiObjectType, type: LegalObjectType,
value: unknown, value: unknown,
): Summary | undefined { ): Summary | undefined {
switch (type) { switch (type) {
@ -113,70 +135,127 @@ export function summarizeLegiObject(
type: "concatenation", type: "concatenation",
} }
} }
case "eli_id": case "id":
return `/eli/ids/TODO` return `/eli/ids/TODO`
case "eli_versions": case "jo": {
return `/eli/ids/TODO` const jo = value as Jo | undefined
case "section": { return jo?.META.META_SPEC.META_CONTENEUR.TITRE
const section = value as Section | undefined
return section?.ID
} }
case "struct": { case "section_ta": {
const struct = value as Struct | undefined const sectionTa = value as SectionTa | undefined
return struct?.META.META_COMMUN.ID if (sectionTa === undefined) {
return undefined
} }
case "texte": { const today = new Date().toISOString().split("T")[0]
const texte = value as TexteVersion | undefined return `${sectionTa.TITRE_TA}${
return texte?.META.META_COMMUN.ID bestItemForDate(sectionTa.CONTEXTE.TEXTE.TITRE_TXT, today)?.["#text"]
}`
} }
case "texte_version": {
const texteVersion = value as TexteVersion | undefined
return texteVersion?.META.META_SPEC.META_TEXTE_VERSION.TITREFULL
}
case "textelr": {
const textelr = value as Textelr | undefined
return textelr?.META.META_COMMUN.ID
}
case "versions":
return `/eli/versions/TODO`
default: default:
assertNeverLegiObjectType(type) assertNeverLegalObjectType(type)
} }
} }
export function summarizeLegiObjectToLink( export function summarizeLegalObjectToLink(
access: Access | undefined, access: Access | undefined,
type: LegiObjectType, type: LegalObjectType,
value: unknown, value: unknown,
): Summary | undefined { ): Summary | undefined {
const objectSummary = summarizeLegiObject(access, type, value) const objectSummary = summarizeLegalObject(access, type, value)
return objectSummary === undefined return objectSummary === undefined
? undefined ? undefined
: { : {
content: objectSummary, content: objectSummary,
href: pathnameFromLegiObject(type, value as LegiObject), href: pathnameFromLegalObject(type, value as LegalObject),
type: "link", type: "link",
} }
} }
export const summarizeSectionProperties: Summarizer = (access, value) => { export const summarizeLienArt: Summarizer = (access, value) => {
if (access?.key === "section") { const lienArt = value as LienArt | undefined
return summarizeLegiObject(access, "section", value) if (lienArt === undefined) {
return undefined
} }
if (access?.access?.key === "sections") { return {
return summarizeLegiObjectToLink(access, "section", value) content: `Article n° ${lienArt["@num"]}`,
href: pathnameFromLegalObjectId("article", lienArt["@id"]),
type: "link",
}
}
export const summarizeLienArtId: Summarizer = (access, value) => {
const lienArt = value as LienArt | undefined
if (lienArt === undefined) {
return undefined
}
return {
items: [
{
content: JSON.stringify(lienArt["@id"]),
type: "raw_data",
},
{ class: "mx-1", icon: arrowRight, inline: true, type: "icon" },
summarizeLienArt(access, lienArt)!,
],
type: "concatenation",
}
}
export const summarizeSectionTaProperties: Summarizer = (access, value) => {
if (access?.key === "section_ta" && typeof value !== "number") {
return summarizeLegalObject(access, "section_ta", value)
}
if (typeof access?.key === "number" && access?.access?.key === "section_ta") {
return summarizeLegalObjectToLink(access, "section_ta", value)
}
if (
access?.key === "@id" &&
(access?.access?.key === "LIEN_ART" ||
(typeof access?.access?.key === "number" &&
access?.access?.access?.key === "LIEN_ART"))
) {
return summarizeLienArtId(access.access, access.parent)
}
if (access?.key === "LIEN_ART" && !Array.isArray(value)) {
return summarizeLienArt(access, value)
}
if (typeof access?.key === "number" && access?.access?.key === "LIEN_ART") {
return summarizeLienArt(access, value)
}
return undefined
}
export const summarizeTextelrProperties: Summarizer = (access, value) => {
if (access?.key === "textelr" && typeof value !== "number") {
return summarizeLegalObject(access, "textelr", value)
}
if (typeof access?.key === "number" && access?.access?.key === "textelr") {
return summarizeLegalObjectToLink(access, "textelr", value)
} }
return undefined return undefined
} }
export const summarizeStructProperties: Summarizer = (access, value) => { export const summarizeTexteVersionProperties: Summarizer = (access, value) => {
if (access?.key === "struct") { if (access?.key === "texte_version" && typeof value !== "number") {
return summarizeLegiObject(access, "struct", value) return summarizeLegalObject(access, "texte_version", value)
} }
if (access?.access?.key === "structs") { if (
return summarizeLegiObjectToLink(access, "struct", value) typeof access?.key === "number" &&
} access?.access?.key === "texte_version"
) {
return undefined return summarizeLegalObjectToLink(access, "texte_version", value)
}
export const summarizeTexteProperties: Summarizer = (access, value) => {
if (access?.key === "texte") {
return summarizeLegiObject(access, "texte", value)
}
if (access?.access?.key === "textes") {
return summarizeLegiObjectToLink(access, "texte", value)
} }
return undefined return undefined

View file

@ -14,6 +14,7 @@
href?: string href?: string
items?: MenuItemLink[] items?: MenuItemLink[]
label: string label: string
title?: string
} }
interface MenuItemLink extends MenuItemBase { interface MenuItemLink extends MenuItemBase {
@ -27,16 +28,36 @@
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
{ href: "/recherche", label: "Recherche" }, { href: "/recherche", label: "Recherche" },
{ {
label: "Données",
items: [ items: [
{ href: "/articles", label: "Articles" }, { href: "/article", label: "ARTICLE" },
{ href: "/eli/ids", label: "ELI ID" }, // { href: "/eli/ids", label: "ID" },
{ href: "/eli/versions", label: "ELI versions" }, { href: "/jo", label: "JO" },
{ href: "/sections", label: "Sections" }, { href: "/section_ta", label: "SECTION_TA" },
{ href: "/structs", label: "Structures" }, { href: "/texte_version", label: "TEXTE_VERSION" },
{ href: "/textes", label: "Textes" }, { href: "/textelr", label: "TEXTELR" },
// { href: "/eli/versions", label: "VERSIONS" },
], ],
label: "Données",
}, },
// {
// items: [
// { href: "/jo", label: "JO" },
// ],
// label: "JORF",
// title: "Textes publiés au Journal officiel de la République française",
// },
// {
// items: [
// { href: "/article", label: "ARTICLE" },
// // { href: "/eli/ids", label: "ID" },
// { href: "/section_ta", label: "SECTION_TA" },
// { href: "/texte_version", label: "TEXTE_VERSION" },
// { href: "/textelr", label: "TEXTELR" },
// // { href: "/eli/versions", label: "VERSIONS" },
// ],
// label: "LEGI",
// title: "Codes, lois et règlements consolidés",
// },
] ]
const title = $session.title const title = $session.title
</script> </script>
@ -51,18 +72,18 @@
tabindex="0" tabindex="0"
class="dropdown-content menu rounded-box menu-compact mt-3 w-52 bg-neutral p-2 text-neutral-content shadow" class="dropdown-content menu rounded-box menu-compact mt-3 w-52 bg-neutral p-2 text-neutral-content shadow"
> >
{#each menuItems as { href, items, label }} {#each menuItems as { href, items, label, title }}
{#if href !== undefined} {#if href !== undefined}
<li><a {href}>{label}</a></li> <li><a {href} {title}>{label}</a></li>
{:else if items !== undefined} {:else if items !== undefined}
<li tabindex="0"> <li tabindex="0">
<span class="justify-between"> <span class="justify-between" {title}>
{label} {label}
<Icon class="h-5 w-5" icon={chevronRight} /> <Icon class="h-5 w-5" icon={chevronRight} />
</span> </span>
<ul class="p-2 bg-neutral text-neutral-content"> <ul class="p-2 bg-neutral text-neutral-content">
{#each items as { href, label }} {#each items as { href, label }}
<li><a {href}>{label}</a></li> <li><a {href} {title}>{label}</a></li>
{/each} {/each}
</ul> </ul>
</li> </li>
@ -74,18 +95,18 @@
</div> </div>
<div class="navbar-center hidden lg:flex"> <div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal p-0"> <ul class="menu menu-horizontal p-0">
{#each menuItems as { href, items, label }} {#each menuItems as { href, items, label, title }}
{#if href !== undefined} {#if href !== undefined}
<li><a {href}>{label}</a></li> <li><a {href} {title}>{label}</a></li>
{:else if items !== undefined} {:else if items !== undefined}
<li tabindex="0"> <li tabindex="0">
<span class="justify-between"> <span class="justify-between" {title}>
{label} {label}
<Icon class="h-5 w-5" icon={chevronDown} /> <Icon class="h-5 w-5" icon={chevronDown} />
</span> </span>
<ul class="p-2 bg-neutral text-neutral-content"> <ul class="p-2 bg-neutral text-neutral-content">
{#each items as { href, label }} {#each items as { href, label }}
<li><a {href}>{label}</a></li> <li><a {href} {title}>{label}</a></li>
{/each} {/each}
</ul> </ul>
</li> </li>

View file

@ -5,13 +5,13 @@
import type { Article } from "$lib/data" import type { Article } from "$lib/data"
import { import {
summarizeArticleProperties, summarizeArticleProperties,
summarizeLegiObject, summarizeLegalObject,
} from "$lib/summaries" } from "$lib/summaries"
export let error: unknown export let error: unknown
export let article: Article export let article: Article
const summary = summarizeLegiObject({ key: "article" }, "article", article) const summary = summarizeLegalObject({ key: "article" }, "article", article)
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">

View file

@ -4,10 +4,10 @@ import type { JSONObject } from "@sveltejs/kit/types/private"
import type { Article } from "$lib/data" import type { Article } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export const GET: RequestHandler = async ({ params, url }) => { export const GET: RequestHandler = async ({ params }) => {
const article = ( const article = (
await db<{ data: Article }[]>` await db<{ data: Article }[]>`
SELECT data FROM articles SELECT data FROM article
WHERE id = ${params.id} WHERE id = ${params.id}
` `
).map(({ data }) => data)[0] ).map(({ data }) => data)[0]

View file

@ -6,10 +6,12 @@
// import { page } from "$app/stores" // import { page } from "$app/stores"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import Pagination from "$lib/components/Pagination.svelte" import Pagination from "$lib/components/Pagination.svelte"
import type { Article } from "$lib/data"
import { summarizeArticleProperties } from "$lib/summaries" import { summarizeArticleProperties } from "$lib/summaries"
let articles: Article[]
export { articles as article }
export let error: unknown export let error: unknown
export let articles: unknown[]
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
@ -55,7 +57,7 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "articles" }} access={{ key: "article" }}
frame={false} frame={false}
open open
summarize={summarizeArticleProperties} summarize={summarizeArticleProperties}

View file

@ -60,13 +60,13 @@ export const GET: RequestHandler = async ({ url }) => {
const articles = ( const articles = (
await db<{ data: Article }[]>` await db<{ data: Article }[]>`
SELECT data FROM articles SELECT data FROM article
OFFSET ${offset} OFFSET ${offset}
LIMIT ${limit} LIMIT ${limit}
` `
).map(({ data }) => data) ).map(({ data }) => data)
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { articles: articles as unknown as JSONObject[] }, body: { article: articles as unknown as JSONObject[] },
} }
} }

View file

@ -2,17 +2,17 @@
import { TreeView, SummaryView } from "augmented-data-viewer" import { TreeView, SummaryView } from "augmented-data-viewer"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import type { TexteVersion } from "$lib/data" import type { Jo } from "$lib/data"
import { summarizeTexteProperties, summarizeLegiObject } from "$lib/summaries" import { summarizeJoProperties, summarizeLegalObject } from "$lib/summaries"
export let error: unknown export let error: unknown
export let texte: TexteVersion export let jo: Jo
const summary = summarizeLegiObject({ key: "texte" }, "texte", texte) const summary = summarizeLegalObject({ key: "jo" }, "jo", jo)
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h2>Texte</h2> <h2>JO</h2>
{#if summary !== undefined} {#if summary !== undefined}
<h1> <h1>
<SummaryView {summary} /> <SummaryView {summary} />
@ -26,10 +26,10 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "texte" }} access={{ key: "jo" }}
frame={false} frame={false}
open open
summarize={summarizeTexteProperties} summarize={summarizeJoProperties}
value={texte} value={jo}
/> />
{/if} {/if}

View file

@ -1,22 +1,22 @@
import type { RequestHandler } from "@sveltejs/kit" import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private" import type { JSONObject } from "@sveltejs/kit/types/private"
import type { Struct } from "$lib/data" import type { Jo } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export const GET: RequestHandler = async ({ params, url }) => { export const GET: RequestHandler = async ({ params }) => {
const struct = ( const jo = (
await db<{ data: Struct }[]>` await db<{ data: Jo }[]>`
SELECT data FROM structs SELECT data FROM jo
WHERE id = ${params.id} WHERE id = ${params.id}
` `
).map(({ data }) => data)[0] ).map(({ data }) => data)[0]
if (struct === undefined) { if (jo === undefined) {
return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 } return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 }
} }
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { struct: struct as unknown as JSONObject }, body: { jo: jo as unknown as JSONObject },
} }
} }

View file

@ -6,14 +6,16 @@
// import { page } from "$app/stores" // import { page } from "$app/stores"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import Pagination from "$lib/components/Pagination.svelte" import Pagination from "$lib/components/Pagination.svelte"
import { summarizeStructProperties } from "$lib/summaries" import type { Jo } from "$lib/data"
import { summarizeJoProperties } from "$lib/summaries"
export let error: unknown export let error: unknown
export let structs: unknown[] let jos: Jo[]
export { jos as jo }
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h1>Structures</h1> <h1>JO</h1>
</header> </header>
<!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get"> <!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get">
@ -39,7 +41,7 @@
bind:value={legislature} bind:value={legislature}
> >
<option value="">Toutes</option> <option value="">Toutes</option>
{#each Object.entries(Legislature) as [key, value]} {#each Object.entries(Jorfslature) as [key, value]}
{#if value !== "*"} {#if value !== "*"}
<option {value}>{key}</option> <option {value}>{key}</option>
{/if} {/if}
@ -55,12 +57,12 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "structs" }} access={{ key: "jo" }}
frame={false} frame={false}
open open
summarize={summarizeStructProperties} summarize={summarizeJoProperties}
value={structs} value={jos}
/> />
<Pagination currentPageCount={structs.length ?? 0} /> <Pagination currentPageCount={jos.length ?? 0} />
{/if} {/if}

View file

@ -3,7 +3,7 @@ import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private" import type { JSONObject } from "@sveltejs/kit/types/private"
import { auditSearchQueryContent } from "$lib/auditors/queries" import { auditSearchQueryContent } from "$lib/auditors/queries"
import type { Struct } from "$lib/data" import type { Jo } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export function auditQuery( export function auditQuery(
@ -58,15 +58,15 @@ export const GET: RequestHandler = async ({ url }) => {
} }
const { limit, offset } = query const { limit, offset } = query
const structs = ( const jos = (
await db<{ data: Struct }[]>` await db<{ data: Jo }[]>`
SELECT data FROM structs SELECT data FROM jo
OFFSET ${offset} OFFSET ${offset}
LIMIT ${limit} LIMIT ${limit}
` `
).map(({ data }) => data) ).map(({ data }) => data)
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { structs: structs as unknown as JSONObject[] }, body: { jo: jos as unknown as JSONObject[] },
} }
} }

View file

@ -2,20 +2,25 @@
import { TreeView, SummaryView } from "augmented-data-viewer" import { TreeView, SummaryView } from "augmented-data-viewer"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import type { Section } from "$lib/data" import type { SectionTa } from "$lib/data"
import { import {
summarizeSectionProperties, summarizeSectionTaProperties,
summarizeLegiObject, summarizeLegalObject,
} from "$lib/summaries" } from "$lib/summaries"
export let error: unknown export let error: unknown
export let section: Section let sectionTa: SectionTa
export { sectionTa as section_ta }
const summary = summarizeLegiObject({ key: "section" }, "section", section) const summary = summarizeLegalObject(
{ key: "section_ta" },
"section_ta",
sectionTa,
)
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h2>Section</h2> <h2>SECTION_TA</h2>
{#if summary !== undefined} {#if summary !== undefined}
<h1> <h1>
<SummaryView {summary} /> <SummaryView {summary} />
@ -29,10 +34,10 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "section" }} access={{ key: "section_ta" }}
frame={false} frame={false}
open open
summarize={summarizeSectionProperties} summarize={summarizeSectionTaProperties}
value={section} value={sectionTa}
/> />
{/if} {/if}

View file

@ -0,0 +1,22 @@
import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private"
import type { SectionTa } from "$lib/data"
import { db } from "$lib/server/database"
export const GET: RequestHandler = async ({ params }) => {
const sectionTa = (
await db<{ data: SectionTa }[]>`
SELECT data FROM section_ta
WHERE id = ${params.id}
`
).map(({ data }) => data)[0]
if (sectionTa === undefined) {
return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 }
}
return {
headers: { "Access-Control-Allow-Origin": "*" },
body: { section_ta: sectionTa as unknown as JSONObject },
}
}

View file

@ -6,14 +6,16 @@
// import { page } from "$app/stores" // import { page } from "$app/stores"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import Pagination from "$lib/components/Pagination.svelte" import Pagination from "$lib/components/Pagination.svelte"
import { summarizeTexteProperties } from "$lib/summaries" import type { SectionTa } from "$lib/data"
import { summarizeSectionTaProperties } from "$lib/summaries"
export let error: unknown export let error: unknown
export let textes: unknown[] let sectionTaArray: SectionTa[]
export { sectionTaArray as section_ta }
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h1>Textes</h1> <h1>SECTION_TA</h1>
</header> </header>
<!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get"> <!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get">
@ -55,12 +57,12 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "textes" }} access={{ key: "section_ta" }}
frame={false} frame={false}
open open
summarize={summarizeTexteProperties} summarize={summarizeSectionTaProperties}
value={textes} value={sectionTaArray}
/> />
<Pagination currentPageCount={textes.length ?? 0} /> <Pagination currentPageCount={sectionTaArray.length ?? 0} />
{/if} {/if}

View file

@ -0,0 +1,72 @@
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private"
import { auditSearchQueryContent } from "$lib/auditors/queries"
import type { SectionTa } from "$lib/data"
import { db } from "$lib/server/database"
export function auditQuery(
audit: Audit,
query: URLSearchParams,
): [unknown, unknown] {
if (query == null) {
return [query, null]
}
if (!(query instanceof URLSearchParams)) {
return audit.unexpectedType(query, "URLSearchParams")
}
const data: { [key: string]: unknown } = {}
for (const [key, value] of query.entries()) {
let values = data[key] as string[] | undefined
if (values === undefined) {
values = data[key] = []
}
values.push(value)
}
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
auditSearchQueryContent(audit, data, errors, remainingKeys)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
export const GET: RequestHandler = async ({ url }) => {
const [query, queryError] = auditQuery(cleanAudit, url.searchParams) as [
{ limit: number; offset: number; q?: string },
unknown,
]
if (queryError !== null) {
console.error(
`Error in ${url.pathname} query:\n${JSON.stringify(
query,
null,
2,
)}\n\nError:\n${JSON.stringify(queryError, null, 2)}`,
)
return {
// status: 400,
headers: { "Access-Control-Allow-Origin": "*" },
body: {
error: {
query: queryError as unknown as JSONObject,
},
},
}
}
const { limit, offset } = query
const sectionTaArray = (
await db<{ data: SectionTa }[]>`
SELECT data FROM section_ta
OFFSET ${offset}
LIMIT ${limit}
`
).map(({ data }) => data)
return {
headers: { "Access-Control-Allow-Origin": "*" },
body: { section_ta: sectionTaArray as unknown as JSONObject[] },
}
}

View file

@ -0,0 +1,43 @@
<script lang="ts">
import { TreeView, SummaryView } from "augmented-data-viewer"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import type { TexteVersion } from "$lib/data"
import {
summarizeTexteVersionProperties,
summarizeLegalObject,
} from "$lib/summaries"
export let error: unknown
let texteVersion: TexteVersion
export { texteVersion as texte_version }
const summary = summarizeLegalObject(
{ key: "texte_version" },
"texte_version",
texteVersion,
)
</script>
<header class="prose my-6 max-w-full">
<h2>TEXTE_VERSION</h2>
{#if summary !== undefined}
<h1>
<SummaryView {summary} />
</h1>
{/if}
</header>
{#if error != null}
<ErrorAlert {error} />
{/if}
{#if error == null}
<TreeView
access={{ key: "texte_version" }}
frame={false}
open
summarize={summarizeTexteVersionProperties}
value={texteVersion}
/>
{/if}

View file

@ -4,19 +4,19 @@ import type { JSONObject } from "@sveltejs/kit/types/private"
import type { TexteVersion } from "$lib/data" import type { TexteVersion } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export const GET: RequestHandler = async ({ params, url }) => { export const GET: RequestHandler = async ({ params }) => {
const texte = ( const texteVersion = (
await db<{ data: TexteVersion }[]>` await db<{ data: TexteVersion }[]>`
SELECT data FROM textes_versions SELECT data FROM texte_version
WHERE id = ${params.id} WHERE id = ${params.id}
` `
).map(({ data }) => data)[0] ).map(({ data }) => data)[0]
if (texte === undefined) { if (texteVersion === undefined) {
return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 } return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 }
} }
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { texte: texte as unknown as JSONObject }, body: { texte_version: texteVersion as unknown as JSONObject },
} }
} }

View file

@ -0,0 +1,68 @@
<script lang="ts">
// import Icon from "@iconify/svelte"
// import searchIcon from "@iconify-icons/codicon/search"
import { TreeView } from "augmented-data-viewer"
// import { page } from "$app/stores"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import Pagination from "$lib/components/Pagination.svelte"
import type { TexteVersion } from "$lib/data"
import { summarizeTexteVersionProperties } from "$lib/summaries"
export let error: unknown
let texteVersionArray: TexteVersion[]
export { texteVersionArray as texte_version }
</script>
<header class="prose my-6 max-w-full">
<h1>TEXTE_VERSION</h1>
</header>
<!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get">
<div class="form-control">
<div class="input-group">
<input
class="input input-bordered"
name="q"
type="search"
bind:value={q}
/>
<button class="btn btn-square" type="submit">
<Icon icon={searchIcon} />
</button>
</div>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Législature</span>
<select
class="select select-bordered"
name="legislature"
bind:value={legislature}
>
<option value="">Toutes</option>
{#each Object.entries(Legislature) as [key, value]}
{#if value !== "*"}
<option {value}>{key}</option>
{/if}
{/each}
</select>
</label>
</div>
</form> -->
{#if error != null}
<ErrorAlert {error} />
{/if}
{#if error == null}
<TreeView
access={{ key: "texte_version" }}
frame={false}
open
summarize={summarizeTexteVersionProperties}
value={texteVersionArray}
/>
<Pagination currentPageCount={texteVersionArray.length ?? 0} />
{/if}

View file

@ -58,15 +58,15 @@ export const GET: RequestHandler = async ({ url }) => {
} }
const { limit, offset } = query const { limit, offset } = query
const textes = ( const texteVersionArray = (
await db<{ data: TexteVersion }[]>` await db<{ data: TexteVersion }[]>`
SELECT data FROM textes_versions SELECT data FROM texte_version
OFFSET ${offset} OFFSET ${offset}
LIMIT ${limit} LIMIT ${limit}
` `
).map(({ data }) => data) ).map(({ data }) => data)
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { textes: textes as unknown as JSONObject[] }, body: { texte_version: texteVersionArray as unknown as JSONObject[] },
} }
} }

View file

@ -2,20 +2,20 @@
import { TreeView, SummaryView } from "augmented-data-viewer" import { TreeView, SummaryView } from "augmented-data-viewer"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import type { Struct } from "$lib/data" import type { Textelr } from "$lib/data"
import { import {
summarizeStructProperties, summarizeTextelrProperties,
summarizeLegiObject, summarizeLegalObject,
} from "$lib/summaries" } from "$lib/summaries"
export let error: unknown export let error: unknown
export let struct: Struct export let textelr: Textelr
const summary = summarizeLegiObject({ key: "struct" }, "struct", struct) const summary = summarizeLegalObject({ key: "textelr" }, "textelr", textelr)
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h2>Structure</h2> <h2>TEXTELR</h2>
{#if summary !== undefined} {#if summary !== undefined}
<h1> <h1>
<SummaryView {summary} /> <SummaryView {summary} />
@ -29,10 +29,10 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "struct" }} access={{ key: "textelr" }}
frame={false} frame={false}
open open
summarize={summarizeStructProperties} summarize={summarizeTextelrProperties}
value={struct} value={textelr}
/> />
{/if} {/if}

View file

@ -1,22 +1,22 @@
import type { RequestHandler } from "@sveltejs/kit" import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private" import type { JSONObject } from "@sveltejs/kit/types/private"
import type { Section } from "$lib/data" import type { Textelr } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export const GET: RequestHandler = async ({ params, url }) => { export const GET: RequestHandler = async ({ params }) => {
const section = ( const textelr = (
await db<{ data: Section }[]>` await db<{ data: Textelr }[]>`
SELECT data FROM sections SELECT data FROM textelr
WHERE id = ${params.id} WHERE id = ${params.id}
` `
).map(({ data }) => data)[0] ).map(({ data }) => data)[0]
if (section === undefined) { if (textelr === undefined) {
return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 } return { headers: { "Access-Control-Allow-Origin": "*" }, status: 404 }
} }
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { section: section as unknown as JSONObject }, body: { textelr: textelr as unknown as JSONObject },
} }
} }

View file

@ -6,14 +6,16 @@
// import { page } from "$app/stores" // import { page } from "$app/stores"
import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte" import ErrorAlert from "$lib/components/errors/ErrorAlert.svelte"
import Pagination from "$lib/components/Pagination.svelte" import Pagination from "$lib/components/Pagination.svelte"
import { summarizeSectionProperties } from "$lib/summaries" import type { Textelr } from "$lib/data"
import { summarizeTextelrProperties } from "$lib/summaries"
export let error: unknown export let error: unknown
export let sections: unknown[] let textelrArray: Textelr[]
export { textelrArray as textelr }
</script> </script>
<header class="prose my-6 max-w-full"> <header class="prose my-6 max-w-full">
<h1>Sections</h1> <h1>TEXTELR</h1>
</header> </header>
<!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get"> <!-- <form action={$page.url.pathname} class="mx-auto max-w-sm" method="get">
@ -55,12 +57,12 @@
{#if error == null} {#if error == null}
<TreeView <TreeView
access={{ key: "sections" }} access={{ key: "textelr" }}
frame={false} frame={false}
open open
summarize={summarizeSectionProperties} summarize={summarizeTextelrProperties}
value={sections} value={textelrArray}
/> />
<Pagination currentPageCount={sections.length ?? 0} /> <Pagination currentPageCount={textelrArray.length ?? 0} />
{/if} {/if}

View file

@ -3,7 +3,7 @@ import type { RequestHandler } from "@sveltejs/kit"
import type { JSONObject } from "@sveltejs/kit/types/private" import type { JSONObject } from "@sveltejs/kit/types/private"
import { auditSearchQueryContent } from "$lib/auditors/queries" import { auditSearchQueryContent } from "$lib/auditors/queries"
import type { Section } from "$lib/data" import type { Textelr } from "$lib/data"
import { db } from "$lib/server/database" import { db } from "$lib/server/database"
export function auditQuery( export function auditQuery(
@ -58,15 +58,15 @@ export const GET: RequestHandler = async ({ url }) => {
} }
const { limit, offset } = query const { limit, offset } = query
const sections = ( const textelrArray = (
await db<{ data: Section }[]>` await db<{ data: Textelr }[]>`
SELECT data FROM sections SELECT data FROM textelr
OFFSET ${offset} OFFSET ${offset}
LIMIT ${limit} LIMIT ${limit}
` `
).map(({ data }) => data) ).map(({ data }) => data)
return { return {
headers: { "Access-Control-Allow-Origin": "*" }, headers: { "Access-Control-Allow-Origin": "*" },
body: { sections: sections as unknown as JSONObject[] }, body: { textelr: textelrArray as unknown as JSONObject[] },
} }
} }