Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 157 additions & 4 deletions api/src/routes/datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,60 @@ const isPermittedTo = accessControl('datasets');
const router = express.Router();
const prisma = new PrismaClient();

const build_include_object = ({
include_users = true,
include_datasets = true,
include_contacts = true,
} = {}) => _.omitBy(_.isUndefined)({
users: include_users ? {
select: {
user: true,
assigned_at: true,
assignor: {
select: {
id: true,
username: true,
name: true,
},
},
},
} : undefined,
datasets: include_datasets ? {
select: {
dataset: {
include: {
workflows: {
select: {
id: true,
},
},
},
},
assigned_at: true,
assignor: {
select: {
id: true,
username: true,
name: true,
},
},
},
} : undefined,
contacts: include_contacts ? {
select: {
contact: true,
assigned_at: true,
assignor: {
select: {
id: true,
username: true,
name: true,
},
},
},
} : undefined,
});

// stats - UI
router.get(
'/stats',
Expand Down Expand Up @@ -62,17 +116,17 @@ router.get(
});
} else {
result = await prisma.$queryRaw`
select
count(*) as "count",
sum(du_size) as total_size,
select
count(*) as "count",
sum(du_size) as total_size,
SUM(
CASE
WHEN metadata -> 'num_genome_files' IS NOT NULL
THEN (metadata ->> 'num_genome_files')::int
ELSE 0
END
) AS total_num_genome_files
from dataset
from dataset
where is_deleted = false;
`;

Expand Down Expand Up @@ -386,6 +440,105 @@ router.post(
}),
);

// Route to fetch projects linked to a specific dataset ID
router.get(
'/:id/projects', // Adding /:id to represent DatasetID
Copy link
Contributor

@ri-pandey ri-pandey Apr 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have two separate routes - one which gets all projects for a given dataset id, and another which only fetches the projects which the current user is allowed to read. These routes can have paths /datasets/:id/projects and /datasets/:id/:username/projects.

For these two routes, determine whether the current user is permitted to only view their own projects or all projects, via:

const isPermittedToProjects = accessControls('projects')

# For the /datasets/:id/pprojects route:
isPermittedToProjects('read' )

# For the /datasets/:id/:username/projects route: 
isPermittedToProjects('read', { checkOwnership: true } )

Then, in the /datasets/:id/:username/projects route, make sure that the logged-in user is assigned to the projects being retrieved. The other route can return all projects associated with the dataset.

There is a standard pattern we use to tell whether entities retrieved from the API should be filtered by the logged-in user or not. For example, here is a method that retrieves projects either based on the logged-in user, or by ignoring the logged in user. You can see this at https://github.com/IUSCA/bioloop/blob/main/ui/src/services/projects.js#L7

isPermittedTo('read'),
validate([
query('take').default(25).isInt().toInt(),
query('skip').default(0).isInt().toInt(),
query('search').default(''), // Adding search query validation
query('sort_order').default('desc').isIn(['asc', 'desc']),
query('sort_by').default('updated_at').isIn(['name', 'created_at', 'updated_at']),
]),
asyncHandler(async (req, res, next) => {
const { search, sort_order, sort_by } = req.query;
// const datasetID = req.params.id; // DatasetID is retrieved from the URL parameter
const datasetID = parseInt(req.params.id, 10);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this conversion is needed


// Check if the datasetID has any project association
const hasProjectDatasetAssociation = await datasetService.has_project_dataset_assoc({
DatasetId: datasetID,
});

if (!hasProjectDatasetAssociation) {
return res.status(404).json({
message: 'No projects found for the specified dataset',
});
}

// Building filters for project retrieval, including datasetID filter
const filters = {
datasets: {
some: {
dataset_id: datasetID, // Filter projects by the datasetID
},
},
};

if (search) {
filters.OR = [
{
name: {
contains: search,
mode: 'insensitive', // Case-insensitive search for project name
},
},
{
users: {
some: {
user: {
username: {
contains: search,
mode: 'insensitive', // Case-insensitive search for user username
},
},
},
},
},
{
datasets: {
some: {
dataset: {
name: {
contains: search,
mode: 'insensitive',
},
},
},
},
},
];
}

const sort_obj = {
[sort_by]: sort_order,
};

// Retrieving projects linked to the specified datasetID and applying the filters
const [projects, totalCount] = await prisma.$transaction([
prisma.project.findMany({
skip: req.query.skip,
take: req.query.take,
orderBy: sort_obj,
where: filters,
include: build_include_object(), // Including related data as per existing logic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename build_include_object to build_projects_include_object

}),
prisma.project.count({
where: filters, // Counting the total number of projects with the dataset filter
}),
]);

// Sending back the retrieved projects and total count in the response
res.json({
metadata: { count: totalCount },
projects,
});
}),
);

module.exports = router;

// modify - worker
router.patch(
'/:id',
Expand Down
12 changes: 12 additions & 0 deletions api/src/services/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ async function has_dataset_assoc({
return projects.length > 0;
}

// Code to check if a particular dataset is linked to any projects or not.

async function has_project_dataset_assoc({ datasetId }) {
const projectDatasetAssociations = await prisma.project_dataset.findMany({
where: {
dataset_id: datasetId,
},
});
return projectDatasetAssociations.length > 0;
}

// async function search_files({ dataset_id, query }) {
// const file_matches_promise = prisma.$queryRaw`
// select
Expand Down Expand Up @@ -486,6 +497,7 @@ module.exports = {
create_workflow,
create_filetree,
has_dataset_assoc,
has_project_dataset_assoc,
files_ls,
search_files,
add_files,
Expand Down
Loading