chore(server): Check album permissions in bulk (#5290)

* chore(server): Check album permissions in bulk

Modify Access repository, to evaluate `album` permissions in bulk.
Queries have been validated to match what they currently generate for
single ids.

Queries:

* Owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
  WHERE
    "AlbumEntity"."id" = $1
    AND "AlbumEntity"."ownerId" = $2
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "AlbumEntity"."id" AS "AlbumEntity_id"
FROM "albums" "AlbumEntity"
WHERE
  "AlbumEntity"."id" IN ($1, $2)
  AND "AlbumEntity"."ownerId" = $3
  AND "AlbumEntity"."deletedAt" IS NULL
```

* Shared link access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "shared_links" "SharedLinkEntity"
  WHERE
    "SharedLinkEntity"."id" = $1
    AND "SharedLinkEntity"."albumId" = $2
)
LIMIT 1

-- After
SELECT
  "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
  "SharedLinkEntity"."id" AS "SharedLinkEntity_id"
FROM "shared_links" "SharedLinkEntity"
WHERE
  "SharedLinkEntity"."id" = $1
  AND "SharedLinkEntity"."albumId" IN ($2, $3)
```

* Shared album access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
    LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id"
    LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
      AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
  WHERE
    "AlbumEntity"."id" = $1
    AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $2
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "AlbumEntity"."id" AS "AlbumEntity_id"
FROM "albums" "AlbumEntity"
  LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"
    ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id"
  LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers"
    ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
    AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
WHERE
  "AlbumEntity"."id" IN ($1, $2)
  AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $3
  AND "AlbumEntity"."deletedAt" IS NULL
```

* chore(server): Add set utils, avoid double queries for same ids

* chore(server): Review feedback
This commit is contained in:
Michael Manganiello
2023-11-25 17:56:23 -05:00
committed by GitHub
parent 698226634e
commit 6d1b325b34
10 changed files with 179 additions and 126 deletions
+6 -7
View File
@@ -24,7 +24,7 @@ describe(ActivityService.name, () => {
describe('getAll', () => {
it('should get all', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
activityMock.search.mockResolvedValue([]);
await expect(sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id' })).resolves.toEqual([]);
@@ -37,7 +37,7 @@ describe(ActivityService.name, () => {
});
it('should filter by type=like', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
activityMock.search.mockResolvedValue([]);
await expect(
@@ -52,7 +52,7 @@ describe(ActivityService.name, () => {
});
it('should filter by type=comment', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
activityMock.search.mockResolvedValue([]);
await expect(
@@ -70,7 +70,7 @@ describe(ActivityService.name, () => {
describe('getStatistics', () => {
it('should get the comment count', async () => {
activityMock.getStatistics.mockResolvedValue(1);
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set([activityStub.oneComment.albumId]));
await expect(
sut.getStatistics(authStub.admin, {
assetId: 'asset-id',
@@ -82,7 +82,6 @@ describe(ActivityService.name, () => {
describe('addComment', () => {
it('should require access to the album', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(false);
await expect(
sut.create(authStub.admin, {
albumId: 'album-id',
@@ -114,7 +113,7 @@ describe(ActivityService.name, () => {
});
it('should fail because activity is disabled for the album', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
accessMock.activity.hasCreateAccess.mockResolvedValue(false);
activityMock.create.mockResolvedValue(activityStub.oneComment);
@@ -148,7 +147,7 @@ describe(ActivityService.name, () => {
});
it('should skip if like exists', async () => {
accessMock.album.hasOwnerAccess.mockResolvedValue(true);
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
accessMock.activity.hasCreateAccess.mockResolvedValue(true);
activityMock.search.mockResolvedValue([activityStub.liked]);