Merge pull request #3002 from rommapp/romm-2974

[ROMM-2974] Stop displaying public collections in "add to collection" dialog the user doesn't own
This commit is contained in:
Georges-Antoine Assi
2026-02-08 20:41:58 -05:00
committed by GitHub
34 changed files with 184 additions and 98 deletions

View File

@@ -28,7 +28,7 @@ class CollectionSchema(BaseCollectionSchema):
id: int
url_cover: str | None
user_id: int
user__username: str
owner_username: str
class Config:
from_attributes = True
@@ -60,7 +60,7 @@ class SmartCollectionSchema(BaseCollectionSchema):
filter_criteria: dict[str, Any]
filter_summary: str
user_id: int
user__username: str
owner_username: str
is_smart: bool = True
class Config:

View File

@@ -117,7 +117,6 @@ def rom_user_schema_factory() -> RomUserSchema:
difficulty=0,
completion=0,
status=None,
user__username="",
)
@@ -136,7 +135,6 @@ class RomUserSchema(BaseModel):
difficulty: int
completion: int
status: RomUserStatus | None
user__username: str
class Config:
from_attributes = True

View File

@@ -43,7 +43,7 @@ class Collection(BaseModel):
user: Mapped["User"] = relationship(lazy="joined", back_populates="collections")
@property
def user__username(self) -> str:
def owner_username(self) -> str:
return self.user.username
@property
@@ -228,7 +228,7 @@ class SmartCollection(BaseModel):
)
@property
def user__username(self) -> str:
def owner_username(self) -> str:
return self.user.username
@property

View File

@@ -472,10 +472,6 @@ class RomNote(BaseModel):
rom: Mapped[Rom] = relationship(lazy="joined", back_populates="notes")
user: Mapped[User] = relationship(lazy="joined", back_populates="notes")
@property
def user__username(self) -> str:
return self.user.username
class RomUser(BaseModel):
__tablename__ = "rom_user"
@@ -503,7 +499,3 @@ class RomUser(BaseModel):
rom: Mapped[Rom] = relationship(lazy="joined", back_populates="rom_users")
user: Mapped[User] = relationship(lazy="joined", back_populates="rom_users")
@property
def user__username(self) -> str:
return self.user.username

View File

@@ -8,6 +8,7 @@ export type { Body_add_collection_api_collections_post } from './models/Body_add
export type { Body_add_firmware_api_firmware_post } from './models/Body_add_firmware_api_firmware_post';
export type { Body_add_platform_api_platforms_post } from './models/Body_add_platform_api_platforms_post';
export type { Body_add_user_api_users_post } from './models/Body_add_user_api_users_post';
export type { Body_confirm_download_api_saves__id__downloaded_post } from './models/Body_confirm_download_api_saves__id__downloaded_post';
export type { Body_create_user_from_invite_api_users_register_post } from './models/Body_create_user_from_invite_api_users_register_post';
export type { Body_delete_firmware_api_firmware_delete_post } from './models/Body_delete_firmware_api_firmware_delete_post';
export type { Body_delete_roms_api_roms_delete_post } from './models/Body_delete_roms_api_roms_delete_post';
@@ -17,6 +18,8 @@ export type { Body_refresh_retro_achievements_api_users__id__ra_refresh_post } f
export type { Body_request_password_reset_api_forgot_password_post } from './models/Body_request_password_reset_api_forgot_password_post';
export type { Body_reset_password_api_reset_password_post } from './models/Body_reset_password_api_reset_password_post';
export type { Body_token_api_token_post } from './models/Body_token_api_token_post';
export type { Body_track_save_api_saves__id__track_post } from './models/Body_track_save_api_saves__id__track_post';
export type { Body_untrack_save_api_saves__id__untrack_post } from './models/Body_untrack_save_api_saves__id__untrack_post';
export type { Body_update_collection_api_collections__id__put } from './models/Body_update_collection_api_collections__id__put';
export type { Body_update_platform_api_platforms__id__put } from './models/Body_update_platform_api_platforms__id__put';
export type { Body_update_rom_api_roms__id__put } from './models/Body_update_rom_api_roms__id__put';
@@ -32,6 +35,11 @@ export type { ConversionTaskMeta } from './models/ConversionTaskMeta';
export type { ConversionTaskStatusResponse } from './models/ConversionTaskStatusResponse';
export type { CustomLimitOffsetPage_SimpleRomSchema_ } from './models/CustomLimitOffsetPage_SimpleRomSchema_';
export type { DetailedRomSchema } from './models/DetailedRomSchema';
export type { DeviceCreatePayload } from './models/DeviceCreatePayload';
export type { DeviceCreateResponse } from './models/DeviceCreateResponse';
export type { DeviceSchema } from './models/DeviceSchema';
export type { DeviceSyncSchema } from './models/DeviceSyncSchema';
export type { DeviceUpdatePayload } from './models/DeviceUpdatePayload';
export type { EarnedAchievement } from './models/EarnedAchievement';
export type { EjsControls } from './models/EjsControls';
export type { EjsControlsButton } from './models/EjsControlsButton';
@@ -77,6 +85,7 @@ export type { RomUserSchema } from './models/RomUserSchema';
export type { RomUserStatus } from './models/RomUserStatus';
export type { RoomsResponse } from './models/RoomsResponse';
export type { SaveSchema } from './models/SaveSchema';
export type { SaveSummarySchema } from './models/SaveSummarySchema';
export type { ScanStats } from './models/ScanStats';
export type { ScanTaskMeta } from './models/ScanTaskMeta';
export type { ScanTaskStatusResponse } from './models/ScanTaskStatusResponse';
@@ -86,9 +95,11 @@ export type { SearchRomSchema } from './models/SearchRomSchema';
export type { SGDBResource } from './models/SGDBResource';
export type { SiblingRomSchema } from './models/SiblingRomSchema';
export type { SimpleRomSchema } from './models/SimpleRomSchema';
export type { SlotSummarySchema } from './models/SlotSummarySchema';
export type { SmartCollectionSchema } from './models/SmartCollectionSchema';
export type { StateSchema } from './models/StateSchema';
export type { StatsReturn } from './models/StatsReturn';
export type { SyncMode } from './models/SyncMode';
export type { SystemDict } from './models/SystemDict';
export type { TaskExecutionResponse } from './models/TaskExecutionResponse';
export type { TaskInfo } from './models/TaskInfo';

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type Body_confirm_download_api_saves__id__downloaded_post = {
device_id: string;
};

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type Body_track_save_api_saves__id__track_post = {
device_id: string;
};

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type Body_untrack_save_api_saves__id__untrack_post = {
device_id: string;
};

