-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(admin): list and delete index and repeq (#1779)
--------- Co-authored-by: Pierre-Olivier Mauguet <[email protected]>
- Loading branch information
1 parent
9e2a6f9
commit 18f9ec4
Showing
31 changed files
with
1,698 additions
and
655 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,31 @@ | ||
-- declaration | ||
CREATE INDEX IF NOT EXISTS idx_effectifs ON declaration ((data->'entreprise'->'effectifs'->>'tranche')); | ||
CREATE INDEX IF NOT EXISTS idx_status ON declaration (declared_at) WHERE declared_at IS NOT NULL; | ||
CREATE INDEX IF NOT EXISTS idx_declaration_ues_name ON declaration ((data->'entreprise'->'ues'->>'name')) | ||
WHERE data->'entreprise'->'ues'->>'name' IS NOT NULL; | ||
CREATE INDEX IF NOT EXISTS idx_status ON declaration (declared_at) | ||
WHERE declared_at IS NOT NULL; | ||
CREATE INDEX IF NOT EXISTS idx_declaration_email ON declaration ((data->'déclarant'->>'email')); | ||
CREATE INDEX IF NOT EXISTS idx_declaration_siren ON declaration (siren); | ||
CREATE INDEX IF NOT EXISTS idx_declaration_year ON declaration (year); | ||
-- search | ||
CREATE INDEX IF NOT EXISTS idx_ft ON search USING GIN (ft); | ||
CREATE INDEX IF NOT EXISTS idx_region ON search(region); | ||
CREATE INDEX IF NOT EXISTS idx_departement ON search(departement); | ||
CREATE INDEX IF NOT EXISTS idx_naf ON search(section_naf); | ||
CREATE INDEX IF NOT EXISTS idx_declared_at ON search (declared_at); | ||
CREATE INDEX IF NOT EXISTS idx_email ON representation_equilibree((data->'déclarant'->>'email')); | ||
CREATE INDEX IF NOT EXISTS idx_siren ON representation_equilibree(siren); | ||
CREATE INDEX IF NOT EXISTS idx_search_siren ON search (siren); | ||
CREATE INDEX IF NOT EXISTS idx_search_year ON search (year); | ||
-- representation_equilibree | ||
CREATE INDEX IF NOT EXISTS idx_email ON representation_equilibree ((data->'déclarant'->>'email')); | ||
CREATE INDEX IF NOT EXISTS idx_siren ON representation_equilibree (siren); | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_status ON representation_equilibree (declared_at) | ||
WHERE declared_at IS NOT NULL; | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_year ON representation_equilibree (year); | ||
-- search | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_ft ON search_representation_equilibree USING GIN (ft); | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_region ON search_representation_equilibree(region); | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_departement ON search_representation_equilibree(departement); | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_naf ON search_representation_equilibree(section_naf); | ||
CREATE INDEX IF NOT EXISTS idx_representation_equilibree_declared_at ON search_representation_equilibree (declared_at); | ||
CREATE INDEX IF NOT EXISTS idx_search_representation_equilibree_siren ON search_representation_equilibree (siren); | ||
CREATE INDEX IF NOT EXISTS idx_search_representation_equilibree_year ON search_representation_equilibree (year); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
packages/app/src/api/core-domain/repo/IAdminDeclarationRepo.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { type AdminDeclarationDTO } from "@common/core-domain/dtos/AdminDeclarationDTO"; | ||
import { type SearchAdminDeclarationInput } from "@common/core-domain/dtos/SearchDeclarationDTO"; | ||
import { type SearchDefaultCriteria, type SearchDTORepo } from "@common/shared-domain"; | ||
|
||
import { type AdminDeclarationRaw } from "../infra/db/raw"; | ||
|
||
export type AdminDeclarationSearchCriteria = SearchAdminDeclarationInput & SearchDefaultCriteria; | ||
|
||
export interface IAdminDeclarationRepo extends SearchDTORepo<AdminDeclarationSearchCriteria, AdminDeclarationDTO> {} | ||
|
||
export const orderByMap = { | ||
createdAt: "created_at", | ||
declarantEmail: "declarant_email", | ||
declarantFirstName: "declarant_firstname", | ||
declarantLastName: "declarant_lastname", | ||
index: "index", | ||
name: "name", | ||
siren: "siren", | ||
type: "type", | ||
year: "year", | ||
} as const satisfies Record<Exclude<AdminDeclarationSearchCriteria["orderBy"], undefined>, keyof AdminDeclarationRaw>; |
190 changes: 190 additions & 0 deletions
190
packages/app/src/api/core-domain/repo/impl/PostgresAdminDeclarationRepo.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import { type AdminDeclarationRaw } from "@api/core-domain/infra/db/raw"; | ||
import { sql } from "@api/shared-domain/infra/db/postgres"; | ||
import { type AdminDeclarationDTO } from "@common/core-domain/dtos/AdminDeclarationDTO"; | ||
import { type SQLCount } from "@common/shared-domain"; | ||
import { cleanFullTextSearch } from "@common/utils/postgres"; | ||
import { isFinite } from "lodash"; | ||
import { type Helper } from "postgres"; | ||
|
||
import { type AdminDeclarationSearchCriteria, type IAdminDeclarationRepo, orderByMap } from "../IAdminDeclarationRepo"; | ||
|
||
export class PostgresAdminDeclarationRepo implements IAdminDeclarationRepo { | ||
private declarationTable = sql("declaration"); | ||
private representationEquilibreeTable = sql("representation_equilibree"); | ||
private searchTable = sql("search"); | ||
private searchRepresentationEquilibreeTable = sql("search_representation_equilibree"); | ||
|
||
public async search(criteria: AdminDeclarationSearchCriteria): Promise<AdminDeclarationDTO[]> { | ||
const cteCombined = sql("cte_combined"); | ||
|
||
const raws = await sql<AdminDeclarationRaw[]>` | ||
WITH ${cteCombined} AS ( | ||
SELECT ${this.declarationTable}.declared_at AS created_at, | ||
${this.declarationTable}.data->'déclarant'->>'email' AS declarant_email, | ||
${this.declarationTable}.data->'déclarant'->>'prénom' AS declarant_firstname, | ||
${this.declarationTable}.data->'déclarant'->>'nom' AS declarant_lastname, | ||
${this.declarationTable}.data->'entreprise'->>'raison_sociale' AS name, | ||
${this.declarationTable}.data->'entreprise'->>'siren' AS siren, | ||
'index' AS type, | ||
${this.declarationTable}.year AS year, | ||
(${this.declarationTable}.data->'déclaration'->>'index')::int AS index, | ||
${this.declarationTable}.data->'entreprise'->'ues' AS ues | ||
FROM ${this.declarationTable} | ||
JOIN ${this.searchTable} ON ${this.declarationTable}.siren = ${this.searchTable}.siren | ||
AND ${this.searchTable}.year = ${this.declarationTable}.year | ||
${this.buildSearchWhereClause(criteria, this.searchTable, this.declarationTable)} | ||
UNION ALL | ||
SELECT ${this.representationEquilibreeTable}.declared_at AS created_at, | ||
${this.representationEquilibreeTable}.data->'déclarant'->>'email' AS declarant_email, | ||
${this.representationEquilibreeTable}.data->'déclarant'->>'prénom' AS declarant_firstname, | ||
${this.representationEquilibreeTable}.data->'déclarant'->>'nom' AS declarant_lastname, | ||
${this.representationEquilibreeTable}.data->'entreprise'->>'raison_sociale' AS name, | ||
${this.representationEquilibreeTable}.data->'entreprise'->>'siren' AS siren, | ||
'repeq' AS type, | ||
${this.representationEquilibreeTable}.year, | ||
NULL AS index, | ||
NULL AS ues | ||
FROM ${this.representationEquilibreeTable} | ||
JOIN ${this.searchRepresentationEquilibreeTable} ON ${this.representationEquilibreeTable}.siren = ${ | ||
this.searchRepresentationEquilibreeTable | ||
}.siren | ||
AND ${this.searchRepresentationEquilibreeTable}.year = ${this.representationEquilibreeTable}.year | ||
${this.buildSearchWhereClause( | ||
criteria, | ||
this.searchRepresentationEquilibreeTable, | ||
this.representationEquilibreeTable, | ||
)} | ||
) | ||
SELECT * | ||
FROM ${cteCombined} | ||
ORDER BY ${sql(criteria.orderBy ? orderByMap[criteria.orderBy] : sql("created_at"))} ${ | ||
criteria.orderDirection === "asc" ? sql`asc` : sql`desc` | ||
} | ||
LIMIT ${criteria.limit ?? 100} | ||
OFFSET ${criteria.offset ?? 0};`; | ||
|
||
return raws.map( | ||
raw => | ||
({ | ||
createdAt: raw.created_at, | ||
declarantEmail: raw.declarant_email, | ||
declarantFirstName: raw.declarant_firstname, | ||
declarantLastName: raw.declarant_lastname, | ||
name: raw.name, | ||
siren: raw.siren, | ||
type: raw.type, | ||
year: raw.year, | ||
...(raw.type === "index" | ||
? { | ||
index: raw.index, | ||
ues: raw.ues | ||
? { | ||
name: raw.ues.nom!, | ||
companies: raw.ues.entreprises?.map(entreprise => ({ | ||
name: entreprise.raison_sociale, | ||
siren: entreprise.siren, | ||
})), | ||
} | ||
: void 0, | ||
} | ||
: {}), | ||
}) as AdminDeclarationDTO, | ||
); | ||
} | ||
|
||
public async count(criteria: AdminDeclarationSearchCriteria): Promise<number> { | ||
const cteCountDeclaration = sql("cte_count_declaration"); | ||
const cteCountRepresentationEquilibree = sql("cte_count_representation_equilibree"); | ||
|
||
const [{ count }] = await sql<SQLCount>` | ||
WITH | ||
${cteCountDeclaration} AS ( | ||
SELECT COUNT(distinct(${this.searchTable}.siren)) AS count1 | ||
FROM ${this.searchTable} | ||
JOIN ${this.declarationTable} ON ${this.searchTable}.siren = ${this.declarationTable}.siren | ||
AND ${this.declarationTable}.year = ${this.searchTable}.year | ||
${this.buildSearchWhereClause(criteria, this.searchTable, this.declarationTable)} | ||
), | ||
${cteCountRepresentationEquilibree} AS ( | ||
SELECT COUNT(distinct(${this.searchRepresentationEquilibreeTable}.siren)) AS count2 | ||
FROM ${this.searchRepresentationEquilibreeTable} | ||
JOIN ${this.representationEquilibreeTable} ON ${this.searchRepresentationEquilibreeTable}.siren = ${ | ||
this.representationEquilibreeTable | ||
}.siren | ||
AND ${this.representationEquilibreeTable}.year = ${this.searchRepresentationEquilibreeTable}.year | ||
${this.buildSearchWhereClause( | ||
criteria, | ||
this.searchRepresentationEquilibreeTable, | ||
this.representationEquilibreeTable, | ||
)} | ||
) | ||
SELECT | ||
(count1 + count2) AS count | ||
FROM | ||
${cteCountDeclaration}, ${cteCountRepresentationEquilibree};`; | ||
return +count; | ||
} | ||
|
||
private buildSearchWhereClause( | ||
criteria: AdminDeclarationSearchCriteria, | ||
searchTable: Helper<string>, | ||
table: Helper<string>, | ||
) { | ||
let hasWhere = false; | ||
|
||
let sqlYear = sql``; | ||
if (typeof criteria.year === "number") { | ||
// no sql`and` here because it's the first condition | ||
sqlYear = sql`${searchTable}.year=${criteria.year}`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlEmail = sql``; | ||
if (criteria.email) { | ||
sqlEmail = sql`${hasWhere ? sql`and` : sql``} ${table}.data->'déclarant'->>'email' like ${`%${criteria.email}%`}`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlIndexComparison = sql``; | ||
if (typeof criteria.index === "number" && criteria.indexComparison) { | ||
sqlIndexComparison = sql`${hasWhere ? sql`and` : sql``} (${table}.data->'déclaration'->>'index')::int ${ | ||
criteria.indexComparison === "gt" ? sql`>` : criteria.indexComparison === "lt" ? sql`<` : sql`=` | ||
} ${criteria.index}`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlMinDate = sql``; | ||
if (criteria.minDate) { | ||
sqlMinDate = sql`${hasWhere ? sql`and` : sql``} ${searchTable}.declared_at >= ${criteria.minDate}`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlMaxDate = sql``; | ||
if (criteria.maxDate) { | ||
sqlMaxDate = sql`${hasWhere ? sql`and` : sql``} ${searchTable}.declared_at <= ${criteria.maxDate}`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlUes = sql``; | ||
if (criteria.ues) { | ||
sqlUes = sql`${hasWhere ? sql`and` : sql``} ${table}.data->'entreprise'->'ues' IS NOT NULL`; | ||
hasWhere = true; | ||
} | ||
|
||
let sqlQuery = sql``; | ||
if (criteria.query) { | ||
if (criteria.query.length === 9 && isFinite(+criteria.query)) { | ||
sqlQuery = sql`${hasWhere ? sql`and` : sql``} ${searchTable}.siren=${criteria.query}`; | ||
} else { | ||
sqlQuery = sql`${hasWhere ? sql`and` : sql``} ${searchTable}.ft @@ to_tsquery('ftdict', ${cleanFullTextSearch( | ||
criteria.query, | ||
)})`; | ||
} | ||
hasWhere = true; | ||
} | ||
|
||
return hasWhere | ||
? sql`where ${sqlYear} ${sqlEmail} ${sqlIndexComparison} ${sqlMinDate} ${sqlMaxDate} ${sqlUes} ${sqlQuery}` | ||
: sql``; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/app/src/app/(default)/representation-equilibree/opengraph-image.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.