revert fs_name sibling roms

This commit is contained in:
Georges-Antoine Assi
2026-04-05 17:57:48 -04:00
parent cb96c861a6
commit 7c41fb5bac
7 changed files with 127 additions and 12 deletions

View File

@@ -87,11 +87,13 @@ jobs:
- name: Comment PR with Docker image link
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
HEAD_REF: ${{ github.head_ref }}
with:
script: |
github.rest.issues.updateComment({
comment_id: ${{ steps.build-comment.outputs.comment-id }},
owner: context.repo.owner,
repo: context.repo.repo,
body: `✅ Preview build completed!\n\nDocker image: \`rommapp/romm-testing:${{ github.head_ref }}\``
body: `✅ Preview build completed!\n\nDocker image: \`rommapp/romm-testing:${process.env.HEAD_REF}\``
})

View File

@@ -0,0 +1,117 @@
"""Remove fs_name_no_tags matching from sibling_roms view
Revision ID: 0073_sibling_roms_metadata_only
Revises: 0072_client_tokens
Create Date: 2026-04-05 00:00:00.000000
"""
import sqlalchemy as sa
from alembic import op
from utils.database import is_postgresql
# revision identifiers, used by Alembic.
revision = "0073_sibling_roms_metadata_only"
down_revision = "0072_client_tokens"
branch_labels = None
depends_on = None
def upgrade() -> None:
connection = op.get_bind()
null_safe_equal_operator = (
"IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>"
)
connection.execute(
sa.text(f"""
CREATE OR REPLACE VIEW sibling_roms AS
SELECT
r1.id AS rom_id,
r2.id AS sibling_rom_id,
r1.platform_id AS platform_id,
NOW() AS created_at,
NOW() AS updated_at,
CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id,
CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id,
CASE WHEN r1.ss_id {null_safe_equal_operator} r2.ss_id THEN r1.ss_id END AS ss_id,
CASE WHEN r1.launchbox_id {null_safe_equal_operator} r2.launchbox_id THEN r1.launchbox_id END AS launchbox_id,
CASE WHEN r1.ra_id {null_safe_equal_operator} r2.ra_id THEN r1.ra_id END AS ra_id,
CASE WHEN r1.hasheous_id {null_safe_equal_operator} r2.hasheous_id THEN r1.hasheous_id END AS hasheous_id,
CASE WHEN r1.tgdb_id {null_safe_equal_operator} r2.tgdb_id THEN r1.tgdb_id END AS tgdb_id
FROM
roms r1
JOIN
roms r2
ON
r1.platform_id = r2.platform_id
AND r1.id != r2.id
AND (
(r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL)
OR
(r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL)
OR
(r1.ss_id = r2.ss_id AND r1.ss_id IS NOT NULL)
OR
(r1.launchbox_id = r2.launchbox_id AND r1.launchbox_id IS NOT NULL)
OR
(r1.ra_id = r2.ra_id AND r1.ra_id IS NOT NULL)
OR
(r1.hasheous_id = r2.hasheous_id AND r1.hasheous_id IS NOT NULL)
OR
(r1.tgdb_id = r2.tgdb_id AND r1.tgdb_id IS NOT NULL)
);
"""), # nosec B608
)
def downgrade() -> None:
connection = op.get_bind()
null_safe_equal_operator = (
"IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>"
)
# Restore view with fs_name_no_tags matching (from 0071)
connection.execute(
sa.text(f"""
CREATE OR REPLACE VIEW sibling_roms AS
SELECT
r1.id AS rom_id,
r2.id AS sibling_rom_id,
r1.platform_id AS platform_id,
NOW() AS created_at,
NOW() AS updated_at,
CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id,
CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id,
CASE WHEN r1.ss_id {null_safe_equal_operator} r2.ss_id THEN r1.ss_id END AS ss_id,
CASE WHEN r1.launchbox_id {null_safe_equal_operator} r2.launchbox_id THEN r1.launchbox_id END AS launchbox_id,
CASE WHEN r1.ra_id {null_safe_equal_operator} r2.ra_id THEN r1.ra_id END AS ra_id,
CASE WHEN r1.hasheous_id {null_safe_equal_operator} r2.hasheous_id THEN r1.hasheous_id END AS hasheous_id,
CASE WHEN r1.tgdb_id {null_safe_equal_operator} r2.tgdb_id THEN r1.tgdb_id END AS tgdb_id
FROM
roms r1
JOIN
roms r2
ON
r1.platform_id = r2.platform_id
AND r1.id != r2.id
AND (
(r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL)
OR
(r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL)
OR
(r1.ss_id = r2.ss_id AND r1.ss_id IS NOT NULL)
OR
(r1.launchbox_id = r2.launchbox_id AND r1.launchbox_id IS NOT NULL)
OR
(r1.ra_id = r2.ra_id AND r1.ra_id IS NOT NULL)
OR
(r1.hasheous_id = r2.hasheous_id AND r1.hasheous_id IS NOT NULL)
OR
(r1.tgdb_id = r2.tgdb_id AND r1.tgdb_id IS NOT NULL)
OR
(r1.fs_name_no_tags = r2.fs_name_no_tags AND r1.fs_name_no_tags != '')
);
"""), # nosec B608
)

