Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
b6721b6
program mutation and node
Rajgupta36 Jun 30, 2025
e9ac12d
UI for program create , view and edit
Rajgupta36 Jul 1, 2025
b58802f
backend completed
Rajgupta36 Jul 3, 2025
abf873b
create module edit and view page
Rajgupta36 Jul 6, 2025
79b6268
type fix
Rajgupta36 Jul 6, 2025
eb8f076
update components
Rajgupta36 Jul 6, 2025
d6231c0
updated backend
Rajgupta36 Jul 6, 2025
fbb82ea
slugify urls
Rajgupta36 Jul 6, 2025
8d571fc
pre commit
Rajgupta36 Jul 6, 2025
468f0b8
fix components
Rajgupta36 Jul 6, 2025
a3c5d3b
fix merge conflicts
Rajgupta36 Jul 6, 2025
dbed9b9
update suggestions
Rajgupta36 Jul 7, 2025
be106ca
Merge branch 'main' into feat/mentorship-dev
Rajgupta36 Jul 7, 2025
ca3d122
update pnpm-lock file
Rajgupta36 Jul 7, 2025
ad1d76f
fix spell check
Rajgupta36 Jul 7, 2025
779df27
fix publish program
Rajgupta36 Jul 7, 2025
fb951f6
fix unit test cases
Rajgupta36 Jul 7, 2025
93236e4
Merge branch 'main' into feat/mentorship-dev
kasya Jul 8, 2025
c285a54
fix pre-commits
Rajgupta36 Jul 8, 2025
d2f0864
update frontend logic
Rajgupta36 Jul 9, 2025
a98cd10
format
Rajgupta36 Jul 9, 2025
471d502
refactor code
Rajgupta36 Jul 11, 2025
d0e8c7a
fix terminal
Rajgupta36 Jul 11, 2025
c96ed0b
suggestion fixes
Rajgupta36 Jul 11, 2025
3c1a25a
added unit test cases
Rajgupta36 Jul 12, 2025
99474f3
fix test cases
Rajgupta36 Jul 12, 2025
028e6b9
optimization
Rajgupta36 Jul 12, 2025
53035d1
Merge branch 'main' into feat/mentorship-dev
Rajgupta36 Jul 12, 2025
668b0ea
generated code
Rajgupta36 Jul 12, 2025
8844c2d
update code
Rajgupta36 Jul 12, 2025
fe3ff9e
update code
Rajgupta36 Jul 12, 2025
0b09822
update naming,validation logic
Rajgupta36 Jul 13, 2025
454cd66
Merge branch 'main' into feat/mentorship-dev
Rajgupta36 Jul 13, 2025
94cceed
cleanup
Rajgupta36 Jul 13, 2025
4d56bcf
fix project suggestion bug
Rajgupta36 Jul 14, 2025
1579788
Merge branch 'main' into feat/mentorship-dev
Rajgupta36 Jul 14, 2025
0468fcf
update code
Rajgupta36 Jul 14, 2025
d5c4c91
merge main into feat/mentorship-dev
Rajgupta36 Jul 15, 2025
3a1ee00
fixes
Rajgupta36 Jul 15, 2025
8d5e9fd
apply suggestions
Rajgupta36 Jul 16, 2025
3cbb44c
merge main into feat/mentorship-dev
Rajgupta36 Jul 16, 2025
32962c9
update suggestion
Rajgupta36 Jul 16, 2025
d1d89ab
merge main into feat/mentorship-dev
Rajgupta36 Jul 17, 2025
a67a3fb
update code
Rajgupta36 Jul 18, 2025
aa42c75
merge main into feat/mentorship-dev
Rajgupta36 Jul 18, 2025
45fe1e0
merge main into feat/mentorship-dev
Rajgupta36 Jul 18, 2025
7596754
Merge branch 'main' into feat/mentorship-dev
kasya Jul 20, 2025
d806214
merge main into feat/mentorship-dev
Rajgupta36 Jul 22, 2025
8ba10b8
update code
Rajgupta36 Jul 22, 2025
61b3725
Merge branch 'main' into feat/mentorship-dev
Rajgupta36 Jul 22, 2025
30ff8e7
update frozen-lockfile
Rajgupta36 Jul 22, 2025
b518fc9
Merge branch 'main' into feat/mentorship-dev
kasya Jul 23, 2025
8eb6522
merge main into feat/mentorship-dev
Rajgupta36 Jul 24, 2025
81b48aa
Merge remote-tracking branch 'upstream/main' into feat/mentorship-dev
Rajgupta36 Jul 24, 2025
478d3cf
update code and make program page view only
Rajgupta36 Jul 26, 2025
4ddf608
updated migration and add query
Rajgupta36 Jul 26, 2025
8b4e085
added index
Rajgupta36 Jul 27, 2025
cb8665a
updated components, mutaitons and nodes
Rajgupta36 Jul 28, 2025
2e8fc2a
update code
Rajgupta36 Jul 28, 2025
7197275
added constraints
Rajgupta36 Jul 28, 2025
63ecb63
fix test case
Rajgupta36 Jul 28, 2025
eb24e0b
merge main into feat/mentorship-dev
Rajgupta36 Jul 28, 2025
4b7d5dc
added unit test case for program page
Rajgupta36 Jul 28, 2025
8e7cb8f
merge main into feat/mentorship-dev
Rajgupta36 Jul 28, 2025
b3f51ec
added remaining unit test cases
Rajgupta36 Jul 28, 2025
5689e93
fix bug
Rajgupta36 Jul 28, 2025
b98893f
merge main into feat/mentorship-dev
Rajgupta36 Jul 29, 2025
f1d3b24
update code
Rajgupta36 Aug 1, 2025
27839f8
merge main into feat/mentorship-dev
Rajgupta36 Aug 1, 2025
a27a4d6
updated auth and projectleader code
Rajgupta36 Aug 2, 2025
ee84b4a
update test cases
Rajgupta36 Aug 2, 2025
d2ef0ce
update code
Rajgupta36 Aug 2, 2025
06bb530
revert test case
Rajgupta36 Aug 2, 2025
80a83c0
merge upstream into main
Rajgupta36 Aug 5, 2025
29afecd
fix unit test cases
Rajgupta36 Aug 5, 2025
04e060b
migration fix
Rajgupta36 Aug 6, 2025
0405292
merge upstream into main
Rajgupta36 Aug 6, 2025
f446ecf
increase test coverage
Rajgupta36 Aug 6, 2025
3e4c9e4
update suggestions
Rajgupta36 Aug 7, 2025
75ae1ba
added function for cache clear by index_name
Rajgupta36 Aug 7, 2025
865dbac
created component for single module
Rajgupta36 Aug 7, 2025
9e28465
fix typo
Rajgupta36 Aug 7, 2025
a9a734c
fix some frontend test cases
Rajgupta36 Aug 7, 2025
2f5e88d
added post save signal
Rajgupta36 Aug 8, 2025
3e51bec
added more test cases
Rajgupta36 Aug 8, 2025
1b6f6f4
merge upstream into main
Rajgupta36 Aug 8, 2025
95f824b
merge upstream into main and update singlemodulecard
Rajgupta36 Aug 8, 2025
7fe07d4
pnpm updated
Rajgupta36 Aug 8, 2025
e3b1c5b
increase coverage
Rajgupta36 Aug 8, 2025
14f5519
Merge branch 'main' into feat/mentorship-dev
kasya Aug 9, 2025
df539bc
update suggestion
Rajgupta36 Aug 9, 2025
9674fb0
updated backend suggestions
Rajgupta36 Aug 10, 2025
c522b1c
merge upstream into main
Rajgupta36 Aug 10, 2025
f66ddde
added isMentor query
Rajgupta36 Aug 10, 2025
376ccdb
fix page loading
Rajgupta36 Aug 10, 2025
dc97d9a
merge upstream into main
Rajgupta36 Aug 11, 2025
dd6f087
Update module description styling
kasya Aug 12, 2025
fb1f7e8
merge upstream into main
Rajgupta36 Aug 12, 2025
e4fa7a4
merge upstream into main
Rajgupta36 Aug 12, 2025
87fbdd2
Update code
arkid15r Aug 13, 2025
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
4 changes: 4 additions & 0 deletions backend/apps/mentorship/graphql/mutations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Core Mentorship mutations."""

from .module import ModuleMutation
from .program import ProgramMutation
170 changes: 170 additions & 0 deletions backend/apps/mentorship/graphql/mutations/module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""Mentorship Module GraphQL Mutations."""

