Skip to content

Commit

Permalink
feat: get folder ancestors excluding root children direct root childr…
Browse files Browse the repository at this point in the history
…en, useful for workspaces
  • Loading branch information
jzunigax2 committed Oct 7, 2024
1 parent 4ea09f1 commit aacb229
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
CREATE OR REPLACE FUNCTION get_folder_ancestors_excluding_root_children(folder_id UUID, u_id INT)
RETURNS setof folders AS $$
BEGIN
RETURN QUERY
WITH RECURSIVE hier AS (
SELECT c.*
FROM folders c
WHERE c.removed = FALSE
AND c.uuid = folder_id
UNION
SELECT f.*
FROM folders f
INNER JOIN hier fh ON fh.parent_id = f.id
WHERE f.removed = FALSE
AND f.user_id = u_id
AND f.parent_id IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM folders root
WHERE root.id = f.parent_id
AND root.parent_id IS NULL
)
)
SELECT * FROM hier
WHERE parent_id IS NOT NULL; -- Exclude the root folder itself
END;
$$ LANGUAGE plpgsql;
`);
},

async down(queryInterface) {
await queryInterface.sequelize.query(
'drop function get_folder_ancestors_excluding_root_children;',
);
},
};
62 changes: 62 additions & 0 deletions src/modules/folder/folder.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,66 @@ describe('FolderController', () => {
).rejects.toThrow(InvalidParentFolderException);
});
});

describe('getgetFolderAncestors', () => {
it('When get folder ancestors is requested with workspace query param as false, then it should return the ancestors', async () => {
const user = newUser();
const folder = newFolder({ owner: user });
const mockAncestors = [
newFolder({
attributes: { parentUuid: folder.parentUuid },
owner: user,
}),
newFolder({
attributes: { parentUuid: folder.parentUuid },
owner: user,
}),
];

jest
.spyOn(folderUseCases, 'getFolderAncestors')
.mockResolvedValue(mockAncestors);

const result = await folderController.getFolderAncestors(
user,
folder.uuid,
false,
);
expect(result).toEqual({ ancestors: mockAncestors });
expect(folderUseCases.getFolderAncestors).toHaveBeenCalledWith(
user,
folder.uuid,
);
});

it('When get folder ancestors is requested with workspace query param as true, then it should return the ancestors', async () => {
const user = newUser();
const folder = newFolder({ owner: user });
const mockAncestors = [
newFolder({
attributes: { parentUuid: folder.parentUuid },
owner: user,
}),
newFolder({
attributes: { parentUuid: folder.parentUuid },
owner: user,
}),
];

jest
.spyOn(folderUseCases, 'getFolderAncestors')
.mockResolvedValue(mockAncestors);

const result = await folderController.getFolderAncestors(
user,
folder.uuid,
true,
);
expect(result).toEqual({ ancestors: mockAncestors });
expect(folderUseCases.getFolderAncestorsInWorkspace).toHaveBeenCalledWith(
user,
folder.uuid,
);
});
});
});
11 changes: 10 additions & 1 deletion src/modules/folder/folder.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ApiOkResponse,
ApiOperation,
ApiParam,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import { FolderUseCases } from './folder.usecase';
Expand Down Expand Up @@ -690,16 +691,24 @@ export class FolderController {
value: 'folder',
},
])
@ApiQuery({
name: 'workspace',
description: 'If true, will return ancestors in workspace',
type: Boolean,
})
@WorkspacesInBehalfValidationFolder()
async getFolderAncestors(
@UserDecorator() user: User,
@Param('uuid') folderUuid: Folder['uuid'],
@Query('workspace') workspace: boolean,
) {
if (!validate(folderUuid)) {
throw new BadRequestException('Invalid UUID provided');
}

return this.folderUseCases.getFolderAncestors(user, folderUuid);
return !workspace
? this.folderUseCases.getFolderAncestors(user, folderUuid)
: this.folderUseCases.getFolderAncestorsInWorkspace(user, folderUuid);
}

@Get('/:uuid/tree')
Expand Down
23 changes: 23 additions & 0 deletions src/modules/folder/folder.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export interface FolderRepository {
user: User,
uuids: FolderAttributes['uuid'][],
): Promise<Folder[]>;
getFolderAncestorsInWorkspace(
user: User,
folderUuid: Folder['uuid'],
): Promise<Folder[]>;
}

@Injectable()
Expand Down Expand Up @@ -501,6 +505,25 @@ export class SequelizeFolderRepository implements FolderRepository {
return folders;
}

async getFolderAncestorsInWorkspace(
user: User,
folderUuid: Folder['uuid'],
): Promise<Folder[]> {
const [rawFolders] = await this.folderModel.sequelize.query(
'SELECT * FROM get_folder_ancestors_excluding_root_children(:folder_id, :user_id)',
{
replacements: { folder_id: folderUuid, user_id: user.id },
},
);

const camelCasedFolders = rawFolders.map(mapSnakeCaseToCamelCase);
const folders = this.folderModel
.bulkBuild(camelCasedFolders as any)
.map((folder) => this.toDomain(folder));

return folders;
}

async clearOrphansFolders(
userId: FolderAttributes['userId'],
): Promise<number> {
Expand Down
19 changes: 19 additions & 0 deletions src/modules/folder/folder.usecase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1252,4 +1252,23 @@ describe('FolderUseCases', () => {
});
});
});

describe('getFolderAncestorsInWorkspace', () => {
it('Should return the ancestors of a folder in a workspace', async () => {
const user = newUser();
const folder = newFolder({ attributes: { userId: user.id } });
const ancestors = [newFolder({ owner: user })];

jest
.spyOn(folderRepository, 'getFolderAncestorsInWorkspace')
.mockResolvedValueOnce(ancestors);

const result = await service.getFolderAncestorsInWorkspace(
user,
folder.uuid,
);

expect(result).toEqual(ancestors);
});
});
});
10 changes: 10 additions & 0 deletions src/modules/folder/folder.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,16 @@ export class FolderUseCases {
return this.folderRepository.getFolderAncestors(user, folderUuid);
}

getFolderAncestorsInWorkspace(
user: User,
folderUuid: Folder['uuid'],
): Promise<Folder[]> {
return this.folderRepository.getFolderAncestorsInWorkspace(
user,
folderUuid,
);
}

async moveFolder(
user: User,
folderUuid: Folder['uuid'],
Expand Down

0 comments on commit aacb229

Please sign in to comment.