feat: guest role for clients and outsiders
A project member can be a GUEST. In a private project a guest only sees the tickets they take part in (opened, assigned or followed): every other ticket answers 404, also in my work, realtime changes, blockers and linked tickets, and project-wide views (activity, statistics, dashboard, releases, sprints, epics, trash) answer 403. Guests open tickets without assigning or planning them, and comment, attach, follow and vote on their own; they change nothing else. In a public project they see what anyone does. The web gives them only Tablero, Lista and Miembros and loads none of the project-wide data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,14 @@ export class MongoFindIssuesRepository extends FindIssuesRepository {
|
||||
if (filter.dueBefore)
|
||||
query['dueDate'] = { $ne: null, $lt: filter.dueBefore };
|
||||
const and: Record<string, unknown>[] = [];
|
||||
// Projects where the viewer is a guest: only the tickets they are part of.
|
||||
if (filter.guestProjectIds?.length && filter.participantId)
|
||||
and.push({
|
||||
$or: [
|
||||
{ projectId: { $nin: filter.guestProjectIds } },
|
||||
{ $or: participation(filter.participantId) },
|
||||
],
|
||||
});
|
||||
if (filter.typesByProject)
|
||||
and.push({
|
||||
$or: Object.entries(filter.typesByProject).map(
|
||||
@@ -440,6 +448,13 @@ export class MongoFindIssuesRepository extends FindIssuesRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/** Opened it, has it or follows it. */
|
||||
const participation = (userId: string) => [
|
||||
{ 'reporter.id': userId },
|
||||
{ 'assignee.id': userId },
|
||||
{ 'watchers.id': userId },
|
||||
];
|
||||
|
||||
function toMongoFilter(filter: IssueFilter): FilterQuery<IssueDoc> {
|
||||
// Tickets in the trash are only listed by the trash itself.
|
||||
const query: FilterQuery<IssueDoc> = {
|
||||
@@ -476,6 +491,11 @@ function toMongoFilter(filter: IssueFilter): FilterQuery<IssueDoc> {
|
||||
if (filter.unassigned) query['assignee'] = null;
|
||||
else if (filter.assigneeId) query['assignee.id'] = filter.assigneeId;
|
||||
if (filter.watcherId) query['watchers.id'] = filter.watcherId;
|
||||
if (filter.participantId)
|
||||
query.$and = [
|
||||
...(query.$and ?? []),
|
||||
{ $or: participation(filter.participantId) },
|
||||
];
|
||||
if (filter.search?.trim()) {
|
||||
const pattern = new RegExp(escapeRegex(filter.search.trim()), 'i');
|
||||
query.$or = [
|
||||
|
||||
Reference in New Issue
Block a user