diff --git a/src/lib/aggregates.ts b/src/lib/aggregates.ts index 4cac72a..dce814b 100644 --- a/src/lib/aggregates.ts +++ b/src/lib/aggregates.ts @@ -1,4 +1,10 @@ -import type { Article, SectionTa, Textelr, TexteVersion } from "$lib/legal" +import type { + Article, + SectionTa, + Textekali, + Textelr, + TexteVersion, +} from "$lib/legal" export interface Aggregate { article?: { [id: string]: Article } @@ -6,6 +12,7 @@ export interface Aggregate { ids?: string[] section_ta?: { [id: string]: SectionTa } texte_version?: { [id: string]: TexteVersion } + textekali?: { [id: string]: Textekali } textelr?: { [id: string]: Textelr } } @@ -13,7 +20,7 @@ export type Follow = typeof allFollows[number] export interface GetRechercheResult extends Aggregate { follow: Follow[] - id: string + id?: string q?: string } @@ -26,6 +33,7 @@ export interface ListTextesResult extends Aggregate { follow: Follow[] ids: string[] limit: number + nature?: string offset: number q?: string } @@ -36,6 +44,7 @@ export const allFollows = [ "STRUCT.LIEN_SECTION_TA.@id", "STRUCTURE_TA.LIEN_ART.@id", "STRUCTURE_TA.LIEN_SECTION_TA.@id", + "TEXTEKALI", "TEXTELR", ] as const export const allFollowsMutable = [...allFollows] diff --git a/src/lib/auditors/search_params.ts b/src/lib/auditors/search_params.ts index 1c17df7..1806818 100644 --- a/src/lib/auditors/search_params.ts +++ b/src/lib/auditors/search_params.ts @@ -33,6 +33,22 @@ export function auditFollowSearchParams( ) } +export function auditFollowWithFalseSearchParams( + audit: Audit, + data: { [key: string]: unknown }, + errors: { [key: string]: unknown }, + remainingKeys: Set, +): void { + audit.attribute( + data, + "follow", + true, + errors, + remainingKeys, + auditSearchParamsOptionsSet([...allFollowsMutable, "false"]), + ) +} + export function auditLimitSearchParam( audit: Audit, data: { [key: string]: unknown }, diff --git a/src/lib/components/Pagination.svelte b/src/lib/components/Pagination.svelte index f823f9e..31135c4 100644 --- a/src/lib/components/Pagination.svelte +++ b/src/lib/components/Pagination.svelte @@ -4,6 +4,7 @@ import { page } from "$app/stores" import { auditPaginationSearchParams } from "$lib/auditors/search_params" import type { PaginationQuery } from "$lib/queries" + import { urlFromUrlAndQuery } from "$lib/urls" export let count: number | undefined | null = undefined // Count is not always known. export let currentPageCount: number @@ -47,7 +48,7 @@ | string | undefined | null - | Array + | Iterable }, { limit, offset }: { limit?: number; offset?: number } = {}, ) { @@ -62,23 +63,7 @@ } else { delete query.offset } - const search = new URLSearchParams( - Object.entries(query).reduce((couples, [key, value]) => { - if (value != null) { - if (Array.isArray(value)) { - for (const item of value) { - if (item != null) { - couples.push([key, item.toString()]) - } - } - } else { - couples.push([key, value.toString()]) - } - } - return couples - }, [] as Array<[string, string]>), - ).toString() - return search ? `${pathname}?${search}` : pathname + return urlFromUrlAndQuery(pathname, query) } function pageNumber(limit: number, offset: number): number { diff --git a/src/lib/components/TexteVersionView.svelte b/src/lib/components/TexteVersionView.svelte index 4c411d3..07eb1ae 100644 --- a/src/lib/components/TexteVersionView.svelte +++ b/src/lib/components/TexteVersionView.svelte @@ -13,7 +13,7 @@ $: id = texteVersion.META.META_COMMUN.ID - $: textelr = data.textelr?.[id] + $: textelr = data.textekali?.[id] ?? data.textelr?.[id] $: summary = summarizeLegalObject( { key: "texte_version" }, diff --git a/src/lib/legal.ts b/src/lib/legal.ts index 671df52..5af66a1 100644 --- a/src/lib/legal.ts +++ b/src/lib/legal.ts @@ -153,6 +153,10 @@ export interface LienSectionTa { export interface MetaCommun { ID: string + URL: string + NATURE: string + ORIGINE: string + ANCIEN_ID: string } export interface MetaTexteChronicle { @@ -180,15 +184,9 @@ export interface SectionTa { } } -export interface Textekali { - META: { - META_COMMUN: MetaCommun - META_SPEC: { - META_TEXTE_CHRONICLE: MetaTexteChronicle - } - } -} +export type Textekali = Textelr +/// Racine de l'arborescence d'un texte législatif ou règlementaire export interface Textelr { META: { META_COMMUN: MetaCommun @@ -383,7 +381,9 @@ export function menuItemsFromLegalId( return [ { href: `/textes/${id}`, label: "Présentation" }, { href: `/texte_version/${id}`, label: "TEXTE_VERSION" }, - { href: `/textelr/${id}`, label: "TEXTELR" }, + id.startsWith("KALI") + ? { href: `/textekali/${id}`, label: "TEXTEKALI" } + : { href: `/textelr/${id}`, label: "TEXTELR" }, ] default: return undefined @@ -395,6 +395,9 @@ export function pathnameFromLegalId(id: string): string | undefined { if (rootType === undefined) { return undefined } + if (["texte_version", "textekali", "textelr"].includes(rootType)) { + return `/textes/${id}` + } return `/${rootType}/${id}` } @@ -418,11 +421,14 @@ export function pathnameFromLegalObject( case "section_ta": return `/section_ta/${(object as SectionTa).ID}` case "texte_version": - return `/texte_version/${(object as TexteVersion).META.META_COMMUN.ID}` + // return `/texte_version/${(object as TexteVersion).META.META_COMMUN.ID}` + return `/textes/${(object as TexteVersion).META.META_COMMUN.ID}` case "textekali": - return `/textekali/${(object as Textekali).META.META_COMMUN.ID}` + // return `/textekali/${(object as Textekali).META.META_COMMUN.ID}` + return `/textes/${(object as TexteVersion).META.META_COMMUN.ID}` case "textelr": - return `/textelr/${(object as Textelr).META.META_COMMUN.ID}` + // return `/textelr/${(object as Textelr).META.META_COMMUN.ID}` + return `/textes/${(object as TexteVersion).META.META_COMMUN.ID}` case "versions": return `/versions/${(object as VersionsWrapper).eli}` default: @@ -449,11 +455,14 @@ export function pathnameFromLegalObjectId( case "section_ta": return `/section_ta/${id}` case "texte_version": - return `/texte_version/${id}` + // return `/texte_version/${id}` + return `/textes/${id}` case "textekali": - return `/textekali/${id}` + // return `/textekali/${id}` + return `/textes/${id}` case "textelr": - return `/textelr/${id}` + // return `/textelr/${id}` + return `/textes/${id}` case "versions": // Here, id is an ELI. return `/versions/${id}` diff --git a/src/lib/scripts/import_jorf.ts b/src/lib/scripts/import_jorf.ts index 16f1b8f..362a309 100644 --- a/src/lib/scripts/import_jorf.ts +++ b/src/lib/scripts/import_jorf.ts @@ -232,20 +232,34 @@ async function importJorf({ resume }: { resume?: string } = {}): Promise { break } case "TEXTE_VERSION": { - const version = element as TexteVersion + const texteVersion = element as TexteVersion + const textAFragments = [ + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITRE, + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL, + ] await db` INSERT INTO texte_version ( id, - data + data, + nature, + text_search ) VALUES ( - ${version.META.META_COMMUN.ID}, - ${db.json(version as unknown as JSONValue)} + ${texteVersion.META.META_COMMUN.ID}, + ${db.json(texteVersion as unknown as JSONValue)}, + ${texteVersion.META.META_COMMUN.NATURE} + setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ) ON CONFLICT (id) DO UPDATE SET - data = ${db.json(version as unknown as JSONValue)} + data = ${db.json(texteVersion as unknown as JSONValue)}, + nature = ${texteVersion.META.META_COMMUN.NATURE}, + text_search = setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ` - texteVersionRemainingIds.delete(version.META.META_COMMUN.ID) + texteVersionRemainingIds.delete(texteVersion.META.META_COMMUN.ID) break } case "TEXTELR": { diff --git a/src/lib/scripts/import_kali.ts b/src/lib/scripts/import_kali.ts index 62df378..78bf8b8 100644 --- a/src/lib/scripts/import_kali.ts +++ b/src/lib/scripts/import_kali.ts @@ -190,20 +190,34 @@ async function importKali({ resume }: { resume?: string } = {}): Promise { break } case "TEXTE_VERSION": { - const version = element as TexteVersion + const texteVersion = element as TexteVersion + const textAFragments = [ + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITRE, + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL, + ] await db` INSERT INTO texte_version ( id, - data + data, + nature, + text_search ) VALUES ( - ${version.META.META_COMMUN.ID}, - ${db.json(version as unknown as JSONValue)} + ${texteVersion.META.META_COMMUN.ID}, + ${db.json(texteVersion as unknown as JSONValue)}, + ${texteVersion.META.META_COMMUN.NATURE} + setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ) ON CONFLICT (id) DO UPDATE SET - data = ${db.json(version as unknown as JSONValue)} + data = ${db.json(texteVersion as unknown as JSONValue)}, + nature = ${texteVersion.META.META_COMMUN.NATURE}, + text_search = setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ` - texteVersionRemainingIds.delete(version.META.META_COMMUN.ID) + texteVersionRemainingIds.delete(texteVersion.META.META_COMMUN.ID) break } case "TEXTEKALI": { diff --git a/src/lib/scripts/import_legi.ts b/src/lib/scripts/import_legi.ts index 0b8bf0a..fd7ac51 100644 --- a/src/lib/scripts/import_legi.ts +++ b/src/lib/scripts/import_legi.ts @@ -77,7 +77,7 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise { ` ).map(({ id }) => id), ) - const texteVersionRemainingElis = new Set( + const texteVersionRemainingIds = new Set( ( await db<{ id: string }[]>` SELECT id @@ -198,20 +198,34 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise { break } case "TEXTE_VERSION": { - const version = element as TexteVersion + const texteVersion = element as TexteVersion + const textAFragments = [ + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITRE, + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL, + ] await db` INSERT INTO texte_version ( id, - data + data, + nature, + text_search ) VALUES ( - ${version.META.META_COMMUN.ID}, - ${db.json(version as unknown as JSONValue)} + ${texteVersion.META.META_COMMUN.ID}, + ${db.json(texteVersion as unknown as JSONValue)}, + ${texteVersion.META.META_COMMUN.NATURE} + setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ) ON CONFLICT (id) DO UPDATE SET - data = ${db.json(version as unknown as JSONValue)} + data = ${db.json(texteVersion as unknown as JSONValue)}, + nature = ${texteVersion.META.META_COMMUN.NATURE}, + text_search = setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') ` - texteVersionRemainingElis.delete(version.META.META_COMMUN.ID) + texteVersionRemainingIds.delete(texteVersion.META.META_COMMUN.ID) break } case "TEXTELR": { @@ -310,7 +324,7 @@ async function importLegi({ resume }: { resume?: string } = {}): Promise { WHERE id = ${id} ` } - for (const id of texteVersionRemainingElis) { + for (const id of texteVersionRemainingIds) { console.log(`Deleting TEXTE_VERSION ${id}…`) await db` DELETE FROM texte_version diff --git a/src/lib/server/aggregates.ts b/src/lib/server/aggregates.ts index 83c700c..786d743 100644 --- a/src/lib/server/aggregates.ts +++ b/src/lib/server/aggregates.ts @@ -6,6 +6,7 @@ import { type Article, type LegalObjectType, type SectionTa, + type Textekali, type Textelr, type TexteVersion, } from "$lib/legal" @@ -17,6 +18,7 @@ export class Aggregator { requestedTypeAndIds: Set = new Set() section_ta: { [id: string]: SectionTa } = {} texte_version: { [id: string]: TexteVersion } = {} + textekali: { [id: string]: Textekali } = {} textelr: { [id: string]: Textelr } = {} visitedTypeAndIds: Set = new Set() @@ -58,6 +60,25 @@ export class Aggregator { } } + addTextekali(textekali: Textekali): void { + const id = textekali.META.META_COMMUN.ID + this.textekali[id] = textekali + + if (this.follow.has("STRUCT.LIEN_ART.@id")) { + for (const lien of iterArrayOrSingleton(textekali.STRUCT.LIEN_ART)) { + this.requestId(lien["@id"]) + } + } + + if (this.follow.has("STRUCT.LIEN_SECTION_TA.@id")) { + for (const lien of iterArrayOrSingleton( + textekali.STRUCT.LIEN_SECTION_TA, + )) { + this.requestId(lien["@id"]) + } + } + } + addTextelr(textelr: Textelr): void { const id = textelr.META.META_COMMUN.ID this.textelr[id] = textelr @@ -79,6 +100,9 @@ export class Aggregator { const id = texteVersion.META.META_COMMUN.ID this.texte_version[id] = texteVersion + if (this.follow.has("TEXTEKALI")) { + this.requestTypeAndId(["textekali", id]) + } if (this.follow.has("TEXTELR")) { this.requestTypeAndId(["textelr", id]) } @@ -97,12 +121,9 @@ export class Aggregator { } async getAll(): Promise { - while (true) { - if (this.requestedTypeAndIds.size === 0) { - break - } + while (this.requestedTypeAndIds.size > 0) { console.log( - "this.requestedTypeAndIds.size", + "Aggregator.getAll: this.requestedTypeAndIds.size =", this.requestedTypeAndIds.size, ) @@ -138,6 +159,22 @@ export class Aggregator { } } + { + const textekaliTypeAndIds = [...this.requestedTypeAndIds].filter( + ([type]) => type === "textekali", + ) + if (textekaliTypeAndIds.length > 0) { + this.deleteFromRequestedTypeAndIds(textekaliTypeAndIds) + for (const { data } of await db<{ id: string; data: Textekali }[]>` + SELECT id, data FROM textekali + WHERE id IN ${db(textekaliTypeAndIds.map(([, id]) => id))} + `) { + this.addTextekali(data) + } + this.addToVisitedTypeAndIds(textekaliTypeAndIds) + } + } + { const textelrTypeAndIds = [...this.requestedTypeAndIds].filter( ([type]) => type === "textelr", @@ -180,6 +217,9 @@ export class Aggregator { if (Object.keys(this.texte_version).length > 0) { json.texte_version = this.texte_version } + if (Object.keys(this.textekali).length > 0) { + json.textekali = this.textekali + } if (Object.keys(this.textelr).length > 0) { json.textelr = this.textelr } diff --git a/src/lib/server/database/configuration.ts b/src/lib/server/database/configuration.ts index 81ccf0d..a07afaa 100644 --- a/src/lib/server/database/configuration.ts +++ b/src/lib/server/database/configuration.ts @@ -1,5 +1,7 @@ import assert from "assert" +import type { SerializableParameter } from "postgres" +import type { TexteVersion } from "$lib/legal" import { db, type Version, versionNumber } from "$lib/server/database" export async function configureDatabase() { @@ -42,6 +44,17 @@ export async function configureDatabase() { await db`ALTER TABLE IF EXISTS textes RENAME TO texte_version` } + if (version.number < 3) { + await db` + ALTER TABLE IF EXISTS texte_version + ADD COLUMN IF NOT EXISTS nature text + ` + await db` + ALTER TABLE IF EXISTS texte_version + ADD COLUMN IF NOT EXISTS text_search tsvector + ` + } + // Types // Tables @@ -108,7 +121,9 @@ export async function configureDatabase() { await db` CREATE TABLE IF NOT EXISTS texte_version ( id char(20) PRIMARY KEY, - data jsonb NOT NULL + data jsonb NOT NULL, + nature text NOT NULL, + text_search tsvector NOT NULL ) ` @@ -139,8 +154,62 @@ export async function configureDatabase() { // Apply patches that must be executed after every table is created. + if (version.number < 3) { + // Fill "nature" column of table texte_version. + for await (const rows of db< + Array<{ data: TexteVersion; id: string; nature: string }> + >`SELECT data, id, nature FROM texte_version`.cursor(100)) { + for (const { data, id, nature } of rows) { + if (data.META.META_COMMUN.NATURE !== nature) { + await db` + UPDATE texte_version + SET nature = ${data.META.META_COMMUN.NATURE} + WHERE id = ${id} + ` + } + } + } + await db` + ALTER TABLE texte_version + ALTER COLUMN nature SET NOT NULL + ` + + // Fill "text_search" column of table texte_version. + for await (const rows of db< + Array<{ data: TexteVersion; id: string }> + >`SELECT data, id FROM texte_version`.cursor(100)) { + for (const { data, id } of rows) { + const textAFragments = [ + data.META.META_SPEC.META_TEXTE_VERSION.TITRE, + data.META.META_SPEC.META_TEXTE_VERSION.TITREFULL, + ] + // const vector = (await db<{vector: SerializableParameter}[]>` + // SELECT + // setweight(to_tsvector('french', ${textAFragments.join(" ")}), 'A') + // AS vector + // `)[0].vector + await db` + UPDATE texte_version + SET text_search = setweight(to_tsvector('french', ${textAFragments.join( + " ", + )}), 'A') + WHERE id = ${id} + ` + } + } + await db` + ALTER TABLE texte_version + ALTER COLUMN text_search SET NOT NULL + ` + } + // Add indexes once every table and column exists. + await db` + CREATE INDEX IF NOT EXISTS texte_version_nature_key + ON texte_version (nature) + ` + // await db` // CREATE INDEX IF NOT EXISTS article_autocompletions_trigrams_idx // ON article_autocompletions diff --git a/src/lib/server/database/index.ts b/src/lib/server/database/index.ts index aa16dab..fc5fd02 100644 --- a/src/lib/server/database/index.ts +++ b/src/lib/server/database/index.ts @@ -14,7 +14,7 @@ export const db = postgres({ port: config.db.port, user: config.db.user, }) -export const versionNumber = 2 +export const versionNumber = 3 /// Check that database exists and is up to date. export async function checkDb(): Promise { diff --git a/src/lib/server/sql.ts b/src/lib/server/sql.ts new file mode 100644 index 0000000..90e297f --- /dev/null +++ b/src/lib/server/sql.ts @@ -0,0 +1,13 @@ +import type { PendingQuery, Row } from "postgres" + +import { db } from "$lib/server/database" + +export function joinSqlClauses( + joiner: PendingQuery, + clauses: Array>, +): PendingQuery { + return clauses.reduce( + (acc, clause, index) => db`${acc} ${index > 0 ? joiner : db``} ${clause}`, + db``, + ) +} diff --git a/src/lib/urls.ts b/src/lib/urls.ts new file mode 100644 index 0000000..2390225 --- /dev/null +++ b/src/lib/urls.ts @@ -0,0 +1,37 @@ +export function urlFromUrlAndQuery( + urlOrPathname: string, + query: { + [key: string]: + | boolean + | number + | string + | undefined + | null + | Iterable + }, +) { + const search = new URLSearchParams( + Object.entries(query).reduce((couples, [key, value]) => { + if (value != null) { + if ( + typeof value !== "string" && + typeof ( + value as Iterable + )[Symbol.iterator] === "function" + ) { + for (const item of value as Iterable< + boolean | number | string | undefined | null + >) { + if (item != null) { + couples.push([key, item.toString()]) + } + } + } else { + couples.push([key, value.toString()]) + } + } + return couples + }, [] as Array<[string, string]>), + ).toString() + return search ? `${urlOrPathname}?${search}` : urlOrPathname +} diff --git a/src/routes/api/textes/+server.ts b/src/routes/api/textes/+server.ts index edcecb2..c27701a 100644 --- a/src/routes/api/textes/+server.ts +++ b/src/routes/api/textes/+server.ts @@ -1,5 +1,11 @@ -import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core" +import { + type Audit, + auditSetNullish, + auditTrimString, + cleanAudit, +} from "@auditors/core" import { error } from "@sveltejs/kit" +import type { PendingQuery, Row } from "postgres" import type { Follow } from "$lib/aggregates" import { @@ -7,10 +13,12 @@ import { auditLimitSearchParam, auditOffsetSearchParam, auditQSearchParam, + auditSingleton, } from "$lib/auditors/search_params" import type { TexteVersion } from "$lib/legal" import { Aggregator } from "$lib/server/aggregates" import { db } from "$lib/server/database" +import { joinSqlClauses } from "$lib/server/sql" import type { RequestHandler } from "./$types" @@ -38,6 +46,14 @@ export function auditSearchParams( auditFollowSearchParams(audit, data, errors, remainingKeys) auditLimitSearchParam(audit, data, errors, remainingKeys) + audit.attribute( + data, + "nature", + true, + errors, + remainingKeys, + auditSingleton(auditTrimString), + ) auditOffsetSearchParam(audit, data, errors, remainingKeys) auditQSearchParam(audit, data, errors, remainingKeys) @@ -54,6 +70,7 @@ export const GET: RequestHandler = async ({ url }) => { limit: number offset: number q?: string + nature?: string }, unknown, ] @@ -67,13 +84,47 @@ export const GET: RequestHandler = async ({ url }) => { ) throw error(400, JSON.stringify(queryError, null, 2)) } - const { follow, limit, offset, q } = query + const { follow, limit, nature, offset, q } = query + + const joinClauses: Array> = [] + const orderByClauses: Array> = [] + const selectClauses: Array> = [db`data`] + const whereClauses: Array> = [] + + if (nature !== undefined) { + whereClauses.push(db`nature = ${nature}`) + } + if (q !== undefined) { + joinClauses.push(db` + CROSS JOIN ( + SELECT plainto_tsquery('french', ${q}) AS query + ) AS constants + `) + orderByClauses.push(db`rank DESC`) + selectClauses.push(db`ts_rank_cd(text_search, query) AS rank`) + whereClauses.push(db`query @@ text_search`) + } + + const joinClause = joinSqlClauses(db``, joinClauses) + const orderByClause = + orderByClauses.length > 0 + ? db`ORDER BY ${joinSqlClauses(db`,`, orderByClauses)}` + : db`` + const selectClause = joinSqlClauses(db`,`, selectClauses) + const whereClause = + whereClauses.length > 0 + ? db`WHERE ${joinSqlClauses(db`AND`, whereClauses)}` + : db`` + const texteVersionArray = ( await db<{ data: TexteVersion }[]>` - SELECT data FROM texte_version - OFFSET ${offset} - LIMIT ${limit} - ` + SELECT ${db`${selectClause}`} FROM texte_version + ${db`${joinClause}`} + ${db`${whereClause}`} + ${db`${orderByClause}`} + OFFSET ${offset} + LIMIT ${limit} + ` ).map(({ data }) => data) const aggregator = new Aggregator(follow) @@ -91,6 +142,7 @@ export const GET: RequestHandler = async ({ url }) => { (texteVersion) => texteVersion.META.META_COMMUN.ID, ), limit, + nature, offset, q, }, diff --git a/src/routes/api/textes/natures/+server.ts b/src/routes/api/textes/natures/+server.ts new file mode 100644 index 0000000..2140412 --- /dev/null +++ b/src/routes/api/textes/natures/+server.ts @@ -0,0 +1,25 @@ +import { db } from "$lib/server/database" + +import type { RequestHandler } from "./$types" + +export const GET: RequestHandler = async ({ url }) => { + const natures = ( + await db<{ nature: string }[]>` + SELECT distinct nature FROM texte_version + ` + ).map(({ nature }) => nature) + return new Response( + JSON.stringify( + { + natures, + }, + null, + 2, + ), + { + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }, + ) +} diff --git a/src/routes/recherche/+page.svelte b/src/routes/recherche/+page.svelte index 0367d2a..3467632 100644 --- a/src/routes/recherche/+page.svelte +++ b/src/routes/recherche/+page.svelte @@ -4,13 +4,16 @@ import { TreeView } from "augmented-data-viewer" import { page } from "$app/stores" - import Pagination from "$lib/components/Pagination.svelte" + // import Pagination from "$lib/components/Pagination.svelte" import { summarizeArticleProperties } from "$lib/summaries" import type { PageData } from "./$types" export let data: PageData - let { articles, q } = data + + $: id = data.id + $: q = data.q + $: article = id === undefined ? undefined : data.article?.[id]
@@ -34,7 +37,7 @@ {#if q !== undefined} - {#if articles === undefined} + {#if article === undefined}

Aucun article trouvé

{:else} - + {/if} {/if} diff --git a/src/routes/textekali/[id]/+page.svelte b/src/routes/textekali/[id]/+page.svelte index 729eeeb..cc8d0ca 100644 --- a/src/routes/textekali/[id]/+page.svelte +++ b/src/routes/textekali/[id]/+page.svelte @@ -1,6 +1,7 @@ + +

TEXTEKALI

{#if summary !== undefined} diff --git a/src/routes/textes/+page.svelte b/src/routes/textes/+page.svelte index 2914dca..0db2d63 100644 --- a/src/routes/textes/+page.svelte +++ b/src/routes/textes/+page.svelte @@ -3,7 +3,7 @@ // import searchIcon from "@iconify-icons/codicon/search" import { TreeView } from "augmented-data-viewer" - // import { page } from "$app/stores" + import { page } from "$app/stores" import Pagination from "$lib/components/Pagination.svelte" import type { Article, SectionTa, Textelr, TexteVersion } from "$lib/legal" import { summarizeTexteVersionProperties } from "$lib/summaries" @@ -12,51 +12,80 @@ export let data: PageData - const articleById = data.article as { [id: string]: Article } - const sectionTaById = data.sectionTa as { [id: string]: SectionTa } - const texteVersionById = data.texte_version as { [id: string]: TexteVersion } - const texteVersionArray = (data.ids as string[]).map( + let nature = data.nature ?? "" + + const naturesPromise = (async (): Promise => { + const apiUrl = "/api/textes/natures" + const response = await fetch(apiUrl, { + headers: { Accept: "application/json" }, + }) + if (!response.ok) { + const text = await response.text() + console.error( + `Error in ${$page.url.pathname} while calling ${apiUrl}:\n${response.status} ${response.statusText}\n\n${text}`, + ) + return undefined + } + return (await response.json()).natures + })() + let q = data.q + + $: texteVersionById = data.texte_version as { [id: string]: TexteVersion } + $: texteVersionArray = (data.ids as string[]).map( (id) => texteVersionById[id], ) - const textelrById = data.textelr as { [id: string]: Textelr[] }

Codes, lois et règlements

- + +
+ + +
+ + + { - const apiUrl = `/api${url.pathname}` + const apiUrl = `/api${url.pathname}${url.search}` const response = await fetch(apiUrl, { headers: { Accept: "application/json" }, }) diff --git a/src/routes/textes/[id]/+page.svelte b/src/routes/textes/[id]/+page.svelte index c0464ac..e50fd85 100644 --- a/src/routes/textes/[id]/+page.svelte +++ b/src/routes/textes/[id]/+page.svelte @@ -9,12 +9,12 @@ export let data: PageData - const id = data.id! - const texteVersion = data.texte_version![id] - let showArticles = false let showRawData = false + $: id = data.id! + $: texteVersion = data.texte_version![id] + $: summary = summarizeLegalObject( { key: "texte_version" }, "texte_version", diff --git a/src/routes/textes/[id]/+page.ts b/src/routes/textes/[id]/+page.ts index 0d1d740..681980b 100644 --- a/src/routes/textes/[id]/+page.ts +++ b/src/routes/textes/[id]/+page.ts @@ -1,11 +1,76 @@ import { error } from "@sveltejs/kit" +import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core" -import type { GetTexteResult } from "$lib/aggregates" +import type { Follow, GetTexteResult } from "$lib/aggregates" +import { auditFollowWithFalseSearchParams } from "$lib/auditors/search_params" +import { urlFromUrlAndQuery } from "$lib/urls" import type { PageLoad } from "./$types" +export function auditSearchParams( + 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)) + + auditFollowWithFalseSearchParams(audit, data, errors, remainingKeys) + + return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({})) +} + export const load: PageLoad = async ({ fetch, url }) => { - const apiUrl = `/api${url.pathname}` + const [query, queryError] = auditSearchParams( + cleanAudit, + url.searchParams, + ) as [ + { + follow: Set + }, + 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)}`, + ) + throw error(400, JSON.stringify(queryError, null, 2)) + } + const { follow: requestedFollow } = query + + let follow = new Set(requestedFollow) + const followFalse = follow.delete("false") + if (follow.size === 0 && !followFalse) { + // Set default follow + follow = new Set([ + // "STRUCT.LIEN_ART.@id", + "STRUCT.LIEN_SECTION_TA.@id", + // "STRUCTURE_TA.LIEN_ART.@id", + "STRUCTURE_TA.LIEN_SECTION_TA.@id", + "TEXTEKALI", + "TEXTELR", + ]) + } + + const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow }) const response = await fetch(apiUrl, { headers: { Accept: "application/json" }, })