import strawberry

from apps.common.utils import slugify
from apps.github.models import User as GithubUser
from apps.mentorship.graphql.nodes.modules import (
CreateModuleInput,
ModuleNode,
UpdateModuleInput,
)
from apps.mentorship.models import Mentor, Module
from apps.mentorship.models.program import Program
from apps.mentorship.utils.user import get_authenticated_user
from apps.owasp.models import Project


@strawberry.type
class ModuleMutation:
"""GraphQL mutations related to module."""

@strawberry.mutation
def create_module(self, info: strawberry.Info, input_data: CreateModuleInput) -> ModuleNode:
"""Create a new mentorship module if the user is a admin."""
request = info.context.request
user = get_authenticated_user(request)
try:
program = Program.objects.get(key=input_data.program_key)
except Program.DoesNotExist as err:
raise Exception("Program not found") from err

try:
project = Project.objects.get(id=input_data.project_id)
except Project.DoesNotExist as err:
raise Exception("Project not found") from err

try:
admin = Mentor.objects.get(nest_user=user)
except Mentor.DoesNotExist as err:
raise Exception("Only mentors can create modules") from err

if admin not in program.admins.all():
raise Exception("You must be an admin of this program to create a module")

if (
input_data.ended_at is not None
and input_data.started_at is not None
and input_data.ended_at <= input_data.started_at
):
raise Exception("End date must be after start date")

