Show CREATION targets of articles.
This commit is contained in:
parent
5b1c33b27e
commit
d331d0ccf8
9 changed files with 345 additions and 4 deletions
|
@ -1,23 +1,36 @@
|
||||||
import type {
|
import type {
|
||||||
Article,
|
Article,
|
||||||
|
DossierLegislatif,
|
||||||
|
Idcc,
|
||||||
|
Jo,
|
||||||
SectionTa,
|
SectionTa,
|
||||||
Textekali,
|
Textekali,
|
||||||
Textelr,
|
Textelr,
|
||||||
TexteVersion,
|
TexteVersion,
|
||||||
|
Versions,
|
||||||
} from "$lib/legal"
|
} from "$lib/legal"
|
||||||
|
|
||||||
export interface Aggregate {
|
export interface Aggregate {
|
||||||
article?: { [id: string]: Article }
|
article?: { [id: string]: Article }
|
||||||
|
dossier_legislatif?: { [id: string]: DossierLegislatif }
|
||||||
id?: string
|
id?: string
|
||||||
|
idcc?: { [id: string]: Idcc }
|
||||||
ids?: string[]
|
ids?: string[]
|
||||||
|
jo?: { [id: string]: Jo }
|
||||||
section_ta?: { [id: string]: SectionTa }
|
section_ta?: { [id: string]: SectionTa }
|
||||||
texte_version?: { [id: string]: TexteVersion }
|
texte_version?: { [id: string]: TexteVersion }
|
||||||
textekali?: { [id: string]: Textekali }
|
textekali?: { [id: string]: Textekali }
|
||||||
textelr?: { [id: string]: Textelr }
|
textelr?: { [id: string]: Textelr }
|
||||||
|
versions?: { [eli: string]: Versions }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Follow = typeof allFollows[number]
|
export type Follow = typeof allFollows[number]
|
||||||
|
|
||||||
|
export interface GetArticleResult extends Aggregate {
|
||||||
|
follow: Follow[]
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetRechercheResult extends Aggregate {
|
export interface GetRechercheResult extends Aggregate {
|
||||||
follow: Follow[]
|
follow: Follow[]
|
||||||
id?: string
|
id?: string
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { iterArrayOrSingleton } from "@tricoteuses/explorer-tools"
|
||||||
import { SummaryView } from "augmented-data-viewer"
|
import { SummaryView } from "augmented-data-viewer"
|
||||||
|
|
||||||
|
import type { Aggregate } from "$lib/aggregates"
|
||||||
|
import LienView from "$lib/components/LienView.svelte"
|
||||||
import type { Article } from "$lib/legal"
|
import type { Article } from "$lib/legal"
|
||||||
import { summarizeLegalObject } from "$lib/summaries"
|
import { summarizeLegalObject } from "$lib/summaries"
|
||||||
|
|
||||||
export let article: Article
|
export let article: Article
|
||||||
|
export let data: Aggregate
|
||||||
export let level = 1
|
export let level = 1
|
||||||
|
|
||||||
|
$: liens = [...iterArrayOrSingleton(article.LIENS?.LIEN)]
|
||||||
|
$: ciblesCreation = liens.filter(
|
||||||
|
(lien) => lien["@sens"] === "cible" && lien["@typelien"] === "CREATION",
|
||||||
|
)
|
||||||
$: summary = summarizeLegalObject({ key: "article" }, "article", article)
|
$: summary = summarizeLegalObject({ key: "article" }, "article", article)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,3 +25,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{@html article.BLOC_TEXTUEL.CONTENU}
|
{@html article.BLOC_TEXTUEL.CONTENU}
|
||||||
|
|
||||||
|
{#if ciblesCreation.length > 0}
|
||||||
|
<ul>
|
||||||
|
{#each liens as lien}
|
||||||
|
<li>
|
||||||
|
<LienView {data} level={level + 1} {lien} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
|
92
src/lib/components/LienView.svelte
Normal file
92
src/lib/components/LienView.svelte
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from "@iconify/svelte"
|
||||||
|
import dash from "@iconify-icons/codicon/dash"
|
||||||
|
import triangleDown from "@iconify-icons/codicon/triangle-down"
|
||||||
|
import triangleRight from "@iconify-icons/codicon/triangle-right"
|
||||||
|
|
||||||
|
import type { Aggregate } from "$lib/aggregates"
|
||||||
|
import ArticleView from "$lib/components/ArticleView.svelte"
|
||||||
|
import SectionTaView from "$lib/components/SectionTaView.svelte"
|
||||||
|
import TexteVersionView from "$lib/components/TexteVersionView.svelte"
|
||||||
|
import {
|
||||||
|
type LegalObject,
|
||||||
|
type LegalObjectType,
|
||||||
|
type Lien,
|
||||||
|
pathnameFromLegalId,
|
||||||
|
rootTypeFromLegalId,
|
||||||
|
} from "$lib/legal"
|
||||||
|
|
||||||
|
export let data: Aggregate
|
||||||
|
export let level = 1
|
||||||
|
export let lien: Lien
|
||||||
|
|
||||||
|
let open = false
|
||||||
|
|
||||||
|
$: id = lien["@id"]
|
||||||
|
$: rootType = rootTypeFromLegalId(id)
|
||||||
|
$: componentAndProperties = componentAndPropertiesFromTypeAndTarget(rootType)
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
open = !open
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentAndPropertiesFromTypeAndTarget(
|
||||||
|
type: LegalObjectType | undefined,
|
||||||
|
) {
|
||||||
|
if (type === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const target =
|
||||||
|
rootType === undefined
|
||||||
|
? undefined
|
||||||
|
: (data[rootType] as { [id: string]: LegalObject })?.[id]
|
||||||
|
switch (type) {
|
||||||
|
case "article":
|
||||||
|
return {
|
||||||
|
component: ArticleView,
|
||||||
|
properties: { article: target, data, level },
|
||||||
|
}
|
||||||
|
case "section_ta":
|
||||||
|
return {
|
||||||
|
component: SectionTaView,
|
||||||
|
properties: { data, level, sectionTa: target },
|
||||||
|
}
|
||||||
|
case "texte_version":
|
||||||
|
return {
|
||||||
|
component: TexteVersionView,
|
||||||
|
properties: { data, level, texteVersion: target },
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if componentAndProperties === undefined}
|
||||||
|
<div class="inline-flex align-top" on:click|stopPropagation={toggle}>
|
||||||
|
<Icon class="mt-1 inline-block shrink-0" icon={dash} inline />
|
||||||
|
<a class="link link-hover link-primary" href={pathnameFromLegalId(id)}>
|
||||||
|
{lien["#text"]}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="inline-flex cursor-pointer align-top"
|
||||||
|
on:click|stopPropagation={toggle}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
class="mt-1 inline-block shrink-0"
|
||||||
|
icon={open ? triangleDown : triangleRight}
|
||||||
|
inline
|
||||||
|
/>
|
||||||
|
{lien["#text"]}
|
||||||
|
</div>
|
||||||
|
{#if open}
|
||||||
|
<div class="ml-2 border-l-4 pl-2">
|
||||||
|
<svelte:component
|
||||||
|
this={componentAndProperties.component}
|
||||||
|
{...componentAndProperties.properties}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -28,7 +28,7 @@
|
||||||
{#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA.LIEN_ART)] as lienArt}
|
{#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA.LIEN_ART)] as lienArt}
|
||||||
{@const article = data.article?.[lienArt["@id"]]}
|
{@const article = data.article?.[lienArt["@id"]]}
|
||||||
{#if article !== undefined}
|
{#if article !== undefined}
|
||||||
<ArticleView {article} level={level + 1} />
|
<ArticleView {article} {data} level={level + 1} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{:else if sectionTa.STRUCTURE_TA.LIEN_ART !== undefined}
|
{:else if sectionTa.STRUCTURE_TA.LIEN_ART !== undefined}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
{#each [...iterArrayOrSingleton(textelr.STRUCT.LIEN_ART)] as lienArt}
|
{#each [...iterArrayOrSingleton(textelr.STRUCT.LIEN_ART)] as lienArt}
|
||||||
{@const article = data.article?.[lienArt["@id"]]}
|
{@const article = data.article?.[lienArt["@id"]]}
|
||||||
{#if article !== undefined}
|
{#if article !== undefined}
|
||||||
<ArticleView {article} level={level + 1} />
|
<ArticleView {article} {data} level={level + 1} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{:else if textelr.STRUCT.LIEN_ART !== undefined}
|
{:else if textelr.STRUCT.LIEN_ART !== undefined}
|
||||||
|
|
|
@ -395,6 +395,9 @@ export function pathnameFromLegalId(id: string): string | undefined {
|
||||||
if (rootType === undefined) {
|
if (rootType === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
if (rootType === "article") {
|
||||||
|
return `/articles/${id}`
|
||||||
|
}
|
||||||
if (["texte_version", "textekali", "textelr"].includes(rootType)) {
|
if (["texte_version", "textekali", "textelr"].includes(rootType)) {
|
||||||
return `/textes/${id}`
|
return `/textes/${id}`
|
||||||
}
|
}
|
||||||
|
@ -407,7 +410,8 @@ export function pathnameFromLegalObject(
|
||||||
): string {
|
): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "article":
|
case "article":
|
||||||
return `/article/${(object as Article).META.META_COMMUN.ID}`
|
// return `/article/${(object as Article).META.META_COMMUN.ID}`
|
||||||
|
return `/articles/${(object as Article).META.META_COMMUN.ID}`
|
||||||
case "dossier_legislatif":
|
case "dossier_legislatif":
|
||||||
return `/dossier_legislatif/${
|
return `/dossier_legislatif/${
|
||||||
(object as DossierLegislatif).META.META_COMMUN.ID
|
(object as DossierLegislatif).META.META_COMMUN.ID
|
||||||
|
@ -442,7 +446,8 @@ export function pathnameFromLegalObjectId(
|
||||||
): string {
|
): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "article":
|
case "article":
|
||||||
return `/article/${id}`
|
// return `/article/${id}`
|
||||||
|
return `/articles/${id}`
|
||||||
case "dossier_legislatif":
|
case "dossier_legislatif":
|
||||||
return `/dossier_legislatif/${id}`
|
return `/dossier_legislatif/${id}`
|
||||||
case "id":
|
case "id":
|
||||||
|
|
91
src/routes/api/articles/[id]/+server.ts
Normal file
91
src/routes/api/articles/[id]/+server.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
|
||||||
|
import { auditFollowSearchParams } from "$lib/auditors/search_params"
|
||||||
|
|
||||||
|
import type { Follow } from "$lib/aggregates"
|
||||||
|
import type { Article } from "$lib/legal"
|
||||||
|
import { Aggregator } from "$lib/server/aggregates"
|
||||||
|
import { db } from "$lib/server/database"
|
||||||
|
|
||||||
|
import type { RequestHandler } 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))
|
||||||
|
|
||||||
|
auditFollowSearchParams(audit, data, errors, remainingKeys)
|
||||||
|
|
||||||
|
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async ({ params, url }) => {
|
||||||
|
const [query, queryError] = auditSearchParams(
|
||||||
|
cleanAudit,
|
||||||
|
url.searchParams,
|
||||||
|
) as [
|
||||||
|
{
|
||||||
|
follow: Set<Follow>
|
||||||
|
},
|
||||||
|
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 } = query
|
||||||
|
const article = (
|
||||||
|
await db<{ data: Article }[]>`
|
||||||
|
SELECT data FROM article
|
||||||
|
WHERE ID = ${params.id}
|
||||||
|
`
|
||||||
|
).map(({ data }) => data)[0]
|
||||||
|
if (article === undefined) {
|
||||||
|
throw error(404, `ARTICLE ${params.id} non trouvé`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const aggregator = new Aggregator(follow)
|
||||||
|
aggregator.addArticle(article)
|
||||||
|
await aggregator.getAll()
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
...aggregator.toJson(),
|
||||||
|
follow: [...follow],
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
44
src/routes/articles/[id]/+page.svelte
Normal file
44
src/routes/articles/[id]/+page.svelte
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { TreeView, SummaryView } from "augmented-data-viewer"
|
||||||
|
|
||||||
|
import ArticleView from "$lib/components/ArticleView.svelte"
|
||||||
|
import IdPagesSwitcher from "$lib/components/IdPagesSwitcher.svelte"
|
||||||
|
import { summarizeLegalObject } from "$lib/summaries"
|
||||||
|
|
||||||
|
import type { PageData } from "./$types"
|
||||||
|
|
||||||
|
export let data: PageData
|
||||||
|
|
||||||
|
let showRawData = false
|
||||||
|
|
||||||
|
$: id = data.id!
|
||||||
|
$: article = data.article![id]
|
||||||
|
|
||||||
|
$: summary = summarizeLegalObject({ key: "article" }, "article", article)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<IdPagesSwitcher {id} />
|
||||||
|
|
||||||
|
<header class="prose my-6 max-w-full">
|
||||||
|
<h2>Articles</h2>
|
||||||
|
{#if summary !== undefined}
|
||||||
|
<h1>
|
||||||
|
<SummaryView {summary} />
|
||||||
|
</h1>
|
||||||
|
{/if}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form class="max-w-xs" on:submit|preventDefault>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label cursor-pointer">
|
||||||
|
<span class="label-text">Données brutes</span>
|
||||||
|
<input class="toggle" type="checkbox" bind:checked={showRawData} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if showRawData}
|
||||||
|
<TreeView frame={false} open value={data} />
|
||||||
|
{:else}
|
||||||
|
<ArticleView {article} {data} />
|
||||||
|
{/if}
|
78
src/routes/articles/[id]/+page.ts
Normal file
78
src/routes/articles/[id]/+page.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
|
||||||
|
|
||||||
|
import type { Follow, GetArticleResult } 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 [query, queryError] = auditSearchParams(
|
||||||
|
cleanAudit,
|
||||||
|
url.searchParams,
|
||||||
|
) as [
|
||||||
|
{
|
||||||
|
follow: Set<Follow | "false">
|
||||||
|
},
|
||||||
|
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(["LIENS.LIEN[@sens=cible,@typelien=CREATION].@id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text()
|
||||||
|
console.error(
|
||||||
|
`Error in ${url.pathname} while calling ${apiUrl}:\n${response.status} ${response.statusText}\n\n${text}`,
|
||||||
|
)
|
||||||
|
throw error(response.status, text)
|
||||||
|
}
|
||||||
|
return (await response.json()) as GetArticleResult
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue