Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
55beb10
Module removal and editing improvements
anurag2787 Dec 27, 2025
e867b1e
fixed coderabbit review
anurag2787 Dec 27, 2025
18b1b20
feat: implement module deletion and editing with proper permissions
anurag2787 Dec 27, 2025
8aec659
fixed check error
anurag2787 Dec 27, 2025
c525845
fixed coderabbit review
anurag2787 Dec 27, 2025
afcdc8d
Merge branch 'main' of github.com:anurag2787/Nest into module-removal…
anurag2787 Dec 29, 2025
454d961
added functionaility to edit module by mentor
anurag2787 Dec 29, 2025
b0b4220
fixed sonar and coderabbit review
anurag2787 Dec 29, 2025
e7540f2
Added no sonar
anurag2787 Dec 29, 2025
44b51e9
fixed nosoanr
anurag2787 Dec 31, 2025
f729e3d
updated nosonar comment
anurag2787 Dec 31, 2025
3b7162d
update nosonar warning
anurag2787 Dec 31, 2025
968b9da
fixed coderabbit review
anurag2787 Dec 31, 2025
8afb6ed
Merge branch 'main' of github.com:anurag2787/Nest into module-removal…
anurag2787 Dec 31, 2025
f11d484
Merge branch 'main' of github.com:anurag2787/Nest into module-removal…
anurag2787 Jan 9, 2026
3d13803
Fixed coderabbit review
anurag2787 Jan 9, 2026
9292706
Resolve coderabbit review
anurag2787 Jan 9, 2026
dca555f
fix code
anurag2787 Jan 9, 2026
1233914
fixed coderabbit comment
anurag2787 Jan 9, 2026
4b79b24
fixed
anurag2787 Jan 9, 2026
0a330f6
fixed check command fail
anurag2787 Jan 9, 2026
9a0284a
Merge branch 'main' of github.com:anurag2787/Nest into module-removal…
anurag2787 Jan 15, 2026
d2afc06
fixed sonarqube warning
anurag2787 Jan 15, 2026
04e794c
Fixed sonarqube warning
anurag2787 Jan 15, 2026
851203b
Remove view Issues from mentor
anurag2787 Jan 16, 2026
a79a8e5
Merge branch 'main' into module-removal-and-editing
anurag2787 Jan 16, 2026
9be6301
Merge branch 'main' into module-removal-and-editing
anurag2787 Jan 24, 2026
3cf76b3
fixed merge conflict
anurag2787 Jan 24, 2026
0e63158
fixed sonarqube issue
anurag2787 Jan 24, 2026
3da7d25
fixed code rabbit review
anurag2787 Jan 24, 2026
b12ad1e
fixed check
anurag2787 Jan 24, 2026
0992bab
fixed coderabbit review
anurag2787 Jan 24, 2026
4da0ada
Merge branch 'main' into module-removal-and-editing
anurag2787 Jan 24, 2026
96d96a5
Merge branch 'main' into module-removal-and-editing
anurag2787 Jan 26, 2026
b224d61
Merge branch 'main' into module-removal-and-editing
anurag2787 Feb 20, 2026
03fdca3
fixed merge conflict error and updated mentor logic
anurag2787 Feb 20, 2026
95ece1c
fix review
anurag2787 Feb 20, 2026
b4e3ce4
Merge branch 'main' of github.com:OWASP/Nest into pr/anurag2787/3054
kasya Feb 22, 2026
7be0b5f
Update mentors permissions to view module issues
kasya Feb 22, 2026
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
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ repos:
args:
- --fix
files: \.md$
language_version: 22.13.0

- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
rev: 0.2.3
Expand Down
74 changes: 68 additions & 6 deletions backend/apps/mentorship/api/internal/mutations/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ def resolve_mentors_from_logins(logins: list[str]) -> set[Mentor]:
return mentors


def _is_mentor_of_module(user, module) -> bool:
"""Check if the given user is a mentor for the module.

Runs a fallback check against github_user if the mentor hasn't linked
their nest_user profile yet.
"""
if Mentor.objects.filter(nest_user=user, modules=module).exists():
return True
return (
hasattr(user, "github_user")
and Mentor.objects.filter(github_user=user.github_user, modules=module).exists()
)


