Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support private projects for event search #7884

Merged
merged 5 commits into from
Aug 15, 2024
Merged
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
16 changes: 10 additions & 6 deletions src/lib/features/events/event-search-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { normalizeQueryParams } from '../../features/feature-search/search-utils
import Controller from '../../routes/controller';
import type { IAuthRequest } from '../../server-impl';
import type { IEvent } from '../../types';
import { anonymiseKeys } from '../../util';
import { anonymiseKeys, extractUserIdFromUser } from '../../util';

const ANON_KEYS = ['email', 'username', 'createdBy'];
const version = 1 as const;
Expand Down Expand Up @@ -67,6 +67,7 @@ export default class EventSearchController extends Controller {
req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
res: Response<EventSearchResponseSchema>,
): Promise<void> {
const { user } = req;
const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
req.query,
{
Expand All @@ -75,11 +76,14 @@ export default class EventSearchController extends Controller {
},
);

const { events, totalEvents } = await this.eventService.searchEvents({
...req.query,
offset: normalizedOffset,
limit: normalizedLimit,
});
const { events, totalEvents } = await this.eventService.searchEvents(
{
...req.query,
offset: normalizedOffset,
limit: normalizedLimit,
},
extractUserIdFromUser(user),
);

this.openApiService.respondWithValidation(
200,
Expand Down
93 changes: 93 additions & 0 deletions src/lib/features/events/event-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { ProjectAccess } from '../private-project/privateProjectStore';
import { filterAccessibleProjects } from './event-service';

describe('filterPrivateProjectsFromParams', () => {
it('should return IS_ANY_OF with allowed projects when projectParam is undefined and mode is limited', () => {
const projectAccess: ProjectAccess = {
mode: 'limited',
projects: ['project1', 'project2'],
};

const projectParam = undefined;

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBe('IS_ANY_OF:project1,project2');
});

it('should return the original projectParam when mode is all', () => {
const projectAccess: ProjectAccess = {
mode: 'all',
};

const projectParam = 'IS:project3';

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBe(projectParam);
});

it('should filter out projects not in allowedProjects when mode is limited', () => {
const projectAccess: ProjectAccess = {
mode: 'limited',
projects: ['project1', 'project2'],
};

const projectParam = 'IS_ANY_OF:project1,project3';

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBe('IS_ANY_OF:project1');
});

it('should return a single project if only one is allowed', () => {
const projectAccess: ProjectAccess = {
mode: 'limited',
projects: ['project1'],
};

const projectParam = 'IS_ANY_OF:project1,project2';

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBe('IS_ANY_OF:project1');
});

it('should return undefined if projectParam is undefined and projectAccess mode is all', () => {
const projectAccess: ProjectAccess = {
mode: 'all',
};

const projectParam = undefined;

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBeUndefined();
});

it('should return the original projectParam if all projects are allowed when mode is limited', () => {
const projectAccess: ProjectAccess = {
mode: 'limited',
projects: ['project1', 'project2', 'project3'],
};

const projectParam = 'IS_ANY_OF:project1,project2';

const result = filterAccessibleProjects(projectParam, projectAccess);

expect(result).toBe('IS_ANY_OF:project1,project2');
});

it('should throw an error if no projects match', () => {
const projectAccess: ProjectAccess = {
mode: 'limited',
projects: ['project1', 'project2'],
};

const projectParam = 'IS_ANY_OF:project3,project4';

expect(() =>
filterAccessibleProjects(projectParam, projectAccess),
).toThrow('No accessible projects in the search parameters');
});
});
47 changes: 46 additions & 1 deletion src/lib/features/events/event-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strateg
import { parseSearchOperatorValue } from '../feature-search/search-utils';
import { endOfDay, formatISO } from 'date-fns';
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import type { ProjectAccess } from '../private-project/privateProjectStore';

export default class EventService {
private logger: Logger;
Expand Down Expand Up @@ -63,8 +64,20 @@ export default class EventService {
};
}

async searchEvents(search: IEventSearchParams): Promise<IEventList> {
async searchEvents(
search: IEventSearchParams,
userId: number,
): Promise<IEventList> {
const projectAccess =
await this.privateProjectChecker.getUserAccessibleProjects(userId);

search.project = filterAccessibleProjects(
search.project,
projectAccess,
);

const queryParams = this.convertToDbParams(search);

const totalEvents = await this.eventStore.searchEventsCount(
{
limit: search.limit,
Expand Down Expand Up @@ -210,3 +223,35 @@ export default class EventService {
return this.eventStore.getEventCreators();
}
}

export const filterAccessibleProjects = (
projectParam: string | undefined,
projectAccess: ProjectAccess,
): string | undefined => {
if (projectAccess.mode !== 'all') {
const allowedProjects = projectAccess.projects;

if (!projectParam) {
return `IS_ANY_OF:${allowedProjects.join(',')}`;
} else {
const searchProjectList = projectParam.split(',');
const filteredProjects = searchProjectList
.filter((proj) =>
allowedProjects.includes(
proj.replace(/^(IS|IS_ANY_OF):/, ''),
),
)
.join(',');

if (!filteredProjects) {
throw new Error(
'No accessible projects in the search parameters',
);
}

return filteredProjects;
}
}

return projectParam;
};
Loading