View File

@@ -641,7 +641,6 @@ class DBRomsHandler(DBBaseHandler):
.with_only_columns(
base_subquery.c.id,
base_subquery.c.fs_name_no_ext,
base_subquery.c.fs_name_no_tags,
base_subquery.c.platform_id,
base_subquery.c.igdb_id,
base_subquery.c.ss_id,
@@ -707,11 +706,6 @@ class DBRomsHandler(DBBaseHandler):
func.nullif(base_subquery.c.fs_name_no_tags, ""),
base_subquery.c.platform_id,
),
_create_metadata_id_case(
"romm",
base_subquery.c.id,
base_subquery.c.platform_id,
),
),
order_by=[
is_main_sibling_order,

View File

@@ -173,7 +173,6 @@ class Rom(BaseModel):
Index("idx_roms_flashpoint_id", "flashpoint_id"),
Index("idx_roms_hltb_id", "hltb_id"),
Index("idx_roms_gamelist_id", "gamelist_id"),
Index("idx_roms_fs_name_no_tags", "fs_name_no_tags"),
)
fs_name: Mapped[str] = mapped_column(String(length=FILE_NAME_MAX_LENGTH))

View File

@@ -203,7 +203,7 @@ def validate_url_for_http_request(url: str, field_name: str = "URL") -> None:
log.error(f"SSRF prevention: {msg} - IP '{ip}'")
raise ValidationError(msg, field_name)
except ValueError:
except ValueError as e:
# ipaddress.ip_address() only handles standard notation. HTTP clients
# also accept hex integers (0x7f000001), decimal integers (2130706433),
# shorthand dotted (127.1), and octal (0177.0.0.1). Use socket.inet_aton()
@@ -238,4 +238,4 @@ def validate_url_for_http_request(url: str, field_name: str = "URL") -> None:
if any(hostname_lower.endswith(tld) for tld in internal_tlds):
msg = f"Invalid {field_name}: internal domain names are not allowed"
log.error(f"SSRF prevention: {msg} - hostname '{hostname}'")
raise ValidationError(msg, field_name)
raise ValidationError(msg, field_name) from e

View File

@@ -40,7 +40,9 @@ async function loadScripts() {
scriptsLoaded = true;
return true;
} catch (error) {
throw new Error(`Failed to load patcher scripts: ${error.message}`);
throw new Error(`Failed to load patcher scripts: ${error.message}`, {
cause: error,
});
}
}

View File

@@ -5,6 +5,7 @@ import type {
BulkOperationResponse,
DetailedRomSchema,
ManualMetadata,
RomUserData,
RomUserSchema,
SearchRomSchema,
SimpleRomSchema,
@@ -544,7 +545,7 @@ async function updateUserRomProps({
removeLastPlayed = false,
}: {
romId: number;
data: Partial<RomUserSchema>;
data: Partial<RomUserData>;
updateLastPlayed?: boolean;
removeLastPlayed?: boolean;
}) {