diff --git a/backend/btrixcloud/colls.py b/backend/btrixcloud/colls.py index f6c13dad59..4c1efe719c 100644 --- a/backend/btrixcloud/colls.py +++ b/backend/btrixcloud/colls.py @@ -13,6 +13,7 @@ import asyncio import pymongo +from pymongo.collation import Collation from fastapi import Depends, HTTPException, Response from fastapi.responses import StreamingResponse from starlette.requests import Request @@ -51,7 +52,7 @@ MIN_UPLOAD_PART_SIZE, PublicCollOut, ) -from .utils import dt_now +from .utils import dt_now, slug_from_name, get_duplicate_key_error_field if TYPE_CHECKING: from .orgs import OrgOps @@ -98,8 +99,17 @@ def set_page_ops(self, ops): async def init_index(self): """init lookup index""" + case_insensitive_collation = Collation(locale="en", strength=1) await self.collections.create_index( - [("oid", pymongo.ASCENDING), ("name", pymongo.ASCENDING)], unique=True + [("oid", pymongo.ASCENDING), ("name", pymongo.ASCENDING)], + unique=True, + collation=case_insensitive_collation, + ) + + await self.collections.create_index( + [("oid", pymongo.ASCENDING), ("slug", pymongo.ASCENDING)], + unique=True, + collation=case_insensitive_collation, ) await self.collections.create_index( @@ -111,16 +121,18 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): crawl_ids = coll_in.crawlIds if coll_in.crawlIds else [] coll_id = uuid4() created = dt_now() - modified = dt_now() + + slug = coll_in.slug or slug_from_name(coll_in.name) coll = Collection( id=coll_id, oid=oid, name=coll_in.name, + slug=slug, description=coll_in.description, caption=coll_in.caption, created=created, - modified=modified, + modified=created, access=coll_in.access, defaultThumbnailName=coll_in.defaultThumbnailName, allowPublicDownload=coll_in.allowPublicDownload, @@ -128,6 +140,8 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): try: await self.collections.insert_one(coll.to_dict()) org = await self.orgs.get_org_by_id(oid) + await self.clear_org_previous_slugs_matching_slug(slug, org) + if crawl_ids: await self.crawl_ops.add_to_collection(crawl_ids, coll_id, org) await self.update_collection_counts_and_tags(coll_id) @@ -139,9 +153,10 @@ async def add_collection(self, oid: UUID, coll_in: CollIn): ) return {"added": True, "id": coll_id, "name": coll.name} - except pymongo.errors.DuplicateKeyError: + except pymongo.errors.DuplicateKeyError as err: # pylint: disable=raise-missing-from - raise HTTPException(status_code=400, detail="collection_name_taken") + field = get_duplicate_key_error_field(err) + raise HTTPException(status_code=400, detail=f"collection_{field}_taken") async def update_collection( self, coll_id: UUID, org: Organization, update: UpdateColl @@ -152,23 +167,55 @@ async def update_collection( if len(query) == 0: raise HTTPException(status_code=400, detail="no_update_data") + name_update = query.get("name") + slug_update = query.get("slug") + + previous_slug = None + + if name_update or slug_update: + # If we're updating slug, save old one to previousSlugs to support redirects + coll = await self.get_collection(coll_id) + previous_slug = coll.slug + + if name_update and not slug_update: + slug = slug_from_name(name_update) + query["slug"] = slug + slug_update = slug + query["modified"] = dt_now() + db_update = {"$set": query} + if previous_slug: + db_update["$push"] = {"previousSlugs": previous_slug} + try: result = await self.collections.find_one_and_update( {"_id": coll_id, "oid": org.id}, - {"$set": query}, + db_update, return_document=pymongo.ReturnDocument.AFTER, ) - except pymongo.errors.DuplicateKeyError: + except pymongo.errors.DuplicateKeyError as err: # pylint: disable=raise-missing-from - raise HTTPException(status_code=400, detail="collection_name_taken") + field = get_duplicate_key_error_field(err) + raise HTTPException(status_code=400, detail=f"collection_{field}_taken") if not result: raise HTTPException(status_code=404, detail="collection_not_found") + if slug_update: + await self.clear_org_previous_slugs_matching_slug(slug_update, org) + return {"updated": True} + async def clear_org_previous_slugs_matching_slug( + self, slug: str, org: Organization + ): + """Clear new slug from previousSlugs array of other collections in same org""" + await self.collections.update_many( + {"oid": org.id, "previousSlugs": slug}, + {"$pull": {"previousSlugs": slug}}, + ) + async def add_crawls_to_collection( self, coll_id: UUID, crawl_ids: List[str], org: Organization ) -> CollOut: @@ -234,6 +281,27 @@ async def get_collection_raw( return result + async def get_collection_raw_by_slug( + self, + coll_slug: str, + previous_slugs: bool = False, + public_or_unlisted_only: bool = False, + ) -> Dict[str, Any]: + """Get collection by slug (current or previous) as dict from database""" + query: dict[str, object] = {} + if previous_slugs: + query["previousSlugs"] = coll_slug + else: + query["slug"] = coll_slug + if public_or_unlisted_only: + query["access"] = {"$in": ["public", "unlisted"]} + + result = await self.collections.find_one(query) + if not result: + raise HTTPException(status_code=404, detail="collection_not_found") + + return result + async def get_collection( self, coll_id: UUID, public_or_unlisted_only: bool = False ) -> Collection: @@ -241,6 +309,26 @@ async def get_collection( result = await self.get_collection_raw(coll_id, public_or_unlisted_only) return Collection.from_dict(result) + async def get_collection_by_slug( + self, coll_slug: str, public_or_unlisted_only: bool = False + ) -> Collection: + """Get collection by slug""" + try: + result = await self.get_collection_raw_by_slug( + coll_slug, public_or_unlisted_only=public_or_unlisted_only + ) + return Collection.from_dict(result) + # pylint: disable=broad-exception-caught + except Exception: + pass + + result = await self.get_collection_raw_by_slug( + coll_slug, + previous_slugs=True, + public_or_unlisted_only=public_or_unlisted_only, + ) + return Collection.from_dict(result) + async def get_collection_out( self, coll_id: UUID, @@ -264,7 +352,10 @@ async def get_collection_out( return CollOut.from_dict(result) async def get_public_collection_out( - self, coll_id: UUID, org: Organization, allow_unlisted: bool = False + self, + coll_id: UUID, + org: Organization, + allow_unlisted: bool = False, ) -> PublicCollOut: """Get PublicCollOut by id""" result = await self.get_collection_raw(coll_id) @@ -1012,13 +1103,13 @@ async def get_org_public_collections( ) @app.get( - "/public/orgs/{org_slug}/collections/{coll_id}", + "/public/orgs/{org_slug}/collections/{coll_slug}", tags=["collections", "public"], response_model=PublicCollOut, ) async def get_public_collection( org_slug: str, - coll_id: UUID, + coll_slug: str, ): try: org = await colls.orgs.get_org_by_slug(org_slug) @@ -1027,16 +1118,18 @@ async def get_public_collection( # pylint: disable=raise-missing-from raise HTTPException(status_code=404, detail="collection_not_found") - return await colls.get_public_collection_out(coll_id, org, allow_unlisted=True) + coll = await colls.get_collection_by_slug(coll_slug) + + return await colls.get_public_collection_out(coll.id, org, allow_unlisted=True) @app.get( - "/public/orgs/{org_slug}/collections/{coll_id}/download", + "/public/orgs/{org_slug}/collections/{coll_slug}/download", tags=["collections", "public"], response_model=bytes, ) async def download_public_collection( org_slug: str, - coll_id: UUID, + coll_slug: str, ): try: org = await colls.orgs.get_org_by_slug(org_slug) @@ -1046,12 +1139,14 @@ async def download_public_collection( raise HTTPException(status_code=404, detail="collection_not_found") # Make sure collection exists and is public/unlisted - coll = await colls.get_collection(coll_id, public_or_unlisted_only=True) + coll = await colls.get_collection_by_slug( + coll_slug, public_or_unlisted_only=True + ) if coll.allowPublicDownload is False: raise HTTPException(status_code=403, detail="not_allowed") - return await colls.download_collection(coll_id, org) + return await colls.download_collection(coll.id, org) @app.get( "/orgs/{oid}/collections/{coll_id}/urls", diff --git a/backend/btrixcloud/db.py b/backend/btrixcloud/db.py index 9f0bab0446..504830f068 100644 --- a/backend/btrixcloud/db.py +++ b/backend/btrixcloud/db.py @@ -17,7 +17,7 @@ from .migrations import BaseMigration -CURR_DB_VERSION = "0038" +CURR_DB_VERSION = "0039" # ============================================================================ diff --git a/backend/btrixcloud/migrations/migration_0039_coll_slugs.py b/backend/btrixcloud/migrations/migration_0039_coll_slugs.py new file mode 100644 index 0000000000..6fa4cd01b0 --- /dev/null +++ b/backend/btrixcloud/migrations/migration_0039_coll_slugs.py @@ -0,0 +1,38 @@ +""" +Migration 0039 -- collection slugs +""" + +from btrixcloud.migrations import BaseMigration +from btrixcloud.utils import slug_from_name + + +MIGRATION_VERSION = "0039" + + +class Migration(BaseMigration): + """Migration class.""" + + # pylint: disable=unused-argument + def __init__(self, mdb, **kwargs): + super().__init__(mdb, migration_version=MIGRATION_VERSION) + + async def migrate_up(self): + """Perform migration up. + + Add slug to collections that don't have one yet, based on name + """ + colls_mdb = self.mdb["collections"] + + async for coll_raw in colls_mdb.find({"slug": None}): + coll_id = coll_raw["_id"] + try: + await colls_mdb.find_one_and_update( + {"_id": coll_id}, + {"$set": {"slug": slug_from_name(coll_raw.get("name", ""))}}, + ) + # pylint: disable=broad-exception-caught + except Exception as err: + print( + f"Error saving slug for collection {coll_id}: {err}", + flush=True, + ) diff --git a/backend/btrixcloud/models.py b/backend/btrixcloud/models.py index 512e3cd42a..02184b6fa3 100644 --- a/backend/btrixcloud/models.py +++ b/backend/btrixcloud/models.py @@ -1235,7 +1235,9 @@ class CollAccessType(str, Enum): class Collection(BaseMongoModel): """Org collection structure""" + id: UUID name: str = Field(..., min_length=1) + slug: str = Field(..., min_length=1) oid: UUID description: Optional[str] = None caption: Optional[str] = None @@ -1264,12 +1266,15 @@ class Collection(BaseMongoModel): allowPublicDownload: Optional[bool] = True + previousSlugs: List[str] = [] + # ============================================================================ class CollIn(BaseModel): """Collection Passed in By User""" name: str = Field(..., min_length=1) + slug: Optional[str] = None description: Optional[str] = None caption: Optional[str] = None crawlIds: Optional[List[str]] = [] @@ -1284,7 +1289,9 @@ class CollIn(BaseModel): class CollOut(BaseMongoModel): """Collection output model with annotations.""" + id: UUID name: str + slug: str oid: UUID description: Optional[str] = None caption: Optional[str] = None @@ -1318,7 +1325,9 @@ class CollOut(BaseMongoModel): class PublicCollOut(BaseMongoModel): """Collection output model with annotations.""" + id: UUID name: str + slug: str oid: UUID description: Optional[str] = None caption: Optional[str] = None @@ -1349,6 +1358,7 @@ class UpdateColl(BaseModel): """Update collection""" name: Optional[str] = None + slug: Optional[str] = None description: Optional[str] = None caption: Optional[str] = None access: Optional[CollAccessType] = None diff --git a/backend/btrixcloud/orgs.py b/backend/btrixcloud/orgs.py index d0ca47b2d0..318cf6e2fd 100644 --- a/backend/btrixcloud/orgs.py +++ b/backend/btrixcloud/orgs.py @@ -1306,6 +1306,8 @@ async def import_org( # collections for collection in org_data.get("collections", []): collection = json_stream.to_standard_types(collection) + if not collection.get("slug"): + collection["slug"] = slug_from_name(collection["name"]) await self.colls_db.insert_one(Collection.from_dict(collection).to_dict()) async def delete_org_and_data( diff --git a/backend/btrixcloud/utils.py b/backend/btrixcloud/utils.py index 468c892421..8a555a4c7c 100644 --- a/backend/btrixcloud/utils.py +++ b/backend/btrixcloud/utils.py @@ -171,15 +171,16 @@ def stream_dict_list_as_csv( def get_duplicate_key_error_field(err: DuplicateKeyError) -> str: """Get name of duplicate field from pymongo DuplicateKeyError""" - dupe_field = "name" + allowed_fields = ("name", "slug", "subscription.subId") + if err.details: key_value = err.details.get("keyValue") if key_value: - try: - dupe_field = list(key_value.keys())[0] - except IndexError: - pass - return dupe_field + for field in key_value.keys(): + if field in allowed_fields: + return field + + return "name" def get_origin(headers) -> str: diff --git a/backend/test/test_collections.py b/backend/test/test_collections.py index 7b274bf8fe..751b554ffe 100644 --- a/backend/test/test_collections.py +++ b/backend/test/test_collections.py @@ -9,12 +9,16 @@ from .utils import read_in_chunks COLLECTION_NAME = "Test collection" +COLLECTION_SLUG = "test-collection" PUBLIC_COLLECTION_NAME = "Public Test collection" +PUBLIC_COLLECTION_SLUG = "custom-public-collection-slug" UPDATED_NAME = "Updated tést cöllection" +UPDATED_SLUG = "updated-test-collection" SECOND_COLLECTION_NAME = "second-collection" DESCRIPTION = "Test description" CAPTION = "Short caption" UPDATED_CAPTION = "Updated caption" +SECOND_PUBLIC_COLL_SLUG = "second-public-collection" NON_PUBLIC_COLL_FIELDS = ( "tags", @@ -74,6 +78,7 @@ def test_create_collection( assert data["id"] == _coll_id assert data["name"] == COLLECTION_NAME + assert data["slug"] == COLLECTION_SLUG assert data["caption"] == CAPTION assert data["crawlCount"] == 1 assert data["pageCount"] > 0 @@ -98,6 +103,7 @@ def test_create_public_collection( json={ "crawlIds": [crawler_crawl_id], "name": PUBLIC_COLLECTION_NAME, + "slug": PUBLIC_COLLECTION_SLUG, "caption": CAPTION, "access": "public", "allowPublicDownload": False, @@ -131,7 +137,7 @@ def test_create_collection_taken_name( }, ) assert r.status_code == 400 - assert r.json()["detail"] == "collection_name_taken" + assert r.json()["detail"] in ("collection_name_taken", "collection_slug_taken") def test_create_collection_empty_name( @@ -207,6 +213,7 @@ def test_rename_collection( assert data["id"] == _coll_id assert data["name"] == UPDATED_NAME + assert data["slug"] == UPDATED_SLUG assert data["modified"] >= modified @@ -237,7 +244,16 @@ def test_rename_collection_taken_name( json={"name": SECOND_COLLECTION_NAME}, ) assert r.status_code == 400 - assert r.json()["detail"] == "collection_name_taken" + assert r.json()["detail"] in ("collection_name_taken", "collection_slug_taken") + + # Try to set first coll's slug to value already used for second collection + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_coll_id}", + headers=crawler_auth_headers, + json={"slug": SECOND_COLLECTION_NAME}, + ) + assert r.status_code == 400 + assert r.json()["detail"] == "collection_slug_taken" def test_add_remove_crawl_from_collection( @@ -324,6 +340,7 @@ def test_get_collection(crawler_auth_headers, default_org_id): data = r.json() assert data["id"] == _coll_id assert data["name"] == UPDATED_NAME + assert data["slug"] == UPDATED_SLUG assert data["oid"] == default_org_id assert data["description"] == DESCRIPTION assert data["caption"] == UPDATED_CAPTION @@ -346,6 +363,7 @@ def test_get_collection_replay(crawler_auth_headers, default_org_id): data = r.json() assert data["id"] == _coll_id assert data["name"] == UPDATED_NAME + assert data["slug"] == UPDATED_SLUG assert data["oid"] == default_org_id assert data["description"] == DESCRIPTION assert data["caption"] == UPDATED_CAPTION @@ -524,6 +542,7 @@ def test_list_collections( first_coll = [coll for coll in items if coll["name"] == UPDATED_NAME][0] assert first_coll["id"] == _coll_id assert first_coll["name"] == UPDATED_NAME + assert first_coll["slug"] == UPDATED_SLUG assert first_coll["oid"] == default_org_id assert first_coll["description"] == DESCRIPTION assert first_coll["caption"] == UPDATED_CAPTION @@ -540,6 +559,7 @@ def test_list_collections( second_coll = [coll for coll in items if coll["name"] == SECOND_COLLECTION_NAME][0] assert second_coll["id"] assert second_coll["name"] == SECOND_COLLECTION_NAME + assert second_coll["slug"] == SECOND_COLLECTION_NAME assert second_coll["oid"] == default_org_id assert second_coll.get("description") is None assert second_coll["crawlCount"] == 1 @@ -811,6 +831,7 @@ def test_list_public_collections( json={ "crawlIds": [crawler_crawl_id], "name": "Second public collection", + "slug": SECOND_PUBLIC_COLL_SLUG, "description": "Lorem ipsum", "access": "public", }, @@ -888,6 +909,7 @@ def test_list_public_collections( assert collection["name"] assert collection["created"] assert collection["modified"] + assert collection["slug"] assert collection["dateEarliest"] assert collection["dateLatest"] assert collection["crawlCount"] > 0 @@ -1111,7 +1133,7 @@ def test_list_public_colls_home_url_thumbnail(): def test_get_public_collection(default_org_id): r = requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}" + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}" ) assert r.status_code == 200 coll = r.json() @@ -1122,6 +1144,7 @@ def test_get_public_collection(default_org_id): assert coll["name"] assert coll["created"] assert coll["modified"] + assert coll["slug"] == PUBLIC_COLLECTION_SLUG assert coll["resources"] assert coll["dateEarliest"] assert coll["dateLatest"] @@ -1154,22 +1177,22 @@ def test_get_public_collection(default_org_id): # Invalid org slug - don't reveal whether org exists or not, use # same exception as if collection doesn't exist r = requests.get( - f"{API_PREFIX}/public/orgs/doesntexist/collections/{_public_coll_id}" + f"{API_PREFIX}/public/orgs/doesntexist/collections/{PUBLIC_COLLECTION_SLUG}" ) assert r.status_code == 404 assert r.json()["detail"] == "collection_not_found" - # Invalid collection id + # Unused slug random_uuid = uuid4() r = requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{random_uuid}" + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/someslugnotinuse" ) assert r.status_code == 404 assert r.json()["detail"] == "collection_not_found" # Collection isn't public r = requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{ _coll_id}" + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{UPDATED_SLUG}" ) assert r.status_code == 404 assert r.json()["detail"] == "collection_not_found" @@ -1189,7 +1212,7 @@ def test_get_public_collection_unlisted(crawler_auth_headers, default_org_id): # Verify single public collection GET endpoint works for unlisted collection r = requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_second_public_coll_id}" + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{SECOND_PUBLIC_COLL_SLUG}" ) assert r.status_code == 200 coll = r.json() @@ -1200,6 +1223,7 @@ def test_get_public_collection_unlisted(crawler_auth_headers, default_org_id): assert coll["name"] assert coll["created"] assert coll["modified"] + assert coll["slug"] == SECOND_PUBLIC_COLL_SLUG assert coll["resources"] assert coll["dateEarliest"] assert coll["dateLatest"] @@ -1229,7 +1253,7 @@ def test_get_public_collection_unlisted_org_profile_disabled( # Verify we can still get public details for unlisted collection r = requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_second_public_coll_id}" + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{SECOND_PUBLIC_COLL_SLUG}" ) assert r.status_code == 200 coll = r.json() @@ -1240,6 +1264,7 @@ def test_get_public_collection_unlisted_org_profile_disabled( assert coll["name"] assert coll["created"] assert coll["modified"] + assert coll["slug"] assert coll["resources"] assert coll["dateEarliest"] assert coll["dateLatest"] @@ -1314,7 +1339,7 @@ def test_unset_collection_home_url( def test_download_streaming_public_collection(crawler_auth_headers, default_org_id): # Check that download is blocked if allowPublicDownload is False with requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}/download", stream=True, ) as r: assert r.status_code == 403 @@ -1332,7 +1357,7 @@ def test_download_streaming_public_collection(crawler_auth_headers, default_org_ with TemporaryFile() as fh: with requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}/download", stream=True, ) as r: assert r.status_code == 200 @@ -1365,7 +1390,7 @@ def test_download_streaming_public_collection_profile_disabled( with TemporaryFile() as fh: with requests.get( - f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{_public_coll_id}/download", + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}/download", stream=True, ) as r: assert r.status_code == 200 @@ -1382,6 +1407,58 @@ def test_download_streaming_public_collection_profile_disabled( assert zip_file.getinfo(filename).compress_type == ZIP_STORED +def test_get_public_collection_slug_redirect(admin_auth_headers, default_org_id): + # Update public collection slug + new_slug = "new-slug" + + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_public_coll_id}", + headers=admin_auth_headers, + json={ + "slug": new_slug, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Get public collection from previous slug + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}" + ) + assert r.status_code == 200 + coll = r.json() + + assert coll["id"] == _public_coll_id + assert coll["oid"] == default_org_id + assert coll["slug"] == new_slug + + # Rename second public collection slug to now-unused PUBLIC_COLLECTION_SLUG + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}", + headers=admin_auth_headers, + json={ + "slug": PUBLIC_COLLECTION_SLUG, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + # Delete second public collection + r = requests.delete( + f"{API_PREFIX}/orgs/{default_org_id}/collections/{_second_public_coll_id}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + assert r.json()["success"] + + # Verify that trying to go to PUBLIC_COLLECTION_SLUG now 404s instead of taking + # us to the collection that had the slug before it was reassigned + r = requests.get( + f"{API_PREFIX}/public/orgs/{default_org_slug}/collections/{PUBLIC_COLLECTION_SLUG}" + ) + assert r.status_code == 404 + + def test_delete_collection(crawler_auth_headers, default_org_id, crawler_crawl_id): # Delete second collection r = requests.delete( diff --git a/frontend/docs/docs/deploy/customization.md b/frontend/docs/docs/deploy/customization.md index fd3a8eeb20..66260caa8e 100644 --- a/frontend/docs/docs/deploy/customization.md +++ b/frontend/docs/docs/deploy/customization.md @@ -238,8 +238,7 @@ type btrixEvent = ( extra?: { props?: { org_slug: string | null; - collection_id?: string | null; - collection_name?: string | null; + collection_slug?: string | null; logged_in?: boolean; }; }, diff --git a/frontend/src/classes/BtrixElement.ts b/frontend/src/classes/BtrixElement.ts index 8bfc76484c..be977083b1 100644 --- a/frontend/src/classes/BtrixElement.ts +++ b/frontend/src/classes/BtrixElement.ts @@ -32,7 +32,7 @@ export class BtrixElement extends TailwindElement { return this.appState.orgId; } - protected get orgSlug() { + protected get orgSlugState() { return this.appState.orgSlug; } diff --git a/frontend/src/features/collections/collections-grid.ts b/frontend/src/features/collections/collections-grid.ts index 1eb925594a..740a803680 100644 --- a/frontend/src/features/collections/collections-grid.ts +++ b/frontend/src/features/collections/collections-grid.ts @@ -61,7 +61,7 @@ export class CollectionsGrid extends BtrixElement {