View File

@@ -20,6 +20,6 @@ export type CollectionSchema = {
id: number;
url_cover: (string | null);
user_id: number;
user__username: string;
owner_username: string;
};

View File

@@ -0,0 +1,17 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeviceCreatePayload = {
name?: (string | null);
platform?: (string | null);
client?: (string | null);
client_version?: (string | null);
ip_address?: (string | null);
mac_address?: (string | null);
hostname?: (string | null);
allow_existing?: boolean;
allow_duplicate?: boolean;
reset_syncs?: boolean;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeviceCreateResponse = {
device_id: string;
name: (string | null);
created_at: string;
};

View File

@@ -0,0 +1,22 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { SyncMode } from './SyncMode';
export type DeviceSchema = {
id: string;
user_id: number;
name: (string | null);
platform: (string | null);
client: (string | null);
client_version: (string | null);
ip_address: (string | null);
mac_address: (string | null);
hostname: (string | null);
sync_mode: SyncMode;
sync_enabled: boolean;
last_seen: (string | null);
created_at: string;
updated_at: string;
};

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeviceSyncSchema = {
device_id: string;
device_name: (string | null);
last_synced_at: string;
is_untracked: boolean;
is_current: boolean;
};

View File

@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeviceUpdatePayload = {
name?: (string | null);
platform?: (string | null);
client?: (string | null);
client_version?: (string | null);
ip_address?: (string | null);
mac_address?: (string | null);
hostname?: (string | null);
sync_enabled?: (boolean | null);
};

View File

@@ -2,4 +2,4 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type RomFileCategory = 'game' | 'dlc' | 'hack' | 'manual' | 'patch' | 'update' | 'mod' | 'demo' | 'translation' | 'prototype';
export type RomFileCategory = 'game' | 'dlc' | 'hack' | 'manual' | 'patch' | 'update' | 'mod' | 'demo' | 'translation' | 'prototype' | 'cheat';

View File

@@ -18,6 +18,5 @@ export type RomUserSchema = {
difficulty: number;
completion: number;
status: (RomUserStatus | null);
user__username: string;
};

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DeviceSyncSchema } from './DeviceSyncSchema';
import type { ScreenshotSchema } from './ScreenshotSchema';
export type SaveSchema = {
id: number;
@@ -19,6 +20,9 @@ export type SaveSchema = {
created_at: string;
updated_at: string;
emulator: (string | null);
slot?: (string | null);
content_hash?: (string | null);
screenshot: (ScreenshotSchema | null);
device_syncs?: Array<DeviceSyncSchema>;
};

View File

@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { SlotSummarySchema } from './SlotSummarySchema';
export type SaveSummarySchema = {
total_count: number;
slots: Array<SlotSummarySchema>;
};

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { SaveSchema } from './SaveSchema';
export type SlotSummarySchema = {
slot: (string | null);
count: number;
latest: SaveSchema;
};

View File

@@ -21,6 +21,6 @@ export type SmartCollectionSchema = {
filter_criteria: Record<string, any>;
filter_summary: string;
user_id: number;
user__username: string;
owner_username: string;
};

View File

@@ -0,0 +1,5 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type SyncMode = 'api' | 'file_transfer' | 'push_pull';

View File

@@ -36,7 +36,7 @@ const collectionInfoFields = [
label: "Roms",
},
{
key: "user__username",
key: "owner_username",
label: t("collection.owner"),
},
];
@@ -146,7 +146,7 @@ async function updateCollection() {
<div class="position-absolute append-top-right mr-5">
<template
v-if="
currentCollection.user__username === auth.user?.username &&
currentCollection.user_id === auth.user?.id &&
auth.scopes.includes('collections.write')
"
>
@@ -328,7 +328,7 @@ async function updateCollection() {
<RSection
v-if="
auth.scopes.includes('collections.write') &&
currentCollection.user__username === auth.user?.username
currentCollection.user_id === auth.user?.id
"
icon="mdi-alert"
icon-color="red"

View File

@@ -29,7 +29,7 @@ const collectionInfoFields = [
label: "Roms",
},
{
key: "user__username",
key: "owner_username",
label: t("collection.owner"),
},
];
@@ -96,7 +96,7 @@ async function updateCollection() {
<div class="position-absolute append-top-right mr-5">
<template
v-if="
currentSmartCollection.user__username === auth.user?.username &&
currentSmartCollection.user_id === auth.user?.id &&
auth.scopes.includes('collections.write')
"
>
@@ -253,7 +253,7 @@ async function updateCollection() {
<RSection
v-if="
auth.scopes.includes('collections.write') &&
currentSmartCollection.user__username === auth.user?.username
currentSmartCollection.user_id === auth.user?.id
"
icon="mdi-alert"
icon-color="red"

View File

@@ -3,16 +3,12 @@ import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, watch, computed } from "vue";
import { useDisplay } from "vuetify";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeGalleryView from "@/stores/galleryView";
import storePlatforms from "@/stores/platforms";
import storeRoms from "@/stores/roms";
import type { Events } from "@/types/emitter";
const { smAndDown } = useDisplay();
const romsStore = storeRoms();
const platformsStore = storePlatforms();
const galleryFilterStore = storeGalleryFilter();
const galleryViewStore = storeGalleryView();
const { selectedRoms } = storeToRefs(romsStore);
const emitter = inject<Emitter<Events>>("emitter");
@@ -31,11 +27,7 @@ async function fetchRoms() {
});
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
concat: false,
})
.fetchRoms(false)
.then(() => {
emitter?.emit("showLoadingDialog", {
loading: false,

View File

@@ -86,11 +86,7 @@ const emitter = inject<Emitter<Events>>("emitter");
const onFilterChange = debounce(
() => {
romsStore.resetPagination();
romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
concat: false,
});
romsStore.fetchRoms(false);
const url = new URL(window.location.href);
// Update URL with filters

View File

@@ -43,11 +43,7 @@ const onFilterChange = debounce(
() => {
romsStore.resetPagination();
galleryFilterStore.setFilterMissing(true);
romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
concat: false,
});
romsStore.fetchRoms(false);
const url = new URL(window.location.href);
// Update URL with filters
@@ -75,10 +71,7 @@ async function fetchRoms() {
galleryFilterStore.setFilterMissing(true);
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
})
.fetchRoms()
.catch((error) => {
console.error("Error fetching missing games:", error);
emitter?.emit("snackbarShow", {
@@ -98,10 +91,7 @@ function cleanupAll() {
romsStore.setLimit(10000);
galleryFilterStore.setFilterMissing(true);
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
})
.fetchRoms()
.then(() => {
emitter?.emit("showLoadingDialog", {
loading: false,

View File

@@ -92,7 +92,7 @@ function closeDialog() {
density="default"
:label="t('common.collection')"
item-title="name"
:items="collectionsStore.allCollections"
:items="collectionsStore.ownedCollections"
variant="outlined"
hide-details
return-object

View File

@@ -136,10 +136,7 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
romsStore.resetPagination();
romsStore.setOrderBy(key);
romsStore.setOrderDir(order);
romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
});
romsStore.fetchRoms();
}
</script>

View File

@@ -318,11 +318,7 @@ async function fetchRoms() {
romsStore.setOrderDir("asc");
romsStore.resetPagination();
const fetchedRoms = await romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
concat: false,
});
const fetchedRoms = await romsStore.fetchRoms(false);
if (selectedIndex.value >= fetchedRoms.length) selectedIndex.value = 0;
await nextTick();

View File

@@ -6,6 +6,7 @@ import type {
SmartCollectionSchema,
} from "@/__generated__";
import collectionApi from "@/services/api/collection";
import storeAuth from "@/stores/auth";
import type { SimpleRom } from "./roms";
export type Collection = CollectionSchema;
@@ -29,6 +30,10 @@ export default defineStore("collections", {
allCollections.filter((p) =>
p.name.toLowerCase().includes(filterText.toLowerCase()),
),
ownedCollections: ({ allCollections }) => {
const authStore = storeAuth();
return allCollections.filter((c) => c.user_id === authStore.user?.id);
},
filteredVirtualCollections: ({ virtualCollections, filterText }) =>
virtualCollections.filter((p) =>
p.name.toLowerCase().includes(filterText.toLowerCase()),

View File

@@ -193,20 +193,15 @@ export default defineStore("roms", {
galleryFilter.setFilterPlayerCounts(filter_values.player_counts);
}
},
async fetchRoms({
galleryFilter,
platformsStore,
concat = true,
}: {
galleryFilter: GalleryFilterStore;
platformsStore: PlatformsStore;
concat?: boolean;
}): Promise<SimpleRom[]> {
async fetchRoms(concat = true): Promise<SimpleRom[]> {
if (this.fetchingRoms) return Promise.resolve([]);
this.fetchingRoms = true;
const galleryFilterStore = storeGalleryFilter();
const platformsStore = storePlatforms();
// Capture current request parameters to validate background updates
const currentRequestParams = this._buildRequestParams(galleryFilter);
const currentRequestParams = this._buildRequestParams(galleryFilterStore);
return new Promise((resolve, reject) => {
cachedApiService
@@ -214,14 +209,14 @@ export default defineStore("roms", {
if (concat && this.fetchOffset != 0) return;
// Check if parameters have changed since the request was made
const currentParams = this._buildRequestParams(galleryFilter);
const currentParams = this._buildRequestParams(galleryFilterStore);
const paramsChanged =
JSON.stringify(currentParams) !==
JSON.stringify(currentRequestParams);
if (paramsChanged) return;
this._postFetchRoms(
response,
galleryFilter,
galleryFilterStore,
platformsStore,
concat,
);
@@ -229,7 +224,7 @@ export default defineStore("roms", {
.then((response) => {
this._postFetchRoms(
response.data,
galleryFilter,
galleryFilterStore,
platformsStore,
concat,
);

View File

@@ -15,7 +15,6 @@ import GameTable from "@/components/common/Game/VirtualTable.vue";
import { type CollectionType } from "@/stores/collections";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeGalleryView from "@/stores/galleryView";
import storePlatforms from "@/stores/platforms";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { views } from "@/utils";
@@ -30,7 +29,6 @@ const galleryViewStore = storeGalleryView();
const galleryFilterStore = storeGalleryFilter();
const { scrolledToTop, currentView } = storeToRefs(galleryViewStore);
const romsStore = storeRoms();
const platformsStore = storePlatforms();
const {
filteredRoms,
selectedRoms,
@@ -55,10 +53,7 @@ async function fetchRoms() {
});
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
})
.fetchRoms()
.then(() => {
emitter?.emit("showLoadingDialog", {
loading: false,

View File

@@ -48,10 +48,7 @@ async function fetchRoms() {
});
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
})
.fetchRoms()
.then(() => {
emitter?.emit("showLoadingDialog", {
loading: false,

View File

@@ -13,7 +13,6 @@ import GameCard from "@/components/common/Game/Card/Base.vue";
import GameTable from "@/components/common/Game/VirtualTable.vue";
import storeGalleryFilter from "@/stores/galleryFilter";
import storeGalleryView from "@/stores/galleryView";
import storePlatforms from "@/stores/platforms";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { views } from "@/utils";
@@ -23,7 +22,6 @@ const { scrolledToTop, currentView } = storeToRefs(galleryViewStore);
const galleryFilterStore = storeGalleryFilter();
const { searchTerm } = storeToRefs(galleryFilterStore);
const romsStore = storeRoms();
const platformsStore = storePlatforms();
const {
filteredRoms,
selectedRoms,
@@ -101,19 +99,14 @@ function onGameTouchEnd() {
}
function fetchRoms() {
romsStore
.fetchRoms({
galleryFilter: galleryFilterStore,
platformsStore: platformsStore,
})
.catch((error) => {
emitter?.emit("snackbarShow", {
msg: `Couldn't fetch roms: ${error}`,
icon: "mdi-close-circle",
color: "red",
timeout: 4000,
});
romsStore.fetchRoms().catch((error) => {
emitter?.emit("snackbarShow", {
msg: `Couldn't fetch roms: ${error}`,
icon: "mdi-close-circle",
color: "red",
timeout: 4000,
});
});
}
const { y: windowY } = useScroll(window, { throttle: 500 });