module = Module.objects.create(
name=input_data.name,
key=slugify(input_data.name),
description=input_data.description or "",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's make the description required too

experience_level=input_data.experience_level.value,
started_at=input_data.started_at or program.started_at,
ended_at=input_data.ended_at or program.ended_at,
domains=input_data.domains,
tags=input_data.tags,
program=program,
project=project,
)

resolved_mentors = [admin]
for login in input_data.mentor_logins or []:
try:
github_user = GithubUser.objects.get(login=login)
except GithubUser.DoesNotExist as err:
raise Exception("GitHub username not found.") from err

mentor_obj, _ = Mentor.objects.get_or_create(github_user=github_user)
resolved_mentors.append(mentor_obj)

module.mentors.set(resolved_mentors)

return ModuleNode(
id=module.id,
key=module.key,
name=module.name,
description=module.description,
domains=module.domains,
ended_at=module.ended_at,
experience_level=module.experience_level,
mentors=list(module.mentors.all()),
program=module.program,
project_id=module.project.id if module.project else None,
started_at=module.started_at,
tags=module.tags,
)

@strawberry.mutation
def update_module(self, info: strawberry.Info, input_data: UpdateModuleInput) -> ModuleNode:
"""Update an existing mentorship module. Only admins can update."""
request = info.context.request
user = get_authenticated_user(request)

try:
module = Module.objects.select_related("program").get(key=input_data.key)
except Module.DoesNotExist as err:
raise Exception("Module not found") from err

try:
admin = Mentor.objects.get(nest_user=user)
except Mentor.DoesNotExist as err:
raise Exception("Only mentors can edit modules") from err

if admin not in module.program.admins.all():
raise Exception("You must be an admin of the module's program to edit it")

if (
input_data.ended_at is not None
and input_data.started_at is not None
and input_data.ended_at <= input_data.started_at
):
raise Exception("End date must be after start date")

if input_data.experience_level:
module.experience_level = input_data.experience_level.value

if input_data.project_id:
try:
project = Project.objects.get(id=input_data.project_id)
module.project = project
except Project.DoesNotExist as err:
raise Exception("Project not found") from err

update_fields = {
"key": slugify(input_data.name),
"name": input_data.name,
"description": input_data.description,
"started_at": input_data.started_at,
"ended_at": input_data.ended_at,
"domains": input_data.domains,
"tags": input_data.tags,
}

for field, value in update_fields.items():
if value is not None:
setattr(module, field, value)

module.save()

if input_data.mentor_logins is not None:
resolved_mentors = []
for login in input_data.mentor_logins:
try:
github_user = GithubUser.objects.get(login__iexact=login.lower())
except GithubUser.DoesNotExist as err:
raise Exception("GitHub user not found") from err

mentor_obj, _ = Mentor.objects.get_or_create(github_user=github_user)
resolved_mentors.append(mentor_obj)

module.mentors.set(resolved_mentors)