def _validate_module_dates(started_at, ended_at, program_started_at, program_ended_at) -> tuple:
"""Validate and normalize module start/end dates against program constraints."""
if started_at is None or ended_at is None:
Expand Down Expand Up @@ -144,7 +158,7 @@ def assign_issue_to_user(
if module is None:
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG)

if not Mentor.objects.filter(nest_user=user, modules=module).exists():
if not _is_mentor_of_module(user, module):
raise PermissionDenied(NOT_MENTOR_ASSIGN_MSG)

gh_user = GithubUser.objects.filter(login=user_login).first()
Expand Down Expand Up @@ -183,7 +197,7 @@ def unassign_issue_from_user(
if module is None:
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG)

if not Mentor.objects.filter(nest_user=user, modules=module).exists():
if not _is_mentor_of_module(user, module):
raise PermissionDenied(NOT_MENTOR_UNASSIGN_MSG)

gh_user = GithubUser.objects.filter(login=user_login).first()
Expand Down Expand Up @@ -224,7 +238,7 @@ def set_task_deadline(
if module is None:
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG)

if not Mentor.objects.filter(nest_user=user, modules=module).exists():
if not _is_mentor_of_module(user, module):
raise PermissionDenied(NOT_MENTOR_SET_DEADLINE_MSG)

issue = (
Expand Down Expand Up @@ -286,7 +300,7 @@ def clear_task_deadline(
if module is None:
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG)

if not Mentor.objects.filter(nest_user=user, modules=module).exists():
if not _is_mentor_of_module(user, module):
raise PermissionDenied(NOT_MENTOR_CLEAR_DEADLINE_MSG)

issue = (
Expand Down Expand Up @@ -319,7 +333,14 @@ def clear_task_deadline(
@strawberry.mutation(permission_classes=[IsAuthenticated])
@transaction.atomic
def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) -> ModuleNode:
"""Update an existing mentorship module."""
"""Update an existing mentorship module.

User must either be:
- An admin of the program, or
- A mentor explicitly assigned to this module

Admins and module mentors can edit any field and manage mentor assignments.
"""
user = info.context.request.user

try:
Expand All @@ -332,7 +353,7 @@ def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) ->
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) from e

is_admin = module.program.admins.filter(nest_user=user).exists()
is_mentor = Mentor.objects.filter(nest_user=user, modules=module).exists()
is_mentor = _is_mentor_of_module(user, module)
if not (is_admin or is_mentor):
msg = "Only admins of the program or mentors of this module can edit modules."
raise PermissionDenied(msg)
Expand Down Expand Up @@ -391,3 +412,44 @@ def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) ->
module.program.save(update_fields=["experience_levels"])

return module

@strawberry.mutation(permission_classes=[IsAuthenticated])
@transaction.atomic
def delete_module(
self,
info: strawberry.Info,
program_key: str,
module_key: str,
) -> str:
"""Delete a mentorship module. User must be an admin of the program."""
user = info.context.request.user

try:
module = Module.objects.select_related("program").get(
key=module_key, program__key=program_key
)
except Module.DoesNotExist as e:
raise ObjectDoesNotExist(MODULE_NOT_FOUND_MSG) from e

if not module.program.admins.filter(nest_user=user).exists():
msg = "Only program admins can delete modules."
raise PermissionDenied(msg)

program = module.program
module_name = module.name

experience_level_to_remove = module.experience_level
if (
experience_level_to_remove in program.experience_levels
and not Module.objects.filter(
program=program, experience_level=experience_level_to_remove
)
.exclude(id=module.id)
.exists()
):
program.experience_levels.remove(experience_level_to_remove)
program.save(update_fields=["experience_levels"])

module.delete()

return f"Module '{module_name}' has been deleted successfully."
Comment thread
anurag2787 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -445,16 +445,18 @@ jest.mock('components/EntityActions', () => ({
moduleKey,
status: _status,
setStatus: _setStatus,
isAdmin,
...props
}: {
type: string
programKey?: string
moduleKey?: string
status?: string
setStatus?: (status: string) => void
isAdmin?: boolean
[key: string]: unknown
}) => (
<div data-testid="entity-actions" {...props}>
<div data-testid="entity-actions" {...props} data-isadmin={isAdmin}>
EntityActions: type={type}, programKey={programKey}, moduleKey={moduleKey}
</div>
),
Expand Down
Loading