fix(backend): add missing domain foreign keys
Some checks failed
deploy-socialize / image (push) Failing after 44s
deploy-socialize / deploy (push) Has been skipped

This commit is contained in:
2026-05-07 15:48:12 -04:00
parent e9fb1c5ee0
commit 49e2ca1774
14 changed files with 3109 additions and 26 deletions

View File

@@ -14,11 +14,6 @@ SVG_FILE="${OUT_DIR}/database-diagram.svg"
PNG_FILE="${OUT_DIR}/database-diagram.png"
HTML_FILE="${OUT_DIR}/database-diagram.html"
if ! command -v psql >/dev/null 2>&1; then
echo "psql is required to generate the database diagram." >&2
exit 1
fi
export PGCONNECT_TIMEOUT="${PGCONNECT_TIMEOUT:-5}"
export PGHOST="${PGHOST:-localhost}"
export PGPORT="${PGPORT:-5433}"
@@ -38,6 +33,11 @@ else
PSQL=(psql "${PSQL_ARGS[@]}")
fi
if [[ "${PSQL[0]}" == "psql" ]] && ! command -v psql >/dev/null 2>&1; then
echo "psql is required to generate the database diagram when not using the local Docker container." >&2
exit 1
fi
echo "Reading schema from ${CONNECTION_LABEL}..."
if ! "${PSQL[@]}" -c "select 1" >/dev/null 2>&1; then
@@ -50,9 +50,31 @@ fi
SELECT
c.table_schema || '.' || c.table_name AS table_id,
c.column_name,
c.udt_name,
CASE
WHEN c.data_type = 'character varying'
THEN 'varchar(' || c.character_maximum_length || ')'
WHEN c.data_type = 'character'
THEN 'char(' || c.character_maximum_length || ')'
WHEN c.data_type = 'text'
THEN 'text'
WHEN c.data_type = 'numeric' AND c.numeric_precision IS NOT NULL AND c.numeric_scale IS NOT NULL
THEN 'numeric(' || c.numeric_precision || ',' || c.numeric_scale || ')'
WHEN c.data_type = 'numeric' AND c.numeric_precision IS NOT NULL
THEN 'numeric(' || c.numeric_precision || ')'
WHEN c.data_type = 'timestamp with time zone'
THEN 'timestamptz'
WHEN c.data_type = 'timestamp without time zone'
THEN 'timestamp'
WHEN c.data_type = 'USER-DEFINED'
THEN c.udt_name
ELSE c.data_type
END AS formatted_type,
c.is_nullable,
CASE WHEN pk.column_name IS NULL THEN '' ELSE 'PK' END AS key_type
concat_ws(' ',
CASE WHEN pk.column_name IS NULL THEN NULL ELSE 'PK' END,
CASE WHEN fk.column_name IS NULL THEN NULL ELSE 'FK' END,
CASE WHEN ix.column_name IS NULL THEN NULL ELSE 'IX' END
) AS column_markers
FROM information_schema.columns c
LEFT JOIN (
SELECT
@@ -69,6 +91,39 @@ LEFT JOIN (
ON pk.table_schema = c.table_schema
AND pk.table_name = c.table_name
AND pk.column_name = c.column_name
LEFT JOIN (
SELECT DISTINCT
kcu.table_schema,
kcu.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON kcu.constraint_catalog = tc.constraint_catalog
AND kcu.constraint_schema = tc.constraint_schema
AND kcu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
) fk
ON fk.table_schema = c.table_schema
AND fk.table_name = c.table_name
AND fk.column_name = c.column_name
LEFT JOIN (
SELECT DISTINCT
indexed_schema.nspname AS table_schema,
indexed_table.relname AS table_name,
indexed_attribute.attname AS column_name
FROM pg_catalog.pg_index index_definition
JOIN pg_catalog.pg_class indexed_table
ON indexed_table.oid = index_definition.indrelid
JOIN pg_catalog.pg_namespace indexed_schema
ON indexed_schema.oid = indexed_table.relnamespace
JOIN pg_catalog.pg_attribute indexed_attribute
ON indexed_attribute.attrelid = indexed_table.oid
AND indexed_attribute.attnum = ANY(index_definition.indkey)
WHERE indexed_schema.nspname = 'public'
) ix
ON ix.table_schema = c.table_schema
AND ix.table_name = c.table_name
AND ix.column_name = c.column_name
WHERE c.table_schema = 'public'
AND c.table_name <> '__EFMigrationsHistory'
ORDER BY c.table_name, c.ordinal_position;
@@ -81,7 +136,27 @@ SELECT
kcu.column_name AS child_column,
ccu.table_schema || '.' || ccu.table_name AS parent_table,
ccu.column_name AS parent_column,
col.is_nullable
col.is_nullable,
CASE
WHEN EXISTS (
SELECT 1
FROM pg_catalog.pg_index unique_index
JOIN pg_catalog.pg_class unique_table
ON unique_table.oid = unique_index.indrelid
JOIN pg_catalog.pg_namespace unique_schema
ON unique_schema.oid = unique_table.relnamespace
JOIN pg_catalog.pg_attribute unique_attribute
ON unique_attribute.attrelid = unique_table.oid
AND unique_attribute.attnum = ANY(unique_index.indkey)
WHERE unique_schema.nspname = kcu.table_schema
AND unique_table.relname = kcu.table_name
AND unique_attribute.attname = kcu.column_name
AND unique_index.indisunique
AND unique_index.indnatts = 1
)
THEN 'YES'
ELSE 'NO'
END AS child_column_is_unique
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON kcu.constraint_catalog = tc.constraint_catalog
@@ -119,11 +194,18 @@ node_id() {
printf '%s' "$value"
}
dot_label_escape() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
printf '%s' "$value"
}
declare -A TABLE_COLUMNS
declare -A TABLE_SEEN
declare -a TABLE_ORDER
while IFS=$'\t' read -r table_name column_name column_type nullable key_type; do
while IFS=$'\t' read -r table_name column_name column_type nullable column_markers; do
[[ -n "$table_name" ]] || continue
if [[ -z "${TABLE_SEEN[$table_name]:-}" ]]; then
@@ -133,11 +215,12 @@ while IFS=$'\t' read -r table_name column_name column_type nullable key_type; do
label="$(html_escape "$column_name")"
type_label="$(html_escape "$column_type")"
suffix=""
[[ "$key_type" == "PK" ]] && suffix=" <FONT POINT-SIZE=\"10\">PK</FONT>"
[[ "$nullable" == "YES" ]] && suffix="${suffix} <FONT POINT-SIZE=\"10\">nullable</FONT>"
marker_label="$(html_escape "$column_markers")"
[[ -n "$marker_label" ]] || marker_label="&#160;"
nullable_label=""
[[ "$nullable" == "YES" ]] && nullable_label=' <FONT POINT-SIZE="10">nullable</FONT>'
TABLE_COLUMNS["$table_name"]+=$' <TR><TD ALIGN="LEFT" PORT="'$(html_escape "$column_name")$'"><FONT FACE="monospace">'"${label}"$'</FONT></TD><TD ALIGN="LEFT"><FONT POINT-SIZE="10">'"${type_label}${suffix}"$'</FONT></TD></TR>\n'
TABLE_COLUMNS["$table_name"]+=$' <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="10">'"${marker_label}"$'</FONT></TD><TD ALIGN="LEFT" PORT="'$(html_escape "$column_name")$'"><FONT FACE="monospace">'"${label}"$'</FONT></TD><TD ALIGN="LEFT"><FONT POINT-SIZE="10">'"${type_label}${nullable_label}"$'</FONT></TD></TR>\n'
done < "$TABLES_TSV"
{
@@ -149,7 +232,7 @@ digraph database {
pad=0.4,
nodesep=0.6,
ranksep=1.0,
splines=ortho,
splines=spline,
overlap=false
];
@@ -162,9 +245,26 @@ digraph database {
color="#64748b",
arrowsize=0.7,
fontname="Arial",
fontsize=10
fontsize=10,
labeldistance=1.8,
labelangle=22
];
legend [label=<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="6" COLOR="#94a3b8">
<TR><TD BGCOLOR="#334155" COLSPAN="2"><FONT COLOR="white"><B>Legend</B></FONT></TD></TR>
<TR><TD ALIGN="LEFT">PK</TD><TD ALIGN="LEFT">primary key column</TD></TR>
<TR><TD ALIGN="LEFT">FK</TD><TD ALIGN="LEFT">foreign key column</TD></TR>
<TR><TD ALIGN="LEFT">IX</TD><TD ALIGN="LEFT">indexed column</TD></TR>
<TR><TD ALIGN="LEFT">solid line</TD><TD ALIGN="LEFT">required FK</TD></TR>
<TR><TD ALIGN="LEFT">dashed line</TD><TD ALIGN="LEFT">nullable FK</TD></TR>
<TR><TD ALIGN="LEFT">1</TD><TD ALIGN="LEFT">exactly one referenced row</TD></TR>
<TR><TD ALIGN="LEFT">0..1</TD><TD ALIGN="LEFT">zero or one referencing row</TD></TR>
<TR><TD ALIGN="LEFT">0..*</TD><TD ALIGN="LEFT">zero or many referencing rows</TD></TR>
<TR><TD ALIGN="LEFT" COLSPAN="2">Edges point from referenced table to referencing table.</TD></TR>
</TABLE>
>];
DOT
for table_name in "${TABLE_ORDER[@]}"; do
@@ -172,20 +272,22 @@ DOT
title="$(html_escape "${table_name#public.}")"
printf ' %s [label=<\n' "$id"
printf ' <TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0" CELLPADDING="6" COLOR="#94a3b8">\n'
printf ' <TR><TD BGCOLOR="#0f766e" COLSPAN="2"><FONT COLOR="white"><B>%s</B></FONT></TD></TR>\n' "$title"
printf ' <TR><TD BGCOLOR="#0f766e" COLSPAN="3"><FONT COLOR="white"><B>%s</B></FONT></TD></TR>\n' "$title"
printf '%s' "${TABLE_COLUMNS[$table_name]}"
printf ' </TABLE>\n'
printf ' >];\n\n'
done
while IFS=$'\t' read -r constraint_name child_table child_column parent_table parent_column nullable; do
while IFS=$'\t' read -r constraint_name child_table child_column parent_table parent_column nullable child_column_is_unique; do
[[ -n "$constraint_name" ]] || continue
child_id="$(node_id "$child_table")"
parent_id="$(node_id "$parent_table")"
label="$(html_escape "${child_column} -> ${parent_column}")"
child_cardinality="0..*"
[[ "$child_column_is_unique" == "YES" ]] && child_cardinality="0..1"
label="$(dot_label_escape "${parent_column} -> ${child_column}")"
style="solid"
[[ "$nullable" == "YES" ]] && style="dashed"
printf ' %s -> %s [label="%s", style="%s"];\n' "$child_id" "$parent_id" "$label" "$style"
printf ' %s -> %s [label="%s", taillabel="1", headlabel="%s", style="%s"];\n' "$parent_id" "$child_id" "$label" "$child_cardinality" "$style"
done < "$RELATIONS_TSV"
printf '}\n'
@@ -219,14 +321,18 @@ cat > "$HTML_FILE" <<HTML
position: sticky;
top: 0;
z-index: 1;
display: flex;
gap: 16px;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #cbd5e1;
background: #ffffff;
}
.bar {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 8px;
}
header h1 {
margin: 0;
font-size: 18px;
@@ -239,6 +345,29 @@ cat > "$HTML_FILE" <<HTML
text-decoration: none;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
margin: 0;
padding: 0;
font-size: 13px;
color: #334155;
list-style: none;
}
.line {
display: inline-block;
width: 28px;
margin-right: 6px;
border-top: 2px solid #64748b;
vertical-align: middle;
}
.line.dashed {
border-top-style: dashed;
}
main {
padding: 16px;
}
@@ -260,10 +389,20 @@ cat > "$HTML_FILE" <<HTML
</head>
<body>
<header>
<h1>Socialize Database Diagram</h1>
<a href="./database-diagram.svg">SVG</a>
<a href="./database-diagram.png">PNG</a>
<a href="./database-diagram.dot">DOT</a>
<div class="bar">
<h1>Socialize Database Diagram</h1>
<a href="./database-diagram.svg">SVG</a>
<a href="./database-diagram.png">PNG</a>
<a href="./database-diagram.dot">DOT</a>
</div>
<ul class="legend">
<li><span class="line"></span>required FK</li>
<li><span class="line dashed"></span>nullable FK</li>
<li><strong>1</strong>: exactly one referenced row</li>
<li><strong>0..1</strong>: zero or one referencing row</li>
<li><strong>0..*</strong>: zero or many referencing rows</li>
<li>Edges point from referenced table to referencing table.</li>
</ul>
</header>
<main>
<div class="canvas">