return ModuleNode(
id=module.id,
key=module.key,
name=module.name,
description=module.description,
domains=module.domains,
ended_at=module.ended_at,
experience_level=module.experience_level,
mentors=list(module.mentors.all()),
program=module.program,
project_id=module.project.id if module.project else None,
started_at=module.started_at,
tags=module.tags,
)
151 changes: 151 additions & 0 deletions backend/apps/mentorship/graphql/mutations/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""Mentorship Program GraphQL Mutations."""

import strawberry

from apps.common.utils import slugify
from apps.github.models import User as GithubUser
from apps.mentorship.graphql.nodes.program import (
CreateProgramInput,
ProgramNode,
UpdateProgramInput,
)
from apps.mentorship.models import Mentor, Program
from apps.mentorship.utils.user import get_authenticated_user


@strawberry.type
class ProgramMutation:
"""GraphQL mutations related to program."""

@strawberry.mutation
def create_program(self, info: strawberry.Info, input_data: CreateProgramInput) -> ProgramNode:
"""Create a new mentorship program if the user is a mentor."""
request = info.context.request
user = get_authenticated_user(request)

if user.role != "mentor":
raise Exception("You must be a mentor to create a program")

if input_data.ended_at <= input_data.started_at:
raise Exception("End date must be after start date")

mentor, _ = Mentor.objects.get_or_create(
nest_user=user, defaults={"github_user": user.github_user}
)

program = Program.objects.create(
name=input_data.name,
key=slugify(input_data.name),
description=input_data.description,
experience_levels=[lvl.value for lvl in input_data.experience_levels],
mentees_limit=input_data.mentees_limit,
started_at=input_data.started_at,
ended_at=input_data.ended_at,
domains=input_data.domains,
tags=input_data.tags,
status=input_data.status.value,
)

resolved_mentors = {mentor}

for login in input_data.admin_logins:
try:
github_user = GithubUser.objects.get(login__iexact=login.lower())
except GithubUser.DoesNotExist as err:
raise Exception("GitHub user with username not found.") from err
m, _ = Mentor.objects.get_or_create(github_user=github_user)
resolved_mentors.add(m)

program.admins.set(resolved_mentors)

return ProgramNode(
id=program.id,
key=program.key,
name=program.name,
description=program.description,
experience_levels=program.experience_levels,
mentees_limit=program.mentees_limit,
started_at=program.started_at,
ended_at=program.ended_at,
domains=program.domains,
tags=program.tags,
status=program.status,
admins=list(program.admins.all()),
)

@strawberry.mutation
def update_program(self, info: strawberry.Info, input_data: UpdateProgramInput) -> ProgramNode:
"""Update an existing mentorship program. Only admins can update."""
request = info.context.request
user = get_authenticated_user(request)

try:
program = Program.objects.get(key=input_data.key)
except Program.DoesNotExist as err:
raise Exception("Program not found") from err

try:
admin = Mentor.objects.get(nest_user=user)
except Mentor.DoesNotExist as err:
raise Exception("You must be a mentor to update a program") from err

if admin not in program.admins.all():
raise Exception("You must be an admin of this program to update it")

if (
input_data.ended_at is not None
and input_data.started_at is not None
and input_data.ended_at <= input_data.started_at
):
raise Exception("End date must be after start date")

simple_fields = {
"key": slugify(input_data.name),
"name": input_data.name,
"description": input_data.description,
"mentees_limit": input_data.mentees_limit,
"started_at": input_data.started_at,
"ended_at": input_data.ended_at,
"domains": input_data.domains,
"tags": input_data.tags,
}

for field, value in simple_fields.items():
if value is not None:
setattr(program, field, value)

if input_data.experience_levels is not None:
program.experience_levels = [lvl.value for lvl in input_data.experience_levels]

if input_data.status is not None:
program.status = input_data.status.value

program.save()

if input_data.admin_logins is not None:
resolved_mentors = []
for login in input_data.admin_logins:
try:
github_user = GithubUser.objects.get(login__iexact=login.lower())
except GithubUser.DoesNotExist as err:
raise Exception("GitHub user not found.") from err

mentor, _ = Mentor.objects.get_or_create(github_user=github_user)
resolved_mentors.append(mentor)

program.admins.set(resolved_mentors)

return ProgramNode(
id=program.id,
key=program.key,
name=program.name,
description=program.description,
experience_levels=program.experience_levels,
mentees_limit=program.mentees_limit,
started_at=program.started_at,
ended_at=program.ended_at,
domains=program.domains,
tags=program.tags,
status=program.status,
admins=list(program.admins.all()),
)
1 change: 1 addition & 0 deletions backend/apps/mentorship/graphql/nodes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file marks the directory as a Python package.
24 changes: 24 additions & 0 deletions backend/apps/mentorship/graphql/nodes/enum.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use model class as source of truth for these enums?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes we can use

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""GraphQL enum for Mentorship App."""

import enum

import strawberry


@strawberry.enum
class ExperienceLevelEnum(enum.Enum):
"""experience level enum."""

BEGINNER = "beginner"
INTERMEDIATE = "intermediate"
ADVANCED = "advanced"
EXPERT = "expert"


@strawberry.enum
class ProgramStatusEnum(enum.Enum):
"""program status enum."""

DRAFT = "draft"
PUBLISHED = "published"
COMPLETED = "completed"
31 changes: 31 additions & 0 deletions backend/apps/mentorship/graphql/nodes/mentor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""GraphQL node for Mentor model."""

import strawberry


@strawberry.type
class MentorNode:
"""A GraphQL node representing a mentorship mentor."""

id: strawberry.ID

@strawberry.field
def avatar_url(self) -> str:
"""Get the GitHub avatar url of the mentor."""
if not self.github_user:
return ""
return self.github_user.avatar_url

@strawberry.field
def name(self) -> str:
"""Get the GitHub name of the mentor."""
if not self.github_user:
return ""
return self.github_user.name

@strawberry.field
def login(self) -> str:
"""Get the GitHub login of the mentor."""
if not self.github_user:
return ""
return self.github_user.login
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you refactor those to if/else one-liners?

Loading