<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dshah24</id>
	<title>Expertiza_Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.expertiza.ncsu.edu/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dshah24"/>
	<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=Special:Contributions/Dshah24"/>
	<updated>2026-08-07T10:43:16Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.0</generator>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167363</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167363"/>
		<updated>2025-12-03T01:45:11Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Joinrequest.png|thumb|center|850px|Join Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 1: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:acceptrequest2.png|center|850px|Accept Request]]&lt;br /&gt;
&lt;br /&gt;
[[File:acceptrequest1.png|center|850px|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''Figure 2: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167361</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167361"/>
		<updated>2025-12-03T01:43:39Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Joinrequest.png|thumb|center|850px|Join Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 1: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:acceptrequest2.png|center|850px|Accept Request]]&lt;br /&gt;
&lt;br /&gt;
[[File:acceptrequest1.png|center|850px|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''Figure 2: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167356</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167356"/>
		<updated>2025-12-03T01:36:50Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Joinrequest.png|thumb|center|850px|Join Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 1: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:acceptrequest2.png|800px|Accept Request]]&lt;br /&gt;
| [[File:acceptrequest1.png|800px|Accept Request Confirmation]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
''Figure 2: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_TestResults_Screenshot.png|700px|center|Test Results]]&lt;br /&gt;
&lt;br /&gt;
''Figure 9: RSpec test output showing all tests passing. The documentation format displays each test case with its description, making it easy to understand what functionality is being verified.''&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Frontend_Architecture.png|700px|center|Frontend Component Structure]]&lt;br /&gt;
&lt;br /&gt;
''Figure 10: Frontend component hierarchy showing how the main JoinTeamRequests component composes smaller, focused components for list display, forms, and actions.''&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_UI_Overview.png|800px|center|UI Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 11: Complete UI overview showing the join team requests interface. The left panel shows the list of requests with filtering options, and the right panel shows the details of a selected request with action buttons for team members.''&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Acceptrequest2.png&amp;diff=167355</id>
		<title>File:Acceptrequest2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Acceptrequest2.png&amp;diff=167355"/>
		<updated>2025-12-03T01:35:36Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Acceptrequest1.png&amp;diff=167354</id>
		<title>File:Acceptrequest1.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Acceptrequest1.png&amp;diff=167354"/>
		<updated>2025-12-03T01:32:17Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Joinrequest.png&amp;diff=167353</id>
		<title>File:Joinrequest.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Joinrequest.png&amp;diff=167353"/>
		<updated>2025-12-03T01:27:34Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167350</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167350"/>
		<updated>2025-12-03T01:15:28Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Create_Screenshot.png|700px|center|Create Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 4: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Accept_Screenshot.png|700px|center|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
''Figure 5: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details and comments. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_TestResults_Screenshot.png|700px|center|Test Results]]&lt;br /&gt;
&lt;br /&gt;
''Figure 9: RSpec test output showing all tests passing. The documentation format displays each test case with its description, making it easy to understand what functionality is being verified.''&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Frontend_Architecture.png|700px|center|Frontend Component Structure]]&lt;br /&gt;
&lt;br /&gt;
''Figure 10: Frontend component hierarchy showing how the main JoinTeamRequests component composes smaller, focused components for list display, forms, and actions.''&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_UI_Overview.png|800px|center|UI Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 11: Complete UI overview showing the join team requests interface. The left panel shows the list of requests with filtering options, and the right panel shows the details of a selected request with action buttons for team members.''&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167349</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167349"/>
		<updated>2025-12-03T01:13:30Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Inconsistent API Responses:''' Without proper serialization, API responses varied in structure and content, making frontend integration difficult and error-prone. The lack of standardized response formats also complicated error handling on the client side.&lt;br /&gt;
&lt;br /&gt;
'''Limited Query Capabilities:''' The original implementation only provided basic CRUD operations. There were no endpoints for filtering requests by team, by user, or by status, forcing the frontend to fetch all requests and filter client-side—an inefficient approach that doesn't scale well.&lt;br /&gt;
&lt;br /&gt;
'''Missing Frontend Interface:''' While backend functionality existed, there was no dedicated user interface for managing join team requests, leaving users without a clear way to interact with this feature.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Create_Screenshot.png|700px|center|Create Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 4: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Accept_Screenshot.png|700px|center|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
''Figure 5: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details and comments. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_TestResults_Screenshot.png|700px|center|Test Results]]&lt;br /&gt;
&lt;br /&gt;
''Figure 9: RSpec test output showing all tests passing. The documentation format displays each test case with its description, making it easy to understand what functionality is being verified.''&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Frontend_Architecture.png|700px|center|Frontend Component Structure]]&lt;br /&gt;
&lt;br /&gt;
''Figure 10: Frontend component hierarchy showing how the main JoinTeamRequests component composes smaller, focused components for list display, forms, and actions.''&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_UI_Overview.png|800px|center|UI Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 11: Complete UI overview showing the join team requests interface. The left panel shows the list of requests with filtering options, and the right panel shows the details of a selected request with action buttons for team members.''&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167348</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167348"/>
		<updated>2025-12-03T01:06:56Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Inconsistent API Responses:''' Without proper serialization, API responses varied in structure and content, making frontend integration difficult and error-prone. The lack of standardized response formats also complicated error handling on the client side.&lt;br /&gt;
&lt;br /&gt;
'''Limited Query Capabilities:''' The original implementation only provided basic CRUD operations. There were no endpoints for filtering requests by team, by user, or by status, forcing the frontend to fetch all requests and filter client-side—an inefficient approach that doesn't scale well.&lt;br /&gt;
&lt;br /&gt;
'''Missing Frontend Interface:''' While backend functionality existed, there was no dedicated user interface for managing join team requests, leaving users without a clear way to interact with this feature.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 2. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Create_Screenshot.png|700px|center|Create Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 4: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 3. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 4. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Accept_Screenshot.png|700px|center|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
''Figure 5: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details and comments. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 6. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_TestResults_Screenshot.png|700px|center|Test Results]]&lt;br /&gt;
&lt;br /&gt;
''Figure 9: RSpec test output showing all tests passing. The documentation format displays each test case with its description, making it easy to understand what functionality is being verified.''&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Frontend_Architecture.png|700px|center|Frontend Component Structure]]&lt;br /&gt;
&lt;br /&gt;
''Figure 10: Frontend component hierarchy showing how the main JoinTeamRequests component composes smaller, focused components for list display, forms, and actions.''&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_UI_Overview.png|800px|center|UI Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 11: Complete UI overview showing the join team requests interface. The left panel shows the list of requests with filtering options, and the right panel shows the details of a selected request with action buttons for team members.''&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== New Features ===&lt;br /&gt;
&lt;br /&gt;
# '''Fine-grained authorization''' based on user roles (admin/student) and relationships (request creator/team member)&lt;br /&gt;
# '''Filtering endpoints''' (&amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt;) for efficient data retrieval&lt;br /&gt;
# '''Accept/Decline workflow''' with team member validation and automatic team membership management&lt;br /&gt;
# '''Duplicate request prevention''' to avoid confusion from multiple pending requests to the same team&lt;br /&gt;
# '''Comprehensive error handling''' with user-friendly error messages suitable for display&lt;br /&gt;
# '''ActiveModel Serializer integration''' for consistent, predictable JSON responses&lt;br /&gt;
# '''Full test coverage''' for all endpoints and authorization scenarios&lt;br /&gt;
&lt;br /&gt;
== Team Members ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Unity ID !! Contribution&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 1] || [unity_id] || Backend controller implementation, authorization logic&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 2] || [unity_id] || Model development, serializer implementation&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 3] || [unity_id] || Test coverage, documentation, code review&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 4] || [unity_id] || Frontend integration, UI components&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
The implementation follows Rails best practices and integrates seamlessly with the existing Expertiza codebase, providing a solid foundation for team formation workflows in the system.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167303</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167303"/>
		<updated>2025-12-02T23:08:47Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!-- filepath: /home/devyash/Downloads/NCSU/OODD/Final Project/markdown.md --&amp;gt;&lt;br /&gt;
= CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This project focuses on the integration and enhancement of the '''JoinTeamRequests''' functionality in the Expertiza reimplementation. The JoinTeamRequests feature allows students to request to join existing teams within assignments, providing a structured workflow for team formation and management. &lt;br /&gt;
&lt;br /&gt;
The Expertiza system is a peer-review platform used extensively in academic settings, where students collaborate on assignments in teams. A critical component of this collaboration is the ability for students who are not yet part of a team to request membership in an existing team. This implementation delivers a complete backend API with proper authorization, serialization, and comprehensive test coverage, along with frontend components for managing join team requests.&lt;br /&gt;
&lt;br /&gt;
The goal of this project is to create a seamless, secure, and user-friendly experience for both students requesting to join teams and team members who must review and respond to these requests.&lt;br /&gt;
&lt;br /&gt;
== Problem Statement ==&lt;br /&gt;
&lt;br /&gt;
In the Expertiza system, students need a mechanism to request membership in existing teams when they are not yet part of one. This process involves multiple stakeholders with different roles and permissions:&lt;br /&gt;
&lt;br /&gt;
* '''Students without a team''' need to browse available teams and submit join requests&lt;br /&gt;
* '''Existing team members''' need to review incoming requests and decide whether to accept or decline them&lt;br /&gt;
* '''Administrators''' need oversight of all join team requests across the system for monitoring and troubleshooting purposes&lt;br /&gt;
&lt;br /&gt;
The existing implementation in the Expertiza reimplementation had several limitations that this project addresses:&lt;br /&gt;
&lt;br /&gt;
'''Authorization Gaps:''' The previous implementation lacked fine-grained access control. There was no proper distinction between who could view, create, modify, or act upon join team requests. This created potential security vulnerabilities where unauthorized users could access or manipulate requests.&lt;br /&gt;
&lt;br /&gt;
'''Inconsistent API Responses:''' Without proper serialization, API responses varied in structure and content, making frontend integration difficult and error-prone. The lack of standardized response formats also complicated error handling on the client side.&lt;br /&gt;
&lt;br /&gt;
'''Limited Query Capabilities:''' The original implementation only provided basic CRUD operations. There were no endpoints for filtering requests by team, by user, or by status, forcing the frontend to fetch all requests and filter client-side—an inefficient approach that doesn't scale well.&lt;br /&gt;
&lt;br /&gt;
'''Missing Frontend Interface:''' While backend functionality existed, there was no dedicated user interface for managing join team requests, leaving users without a clear way to interact with this feature.&lt;br /&gt;
&lt;br /&gt;
'''Insufficient Test Coverage:''' The existing tests did not cover edge cases or the full range of authorization scenarios, leaving potential bugs undiscovered.&lt;br /&gt;
&lt;br /&gt;
== Design Goals ==&lt;br /&gt;
&lt;br /&gt;
Our implementation focuses on five key design principles that guide all development decisions:&lt;br /&gt;
&lt;br /&gt;
'''1. Robust Authorization:''' We implemented fine-grained access control based on user roles (administrator, student) and relationships (request creator, team member). Each API endpoint explicitly defines who can access it, and the authorization logic is centralized in a single method for consistency and maintainability. This ensures that users can only perform actions appropriate to their role and relationship to the request.&lt;br /&gt;
&lt;br /&gt;
'''2. RESTful API Design:''' The API follows REST conventions with proper HTTP semantics. We use appropriate HTTP methods (GET for retrieval, POST for creation, PATCH for updates, DELETE for removal) and return meaningful HTTP status codes (200 for success, 201 for creation, 404 for not found, 422 for validation errors). This makes the API intuitive for developers and compatible with standard HTTP clients.&lt;br /&gt;
&lt;br /&gt;
'''3. Consistent Serialization:''' All API responses pass through ActiveModel Serializers, ensuring consistent JSON structure across all endpoints. This includes nested representations of related objects (participant and team information) that the frontend needs to display meaningful information to users without making additional API calls.&lt;br /&gt;
&lt;br /&gt;
'''4. Comprehensive Testing:''' Every endpoint and authorization scenario is covered by RSpec tests. We test both the &amp;quot;happy path&amp;quot; (expected behavior) and edge cases (error conditions, unauthorized access attempts). This gives us confidence that the implementation works correctly and will continue to work as the codebase evolves.&lt;br /&gt;
&lt;br /&gt;
'''5. Frontend Integration:''' The backend is designed with frontend consumption in mind. Response formats include all necessary data for UI rendering, error messages are user-friendly, and filtering endpoints reduce the need for client-side data processing.&lt;br /&gt;
&lt;br /&gt;
== Implementation ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Architecture Overview ===&lt;br /&gt;
&lt;br /&gt;
The backend implementation follows the standard Rails MVC pattern with additional layers for serialization and authorization. The architecture consists of:&lt;br /&gt;
&lt;br /&gt;
* '''Controller:''' Handles HTTP requests, enforces authorization, and coordinates between models and serializers&lt;br /&gt;
* '''Model:''' Defines data structure, relationships, and validations&lt;br /&gt;
* '''Serializer:''' Transforms model data into consistent JSON responses&lt;br /&gt;
* '''Routes:''' Maps URLs to controller actions&lt;br /&gt;
&lt;br /&gt;
The following diagram illustrates the request flow through these components:&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Architecture.png|800px|center|Architecture Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 1: Request flow architecture showing how HTTP requests are processed through the controller, which interacts with models and returns serialized responses.''&lt;br /&gt;
&lt;br /&gt;
=== Controller: Api::V1::JoinTeamRequestsController ===&lt;br /&gt;
&lt;br /&gt;
The controller is the heart of our implementation, located at &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt;. It handles all join team request operations and enforces authorization rules.&lt;br /&gt;
&lt;br /&gt;
==== Constants and Status Management ====&lt;br /&gt;
&lt;br /&gt;
We define status constants at the class level to ensure consistency throughout the codebase and avoid magic strings. This approach makes the code more maintainable—if we ever need to change a status value, we only need to update it in one place.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class Api::V1::JoinTeamRequestsController &amp;lt; ApplicationController&lt;br /&gt;
  # Constants used to indicate status for the request&lt;br /&gt;
  # Using constants prevents typos and enables easy refactoring&lt;br /&gt;
  PENDING = 'PENDING'&lt;br /&gt;
  DECLINED = 'DECLINED'&lt;br /&gt;
  ACCEPTED = 'ACCEPTED'&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Before Actions ====&lt;br /&gt;
&lt;br /&gt;
Rails before_action filters allow us to extract common logic that runs before specific controller actions. This follows the DRY (Don't Repeat Yourself) principle and ensures consistent behavior across related endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
  # This filter runs before the create action, checking if the team is full&lt;br /&gt;
  # Checking this early prevents unnecessary database operations if the request would fail anyway&lt;br /&gt;
  before_action :check_team_status, only: [:create]&lt;br /&gt;
&lt;br /&gt;
  # This filter runs before the specified actions, finding the join team request&lt;br /&gt;
  # Centralizing this lookup ensures consistent 404 handling and reduces code duplication&lt;br /&gt;
  before_action :find_request, only: %i[show update destroy decline accept]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Authorization Implementation ====&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method is the cornerstone of our authorization system. Unlike simple role-based checks, our implementation considers both the user's role AND their relationship to the specific resource being accessed. This provides security while maintaining usability.&lt;br /&gt;
&lt;br /&gt;
The following table summarizes the authorization rules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Action !! Authorization Rule !! Rationale&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; || Only administrators || Viewing all requests system-wide is an administrative function&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;create&amp;lt;/code&amp;gt; || Any student || All students should be able to request to join teams&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;show&amp;lt;/code&amp;gt; || Request creator OR team member || Both parties need to see request details&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;update&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;destroy&amp;lt;/code&amp;gt; || Only the request creator || Only the requester should modify or withdraw their request&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;accept&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;decline&amp;lt;/code&amp;gt; || Only team members || Only existing team members can decide on membership&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; || Any student || Filtered views help students find relevant requests&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The implementation of this authorization logic demonstrates several important patterns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'index'&lt;br /&gt;
    # Only administrators can view all join team requests&lt;br /&gt;
    # This prevents students from seeing requests for teams they're not part of&lt;br /&gt;
    current_user_has_admin_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'create'&lt;br /&gt;
    # Any student can create a join team request&lt;br /&gt;
    # Additional validation (participant check, team membership) happens in the action itself&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  when 'show'&lt;br /&gt;
    # The participant who made the request OR any team member can view it&lt;br /&gt;
    # This allows both parties to stay informed about the request status&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    &lt;br /&gt;
    # Load the request for authorization check (memoized to avoid duplicate queries)&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    &lt;br /&gt;
    # Check if user is either the creator or a team member&lt;br /&gt;
    current_user_is_request_creator? || current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'update', 'destroy'&lt;br /&gt;
    # Only the participant who created the request can update or delete it&lt;br /&gt;
    # This prevents team members from modifying requests they didn't create&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_request_creator?&lt;br /&gt;
  &lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    # Only team members of the target team can accept/decline a request&lt;br /&gt;
    # The requester themselves cannot accept their own request&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  &lt;br /&gt;
  when 'for_team', 'by_user', 'pending'&lt;br /&gt;
    # Students can view filtered lists&lt;br /&gt;
    # These endpoints are scoped appropriately in their implementations&lt;br /&gt;
    current_user_has_student_privileges?&lt;br /&gt;
  &lt;br /&gt;
  else&lt;br /&gt;
    # Default: deny access for any unrecognized actions&lt;br /&gt;
    # This is a security best practice - fail closed&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== API Endpoints ====&lt;br /&gt;
&lt;br /&gt;
Each endpoint is designed to serve a specific use case. Below we detail each endpoint with its purpose, implementation, and usage examples.&lt;br /&gt;
&lt;br /&gt;
===== 1. Index - List All Requests (Admin Only) =====&lt;br /&gt;
&lt;br /&gt;
The index endpoint returns all join team requests in the system. This is intended for administrative oversight, allowing administrators to monitor team formation activity and troubleshoot issues.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests&lt;br /&gt;
# Returns all join team requests with eager-loaded associations&lt;br /&gt;
def index&lt;br /&gt;
  # Using includes() prevents N+1 queries by eager loading participant and team data&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Index_Screenshot.png|700px|center|Index Endpoint Result]]&lt;br /&gt;
&lt;br /&gt;
''Figure 2: The admin view showing all join team requests in the system. Administrators can see requests across all teams, including their status and timestamps. Each row is clickable to view full request details.''&lt;br /&gt;
&lt;br /&gt;
===== 2. Show - View Single Request =====&lt;br /&gt;
&lt;br /&gt;
The show endpoint returns detailed information about a specific join team request. Both the requester and team members can access this endpoint to view the current status and any comments.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/1&lt;br /&gt;
# Returns a single join team request with full details&lt;br /&gt;
def show&lt;br /&gt;
  # @join_team_request is set by the find_request before_action&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 3. For Team - Filter by Team =====&lt;br /&gt;
&lt;br /&gt;
This filtering endpoint returns all join requests for a specific team. Team members use this to see who wants to join their team, making it easy to manage incoming requests.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/for_team/:team_id&lt;br /&gt;
# Get all join team requests for a specific team&lt;br /&gt;
def for_team&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  # Access the requests through the team's association for cleaner code&lt;br /&gt;
  join_team_requests = team.join_team_requests.includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
rescue ActiveRecord::RecordNotFound&lt;br /&gt;
  # Provide a clear error message when the team doesn't exist&lt;br /&gt;
  render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_ForTeam_Screenshot.png|700px|center|For Team Endpoint Result]]&lt;br /&gt;
&lt;br /&gt;
''Figure 3: Team members can view all pending requests for their team. The interface shows requester information and allows team members to accept or decline each request. Hovering over a request reveals the requester's comments.''&lt;br /&gt;
&lt;br /&gt;
===== 4. By User - Filter by User =====&lt;br /&gt;
&lt;br /&gt;
This endpoint returns all join requests created by a specific user. Students use this to track the status of their outgoing requests.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/by_user/:user_id&lt;br /&gt;
# Get all join team requests created by a specific user&lt;br /&gt;
def by_user&lt;br /&gt;
  # A user may have multiple participant records (one per assignment)&lt;br /&gt;
  # We need to find all of them to get all their requests&lt;br /&gt;
  participant_ids = Participant.where(user_id: params[:user_id]).pluck(:id)&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(participant_id: participant_ids).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 5. Pending - Get Pending Requests =====&lt;br /&gt;
&lt;br /&gt;
This convenience endpoint returns only pending requests, helping users focus on requests that require action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/pending&lt;br /&gt;
# Get all pending join team requests&lt;br /&gt;
def pending&lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(reply_status: PENDING).includes(:participant, :team)&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 6. Create - Submit New Request =====&lt;br /&gt;
&lt;br /&gt;
The create endpoint is the most complex, with multiple validation checks to ensure data integrity. It demonstrates defensive programming practices by validating inputs before creating records.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# POST api/v1/join_team_requests&lt;br /&gt;
# Creates a new join team request with comprehensive validation&lt;br /&gt;
def create&lt;br /&gt;
  # Step 1: Verify the user is a participant in the assignment&lt;br /&gt;
  # This prevents users from creating requests for assignments they're not enrolled in&lt;br /&gt;
  participant = AssignmentParticipant.find_by(user_id: @current_user.id, parent_id: params[:assignment_id])&lt;br /&gt;
  &lt;br /&gt;
  unless participant&lt;br /&gt;
    return render json: { error: 'You are not a participant in this assignment' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 2: Verify the target team exists&lt;br /&gt;
  team = Team.find_by(id: params[:team_id])&lt;br /&gt;
  unless team&lt;br /&gt;
    return render json: { error: 'Team not found' }, status: :not_found&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 3: Check if user already belongs to the team&lt;br /&gt;
  # This prevents unnecessary requests and potential confusion&lt;br /&gt;
  if team.participants.include?(participant)&lt;br /&gt;
    return render json: { error: 'You already belong to this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 4: Check for duplicate pending requests&lt;br /&gt;
  # Allowing multiple pending requests to the same team would be confusing&lt;br /&gt;
  existing_request = JoinTeamRequest.find_by(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    reply_status: PENDING&lt;br /&gt;
  )&lt;br /&gt;
  &lt;br /&gt;
  if existing_request&lt;br /&gt;
    return render json: { error: 'You already have a pending request for this team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Step 5: Create the request with all validated data&lt;br /&gt;
  join_team_request = JoinTeamRequest.new(&lt;br /&gt;
    participant_id: participant.id,&lt;br /&gt;
    team_id: team.id,&lt;br /&gt;
    comments: params[:comments],&lt;br /&gt;
    reply_status: PENDING  # All new requests start as pending&lt;br /&gt;
  )&lt;br /&gt;
&lt;br /&gt;
  if join_team_request.save&lt;br /&gt;
    render json: join_team_request, serializer: JoinTeamRequestSerializer, status: :created&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
rescue ActiveRecord::RecordNotFound =&amp;gt; e&lt;br /&gt;
  render json: { error: e.message }, status: :not_found&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Create_Screenshot.png|700px|center|Create Request Form]]&lt;br /&gt;
&lt;br /&gt;
''Figure 4: The join request form allows students to select a team and optionally add comments explaining why they want to join. The form validates that the student is not already on a team and that the target team is not full.''&lt;br /&gt;
&lt;br /&gt;
===== 7. Update - Modify Request Comments =====&lt;br /&gt;
&lt;br /&gt;
The update endpoint allows requesters to modify their request comments. We intentionally restrict updates to comments only—status changes must go through the accept/decline endpoints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/1&lt;br /&gt;
# Updates a join team request (comments only, not status)&lt;br /&gt;
def update&lt;br /&gt;
  # Explicitly only allow updating comments&lt;br /&gt;
  # This prevents requesters from accepting their own requests&lt;br /&gt;
  if @join_team_request.update(comments: params[:comments])&lt;br /&gt;
    render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 8. Destroy - Delete Request =====&lt;br /&gt;
&lt;br /&gt;
Students can withdraw their join requests at any time. This is useful if they've found another team or changed their mind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# DELETE api/v1/join_team_requests/1&lt;br /&gt;
# Deletes a join team request (withdraw the request)&lt;br /&gt;
def destroy&lt;br /&gt;
  if @join_team_request.destroy&lt;br /&gt;
    render json: { message: 'Join team request was successfully deleted' }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { error: 'Failed to delete join team request' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 9. Accept - Approve and Add Member =====&lt;br /&gt;
&lt;br /&gt;
The accept endpoint is critical—it not only updates the request status but also adds the requester to the team. This transactional operation ensures data consistency.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/accept&lt;br /&gt;
# Accept a join team request and add the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  # Validation 1: Ensure request hasn't already been processed&lt;br /&gt;
  # This prevents race conditions and duplicate team membership&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Validation 2: Check team capacity before accepting&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'Team is full' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  # Attempt to add the participant to the team&lt;br /&gt;
  begin&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Only update the request status after successfully adding the member&lt;br /&gt;
      @join_team_request.reply_status = ACCEPTED&lt;br /&gt;
      @join_team_request.save&lt;br /&gt;
      render json: { &lt;br /&gt;
        message: 'Join team request accepted successfully', &lt;br /&gt;
        join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
      }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
    end&lt;br /&gt;
  rescue StandardError =&amp;gt; e&lt;br /&gt;
    # Catch any unexpected errors during the add_member operation&lt;br /&gt;
    render json: { error: e.message }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Accept_Screenshot.png|700px|center|Accept Request Confirmation]]&lt;br /&gt;
&lt;br /&gt;
''Figure 5: When a team member clicks the Accept button, a confirmation dialog appears showing the requester's details and comments. Upon confirmation, the requester is added to the team and notified of the acceptance.''&lt;br /&gt;
&lt;br /&gt;
===== 10. Decline - Reject Request =====&lt;br /&gt;
&lt;br /&gt;
The decline endpoint allows team members to reject join requests. Unlike delete, decline preserves the request record for audit purposes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH api/v1/join_team_requests/1/decline&lt;br /&gt;
# Decline a join team request&lt;br /&gt;
def decline&lt;br /&gt;
  # Prevent re-processing of already handled requests&lt;br /&gt;
  unless @join_team_request.reply_status == PENDING&lt;br /&gt;
    return render json: { error: 'This request has already been processed' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  @join_team_request.reply_status = DECLINED&lt;br /&gt;
  if @join_team_request.save&lt;br /&gt;
    render json: { &lt;br /&gt;
      message: 'Join team request declined successfully',&lt;br /&gt;
      join_team_request: JoinTeamRequestSerializer.new(@join_team_request).as_json&lt;br /&gt;
    }, status: :ok&lt;br /&gt;
  else&lt;br /&gt;
    render json: { errors: @join_team_request.errors.full_messages }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Private Helper Methods ====&lt;br /&gt;
&lt;br /&gt;
The private methods encapsulate reusable logic and keep the public action methods clean and focused.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
# Checks if the team is full before allowing request creation&lt;br /&gt;
# Running this as a before_action prevents unnecessary database operations&lt;br /&gt;
def check_team_status&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  if team.full?&lt;br /&gt;
    render json: { message: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Finds the join team request by ID&lt;br /&gt;
# Raises ActiveRecord::RecordNotFound if not found, which Rails converts to a 404 response&lt;br /&gt;
def find_request&lt;br /&gt;
  @join_team_request = JoinTeamRequest.find(params[:id])&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Permits specified parameters for join team requests&lt;br /&gt;
# This is a security measure to prevent mass assignment vulnerabilities&lt;br /&gt;
def join_team_request_params&lt;br /&gt;
  params.require(:join_team_request).permit(:comments, :reply_status)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is the creator of the request&lt;br /&gt;
# Uses safe navigation (&amp;amp;.) to handle nil cases gracefully&lt;br /&gt;
def current_user_is_request_creator?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  participant = Participant.find_by(id: @join_team_request.participant_id)&lt;br /&gt;
  participant&amp;amp;.user_id == @current_user.id&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
# Helper method to check if current user is a member of the target team&lt;br /&gt;
# This involves looking up the user's participant record for the relevant assignment&lt;br /&gt;
def current_user_is_team_member?&lt;br /&gt;
  return false unless @join_team_request &amp;amp;&amp;amp; @current_user&lt;br /&gt;
  &lt;br /&gt;
  team = Team.find_by(id: @join_team_request.team_id)&lt;br /&gt;
  return false unless team&lt;br /&gt;
  &lt;br /&gt;
  # Find the participant record for the current user in the same assignment&lt;br /&gt;
  if team.is_a?(AssignmentTeam)&lt;br /&gt;
    participant = AssignmentParticipant.find_by(&lt;br /&gt;
      user_id: @current_user.id,&lt;br /&gt;
      parent_id: team.parent_id&lt;br /&gt;
    )&lt;br /&gt;
    return false unless participant&lt;br /&gt;
    &lt;br /&gt;
    # Check if this participant is in the team's participants list&lt;br /&gt;
    team.participants.include?(participant)&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Model: JoinTeamRequest ===&lt;br /&gt;
&lt;br /&gt;
The model is intentionally simple, following the Rails principle of keeping models focused on data concerns. It defines relationships and validations, delegating business logic to the controller.&lt;br /&gt;
&lt;br /&gt;
Located at &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  # Associations define the relationships with other models&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  # Status validation ensures only valid statuses can be saved&lt;br /&gt;
  # This is a database-level safeguard complementing the controller logic&lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Serializer: JoinTeamRequestSerializer ===&lt;br /&gt;
&lt;br /&gt;
The serializer transforms model data into a consistent JSON format suitable for frontend consumption. By including related data (participant and team details), we reduce the number of API calls the frontend needs to make.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequestSerializer &amp;lt; ActiveModel::Serializer&lt;br /&gt;
  # Top-level attributes from the JoinTeamRequest model&lt;br /&gt;
  attributes :id, :reply_status, :comments, :created_at, :updated_at&lt;br /&gt;
  &lt;br /&gt;
  # Declare associations (these methods below override default behavior)&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  &lt;br /&gt;
  # Custom participant representation with user details&lt;br /&gt;
  # This saves the frontend from making separate API calls for user information&lt;br /&gt;
  def participant&lt;br /&gt;
    {&lt;br /&gt;
      id: object.participant.id,&lt;br /&gt;
      user_id: object.participant.user_id,&lt;br /&gt;
      user_name: object.participant.user&amp;amp;.name,&lt;br /&gt;
      user_full_name: object.participant.user&amp;amp;.full_name&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Custom team representation with essential details&lt;br /&gt;
  def team&lt;br /&gt;
    {&lt;br /&gt;
      id: object.team.id,&lt;br /&gt;
      name: object.team.name,&lt;br /&gt;
      parent_id: object.team.parent_id&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Routes Configuration ===&lt;br /&gt;
&lt;br /&gt;
Our routes follow RESTful conventions with additional collection and member routes for specialized operations. The route structure makes the API intuitive and self-documenting.&lt;br /&gt;
&lt;br /&gt;
Added to &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    # Filtering endpoints - operate on the collection, not a specific resource&lt;br /&gt;
    get 'for_team/:team_id', action: :for_team&lt;br /&gt;
    get 'by_user/:user_id', action: :by_user&lt;br /&gt;
    get 'pending', action: :pending&lt;br /&gt;
  end&lt;br /&gt;
  member do&lt;br /&gt;
    # Action endpoints - operate on a specific resource&lt;br /&gt;
    patch 'accept', action: :accept&lt;br /&gt;
    patch 'decline', action: :decline&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This configuration generates the following routes:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! HTTP Method !! URL Pattern !! Controller Action !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests || index || List all requests (admin)&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/:id || show || View single request&lt;br /&gt;
|-&lt;br /&gt;
| POST || /api/v1/join_team_requests || create || Submit new request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH/PUT || /api/v1/join_team_requests/:id || update || Modify request&lt;br /&gt;
|-&lt;br /&gt;
| DELETE || /api/v1/join_team_requests/:id || destroy || Delete request&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/for_team/:team_id || for_team || Filter by team&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/by_user/:user_id || by_user || Filter by user&lt;br /&gt;
|-&lt;br /&gt;
| GET || /api/v1/join_team_requests/pending || pending || Get pending only&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/accept || accept || Accept request&lt;br /&gt;
|-&lt;br /&gt;
| PATCH || /api/v1/join_team_requests/:id/decline || decline || Decline request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Database Schema ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;join_team_requests&amp;lt;/code&amp;gt; table stores all join request data. The schema is designed for efficient querying with indexed foreign keys.&lt;br /&gt;
&lt;br /&gt;
From &amp;lt;code&amp;gt;db/schema.rb&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
create_table &amp;quot;join_team_requests&amp;quot;, charset: &amp;quot;utf8mb4&amp;quot;, collation: &amp;quot;utf8mb4_0900_ai_ci&amp;quot;, force: :cascade do |t|&lt;br /&gt;
  t.datetime &amp;quot;created_at&amp;quot;, null: false&lt;br /&gt;
  t.datetime &amp;quot;updated_at&amp;quot;, null: false&lt;br /&gt;
  t.integer &amp;quot;participant_id&amp;quot;    # Foreign key to participants table&lt;br /&gt;
  t.integer &amp;quot;team_id&amp;quot;           # Foreign key to teams table&lt;br /&gt;
  t.text &amp;quot;comments&amp;quot;             # Optional message from the requester&lt;br /&gt;
  t.string &amp;quot;reply_status&amp;quot;       # PENDING, ACCEPTED, or DECLINED&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== API Response Format ===&lt;br /&gt;
&lt;br /&gt;
Consistent response formats make frontend development more predictable. All successful responses follow the same structure, and error responses are clearly formatted.&lt;br /&gt;
&lt;br /&gt;
==== Success Response Example (Single Request) ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot;: 1,&lt;br /&gt;
  &amp;quot;reply_status&amp;quot;: &amp;quot;PENDING&amp;quot;,&lt;br /&gt;
  &amp;quot;comments&amp;quot;: &amp;quot;I would like to join your team for the project.&amp;quot;,&lt;br /&gt;
  &amp;quot;created_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;updated_at&amp;quot;: &amp;quot;2025-01-15T10:30:00.000Z&amp;quot;,&lt;br /&gt;
  &amp;quot;participant&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;user_id&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;user_name&amp;quot;: &amp;quot;student1&amp;quot;,&lt;br /&gt;
    &amp;quot;user_full_name&amp;quot;: &amp;quot;John Doe&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;team&amp;quot;: {&lt;br /&gt;
    &amp;quot;id&amp;quot;: 3,&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Team Alpha&amp;quot;,&lt;br /&gt;
    &amp;quot;parent_id&amp;quot;: 1&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Error Response Examples ====&lt;br /&gt;
&lt;br /&gt;
Validation errors return descriptive messages that can be displayed directly to users:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;You already belong to this team&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;error&amp;quot;: &amp;quot;This request has already been processed&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model validation errors return an array format:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;errors&amp;quot;: [&amp;quot;Reply status is not included in the list&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UML Diagrams ==&lt;br /&gt;
&lt;br /&gt;
=== Class Diagram ===&lt;br /&gt;
&lt;br /&gt;
The following class diagram illustrates the relationships between the main components of our implementation:&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_ClassDiagram.png|800px|center|Class Diagram]]&lt;br /&gt;
&lt;br /&gt;
''Figure 6: Class diagram showing the JoinTeamRequestsController and its relationships with the JoinTeamRequest model, which belongs to both Participant and Team models. The controller methods are organized by visibility (public and private).''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
|   JoinTeamRequestsController     |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
| - PENDING: String                |&lt;br /&gt;
| - DECLINED: String               |&lt;br /&gt;
| - ACCEPTED: String               |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
| + action_allowed?(): Boolean     |&lt;br /&gt;
| + index(): JSON                  |&lt;br /&gt;
| + show(): JSON                   |&lt;br /&gt;
| + for_team(): JSON               |&lt;br /&gt;
| + by_user(): JSON                |&lt;br /&gt;
| + pending(): JSON                |&lt;br /&gt;
| + create(): JSON                 |&lt;br /&gt;
| + update(): JSON                 |&lt;br /&gt;
| + destroy(): JSON                |&lt;br /&gt;
| + accept(): JSON                 |&lt;br /&gt;
| + decline(): JSON                |&lt;br /&gt;
| - check_team_status(): void      |&lt;br /&gt;
| - find_request(): void           |&lt;br /&gt;
| - join_team_request_params()     |&lt;br /&gt;
| - current_user_is_request_creator? |&lt;br /&gt;
| - current_user_is_team_member?   |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
          |&lt;br /&gt;
          | uses&lt;br /&gt;
          v&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
|       JoinTeamRequest            |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
| + id: Integer                    |&lt;br /&gt;
| + participant_id: Integer        |&lt;br /&gt;
| + team_id: Integer               |&lt;br /&gt;
| + comments: Text                 |&lt;br /&gt;
| + reply_status: String           |&lt;br /&gt;
| + created_at: DateTime           |&lt;br /&gt;
| + updated_at: DateTime           |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
| + participant: Participant       |&lt;br /&gt;
| + team: Team                     |&lt;br /&gt;
+----------------------------------+&lt;br /&gt;
          |&lt;br /&gt;
          | belongs_to&lt;br /&gt;
          v&lt;br /&gt;
+------------------+    +------------------+&lt;br /&gt;
|   Participant    |    |      Team        |&lt;br /&gt;
+------------------+    +------------------+&lt;br /&gt;
| + id: Integer    |    | + id: Integer    |&lt;br /&gt;
| + user_id: Int   |    | + name: String   |&lt;br /&gt;
| + parent_id: Int |    | + parent_id: Int |&lt;br /&gt;
+------------------+    | + full?(): Bool  |&lt;br /&gt;
                        | + add_member()   |&lt;br /&gt;
                        +------------------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sequence Diagram - Create Join Team Request ===&lt;br /&gt;
&lt;br /&gt;
This sequence diagram shows the flow of creating a new join team request, including all validation steps:&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_CreateSequence.png|900px|center|Create Sequence Diagram]]&lt;br /&gt;
&lt;br /&gt;
''Figure 7: Sequence diagram for the create action. The controller performs multiple validation checks before creating the request, ensuring data integrity at each step.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Student          Controller           Participant        Team         JoinTeamRequest&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |  POST /join_team_requests             |               |                |&lt;br /&gt;
   |-----------------&amp;gt;|                    |               |                |&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |                  | check_team_status  |               |                |&lt;br /&gt;
   |                  |--------------------|--------------&amp;gt;|                |&lt;br /&gt;
   |                  |                    |   full?()     |                |&lt;br /&gt;
   |                  |&amp;lt;-------------------|---------------|                |&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |                  | find_by (user_id, parent_id)       |                |&lt;br /&gt;
   |                  |-------------------&amp;gt;|               |                |&lt;br /&gt;
   |                  |&amp;lt;-------------------|               |                |&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |                  | participants.include? (participant)|                |&lt;br /&gt;
   |                  |------------------------------------+                |&lt;br /&gt;
   |                  |&amp;lt;-----------------------------------|                |&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |                  | find_by (participant_id, team_id, PENDING)         |&lt;br /&gt;
   |                  |--------------------------------------------------&amp;gt;|&lt;br /&gt;
   |                  |&amp;lt;--------------------------------------------------|&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |                  | new (participant_id, team_id, comments, PENDING)   |&lt;br /&gt;
   |                  |--------------------------------------------------&amp;gt;|&lt;br /&gt;
   |                  |                    |               |        save    |&lt;br /&gt;
   |                  |&amp;lt;--------------------------------------------------|&lt;br /&gt;
   |                  |                    |               |                |&lt;br /&gt;
   |  201 Created     |                    |               |                |&lt;br /&gt;
   |&amp;lt;-----------------|                    |               |                |&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sequence Diagram - Accept Join Team Request ===&lt;br /&gt;
&lt;br /&gt;
This sequence diagram shows the flow of accepting a join team request:&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_AcceptSequence.png|900px|center|Accept Sequence Diagram]]&lt;br /&gt;
&lt;br /&gt;
''Figure 8: Sequence diagram for the accept action. The controller verifies the request status and team capacity before adding the member and updating the request status.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TeamMember       Controller         JoinTeamRequest      Team         Participant&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |  PATCH /join_team_requests/:id/accept                |                |&lt;br /&gt;
    |----------------&amp;gt;|                    |               |                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | find_request       |               |                |&lt;br /&gt;
    |                 |-------------------&amp;gt;|               |                |&lt;br /&gt;
    |                 |&amp;lt;-------------------|               |                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | action_allowed?    |               |                |&lt;br /&gt;
    |                 | (checks team membership)           |                |&lt;br /&gt;
    |                 |------------------------------------+                |&lt;br /&gt;
    |                 |&amp;lt;-----------------------------------|                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | reply_status == PENDING?           |                |&lt;br /&gt;
    |                 |-------------------&amp;gt;|               |                |&lt;br /&gt;
    |                 |&amp;lt;-------------------|               |                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | team.full?()       |               |                |&lt;br /&gt;
    |                 |------------------------------------+                |&lt;br /&gt;
    |                 |&amp;lt;-----------------------------------|                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | team.add_member (participant)      |                |&lt;br /&gt;
    |                 |------------------------------------+---------------&amp;gt;|&lt;br /&gt;
    |                 |&amp;lt;-----------------------------------|----------------|&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |                 | reply_status = ACCEPTED            |                |&lt;br /&gt;
    |                 |-------------------&amp;gt;|               |                |&lt;br /&gt;
    |                 |                    | save          |                |&lt;br /&gt;
    |                 |&amp;lt;-------------------|               |                |&lt;br /&gt;
    |                 |                    |               |                |&lt;br /&gt;
    |  200 OK         |                    |               |                |&lt;br /&gt;
    |&amp;lt;----------------|                    |               |                |&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
=== Test Philosophy ===&lt;br /&gt;
&lt;br /&gt;
Our testing approach follows the principle of testing behavior rather than implementation. Each test describes a user scenario and verifies that the system responds correctly. This makes tests resilient to refactoring and serves as executable documentation.&lt;br /&gt;
&lt;br /&gt;
=== Test Coverage ===&lt;br /&gt;
&lt;br /&gt;
Comprehensive RSpec tests are implemented in &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt;. The tests cover all endpoints and authorization scenarios.&lt;br /&gt;
&lt;br /&gt;
==== Test Categories ====&lt;br /&gt;
&lt;br /&gt;
'''1. Index Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify that only administrators can access the full list of join team requests:&lt;br /&gt;
&lt;br /&gt;
* Returns all join team requests for admin users with proper serialization&lt;br /&gt;
* Returns 403 Forbidden for non-admin users attempting to access the list&lt;br /&gt;
&lt;br /&gt;
'''2. Show Tests'''&lt;br /&gt;
&lt;br /&gt;
Show tests verify the authorization rules for viewing individual requests:&lt;br /&gt;
&lt;br /&gt;
* Returns request details when accessed by the request creator&lt;br /&gt;
* Returns request details when accessed by a team member&lt;br /&gt;
* Returns 403 Forbidden when accessed by an unrelated user&lt;br /&gt;
&lt;br /&gt;
'''3. Create Tests'''&lt;br /&gt;
&lt;br /&gt;
Create tests cover both success cases and all validation scenarios:&lt;br /&gt;
&lt;br /&gt;
* Creates request successfully with valid parameters and returns 201 Created&lt;br /&gt;
* Returns 422 when user tries to create a duplicate pending request&lt;br /&gt;
* Returns 422 when user already belongs to the target team&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 422 when user is not a participant in the assignment&lt;br /&gt;
&lt;br /&gt;
'''4. Update Tests'''&lt;br /&gt;
&lt;br /&gt;
Update tests verify that only creators can modify their requests:&lt;br /&gt;
&lt;br /&gt;
* Updates comments successfully for the request creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to update&lt;br /&gt;
&lt;br /&gt;
'''5. Destroy Tests'''&lt;br /&gt;
&lt;br /&gt;
Destroy tests verify the deletion authorization:&lt;br /&gt;
&lt;br /&gt;
* Deletes request successfully for the creator&lt;br /&gt;
* Returns 403 when a non-creator attempts to delete&lt;br /&gt;
&lt;br /&gt;
'''6. Accept Tests'''&lt;br /&gt;
&lt;br /&gt;
Accept tests cover the complex acceptance workflow:&lt;br /&gt;
&lt;br /&gt;
* Accepts pending request, adds member to team, and updates status to ACCEPTED&lt;br /&gt;
* Returns 422 when attempting to accept an already-processed request&lt;br /&gt;
* Returns 422 when the target team is full&lt;br /&gt;
* Returns 403 when a non-team-member attempts to accept&lt;br /&gt;
&lt;br /&gt;
'''7. Decline Tests'''&lt;br /&gt;
&lt;br /&gt;
Decline tests mirror the accept tests for the decline workflow:&lt;br /&gt;
&lt;br /&gt;
* Declines pending request and updates status to DECLINED&lt;br /&gt;
* Returns 422 when attempting to decline an already-processed request&lt;br /&gt;
* Returns 403 when a non-team-member attempts to decline&lt;br /&gt;
&lt;br /&gt;
'''8. Filtering Endpoint Tests'''&lt;br /&gt;
&lt;br /&gt;
These tests verify the query endpoints return correctly filtered results:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt; returns only requests for the specified team&lt;br /&gt;
* &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt; returns only requests created by the specified user&lt;br /&gt;
* &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt; returns only requests with PENDING status&lt;br /&gt;
&lt;br /&gt;
=== Running Tests ===&lt;br /&gt;
&lt;br /&gt;
The following commands can be used to run the test suite:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Run all join team request tests&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&lt;br /&gt;
# Run with documentation format for readable output&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb --format documentation&lt;br /&gt;
&lt;br /&gt;
# Run a specific test by line number&lt;br /&gt;
bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb:50&lt;br /&gt;
&lt;br /&gt;
# Run tests with coverage report&lt;br /&gt;
COVERAGE=true bundle exec rspec spec/requests/api/v1/join_team_requests_spec.rb&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_TestResults_Screenshot.png|700px|center|Test Results]]&lt;br /&gt;
&lt;br /&gt;
''Figure 9: RSpec test output showing all tests passing. The documentation format displays each test case with its description, making it easy to understand what functionality is being verified.''&lt;br /&gt;
&lt;br /&gt;
== Frontend Integration ==&lt;br /&gt;
&lt;br /&gt;
=== Component Architecture ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation uses React with TypeScript for type safety. Components are organized following a feature-based structure where all join team request related components are grouped together.&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_Frontend_Architecture.png|700px|center|Frontend Component Structure]]&lt;br /&gt;
&lt;br /&gt;
''Figure 10: Frontend component hierarchy showing how the main JoinTeamRequests component composes smaller, focused components for list display, forms, and actions.''&lt;br /&gt;
&lt;br /&gt;
=== React Components ===&lt;br /&gt;
&lt;br /&gt;
The frontend implementation includes the following React components:&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestList:''' Displays all join team requests with filtering capabilities. Users can filter by status (pending, accepted, declined) and search by team name or requester name. Each row in the list is clickable to expand and show the full request details including comments.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestForm:''' A form component for submitting new join requests. It includes team selection via a searchable dropdown and an optional comments field. The form performs client-side validation before submission.&lt;br /&gt;
&lt;br /&gt;
'''JoinTeamRequestActions:''' A component containing Accept and Decline buttons for team members. These buttons are only rendered for users who are members of the target team. Clicking either button shows a confirmation modal before processing.&lt;br /&gt;
&lt;br /&gt;
'''PendingRequestsBadge:''' A notification badge component that shows the count of pending requests. This appears in the navigation bar to alert team members of incoming requests that need attention.&lt;br /&gt;
&lt;br /&gt;
=== API Integration ===&lt;br /&gt;
&lt;br /&gt;
The frontend communicates with the backend using axios. All API calls are centralized in a service file for maintainability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
// src/services/joinTeamRequestService.ts&lt;br /&gt;
import axiosClient from '../utils/axios_client';&lt;br /&gt;
&lt;br /&gt;
interface JoinTeamRequestData {&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  assignment_id: number;&lt;br /&gt;
  comments?: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Fetch all join team requests (admin only)&lt;br /&gt;
export const getJoinTeamRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests');&lt;br /&gt;
&lt;br /&gt;
// Fetch requests for a specific team&lt;br /&gt;
export const getTeamRequests = (teamId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/for_team/${teamId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch requests by a specific user&lt;br /&gt;
export const getUserRequests = (userId: number) =&amp;gt; &lt;br /&gt;
  axiosClient.get(`/join_team_requests/by_user/${userId}`);&lt;br /&gt;
&lt;br /&gt;
// Fetch only pending requests&lt;br /&gt;
export const getPendingRequests = () =&amp;gt; &lt;br /&gt;
  axiosClient.get('/join_team_requests/pending');&lt;br /&gt;
&lt;br /&gt;
// Create a new join request&lt;br /&gt;
export const createJoinRequest = (data: JoinTeamRequestData) =&amp;gt; &lt;br /&gt;
  axiosClient.post('/join_team_requests', data);&lt;br /&gt;
&lt;br /&gt;
// Update request comments&lt;br /&gt;
export const updateJoinRequest = (id: number, comments: string) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}`, { comments });&lt;br /&gt;
&lt;br /&gt;
// Delete a join request&lt;br /&gt;
export const deleteJoinRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.delete(`/join_team_requests/${id}`);&lt;br /&gt;
&lt;br /&gt;
// Accept a join request&lt;br /&gt;
export const acceptRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/accept`);&lt;br /&gt;
&lt;br /&gt;
// Decline a join request&lt;br /&gt;
export const declineRequest = (id: number) =&amp;gt; &lt;br /&gt;
  axiosClient.patch(`/join_team_requests/${id}/decline`);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:JoinTeamRequest_UI_Overview.png|800px|center|UI Overview]]&lt;br /&gt;
&lt;br /&gt;
''Figure 11: Complete UI overview showing the join team requests interface. The left panel shows the list of requests with filtering options, and the right panel shows the details of a selected request with action buttons for team members.''&lt;br /&gt;
&lt;br /&gt;
== Refactoring Improvements ==&lt;br /&gt;
&lt;br /&gt;
This section highlights the key improvements made through refactoring, comparing the original implementation with our enhanced version.&lt;br /&gt;
&lt;br /&gt;
=== Before: Scattered Authorization Logic ===&lt;br /&gt;
&lt;br /&gt;
The original implementation had authorization checks scattered throughout each action method, leading to code duplication and inconsistency:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Authorization mixed with business logic&lt;br /&gt;
def accept&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... more logic&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def decline&lt;br /&gt;
  if current_user.role != 'student'&lt;br /&gt;
    render json: { error: 'Unauthorized' }, status: :forbidden&lt;br /&gt;
    return&lt;br /&gt;
  end&lt;br /&gt;
  # ... duplicate checks&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Centralized Authorization ===&lt;br /&gt;
&lt;br /&gt;
Our refactored implementation centralizes all authorization in the &amp;lt;code&amp;gt;action_allowed?&amp;lt;/code&amp;gt; method:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Centralized authorization with clear rules&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case params[:action]&lt;br /&gt;
  when 'decline', 'accept'&lt;br /&gt;
    return false unless current_user_has_student_privileges?&lt;br /&gt;
    @join_team_request = JoinTeamRequest.find_by(id: params[:id]) unless @join_team_request&lt;br /&gt;
    return false unless @join_team_request&lt;br /&gt;
    current_user_is_team_member?&lt;br /&gt;
  # ... other cases&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Single source of truth for authorization rules&lt;br /&gt;
* Easier to audit and update security policies&lt;br /&gt;
* Reduced code duplication&lt;br /&gt;
* Consistent behavior across all endpoints&lt;br /&gt;
&lt;br /&gt;
=== Before: Inconsistent Response Formats ===&lt;br /&gt;
&lt;br /&gt;
The original implementation returned data in various formats, making frontend integration difficult:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# BEFORE: Inconsistent response structure&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request.attributes&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  render json: JoinTeamRequest.all.map { |r| { id: r.id, status: r.reply_status } }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== After: Serializer-Based Responses ===&lt;br /&gt;
&lt;br /&gt;
Our implementation uses ActiveModel Serializers for consistent output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# AFTER: Consistent serialized responses&lt;br /&gt;
def show&lt;br /&gt;
  render json: @join_team_request, serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
def index&lt;br /&gt;
  join_team_requests = JoinTeamRequest.includes(:participant, :team).all&lt;br /&gt;
  render json: join_team_requests, each_serializer: JoinTeamRequestSerializer, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Benefits:'''&lt;br /&gt;
* Consistent JSON structure across all endpoints&lt;br /&gt;
* Easier frontend development with predictable data shapes&lt;br /&gt;
* Centralized control over what data is exposed&lt;br /&gt;
* Automatic handling of nested relationships&lt;br /&gt;
&lt;br /&gt;
== Summary of Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Files Modified ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! File !! Changes !! Lines Modified&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/controllers/api/v1/join_team_requests_controller.rb&amp;lt;/code&amp;gt; || Complete rewrite with centralized authorization, new filtering endpoints, serializer integration, and comprehensive error handling || ~250&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/models/join_team_request.rb&amp;lt;/code&amp;gt; || Added status validation constants and updated association declarations || ~10&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;app/serializers/join_team_request_serializer.rb&amp;lt;/code&amp;gt; || New file - Custom serializer with nested participant and team data || ~25&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;config/routes.rb&amp;lt;/code&amp;gt; || Added collection routes for filtering and member routes for accept/decline actions || ~12&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;spec/requests/api/v1/join_team_requests_spec.rb&amp;lt;/code&amp;gt; || Comprehensive request specs covering all endpoints and authorization scenarios || ~400&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== New Features ===&lt;br /&gt;
&lt;br /&gt;
# '''Fine-grained authorization''' based on user roles (admin/student) and relationships (request creator/team member)&lt;br /&gt;
# '''Filtering endpoints''' (&amp;lt;code&amp;gt;for_team&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;by_user&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pending&amp;lt;/code&amp;gt;) for efficient data retrieval&lt;br /&gt;
# '''Accept/Decline workflow''' with team member validation and automatic team membership management&lt;br /&gt;
# '''Duplicate request prevention''' to avoid confusion from multiple pending requests to the same team&lt;br /&gt;
# '''Comprehensive error handling''' with user-friendly error messages suitable for display&lt;br /&gt;
# '''ActiveModel Serializer integration''' for consistent, predictable JSON responses&lt;br /&gt;
# '''Full test coverage''' for all endpoints and authorization scenarios&lt;br /&gt;
&lt;br /&gt;
== Team Members ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Unity ID !! Contribution&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 1] || [unity_id] || Backend controller implementation, authorization logic&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 2] || [unity_id] || Model development, serializer implementation&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 3] || [unity_id] || Test coverage, documentation, code review&lt;br /&gt;
|-&lt;br /&gt;
| [Team Member 4] || [unity_id] || Frontend integration, UI components&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Conclusion ==&lt;br /&gt;
&lt;br /&gt;
This project successfully delivers a complete, production-ready implementation of the JoinTeamRequests feature for the Expertiza reimplementation. The key achievements include:&lt;br /&gt;
&lt;br /&gt;
* '''Security:''' Fine-grained authorization ensures users can only access and modify resources appropriate to their role&lt;br /&gt;
* '''Usability:''' Filtering endpoints and consistent response formats make the API easy to consume&lt;br /&gt;
* '''Maintainability:''' Centralized logic, comprehensive tests, and clear documentation facilitate future development&lt;br /&gt;
* '''Reliability:''' Thorough testing covers both happy paths and edge cases&lt;br /&gt;
&lt;br /&gt;
The implementation follows Rails best practices and integrates seamlessly with the existing Expertiza codebase, providing a solid foundation for team formation workflows in the system.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
# [https://wiki.expertiza.ncsu.edu/ Expertiza Wiki] - Project documentation and guidelines&lt;br /&gt;
# [https://api.rubyonrails.org/ Rails API Documentation] - Ruby on Rails framework reference&lt;br /&gt;
# [https://rspec.info/documentation/ RSpec Documentation] - Testing framework documentation&lt;br /&gt;
# [https://github.com/rails-api/active_model_serializers ActiveModel Serializers] - JSON serialization library&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-back-end Expertiza Reimplementation Backend] - Source repository&lt;br /&gt;
# [https://github.com/expertiza/reimplementation-front-end Expertiza Reimplementation Frontend] - Frontend repository&lt;br /&gt;
&lt;br /&gt;
[[Category:CSC/ECE 517]]&lt;br /&gt;
[[Category:Fall 2025]]&lt;br /&gt;
[[Category:Expertiza]]&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167103</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167103"/>
		<updated>2025-11-11T02:24:08Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
This document describes the design and implementation plan for integrating JoinTeamRequests functionality in Expertiza. The feature enables students to respond to teammate advertisements by creating join team requests, which topic holders can then approve or decline. This integration will complete the teammate advertisement workflow by connecting the advertisement creation (already implemented) with the join team request flow.&lt;br /&gt;
&lt;br /&gt;
=== Problem Statement ===&lt;br /&gt;
&lt;br /&gt;
Currently, Expertiza supports teammate advertisements for assignments with topics. Topic holders can create advertisements on the &amp;quot;Your Team&amp;quot; page. However, the complete flow for responding to advertisements and managing join team requests is not fully integrated. The following gaps exist:&lt;br /&gt;
&lt;br /&gt;
# The join_team_requests_controller has action privilege checks scattered across methods instead of using a centralized `action_allowed?` method&lt;br /&gt;
# There is no frontend UI for viewing advertisements on the Signup Sheet page&lt;br /&gt;
# There is no UI for creating join team requests when responding to advertisements&lt;br /&gt;
# The &amp;quot;Received Requests&amp;quot; section is missing from the StudentTeamView page&lt;br /&gt;
# There is no method to accept join team requests (only decline exists)&lt;br /&gt;
# The controller needs better integration with the frontend&lt;br /&gt;
&lt;br /&gt;
=== Goals ===&lt;br /&gt;
&lt;br /&gt;
# Refactor the join_team_requests_controller to use a single `action_allowed?` method for privilege checking&lt;br /&gt;
# Add a trumpet icon on the Signup Sheet page to indicate when advertisements exist for topics&lt;br /&gt;
# Create a frontend UI for viewing advertisements and creating join team requests&lt;br /&gt;
# Add a &amp;quot;Received Requests&amp;quot; section to StudentTeamView.tsx for managing incoming join team requests&lt;br /&gt;
# Implement an accept method for join team requests&lt;br /&gt;
# Ensure proper validation that teams cannot accept requests when full&lt;br /&gt;
# Conduct comprehensive testing of the entire flow&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Controller Refactoring ====&lt;br /&gt;
&lt;br /&gt;
The `Api::V1::JoinTeamRequestsController` currently has privilege checks scattered across methods:&lt;br /&gt;
&lt;br /&gt;
- `index` method checks for administrator role directly&lt;br /&gt;
- `action_allowed?` only checks if user is a student&lt;br /&gt;
&lt;br /&gt;
'''Refactored Design:'''&lt;br /&gt;
&lt;br /&gt;
The `action_allowed?` method will be enhanced to handle different actions based on the action being performed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case action_name.to_sym&lt;br /&gt;
  when :index&lt;br /&gt;
    @current_user.administrator?&lt;br /&gt;
  when :create, :show, :update, :destroy, :decline, :accept&lt;br /&gt;
    @current_user.student?&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All methods will rely on this centralized privilege check, removing individual checks from methods like `index`.&lt;br /&gt;
&lt;br /&gt;
==== New Methods ====&lt;br /&gt;
&lt;br /&gt;
'''1. Accept Method'''&lt;br /&gt;
&lt;br /&gt;
Add a new `accept` method to handle accepting join team requests:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/:id/accept&lt;br /&gt;
# Accepts a join team request and adds the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  &lt;br /&gt;
  # Check if team is full&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Check if participant is already on the team&lt;br /&gt;
  if team.participants.include?(@join_team_request.participant)&lt;br /&gt;
    return render json: { error: 'Participant already belongs to the team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  ActiveRecord::Base.transaction do&lt;br /&gt;
    # Add participant to team&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Update request status&lt;br /&gt;
      @join_team_request.update!(reply_status: ACCEPTED)&lt;br /&gt;
      render json: { message: 'JoinTeamRequest accepted successfully', join_team_request: @join_team_request }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
      raise ActiveRecord::Rollback&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
rescue =&amp;gt; e&lt;br /&gt;
  render json: { error: &amp;quot;Failed to accept request: #{e.message}&amp;quot; }, status: :unprocessable_entity&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''2. Get Join Team Requests for Team'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all join team requests for a specific team (for the &amp;quot;Received Requests&amp;quot; section):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/team/:team_id&lt;br /&gt;
# Gets all join team requests for a specific team&lt;br /&gt;
def team_requests&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  &lt;br /&gt;
  # Verify current user is a member of the team&lt;br /&gt;
  unless team.participants.exists?(user_id: @current_user.id)&lt;br /&gt;
    return render json: { error: 'Unauthorized' }, status: :unauthorized&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(team_id: params[:team_id])&lt;br /&gt;
    .includes(:participant =&amp;gt; :user)&lt;br /&gt;
    .order(created_at: :desc)&lt;br /&gt;
  &lt;br /&gt;
  render json: join_team_requests, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''3. Get Advertisements for Assignment'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all advertisements for an assignment (for the Signup Sheet page):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/signed_up_teams/advertisements/:assignment_id&lt;br /&gt;
# Gets all advertisements for an assignment&lt;br /&gt;
def advertisements&lt;br /&gt;
  assignment = Assignment.find(params[:assignment_id])&lt;br /&gt;
  signed_up_teams = SignedUpTeam.joins(:team, :sign_up_topic)&lt;br /&gt;
    .where(sign_up_topics: { assignment_id: params[:assignment_id] })&lt;br /&gt;
    .where(advertise_for_partner: true)&lt;br /&gt;
    .includes(:team, :sign_up_topic)&lt;br /&gt;
  &lt;br /&gt;
  advertisements = signed_up_teams.map do |sut|&lt;br /&gt;
    {&lt;br /&gt;
      id: sut.id,&lt;br /&gt;
      team_id: sut.team_id,&lt;br /&gt;
      team_name: sut.team.name,&lt;br /&gt;
      topic_id: sut.sign_up_topic_id,&lt;br /&gt;
      topic_name: sut.sign_up_topic.topic_name,&lt;br /&gt;
      comments_for_advertisement: sut.comments_for_advertisement,&lt;br /&gt;
      created_at: sut.created_at&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  render json: advertisements, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Model Updates ====&lt;br /&gt;
&lt;br /&gt;
The `JoinTeamRequest` model needs to establish proper associations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
  &lt;br /&gt;
  scope :pending, -&amp;gt; { where(reply_status: 'PENDING') }&lt;br /&gt;
  scope :for_team, -&amp;gt;(team_id) { where(team_id: team_id) }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Routes Updates ====&lt;br /&gt;
&lt;br /&gt;
Add new routes for the additional endpoints:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    get 'team/:team_id', to: 'join_team_requests#team_requests'&lt;br /&gt;
    post 'decline/:id', to: 'join_team_requests#decline'&lt;br /&gt;
    post 'accept/:id', to: 'join_team_requests#accept'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
resources :signed_up_teams do&lt;br /&gt;
collection do&lt;br /&gt;
get 'advertisements/:assignment_id', to: 'signed_up_teams#advertisements'&lt;br /&gt;
end&lt;br /&gt;
member do&lt;br /&gt;
post :create_advertisement&lt;br /&gt;
patch :update_advertisement&lt;br /&gt;
delete :remove_advertisement&lt;br /&gt;
end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Frontend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Signup Sheet Page Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Create a new component or enhance existing signup sheet to display advertisements:&lt;br /&gt;
&lt;br /&gt;
'''Component: SignupSheet.tsx'''&lt;br /&gt;
&lt;br /&gt;
This component will:&lt;br /&gt;
&lt;br /&gt;
- Display all topics for an assignment&lt;br /&gt;
- Show a trumpet icon (🔔) next to topics that have advertisements&lt;br /&gt;
- Allow users to click on topics with advertisements to view details&lt;br /&gt;
- Provide a modal/dialog to view advertisement details and create a join team request&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface Advertisement {&lt;br /&gt;
  id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  team_name: string;&lt;br /&gt;
  topic_id: number;&lt;br /&gt;
  topic_name: string;&lt;br /&gt;
  comments_for_advertisement: string;&lt;br /&gt;
  created_at: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
interface SignUpTopic {&lt;br /&gt;
id: number;&lt;br /&gt;
topic_name: string;&lt;br /&gt;
topic_identifier: string;&lt;br /&gt;
max_choosers: number;&lt;br /&gt;
description?: string;&lt;br /&gt;
has_advertisement?: boolean;&lt;br /&gt;
advertisement?: Advertisement;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI will show:&lt;br /&gt;
&lt;br /&gt;
- A table/list of topics&lt;br /&gt;
- Trumpet icon indicator for topics with advertisements&lt;br /&gt;
- Click handler to open advertisement details modal&lt;br /&gt;
&lt;br /&gt;
==== Join Team Request Modal ====&lt;br /&gt;
&lt;br /&gt;
Create a modal component for creating join team requests:&lt;br /&gt;
&lt;br /&gt;
'''Component: JoinTeamRequestModal.tsx'''&lt;br /&gt;
&lt;br /&gt;
This modal will:&lt;br /&gt;
&lt;br /&gt;
- Display advertisement details (team name, topic, desired qualifications)&lt;br /&gt;
- Provide a text area for comments (optional)&lt;br /&gt;
- Show a &amp;quot;Send Request&amp;quot; button&lt;br /&gt;
- Handle form submission and API calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequestModalProps {&lt;br /&gt;
  show: boolean;&lt;br /&gt;
  onHide: () =&amp;gt; void;&lt;br /&gt;
  advertisement: Advertisement;&lt;br /&gt;
  assignmentId: number;&lt;br /&gt;
  onSuccess: () =&amp;gt; void;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== StudentTeamView Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Add a &amp;quot;Received Requests&amp;quot; section to `StudentTeamView.tsx`:&lt;br /&gt;
&lt;br /&gt;
'''Section: Received Join Team Requests'''&lt;br /&gt;
&lt;br /&gt;
This section will:&lt;br /&gt;
&lt;br /&gt;
- Display all pending join team requests for the current team&lt;br /&gt;
- Show request details: requester name, email, comments, date&lt;br /&gt;
- Provide &amp;quot;Approve&amp;quot; and &amp;quot;Decline&amp;quot; buttons for each request&lt;br /&gt;
- Disable approve button if team is full&lt;br /&gt;
- Show status for non-pending requests&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequest {&lt;br /&gt;
  id: number;&lt;br /&gt;
  participant_id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  comments: string;&lt;br /&gt;
  reply_status: 'PENDING' | 'ACCEPTED' | 'DECLINED';&lt;br /&gt;
  created_at: string;&lt;br /&gt;
  participant: {&lt;br /&gt;
    id: number;&lt;br /&gt;
    user: {&lt;br /&gt;
      id: number;&lt;br /&gt;
      name: string;&lt;br /&gt;
      full_name: string;&lt;br /&gt;
      email: string;&lt;br /&gt;
    };&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI structure will be similar to the existing &amp;quot;Received Invitations&amp;quot; section, with:&lt;br /&gt;
&lt;br /&gt;
- Table displaying request information&lt;br /&gt;
- Action buttons (Approve/Decline) for pending requests&lt;br /&gt;
- Status display for processed requests&lt;br /&gt;
&lt;br /&gt;
==== API Hooks ====&lt;br /&gt;
&lt;br /&gt;
Create or extend hooks for join team requests:&lt;br /&gt;
&lt;br /&gt;
'''File: hooks/useJoinTeamRequest.ts'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
export const useJoinTeamRequest = () =&amp;gt; {&lt;br /&gt;
  const createRequestAPI = useAPI();&lt;br /&gt;
  const acceptRequestAPI = useAPI();&lt;br /&gt;
  const declineRequestAPI = useAPI();&lt;br /&gt;
  const fetchTeamRequestsAPI = useAPI();&lt;br /&gt;
  const fetchAdvertisementsAPI = useAPI();&lt;br /&gt;
&lt;br /&gt;
const createJoinTeamRequest = (teamId: number, assignmentId: number, comments?: string) =&amp;gt;&lt;br /&gt;
createRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests`,&lt;br /&gt;
method: 'POST',&lt;br /&gt;
data: { team_id: teamId, assignment_id: assignmentId, comments }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const acceptJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
acceptRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/accept/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const declineJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
declineRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/decline/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchTeamRequests = (teamId: number) =&amp;gt;&lt;br /&gt;
fetchTeamRequestsAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/team/${teamId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchAdvertisements = (assignmentId: number) =&amp;gt;&lt;br /&gt;
fetchAdvertisementsAPI.sendRequest({&lt;br /&gt;
url: `/signed_up_teams/advertisements/${assignmentId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
createJoinTeamRequest,&lt;br /&gt;
acceptJoinTeamRequest,&lt;br /&gt;
declineJoinTeamRequest,&lt;br /&gt;
fetchTeamRequests,&lt;br /&gt;
fetchAdvertisements,&lt;br /&gt;
createRequestAPI,&lt;br /&gt;
acceptRequestAPI,&lt;br /&gt;
declineRequestAPI,&lt;br /&gt;
fetchTeamRequestsAPI,&lt;br /&gt;
fetchAdvertisementsAPI&lt;br /&gt;
};&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Implementation Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Phase 1: Backend Refactoring ===&lt;br /&gt;
&lt;br /&gt;
# '''Refactor action_allowed? method'''&lt;br /&gt;
## Update `Api::V1::JoinTeamRequestsController#action_allowed?` to handle all actions&lt;br /&gt;
## Remove individual privilege checks from `index` method&lt;br /&gt;
## Ensure all actions go through the centralized check&lt;br /&gt;
# '''Add accept method'''&lt;br /&gt;
## Implement `accept` method with team full validation&lt;br /&gt;
## Add transaction handling for atomic operations&lt;br /&gt;
## Update request status to ACCEPTED&lt;br /&gt;
## Add participant to team using `team.add_member`&lt;br /&gt;
# '''Add team_requests method'''&lt;br /&gt;
## Implement method to fetch requests for a team&lt;br /&gt;
## Add authorization check (user must be team member)&lt;br /&gt;
## Include participant and user data in response&lt;br /&gt;
# '''Add advertisements method to SignedUpTeamsController'''&lt;br /&gt;
## Implement method to fetch all advertisements for an assignment&lt;br /&gt;
## Join with teams and sign_up_topics&lt;br /&gt;
## Return formatted advertisement data&lt;br /&gt;
# '''Update JoinTeamRequest model'''&lt;br /&gt;
## Add `belongs_to :team` association&lt;br /&gt;
## Add scopes for filtering&lt;br /&gt;
## Update validations&lt;br /&gt;
# '''Update routes'''&lt;br /&gt;
## Add routes for new endpoints&lt;br /&gt;
## Ensure RESTful conventions&lt;br /&gt;
&lt;br /&gt;
=== Phase 2: Frontend - Signup Sheet ===&lt;br /&gt;
&lt;br /&gt;
# '''Create SignupSheet component (if not exists)'''&lt;br /&gt;
## Display topics for an assignment&lt;br /&gt;
## Fetch advertisements for the assignment&lt;br /&gt;
## Map advertisements to topics&lt;br /&gt;
## Display trumpet icon for topics with advertisements&lt;br /&gt;
# '''Create JoinTeamRequestModal component'''&lt;br /&gt;
## Design modal UI&lt;br /&gt;
## Implement form with comments field&lt;br /&gt;
## Add submit handler&lt;br /&gt;
## Handle success/error states&lt;br /&gt;
# '''Integrate modal with SignupSheet'''&lt;br /&gt;
## Add click handler for topics with advertisements&lt;br /&gt;
## Open modal with advertisement details&lt;br /&gt;
## Handle modal close&lt;br /&gt;
&lt;br /&gt;
=== Phase 3: Frontend - StudentTeamView ===&lt;br /&gt;
&lt;br /&gt;
# '''Add Received Requests section'''&lt;br /&gt;
## Create section similar to &amp;quot;Received Invitations&amp;quot;&lt;br /&gt;
## Fetch join team requests for current team&lt;br /&gt;
## Display requests in table format&lt;br /&gt;
# '''Implement approve/decline functionality'''&lt;br /&gt;
## Add approve button handler&lt;br /&gt;
## Add decline button handler&lt;br /&gt;
## Check team full status before allowing approve&lt;br /&gt;
## Update UI after actions&lt;br /&gt;
# '''Add useJoinTeamRequest hook'''&lt;br /&gt;
## Create hook file&lt;br /&gt;
## Implement all API methods&lt;br /&gt;
## Export hook for use in components&lt;br /&gt;
&lt;br /&gt;
=== Phase 4: Integration and Testing ===&lt;br /&gt;
&lt;br /&gt;
# '''End-to-end testing'''&lt;br /&gt;
## Test advertisement creation → request creation → approval flow&lt;br /&gt;
## Test decline flow&lt;br /&gt;
## Test team full validation&lt;br /&gt;
## Test authorization checks&lt;br /&gt;
# '''Unit testing'''&lt;br /&gt;
## Test controller methods&lt;br /&gt;
## Test model validations&lt;br /&gt;
## Test frontend components&lt;br /&gt;
# '''Integration testing'''&lt;br /&gt;
## Test API endpoints&lt;br /&gt;
## Test frontend-backend integration&lt;br /&gt;
## Test error handling&lt;br /&gt;
&lt;br /&gt;
== Files Changed/Added ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `app/controllers/api/v1/join_team_requests_controller.rb` - Refactor action_allowed?, add accept and team_requests methods&lt;br /&gt;
- `app/controllers/api/v1/signed_up_teams_controller.rb` - Add advertisements method&lt;br /&gt;
- `app/models/join_team_request.rb` - Add associations and scopes&lt;br /&gt;
- `config/routes.rb` - Add new routes&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `spec/requests/api/v1/join_team_requests_spec.rb` - Request specs for new methods&lt;br /&gt;
- `spec/models/join_team_request_spec.rb` - Model specs&lt;br /&gt;
&lt;br /&gt;
=== Frontend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/Student Teams/StudentTeamView.tsx` - Add Received Requests section&lt;br /&gt;
- `src/hooks/useStudentTeam.ts` - Add join team request methods (or create new hook)&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.tsx` - Signup sheet component with advertisement indicators&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.tsx` - Modal for creating join team requests&lt;br /&gt;
- `src/hooks/useJoinTeamRequest.ts` - Hook for join team request API calls&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.module.css` - Styles for signup sheet&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.module.css` - Styles for modal&lt;br /&gt;
&lt;br /&gt;
== Testing Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Unit Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequestsController Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test action_allowed? for different actions and user roles&lt;br /&gt;
## Test accept method with valid request&lt;br /&gt;
## Test accept method when team is full&lt;br /&gt;
## Test accept method when participant already on team&lt;br /&gt;
## Test team_requests method authorization&lt;br /&gt;
## Test team_requests method returns correct data&lt;br /&gt;
# '''SignedUpTeamsController Tests'''&lt;br /&gt;
## Test advertisements method returns correct data&lt;br /&gt;
## Test advertisements method filters correctly&lt;br /&gt;
# '''JoinTeamRequest Model Tests'''&lt;br /&gt;
## Test associations&lt;br /&gt;
## Test validations&lt;br /&gt;
## Test scopes&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''End-to-End Flow Tests'''&lt;br /&gt;
## Create advertisement → Create join request → Accept request&lt;br /&gt;
## Create advertisement → Create join request → Decline request&lt;br /&gt;
## Test team full validation prevents acceptance&lt;br /&gt;
## Test authorization prevents unauthorized access&lt;br /&gt;
&lt;br /&gt;
=== Frontend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Component Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''SignUpSheet Component'''&lt;br /&gt;
## Test advertisement indicators display correctly&lt;br /&gt;
## Test modal opens on click&lt;br /&gt;
## Test data fetching&lt;br /&gt;
# '''JoinTeamRequestModal Component'''&lt;br /&gt;
## Test form submission&lt;br /&gt;
## Test validation&lt;br /&gt;
## Test success/error handling&lt;br /&gt;
# '''StudentTeamView Component'''&lt;br /&gt;
## Test Received Requests section displays&lt;br /&gt;
## Test approve functionality&lt;br /&gt;
## Test decline functionality&lt;br /&gt;
## Test team full validation&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''API Integration'''&lt;br /&gt;
## Test all API calls work correctly&lt;br /&gt;
## Test error handling&lt;br /&gt;
## Test loading states&lt;br /&gt;
&lt;br /&gt;
== Edge Cases and Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Team Full Validation'''&lt;br /&gt;
&lt;br /&gt;
- Check team capacity before accepting request&lt;br /&gt;
- Return appropriate error message&lt;br /&gt;
- Prevent race conditions with database transactions&lt;br /&gt;
&lt;br /&gt;
# '''Authorization'''&lt;br /&gt;
&lt;br /&gt;
- Verify user is team member before showing requests&lt;br /&gt;
- Verify user is student before creating requests&lt;br /&gt;
- Verify user is administrator before viewing all requests&lt;br /&gt;
&lt;br /&gt;
# '''Duplicate Requests'''&lt;br /&gt;
&lt;br /&gt;
- Prevent creating duplicate requests from same participant&lt;br /&gt;
- Handle case where participant already on team&lt;br /&gt;
&lt;br /&gt;
# '''Concurrent Modifications'''&lt;br /&gt;
&lt;br /&gt;
- Use transactions for atomic operations&lt;br /&gt;
- Handle race conditions in team membership updates&lt;br /&gt;
&lt;br /&gt;
=== Frontend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Network Errors'''&lt;br /&gt;
&lt;br /&gt;
- Display appropriate error messages&lt;br /&gt;
- Handle timeout scenarios&lt;br /&gt;
- Provide retry mechanisms&lt;br /&gt;
&lt;br /&gt;
# '''Empty States'''&lt;br /&gt;
&lt;br /&gt;
- Show appropriate messages when no advertisements exist&lt;br /&gt;
- Show appropriate messages when no requests exist&lt;br /&gt;
&lt;br /&gt;
# '''Loading States'''&lt;br /&gt;
&lt;br /&gt;
- Display loading indicators during API calls&lt;br /&gt;
- Prevent multiple simultaneous requests&lt;br /&gt;
&lt;br /&gt;
# '''Form Validation'''&lt;br /&gt;
&lt;br /&gt;
- Validate required fields&lt;br /&gt;
- Provide user feedback for errors&lt;br /&gt;
&lt;br /&gt;
== Security Considerations ==&lt;br /&gt;
&lt;br /&gt;
# '''Authorization Checks'''&lt;br /&gt;
&lt;br /&gt;
- All endpoints must verify user permissions&lt;br /&gt;
- Team members can only see requests for their team&lt;br /&gt;
- Students can only create requests for themselves&lt;br /&gt;
&lt;br /&gt;
# '''Input Validation'''&lt;br /&gt;
&lt;br /&gt;
- Sanitize all user inputs&lt;br /&gt;
- Validate team_id and assignment_id parameters&lt;br /&gt;
- Prevent SQL injection through parameterized queries&lt;br /&gt;
&lt;br /&gt;
# '''Rate Limiting'''&lt;br /&gt;
&lt;br /&gt;
- Consider rate limiting for request creation&lt;br /&gt;
- Prevent spam requests&lt;br /&gt;
&lt;br /&gt;
== Future Enhancements ==&lt;br /&gt;
&lt;br /&gt;
# Email notifications when join team requests are created&lt;br /&gt;
&lt;br /&gt;
# Email notifications when requests are approved/declined&lt;br /&gt;
&lt;br /&gt;
# Bulk approve/decline functionality&lt;br /&gt;
&lt;br /&gt;
# Request history and audit trail&lt;br /&gt;
&lt;br /&gt;
# Advanced filtering and search for requests&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
- Expertiza Wiki: E2252. Refactor auth controller.rb &amp;amp; password retrieval controller.rb&lt;br /&gt;
- Rails API Documentation&lt;br /&gt;
- React Router Documentation&lt;br /&gt;
- Expertiza Codebase: Existing invitation and team management implementations&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167101</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167101"/>
		<updated>2025-11-11T02:21:20Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
This document describes the design and implementation plan for integrating JoinTeamRequests functionality in Expertiza. The feature enables students to respond to teammate advertisements by creating join team requests, which topic holders can then approve or decline. This integration will complete the teammate advertisement workflow by connecting the advertisement creation (already implemented) with the join team request flow.&lt;br /&gt;
&lt;br /&gt;
=== Problem Statement ===&lt;br /&gt;
&lt;br /&gt;
Currently, Expertiza supports teammate advertisements for assignments with topics. Topic holders can create advertisements on the &amp;quot;Your Team&amp;quot; page. However, the complete flow for responding to advertisements and managing join team requests is not fully integrated. The following gaps exist:&lt;br /&gt;
&lt;br /&gt;
# The join_team_requests_controller has action privilege checks scattered across methods instead of using a centralized `action_allowed?` method&lt;br /&gt;
# There is no frontend UI for viewing advertisements on the Signup Sheet page&lt;br /&gt;
# There is no UI for creating join team requests when responding to advertisements&lt;br /&gt;
# The &amp;quot;Received Requests&amp;quot; section is missing from the StudentTeamView page&lt;br /&gt;
# There is no method to accept join team requests (only decline exists)&lt;br /&gt;
# The controller needs better integration with the frontend&lt;br /&gt;
&lt;br /&gt;
=== Goals ===&lt;br /&gt;
&lt;br /&gt;
# Refactor the join_team_requests_controller to use a single `action_allowed?` method for privilege checking&lt;br /&gt;
# Add a trumpet icon on the Signup Sheet page to indicate when advertisements exist for topics&lt;br /&gt;
# Create a frontend UI for viewing advertisements and creating join team requests&lt;br /&gt;
# Add a &amp;quot;Received Requests&amp;quot; section to StudentTeamView.tsx for managing incoming join team requests&lt;br /&gt;
# Implement an accept method for join team requests&lt;br /&gt;
# Ensure proper validation that teams cannot accept requests when full&lt;br /&gt;
# Conduct comprehensive testing of the entire flow&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Controller Refactoring ====&lt;br /&gt;
&lt;br /&gt;
The `Api::V1::JoinTeamRequestsController` currently has privilege checks scattered across methods:&lt;br /&gt;
&lt;br /&gt;
- `index` method checks for administrator role directly&lt;br /&gt;
- `action_allowed?` only checks if user is a student&lt;br /&gt;
&lt;br /&gt;
'''Refactored Design:'''&lt;br /&gt;
&lt;br /&gt;
The `action_allowed?` method will be enhanced to handle different actions based on the action being performed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case action_name.to_sym&lt;br /&gt;
  when :index&lt;br /&gt;
    @current_user.administrator?&lt;br /&gt;
  when :create, :show, :update, :destroy, :decline, :accept&lt;br /&gt;
    @current_user.student?&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All methods will rely on this centralized privilege check, removing individual checks from methods like `index`.&lt;br /&gt;
&lt;br /&gt;
==== New Methods ====&lt;br /&gt;
&lt;br /&gt;
'''1. Accept Method'''&lt;br /&gt;
&lt;br /&gt;
Add a new `accept` method to handle accepting join team requests:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/:id/accept&lt;br /&gt;
# Accepts a join team request and adds the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  &lt;br /&gt;
  # Check if team is full&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Check if participant is already on the team&lt;br /&gt;
  if team.participants.include?(@join_team_request.participant)&lt;br /&gt;
    return render json: { error: 'Participant already belongs to the team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  ActiveRecord::Base.transaction do&lt;br /&gt;
    # Add participant to team&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Update request status&lt;br /&gt;
      @join_team_request.update!(reply_status: ACCEPTED)&lt;br /&gt;
      render json: { message: 'JoinTeamRequest accepted successfully', join_team_request: @join_team_request }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
      raise ActiveRecord::Rollback&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
rescue =&amp;gt; e&lt;br /&gt;
  render json: { error: &amp;quot;Failed to accept request: #{e.message}&amp;quot; }, status: :unprocessable_entity&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''2. Get Join Team Requests for Team'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all join team requests for a specific team (for the &amp;quot;Received Requests&amp;quot; section):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/team/:team_id&lt;br /&gt;
# Gets all join team requests for a specific team&lt;br /&gt;
def team_requests&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  &lt;br /&gt;
  # Verify current user is a member of the team&lt;br /&gt;
  unless team.participants.exists?(user_id: @current_user.id)&lt;br /&gt;
    return render json: { error: 'Unauthorized' }, status: :unauthorized&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(team_id: params[:team_id])&lt;br /&gt;
    .includes(:participant =&amp;gt; :user)&lt;br /&gt;
    .order(created_at: :desc)&lt;br /&gt;
  &lt;br /&gt;
  render json: join_team_requests, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''3. Get Advertisements for Assignment'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all advertisements for an assignment (for the Signup Sheet page):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/signed_up_teams/advertisements/:assignment_id&lt;br /&gt;
# Gets all advertisements for an assignment&lt;br /&gt;
def advertisements&lt;br /&gt;
  assignment = Assignment.find(params[:assignment_id])&lt;br /&gt;
  signed_up_teams = SignedUpTeam.joins(:team, :sign_up_topic)&lt;br /&gt;
    .where(sign_up_topics: { assignment_id: params[:assignment_id] })&lt;br /&gt;
    .where(advertise_for_partner: true)&lt;br /&gt;
    .includes(:team, :sign_up_topic)&lt;br /&gt;
  &lt;br /&gt;
  advertisements = signed_up_teams.map do |sut|&lt;br /&gt;
    {&lt;br /&gt;
      id: sut.id,&lt;br /&gt;
      team_id: sut.team_id,&lt;br /&gt;
      team_name: sut.team.name,&lt;br /&gt;
      topic_id: sut.sign_up_topic_id,&lt;br /&gt;
      topic_name: sut.sign_up_topic.topic_name,&lt;br /&gt;
      comments_for_advertisement: sut.comments_for_advertisement,&lt;br /&gt;
      created_at: sut.created_at&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  render json: advertisements, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Model Updates ====&lt;br /&gt;
&lt;br /&gt;
The `JoinTeamRequest` model needs to establish proper associations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
  &lt;br /&gt;
  scope :pending, -&amp;gt; { where(reply_status: 'PENDING') }&lt;br /&gt;
  scope :for_team, -&amp;gt;(team_id) { where(team_id: team_id) }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Routes Updates ====&lt;br /&gt;
&lt;br /&gt;
Add new routes for the additional endpoints:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    get 'team/:team_id', to: 'join_team_requests#team_requests'&lt;br /&gt;
    post 'decline/:id', to: 'join_team_requests#decline'&lt;br /&gt;
    post 'accept/:id', to: 'join_team_requests#accept'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
resources :signed_up_teams do&lt;br /&gt;
collection do&lt;br /&gt;
get 'advertisements/:assignment_id', to: 'signed_up_teams#advertisements'&lt;br /&gt;
end&lt;br /&gt;
member do&lt;br /&gt;
post :create_advertisement&lt;br /&gt;
patch :update_advertisement&lt;br /&gt;
delete :remove_advertisement&lt;br /&gt;
end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Frontend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Signup Sheet Page Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Create a new component or enhance existing signup sheet to display advertisements:&lt;br /&gt;
&lt;br /&gt;
'''Component: SignupSheet.tsx'''&lt;br /&gt;
&lt;br /&gt;
This component will:&lt;br /&gt;
&lt;br /&gt;
- Display all topics for an assignment&lt;br /&gt;
- Show a trumpet icon (🔔) next to topics that have advertisements&lt;br /&gt;
- Allow users to click on topics with advertisements to view details&lt;br /&gt;
- Provide a modal/dialog to view advertisement details and create a join team request&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface Advertisement {&lt;br /&gt;
  id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  team_name: string;&lt;br /&gt;
  topic_id: number;&lt;br /&gt;
  topic_name: string;&lt;br /&gt;
  comments_for_advertisement: string;&lt;br /&gt;
  created_at: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
interface SignUpTopic {&lt;br /&gt;
id: number;&lt;br /&gt;
topic_name: string;&lt;br /&gt;
topic_identifier: string;&lt;br /&gt;
max_choosers: number;&lt;br /&gt;
description?: string;&lt;br /&gt;
has_advertisement?: boolean;&lt;br /&gt;
advertisement?: Advertisement;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI will show:&lt;br /&gt;
&lt;br /&gt;
- A table/list of topics&lt;br /&gt;
- Trumpet icon indicator for topics with advertisements&lt;br /&gt;
- Click handler to open advertisement details modal&lt;br /&gt;
&lt;br /&gt;
==== Join Team Request Modal ====&lt;br /&gt;
&lt;br /&gt;
Create a modal component for creating join team requests:&lt;br /&gt;
&lt;br /&gt;
'''Component: JoinTeamRequestModal.tsx'''&lt;br /&gt;
&lt;br /&gt;
This modal will:&lt;br /&gt;
&lt;br /&gt;
- Display advertisement details (team name, topic, desired qualifications)&lt;br /&gt;
- Provide a text area for comments (optional)&lt;br /&gt;
- Show a &amp;quot;Send Request&amp;quot; button&lt;br /&gt;
- Handle form submission and API calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequestModalProps {&lt;br /&gt;
  show: boolean;&lt;br /&gt;
  onHide: () =&amp;gt; void;&lt;br /&gt;
  advertisement: Advertisement;&lt;br /&gt;
  assignmentId: number;&lt;br /&gt;
  onSuccess: () =&amp;gt; void;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== StudentTeamView Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Add a &amp;quot;Received Requests&amp;quot; section to `StudentTeamView.tsx`:&lt;br /&gt;
&lt;br /&gt;
'''Section: Received Join Team Requests'''&lt;br /&gt;
&lt;br /&gt;
This section will:&lt;br /&gt;
&lt;br /&gt;
- Display all pending join team requests for the current team&lt;br /&gt;
- Show request details: requester name, email, comments, date&lt;br /&gt;
- Provide &amp;quot;Approve&amp;quot; and &amp;quot;Decline&amp;quot; buttons for each request&lt;br /&gt;
- Disable approve button if team is full&lt;br /&gt;
- Show status for non-pending requests&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequest {&lt;br /&gt;
  id: number;&lt;br /&gt;
  participant_id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  comments: string;&lt;br /&gt;
  reply_status: 'PENDING' | 'ACCEPTED' | 'DECLINED';&lt;br /&gt;
  created_at: string;&lt;br /&gt;
  participant: {&lt;br /&gt;
    id: number;&lt;br /&gt;
    user: {&lt;br /&gt;
      id: number;&lt;br /&gt;
      name: string;&lt;br /&gt;
      full_name: string;&lt;br /&gt;
      email: string;&lt;br /&gt;
    };&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI structure will be similar to the existing &amp;quot;Received Invitations&amp;quot; section, with:&lt;br /&gt;
&lt;br /&gt;
- Table displaying request information&lt;br /&gt;
- Action buttons (Approve/Decline) for pending requests&lt;br /&gt;
- Status display for processed requests&lt;br /&gt;
&lt;br /&gt;
==== API Hooks ====&lt;br /&gt;
&lt;br /&gt;
Create or extend hooks for join team requests:&lt;br /&gt;
&lt;br /&gt;
'''File: hooks/useJoinTeamRequest.ts'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
export const useJoinTeamRequest = () =&amp;gt; {&lt;br /&gt;
  const createRequestAPI = useAPI();&lt;br /&gt;
  const acceptRequestAPI = useAPI();&lt;br /&gt;
  const declineRequestAPI = useAPI();&lt;br /&gt;
  const fetchTeamRequestsAPI = useAPI();&lt;br /&gt;
  const fetchAdvertisementsAPI = useAPI();&lt;br /&gt;
&lt;br /&gt;
const createJoinTeamRequest = (teamId: number, assignmentId: number, comments?: string) =&amp;gt;&lt;br /&gt;
createRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests`,&lt;br /&gt;
method: 'POST',&lt;br /&gt;
data: { team_id: teamId, assignment_id: assignmentId, comments }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const acceptJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
acceptRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/accept/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const declineJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
declineRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/decline/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchTeamRequests = (teamId: number) =&amp;gt;&lt;br /&gt;
fetchTeamRequestsAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/team/${teamId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchAdvertisements = (assignmentId: number) =&amp;gt;&lt;br /&gt;
fetchAdvertisementsAPI.sendRequest({&lt;br /&gt;
url: `/signed_up_teams/advertisements/${assignmentId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
createJoinTeamRequest,&lt;br /&gt;
acceptJoinTeamRequest,&lt;br /&gt;
declineJoinTeamRequest,&lt;br /&gt;
fetchTeamRequests,&lt;br /&gt;
fetchAdvertisements,&lt;br /&gt;
createRequestAPI,&lt;br /&gt;
acceptRequestAPI,&lt;br /&gt;
declineRequestAPI,&lt;br /&gt;
fetchTeamRequestsAPI,&lt;br /&gt;
fetchAdvertisementsAPI&lt;br /&gt;
};&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Implementation Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Phase 1: Backend Refactoring ===&lt;br /&gt;
&lt;br /&gt;
# '''Refactor action_allowed? method'''&lt;br /&gt;
## Update `Api::V1::JoinTeamRequestsController#action_allowed?` to handle all actions&lt;br /&gt;
## Remove individual privilege checks from `index` method&lt;br /&gt;
## Ensure all actions go through the centralized check&lt;br /&gt;
# '''Add accept method'''&lt;br /&gt;
## Implement `accept` method with team full validation&lt;br /&gt;
## Add transaction handling for atomic operations&lt;br /&gt;
## Update request status to ACCEPTED&lt;br /&gt;
## Add participant to team using `team.add_member`&lt;br /&gt;
# '''Add team_requests method'''&lt;br /&gt;
## Implement method to fetch requests for a team&lt;br /&gt;
## Add authorization check (user must be team member)&lt;br /&gt;
## Include participant and user data in response&lt;br /&gt;
# '''Add advertisements method to SignedUpTeamsController'''&lt;br /&gt;
## Implement method to fetch all advertisements for an assignment&lt;br /&gt;
## Join with teams and sign_up_topics&lt;br /&gt;
## Return formatted advertisement data&lt;br /&gt;
# '''Update JoinTeamRequest model'''&lt;br /&gt;
## Add `belongs_to :team` association&lt;br /&gt;
## Add scopes for filtering&lt;br /&gt;
## Update validations&lt;br /&gt;
# '''Update routes'''&lt;br /&gt;
## Add routes for new endpoints&lt;br /&gt;
## Ensure RESTful conventions&lt;br /&gt;
&lt;br /&gt;
=== Phase 2: Frontend - Signup Sheet ===&lt;br /&gt;
&lt;br /&gt;
# '''Create SignupSheet component (if not exists)'''&lt;br /&gt;
&lt;br /&gt;
## Display topics for an assignment&lt;br /&gt;
&lt;br /&gt;
## Fetch advertisements for the assignment&lt;br /&gt;
&lt;br /&gt;
## Map advertisements to topics&lt;br /&gt;
&lt;br /&gt;
## Display trumpet icon for topics with advertisements&lt;br /&gt;
&lt;br /&gt;
# '''Create JoinTeamRequestModal component'''&lt;br /&gt;
&lt;br /&gt;
## Design modal UI&lt;br /&gt;
&lt;br /&gt;
## Implement form with comments field&lt;br /&gt;
&lt;br /&gt;
## Add submit handler&lt;br /&gt;
&lt;br /&gt;
## Handle success/error states&lt;br /&gt;
&lt;br /&gt;
# '''Integrate modal with SignupSheet'''&lt;br /&gt;
&lt;br /&gt;
## Add click handler for topics with advertisements&lt;br /&gt;
&lt;br /&gt;
## Open modal with advertisement details&lt;br /&gt;
&lt;br /&gt;
## Handle modal close&lt;br /&gt;
&lt;br /&gt;
=== Phase 3: Frontend - StudentTeamView ===&lt;br /&gt;
&lt;br /&gt;
# '''Add Received Requests section'''&lt;br /&gt;
&lt;br /&gt;
## Create section similar to &amp;quot;Received Invitations&amp;quot;&lt;br /&gt;
&lt;br /&gt;
## Fetch join team requests for current team&lt;br /&gt;
&lt;br /&gt;
## Display requests in table format&lt;br /&gt;
&lt;br /&gt;
# '''Implement approve/decline functionality'''&lt;br /&gt;
&lt;br /&gt;
## Add approve button handler&lt;br /&gt;
&lt;br /&gt;
## Add decline button handler&lt;br /&gt;
&lt;br /&gt;
## Check team full status before allowing approve&lt;br /&gt;
&lt;br /&gt;
## Update UI after actions&lt;br /&gt;
&lt;br /&gt;
# '''Add useJoinTeamRequest hook'''&lt;br /&gt;
&lt;br /&gt;
## Create hook file&lt;br /&gt;
&lt;br /&gt;
## Implement all API methods&lt;br /&gt;
&lt;br /&gt;
## Export hook for use in components&lt;br /&gt;
&lt;br /&gt;
=== Phase 4: Integration and Testing ===&lt;br /&gt;
&lt;br /&gt;
# '''End-to-end testing'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisement creation → request creation → approval flow&lt;br /&gt;
&lt;br /&gt;
## Test decline flow&lt;br /&gt;
&lt;br /&gt;
## Test team full validation&lt;br /&gt;
&lt;br /&gt;
## Test authorization checks&lt;br /&gt;
&lt;br /&gt;
# '''Unit testing'''&lt;br /&gt;
&lt;br /&gt;
## Test controller methods&lt;br /&gt;
&lt;br /&gt;
## Test model validations&lt;br /&gt;
&lt;br /&gt;
## Test frontend components&lt;br /&gt;
&lt;br /&gt;
# '''Integration testing'''&lt;br /&gt;
&lt;br /&gt;
## Test API endpoints&lt;br /&gt;
&lt;br /&gt;
## Test frontend-backend integration&lt;br /&gt;
&lt;br /&gt;
## Test error handling&lt;br /&gt;
&lt;br /&gt;
== Files Changed/Added ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `app/controllers/api/v1/join_team_requests_controller.rb` - Refactor action_allowed?, add accept and team_requests methods&lt;br /&gt;
- `app/controllers/api/v1/signed_up_teams_controller.rb` - Add advertisements method&lt;br /&gt;
- `app/models/join_team_request.rb` - Add associations and scopes&lt;br /&gt;
- `config/routes.rb` - Add new routes&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `spec/requests/api/v1/join_team_requests_spec.rb` - Request specs for new methods&lt;br /&gt;
- `spec/models/join_team_request_spec.rb` - Model specs&lt;br /&gt;
&lt;br /&gt;
=== Frontend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/Student Teams/StudentTeamView.tsx` - Add Received Requests section&lt;br /&gt;
- `src/hooks/useStudentTeam.ts` - Add join team request methods (or create new hook)&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.tsx` - Signup sheet component with advertisement indicators&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.tsx` - Modal for creating join team requests&lt;br /&gt;
- `src/hooks/useJoinTeamRequest.ts` - Hook for join team request API calls&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.module.css` - Styles for signup sheet&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.module.css` - Styles for modal&lt;br /&gt;
&lt;br /&gt;
== Testing Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Unit Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequestsController Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test action_allowed? for different actions and user roles&lt;br /&gt;
&lt;br /&gt;
## Test accept method with valid request&lt;br /&gt;
&lt;br /&gt;
## Test accept method when team is full&lt;br /&gt;
&lt;br /&gt;
## Test accept method when participant already on team&lt;br /&gt;
&lt;br /&gt;
## Test team_requests method authorization&lt;br /&gt;
&lt;br /&gt;
## Test team_requests method returns correct data&lt;br /&gt;
&lt;br /&gt;
# '''SignedUpTeamsController Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisements method returns correct data&lt;br /&gt;
&lt;br /&gt;
## Test advertisements method filters correctly&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequest Model Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test associations&lt;br /&gt;
&lt;br /&gt;
## Test validations&lt;br /&gt;
&lt;br /&gt;
## Test scopes&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''End-to-End Flow Tests'''&lt;br /&gt;
&lt;br /&gt;
## Create advertisement → Create join request → Accept request&lt;br /&gt;
&lt;br /&gt;
## Create advertisement → Create join request → Decline request&lt;br /&gt;
&lt;br /&gt;
## Test team full validation prevents acceptance&lt;br /&gt;
&lt;br /&gt;
## Test authorization prevents unauthorized access&lt;br /&gt;
&lt;br /&gt;
=== Frontend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Component Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''SignUpSheet Component'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisement indicators display correctly&lt;br /&gt;
&lt;br /&gt;
## Test modal opens on click&lt;br /&gt;
&lt;br /&gt;
## Test data fetching&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequestModal Component'''&lt;br /&gt;
&lt;br /&gt;
## Test form submission&lt;br /&gt;
&lt;br /&gt;
## Test validation&lt;br /&gt;
&lt;br /&gt;
## Test success/error handling&lt;br /&gt;
&lt;br /&gt;
# '''StudentTeamView Component'''&lt;br /&gt;
&lt;br /&gt;
## Test Received Requests section displays&lt;br /&gt;
&lt;br /&gt;
## Test approve functionality&lt;br /&gt;
&lt;br /&gt;
## Test decline functionality&lt;br /&gt;
&lt;br /&gt;
## Test team full validation&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''API Integration'''&lt;br /&gt;
&lt;br /&gt;
## Test all API calls work correctly&lt;br /&gt;
&lt;br /&gt;
## Test error handling&lt;br /&gt;
&lt;br /&gt;
## Test loading states&lt;br /&gt;
&lt;br /&gt;
== Edge Cases and Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Team Full Validation'''&lt;br /&gt;
&lt;br /&gt;
- Check team capacity before accepting request&lt;br /&gt;
- Return appropriate error message&lt;br /&gt;
- Prevent race conditions with database transactions&lt;br /&gt;
&lt;br /&gt;
# '''Authorization'''&lt;br /&gt;
&lt;br /&gt;
- Verify user is team member before showing requests&lt;br /&gt;
- Verify user is student before creating requests&lt;br /&gt;
- Verify user is administrator before viewing all requests&lt;br /&gt;
&lt;br /&gt;
# '''Duplicate Requests'''&lt;br /&gt;
&lt;br /&gt;
- Prevent creating duplicate requests from same participant&lt;br /&gt;
- Handle case where participant already on team&lt;br /&gt;
&lt;br /&gt;
# '''Concurrent Modifications'''&lt;br /&gt;
&lt;br /&gt;
- Use transactions for atomic operations&lt;br /&gt;
- Handle race conditions in team membership updates&lt;br /&gt;
&lt;br /&gt;
=== Frontend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Network Errors'''&lt;br /&gt;
&lt;br /&gt;
- Display appropriate error messages&lt;br /&gt;
- Handle timeout scenarios&lt;br /&gt;
- Provide retry mechanisms&lt;br /&gt;
&lt;br /&gt;
# '''Empty States'''&lt;br /&gt;
&lt;br /&gt;
- Show appropriate messages when no advertisements exist&lt;br /&gt;
- Show appropriate messages when no requests exist&lt;br /&gt;
&lt;br /&gt;
# '''Loading States'''&lt;br /&gt;
&lt;br /&gt;
- Display loading indicators during API calls&lt;br /&gt;
- Prevent multiple simultaneous requests&lt;br /&gt;
&lt;br /&gt;
# '''Form Validation'''&lt;br /&gt;
&lt;br /&gt;
- Validate required fields&lt;br /&gt;
- Provide user feedback for errors&lt;br /&gt;
&lt;br /&gt;
== Security Considerations ==&lt;br /&gt;
&lt;br /&gt;
# '''Authorization Checks'''&lt;br /&gt;
&lt;br /&gt;
- All endpoints must verify user permissions&lt;br /&gt;
- Team members can only see requests for their team&lt;br /&gt;
- Students can only create requests for themselves&lt;br /&gt;
&lt;br /&gt;
# '''Input Validation'''&lt;br /&gt;
&lt;br /&gt;
- Sanitize all user inputs&lt;br /&gt;
- Validate team_id and assignment_id parameters&lt;br /&gt;
- Prevent SQL injection through parameterized queries&lt;br /&gt;
&lt;br /&gt;
# '''Rate Limiting'''&lt;br /&gt;
&lt;br /&gt;
- Consider rate limiting for request creation&lt;br /&gt;
- Prevent spam requests&lt;br /&gt;
&lt;br /&gt;
== Future Enhancements ==&lt;br /&gt;
&lt;br /&gt;
# Email notifications when join team requests are created&lt;br /&gt;
&lt;br /&gt;
# Email notifications when requests are approved/declined&lt;br /&gt;
&lt;br /&gt;
# Bulk approve/decline functionality&lt;br /&gt;
&lt;br /&gt;
# Request history and audit trail&lt;br /&gt;
&lt;br /&gt;
# Advanced filtering and search for requests&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
- Expertiza Wiki: E2252. Refactor auth controller.rb &amp;amp; password retrieval controller.rb&lt;br /&gt;
- Rails API Documentation&lt;br /&gt;
- React Router Documentation&lt;br /&gt;
- Expertiza Codebase: Existing invitation and team management implementations&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167100</id>
		<title>CSC/ECE 517 Fall 2025 - E2565. Integration of JoinTeamRequests</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2565._Integration_of_JoinTeamRequests&amp;diff=167100"/>
		<updated>2025-11-11T02:19:21Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2565. Integration of JoinTeamRequests =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
This document describes the design and implementation plan for integrating JoinTeamRequests functionality in Expertiza. The feature enables students to respond to teammate advertisements by creating join team requests, which topic holders can then approve or decline. This integration will complete the teammate advertisement workflow by connecting the advertisement creation (already implemented) with the join team request flow.&lt;br /&gt;
&lt;br /&gt;
=== Problem Statement ===&lt;br /&gt;
&lt;br /&gt;
Currently, Expertiza supports teammate advertisements for assignments with topics. Topic holders can create advertisements on the &amp;quot;Your Team&amp;quot; page. However, the complete flow for responding to advertisements and managing join team requests is not fully integrated. The following gaps exist:&lt;br /&gt;
&lt;br /&gt;
# The join_team_requests_controller has action privilege checks scattered across methods instead of using a centralized `action_allowed?` method&lt;br /&gt;
# There is no frontend UI for viewing advertisements on the Signup Sheet page&lt;br /&gt;
# There is no UI for creating join team requests when responding to advertisements&lt;br /&gt;
# The &amp;quot;Received Requests&amp;quot; section is missing from the StudentTeamView page&lt;br /&gt;
# There is no method to accept join team requests (only decline exists)&lt;br /&gt;
# The controller needs better integration with the frontend&lt;br /&gt;
&lt;br /&gt;
=== Goals ===&lt;br /&gt;
&lt;br /&gt;
# Refactor the join_team_requests_controller to use a single `action_allowed?` method for privilege checking&lt;br /&gt;
# Add a trumpet icon on the Signup Sheet page to indicate when advertisements exist for topics&lt;br /&gt;
# Create a frontend UI for viewing advertisements and creating join team requests&lt;br /&gt;
# Add a &amp;quot;Received Requests&amp;quot; section to StudentTeamView.tsx for managing incoming join team requests&lt;br /&gt;
# Implement an accept method for join team requests&lt;br /&gt;
# Ensure proper validation that teams cannot accept requests when full&lt;br /&gt;
# Conduct comprehensive testing of the entire flow&lt;br /&gt;
&lt;br /&gt;
== Design ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Controller Refactoring ====&lt;br /&gt;
&lt;br /&gt;
The `Api::V1::JoinTeamRequestsController` currently has privilege checks scattered across methods:&lt;br /&gt;
&lt;br /&gt;
- `index` method checks for administrator role directly&lt;br /&gt;
- `action_allowed?` only checks if user is a student&lt;br /&gt;
&lt;br /&gt;
'''Refactored Design:'''&lt;br /&gt;
&lt;br /&gt;
The `action_allowed?` method will be enhanced to handle different actions based on the action being performed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
def action_allowed?&lt;br /&gt;
  case action_name.to_sym&lt;br /&gt;
  when :index&lt;br /&gt;
    @current_user.administrator?&lt;br /&gt;
  when :create, :show, :update, :destroy, :decline, :accept&lt;br /&gt;
    @current_user.student?&lt;br /&gt;
  else&lt;br /&gt;
    false&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All methods will rely on this centralized privilege check, removing individual checks from methods like `index`.&lt;br /&gt;
&lt;br /&gt;
==== New Methods ====&lt;br /&gt;
&lt;br /&gt;
'''1. Accept Method'''&lt;br /&gt;
&lt;br /&gt;
Add a new `accept` method to handle accepting join team requests:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# PATCH/PUT api/v1/join_team_requests/:id/accept&lt;br /&gt;
# Accepts a join team request and adds the participant to the team&lt;br /&gt;
def accept&lt;br /&gt;
  team = @join_team_request.team&lt;br /&gt;
  &lt;br /&gt;
  # Check if team is full&lt;br /&gt;
  if team.full?&lt;br /&gt;
    return render json: { error: 'This team is full.' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  # Check if participant is already on the team&lt;br /&gt;
  if team.participants.include?(@join_team_request.participant)&lt;br /&gt;
    return render json: { error: 'Participant already belongs to the team' }, status: :unprocessable_entity&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  ActiveRecord::Base.transaction do&lt;br /&gt;
    # Add participant to team&lt;br /&gt;
    result = team.add_member(@join_team_request.participant)&lt;br /&gt;
    &lt;br /&gt;
    if result[:success]&lt;br /&gt;
      # Update request status&lt;br /&gt;
      @join_team_request.update!(reply_status: ACCEPTED)&lt;br /&gt;
      render json: { message: 'JoinTeamRequest accepted successfully', join_team_request: @join_team_request }, status: :ok&lt;br /&gt;
    else&lt;br /&gt;
      render json: { error: result[:error] }, status: :unprocessable_entity&lt;br /&gt;
      raise ActiveRecord::Rollback&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
rescue =&amp;gt; e&lt;br /&gt;
  render json: { error: &amp;quot;Failed to accept request: #{e.message}&amp;quot; }, status: :unprocessable_entity&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''2. Get Join Team Requests for Team'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all join team requests for a specific team (for the &amp;quot;Received Requests&amp;quot; section):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/join_team_requests/team/:team_id&lt;br /&gt;
# Gets all join team requests for a specific team&lt;br /&gt;
def team_requests&lt;br /&gt;
  team = Team.find(params[:team_id])&lt;br /&gt;
  &lt;br /&gt;
  # Verify current user is a member of the team&lt;br /&gt;
  unless team.participants.exists?(user_id: @current_user.id)&lt;br /&gt;
    return render json: { error: 'Unauthorized' }, status: :unauthorized&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  join_team_requests = JoinTeamRequest.where(team_id: params[:team_id])&lt;br /&gt;
    .includes(:participant =&amp;gt; :user)&lt;br /&gt;
    .order(created_at: :desc)&lt;br /&gt;
  &lt;br /&gt;
  render json: join_team_requests, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''3. Get Advertisements for Assignment'''&lt;br /&gt;
&lt;br /&gt;
Add a method to fetch all advertisements for an assignment (for the Signup Sheet page):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
# GET api/v1/signed_up_teams/advertisements/:assignment_id&lt;br /&gt;
# Gets all advertisements for an assignment&lt;br /&gt;
def advertisements&lt;br /&gt;
  assignment = Assignment.find(params[:assignment_id])&lt;br /&gt;
  signed_up_teams = SignedUpTeam.joins(:team, :sign_up_topic)&lt;br /&gt;
    .where(sign_up_topics: { assignment_id: params[:assignment_id] })&lt;br /&gt;
    .where(advertise_for_partner: true)&lt;br /&gt;
    .includes(:team, :sign_up_topic)&lt;br /&gt;
  &lt;br /&gt;
  advertisements = signed_up_teams.map do |sut|&lt;br /&gt;
    {&lt;br /&gt;
      id: sut.id,&lt;br /&gt;
      team_id: sut.team_id,&lt;br /&gt;
      team_name: sut.team.name,&lt;br /&gt;
      topic_id: sut.sign_up_topic_id,&lt;br /&gt;
      topic_name: sut.sign_up_topic.topic_name,&lt;br /&gt;
      comments_for_advertisement: sut.comments_for_advertisement,&lt;br /&gt;
      created_at: sut.created_at&lt;br /&gt;
    }&lt;br /&gt;
  end&lt;br /&gt;
  &lt;br /&gt;
  render json: advertisements, status: :ok&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Model Updates ====&lt;br /&gt;
&lt;br /&gt;
The `JoinTeamRequest` model needs to establish proper associations:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
class JoinTeamRequest &amp;lt; ApplicationRecord&lt;br /&gt;
  belongs_to :team&lt;br /&gt;
  belongs_to :participant&lt;br /&gt;
  &lt;br /&gt;
  ACCEPTED_STATUSES = %w[ACCEPTED DECLINED PENDING]&lt;br /&gt;
  validates :reply_status, inclusion: { in: ACCEPTED_STATUSES }&lt;br /&gt;
  &lt;br /&gt;
  scope :pending, -&amp;gt; { where(reply_status: 'PENDING') }&lt;br /&gt;
  scope :for_team, -&amp;gt;(team_id) { where(team_id: team_id) }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Routes Updates ====&lt;br /&gt;
&lt;br /&gt;
Add new routes for the additional endpoints:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;ruby&amp;quot;&amp;gt;&lt;br /&gt;
resources :join_team_requests do&lt;br /&gt;
  collection do&lt;br /&gt;
    get 'team/:team_id', to: 'join_team_requests#team_requests'&lt;br /&gt;
    post 'decline/:id', to: 'join_team_requests#decline'&lt;br /&gt;
    post 'accept/:id', to: 'join_team_requests#accept'&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
resources :signed_up_teams do&lt;br /&gt;
collection do&lt;br /&gt;
get 'advertisements/:assignment_id', to: 'signed_up_teams#advertisements'&lt;br /&gt;
end&lt;br /&gt;
member do&lt;br /&gt;
post :create_advertisement&lt;br /&gt;
patch :update_advertisement&lt;br /&gt;
delete :remove_advertisement&lt;br /&gt;
end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Frontend Design ===&lt;br /&gt;
&lt;br /&gt;
==== Signup Sheet Page Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Create a new component or enhance existing signup sheet to display advertisements:&lt;br /&gt;
&lt;br /&gt;
'''Component: SignupSheet.tsx'''&lt;br /&gt;
&lt;br /&gt;
This component will:&lt;br /&gt;
&lt;br /&gt;
- Display all topics for an assignment&lt;br /&gt;
- Show a trumpet icon (🔔) next to topics that have advertisements&lt;br /&gt;
- Allow users to click on topics with advertisements to view details&lt;br /&gt;
- Provide a modal/dialog to view advertisement details and create a join team request&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface Advertisement {&lt;br /&gt;
  id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  team_name: string;&lt;br /&gt;
  topic_id: number;&lt;br /&gt;
  topic_name: string;&lt;br /&gt;
  comments_for_advertisement: string;&lt;br /&gt;
  created_at: string;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
interface SignUpTopic {&lt;br /&gt;
id: number;&lt;br /&gt;
topic_name: string;&lt;br /&gt;
topic_identifier: string;&lt;br /&gt;
max_choosers: number;&lt;br /&gt;
description?: string;&lt;br /&gt;
has_advertisement?: boolean;&lt;br /&gt;
advertisement?: Advertisement;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI will show:&lt;br /&gt;
&lt;br /&gt;
- A table/list of topics&lt;br /&gt;
- Trumpet icon indicator for topics with advertisements&lt;br /&gt;
- Click handler to open advertisement details modal&lt;br /&gt;
&lt;br /&gt;
==== Join Team Request Modal ====&lt;br /&gt;
&lt;br /&gt;
Create a modal component for creating join team requests:&lt;br /&gt;
&lt;br /&gt;
'''Component: JoinTeamRequestModal.tsx'''&lt;br /&gt;
&lt;br /&gt;
This modal will:&lt;br /&gt;
&lt;br /&gt;
- Display advertisement details (team name, topic, desired qualifications)&lt;br /&gt;
- Provide a text area for comments (optional)&lt;br /&gt;
- Show a &amp;quot;Send Request&amp;quot; button&lt;br /&gt;
- Handle form submission and API calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequestModalProps {&lt;br /&gt;
  show: boolean;&lt;br /&gt;
  onHide: () =&amp;gt; void;&lt;br /&gt;
  advertisement: Advertisement;&lt;br /&gt;
  assignmentId: number;&lt;br /&gt;
  onSuccess: () =&amp;gt; void;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== StudentTeamView Enhancement ====&lt;br /&gt;
&lt;br /&gt;
Add a &amp;quot;Received Requests&amp;quot; section to `StudentTeamView.tsx`:&lt;br /&gt;
&lt;br /&gt;
'''Section: Received Join Team Requests'''&lt;br /&gt;
&lt;br /&gt;
This section will:&lt;br /&gt;
&lt;br /&gt;
- Display all pending join team requests for the current team&lt;br /&gt;
- Show request details: requester name, email, comments, date&lt;br /&gt;
- Provide &amp;quot;Approve&amp;quot; and &amp;quot;Decline&amp;quot; buttons for each request&lt;br /&gt;
- Disable approve button if team is full&lt;br /&gt;
- Show status for non-pending requests&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
interface JoinTeamRequest {&lt;br /&gt;
  id: number;&lt;br /&gt;
  participant_id: number;&lt;br /&gt;
  team_id: number;&lt;br /&gt;
  comments: string;&lt;br /&gt;
  reply_status: 'PENDING' | 'ACCEPTED' | 'DECLINED';&lt;br /&gt;
  created_at: string;&lt;br /&gt;
  participant: {&lt;br /&gt;
    id: number;&lt;br /&gt;
    user: {&lt;br /&gt;
      id: number;&lt;br /&gt;
      name: string;&lt;br /&gt;
      full_name: string;&lt;br /&gt;
      email: string;&lt;br /&gt;
    };&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The UI structure will be similar to the existing &amp;quot;Received Invitations&amp;quot; section, with:&lt;br /&gt;
&lt;br /&gt;
- Table displaying request information&lt;br /&gt;
- Action buttons (Approve/Decline) for pending requests&lt;br /&gt;
- Status display for processed requests&lt;br /&gt;
&lt;br /&gt;
==== API Hooks ====&lt;br /&gt;
&lt;br /&gt;
Create or extend hooks for join team requests:&lt;br /&gt;
&lt;br /&gt;
'''File: hooks/useJoinTeamRequest.ts'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;typescript&amp;quot;&amp;gt;&lt;br /&gt;
export const useJoinTeamRequest = () =&amp;gt; {&lt;br /&gt;
  const createRequestAPI = useAPI();&lt;br /&gt;
  const acceptRequestAPI = useAPI();&lt;br /&gt;
  const declineRequestAPI = useAPI();&lt;br /&gt;
  const fetchTeamRequestsAPI = useAPI();&lt;br /&gt;
  const fetchAdvertisementsAPI = useAPI();&lt;br /&gt;
&lt;br /&gt;
const createJoinTeamRequest = (teamId: number, assignmentId: number, comments?: string) =&amp;gt;&lt;br /&gt;
createRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests`,&lt;br /&gt;
method: 'POST',&lt;br /&gt;
data: { team_id: teamId, assignment_id: assignmentId, comments }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const acceptJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
acceptRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/accept/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const declineJoinTeamRequest = (requestId: number) =&amp;gt;&lt;br /&gt;
declineRequestAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/decline/${requestId}`,&lt;br /&gt;
method: 'POST'&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchTeamRequests = (teamId: number) =&amp;gt;&lt;br /&gt;
fetchTeamRequestsAPI.sendRequest({&lt;br /&gt;
url: `/join_team_requests/team/${teamId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
const fetchAdvertisements = (assignmentId: number) =&amp;gt;&lt;br /&gt;
fetchAdvertisementsAPI.sendRequest({&lt;br /&gt;
url: `/signed_up_teams/advertisements/${assignmentId}`&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
createJoinTeamRequest,&lt;br /&gt;
acceptJoinTeamRequest,&lt;br /&gt;
declineJoinTeamRequest,&lt;br /&gt;
fetchTeamRequests,&lt;br /&gt;
fetchAdvertisements,&lt;br /&gt;
createRequestAPI,&lt;br /&gt;
acceptRequestAPI,&lt;br /&gt;
declineRequestAPI,&lt;br /&gt;
fetchTeamRequestsAPI,&lt;br /&gt;
fetchAdvertisementsAPI&lt;br /&gt;
};&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Implementation Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Phase 1: Backend Refactoring ===&lt;br /&gt;
&lt;br /&gt;
# '''Refactor action_allowed? method'''&lt;br /&gt;
&lt;br /&gt;
## Update `Api::V1::JoinTeamRequestsController#action_allowed?` to handle all actions&lt;br /&gt;
&lt;br /&gt;
## Remove individual privilege checks from `index` method&lt;br /&gt;
&lt;br /&gt;
## Ensure all actions go through the centralized check&lt;br /&gt;
&lt;br /&gt;
# '''Add accept method'''&lt;br /&gt;
&lt;br /&gt;
## Implement `accept` method with team full validation&lt;br /&gt;
&lt;br /&gt;
## Add transaction handling for atomic operations&lt;br /&gt;
&lt;br /&gt;
## Update request status to ACCEPTED&lt;br /&gt;
&lt;br /&gt;
## Add participant to team using `team.add_member`&lt;br /&gt;
&lt;br /&gt;
# '''Add team_requests method'''&lt;br /&gt;
&lt;br /&gt;
## Implement method to fetch requests for a team&lt;br /&gt;
&lt;br /&gt;
## Add authorization check (user must be team member)&lt;br /&gt;
&lt;br /&gt;
## Include participant and user data in response&lt;br /&gt;
&lt;br /&gt;
# '''Add advertisements method to SignedUpTeamsController'''&lt;br /&gt;
&lt;br /&gt;
## Implement method to fetch all advertisements for an assignment&lt;br /&gt;
&lt;br /&gt;
## Join with teams and sign_up_topics&lt;br /&gt;
&lt;br /&gt;
## Return formatted advertisement data&lt;br /&gt;
&lt;br /&gt;
# '''Update JoinTeamRequest model'''&lt;br /&gt;
&lt;br /&gt;
## Add `belongs_to :team` association&lt;br /&gt;
&lt;br /&gt;
## Add scopes for filtering&lt;br /&gt;
&lt;br /&gt;
## Update validations&lt;br /&gt;
&lt;br /&gt;
# '''Update routes'''&lt;br /&gt;
&lt;br /&gt;
## Add routes for new endpoints&lt;br /&gt;
&lt;br /&gt;
## Ensure RESTful conventions&lt;br /&gt;
&lt;br /&gt;
=== Phase 2: Frontend - Signup Sheet ===&lt;br /&gt;
&lt;br /&gt;
# '''Create SignupSheet component (if not exists)'''&lt;br /&gt;
&lt;br /&gt;
## Display topics for an assignment&lt;br /&gt;
&lt;br /&gt;
## Fetch advertisements for the assignment&lt;br /&gt;
&lt;br /&gt;
## Map advertisements to topics&lt;br /&gt;
&lt;br /&gt;
## Display trumpet icon for topics with advertisements&lt;br /&gt;
&lt;br /&gt;
# '''Create JoinTeamRequestModal component'''&lt;br /&gt;
&lt;br /&gt;
## Design modal UI&lt;br /&gt;
&lt;br /&gt;
## Implement form with comments field&lt;br /&gt;
&lt;br /&gt;
## Add submit handler&lt;br /&gt;
&lt;br /&gt;
## Handle success/error states&lt;br /&gt;
&lt;br /&gt;
# '''Integrate modal with SignupSheet'''&lt;br /&gt;
&lt;br /&gt;
## Add click handler for topics with advertisements&lt;br /&gt;
&lt;br /&gt;
## Open modal with advertisement details&lt;br /&gt;
&lt;br /&gt;
## Handle modal close&lt;br /&gt;
&lt;br /&gt;
=== Phase 3: Frontend - StudentTeamView ===&lt;br /&gt;
&lt;br /&gt;
# '''Add Received Requests section'''&lt;br /&gt;
&lt;br /&gt;
## Create section similar to &amp;quot;Received Invitations&amp;quot;&lt;br /&gt;
&lt;br /&gt;
## Fetch join team requests for current team&lt;br /&gt;
&lt;br /&gt;
## Display requests in table format&lt;br /&gt;
&lt;br /&gt;
# '''Implement approve/decline functionality'''&lt;br /&gt;
&lt;br /&gt;
## Add approve button handler&lt;br /&gt;
&lt;br /&gt;
## Add decline button handler&lt;br /&gt;
&lt;br /&gt;
## Check team full status before allowing approve&lt;br /&gt;
&lt;br /&gt;
## Update UI after actions&lt;br /&gt;
&lt;br /&gt;
# '''Add useJoinTeamRequest hook'''&lt;br /&gt;
&lt;br /&gt;
## Create hook file&lt;br /&gt;
&lt;br /&gt;
## Implement all API methods&lt;br /&gt;
&lt;br /&gt;
## Export hook for use in components&lt;br /&gt;
&lt;br /&gt;
=== Phase 4: Integration and Testing ===&lt;br /&gt;
&lt;br /&gt;
# '''End-to-end testing'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisement creation → request creation → approval flow&lt;br /&gt;
&lt;br /&gt;
## Test decline flow&lt;br /&gt;
&lt;br /&gt;
## Test team full validation&lt;br /&gt;
&lt;br /&gt;
## Test authorization checks&lt;br /&gt;
&lt;br /&gt;
# '''Unit testing'''&lt;br /&gt;
&lt;br /&gt;
## Test controller methods&lt;br /&gt;
&lt;br /&gt;
## Test model validations&lt;br /&gt;
&lt;br /&gt;
## Test frontend components&lt;br /&gt;
&lt;br /&gt;
# '''Integration testing'''&lt;br /&gt;
&lt;br /&gt;
## Test API endpoints&lt;br /&gt;
&lt;br /&gt;
## Test frontend-backend integration&lt;br /&gt;
&lt;br /&gt;
## Test error handling&lt;br /&gt;
&lt;br /&gt;
== Files Changed/Added ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `app/controllers/api/v1/join_team_requests_controller.rb` - Refactor action_allowed?, add accept and team_requests methods&lt;br /&gt;
- `app/controllers/api/v1/signed_up_teams_controller.rb` - Add advertisements method&lt;br /&gt;
- `app/models/join_team_request.rb` - Add associations and scopes&lt;br /&gt;
- `config/routes.rb` - Add new routes&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `spec/requests/api/v1/join_team_requests_spec.rb` - Request specs for new methods&lt;br /&gt;
- `spec/models/join_team_request_spec.rb` - Model specs&lt;br /&gt;
&lt;br /&gt;
=== Frontend Files ===&lt;br /&gt;
&lt;br /&gt;
'''Modified:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/Student Teams/StudentTeamView.tsx` - Add Received Requests section&lt;br /&gt;
- `src/hooks/useStudentTeam.ts` - Add join team request methods (or create new hook)&lt;br /&gt;
&lt;br /&gt;
'''Added:'''&lt;br /&gt;
&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.tsx` - Signup sheet component with advertisement indicators&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.tsx` - Modal for creating join team requests&lt;br /&gt;
- `src/hooks/useJoinTeamRequest.ts` - Hook for join team request API calls&lt;br /&gt;
- `src/pages/SignUpSheet/SignUpSheet.module.css` - Styles for signup sheet&lt;br /&gt;
- `src/pages/SignUpSheet/JoinTeamRequestModal.module.css` - Styles for modal&lt;br /&gt;
&lt;br /&gt;
== Testing Plan ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Unit Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequestsController Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test action_allowed? for different actions and user roles&lt;br /&gt;
&lt;br /&gt;
## Test accept method with valid request&lt;br /&gt;
&lt;br /&gt;
## Test accept method when team is full&lt;br /&gt;
&lt;br /&gt;
## Test accept method when participant already on team&lt;br /&gt;
&lt;br /&gt;
## Test team_requests method authorization&lt;br /&gt;
&lt;br /&gt;
## Test team_requests method returns correct data&lt;br /&gt;
&lt;br /&gt;
# '''SignedUpTeamsController Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisements method returns correct data&lt;br /&gt;
&lt;br /&gt;
## Test advertisements method filters correctly&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequest Model Tests'''&lt;br /&gt;
&lt;br /&gt;
## Test associations&lt;br /&gt;
&lt;br /&gt;
## Test validations&lt;br /&gt;
&lt;br /&gt;
## Test scopes&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''End-to-End Flow Tests'''&lt;br /&gt;
&lt;br /&gt;
## Create advertisement → Create join request → Accept request&lt;br /&gt;
&lt;br /&gt;
## Create advertisement → Create join request → Decline request&lt;br /&gt;
&lt;br /&gt;
## Test team full validation prevents acceptance&lt;br /&gt;
&lt;br /&gt;
## Test authorization prevents unauthorized access&lt;br /&gt;
&lt;br /&gt;
=== Frontend Testing ===&lt;br /&gt;
&lt;br /&gt;
==== Component Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''SignUpSheet Component'''&lt;br /&gt;
&lt;br /&gt;
## Test advertisement indicators display correctly&lt;br /&gt;
&lt;br /&gt;
## Test modal opens on click&lt;br /&gt;
&lt;br /&gt;
## Test data fetching&lt;br /&gt;
&lt;br /&gt;
# '''JoinTeamRequestModal Component'''&lt;br /&gt;
&lt;br /&gt;
## Test form submission&lt;br /&gt;
&lt;br /&gt;
## Test validation&lt;br /&gt;
&lt;br /&gt;
## Test success/error handling&lt;br /&gt;
&lt;br /&gt;
# '''StudentTeamView Component'''&lt;br /&gt;
&lt;br /&gt;
## Test Received Requests section displays&lt;br /&gt;
&lt;br /&gt;
## Test approve functionality&lt;br /&gt;
&lt;br /&gt;
## Test decline functionality&lt;br /&gt;
&lt;br /&gt;
## Test team full validation&lt;br /&gt;
&lt;br /&gt;
==== Integration Tests ====&lt;br /&gt;
&lt;br /&gt;
# '''API Integration'''&lt;br /&gt;
&lt;br /&gt;
## Test all API calls work correctly&lt;br /&gt;
&lt;br /&gt;
## Test error handling&lt;br /&gt;
&lt;br /&gt;
## Test loading states&lt;br /&gt;
&lt;br /&gt;
== Edge Cases and Error Handling ==&lt;br /&gt;
&lt;br /&gt;
=== Backend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Team Full Validation'''&lt;br /&gt;
&lt;br /&gt;
- Check team capacity before accepting request&lt;br /&gt;
- Return appropriate error message&lt;br /&gt;
- Prevent race conditions with database transactions&lt;br /&gt;
&lt;br /&gt;
# '''Authorization'''&lt;br /&gt;
&lt;br /&gt;
- Verify user is team member before showing requests&lt;br /&gt;
- Verify user is student before creating requests&lt;br /&gt;
- Verify user is administrator before viewing all requests&lt;br /&gt;
&lt;br /&gt;
# '''Duplicate Requests'''&lt;br /&gt;
&lt;br /&gt;
- Prevent creating duplicate requests from same participant&lt;br /&gt;
- Handle case where participant already on team&lt;br /&gt;
&lt;br /&gt;
# '''Concurrent Modifications'''&lt;br /&gt;
&lt;br /&gt;
- Use transactions for atomic operations&lt;br /&gt;
- Handle race conditions in team membership updates&lt;br /&gt;
&lt;br /&gt;
=== Frontend Edge Cases ===&lt;br /&gt;
&lt;br /&gt;
# '''Network Errors'''&lt;br /&gt;
&lt;br /&gt;
- Display appropriate error messages&lt;br /&gt;
- Handle timeout scenarios&lt;br /&gt;
- Provide retry mechanisms&lt;br /&gt;
&lt;br /&gt;
# '''Empty States'''&lt;br /&gt;
&lt;br /&gt;
- Show appropriate messages when no advertisements exist&lt;br /&gt;
- Show appropriate messages when no requests exist&lt;br /&gt;
&lt;br /&gt;
# '''Loading States'''&lt;br /&gt;
&lt;br /&gt;
- Display loading indicators during API calls&lt;br /&gt;
- Prevent multiple simultaneous requests&lt;br /&gt;
&lt;br /&gt;
# '''Form Validation'''&lt;br /&gt;
&lt;br /&gt;
- Validate required fields&lt;br /&gt;
- Provide user feedback for errors&lt;br /&gt;
&lt;br /&gt;
== Security Considerations ==&lt;br /&gt;
&lt;br /&gt;
# '''Authorization Checks'''&lt;br /&gt;
&lt;br /&gt;
- All endpoints must verify user permissions&lt;br /&gt;
- Team members can only see requests for their team&lt;br /&gt;
- Students can only create requests for themselves&lt;br /&gt;
&lt;br /&gt;
# '''Input Validation'''&lt;br /&gt;
&lt;br /&gt;
- Sanitize all user inputs&lt;br /&gt;
- Validate team_id and assignment_id parameters&lt;br /&gt;
- Prevent SQL injection through parameterized queries&lt;br /&gt;
&lt;br /&gt;
# '''Rate Limiting'''&lt;br /&gt;
&lt;br /&gt;
- Consider rate limiting for request creation&lt;br /&gt;
- Prevent spam requests&lt;br /&gt;
&lt;br /&gt;
== Future Enhancements ==&lt;br /&gt;
&lt;br /&gt;
# Email notifications when join team requests are created&lt;br /&gt;
&lt;br /&gt;
# Email notifications when requests are approved/declined&lt;br /&gt;
&lt;br /&gt;
# Bulk approve/decline functionality&lt;br /&gt;
&lt;br /&gt;
# Request history and audit trail&lt;br /&gt;
&lt;br /&gt;
# Advanced filtering and search for requests&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
- Expertiza Wiki: E2252. Refactor auth controller.rb &amp;amp; password retrieval controller.rb&lt;br /&gt;
- Rails API Documentation&lt;br /&gt;
- React Router Documentation&lt;br /&gt;
- Expertiza Codebase: Existing invitation and team management implementations&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166883</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166883"/>
		<updated>2025-10-29T03:24:35Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah, Vatsalkumar Patel, Smit Raval '''&lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
[[File:Heatgrid_3.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_toggle.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_showallreviews.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Future Work(Suggested) ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166882</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166882"/>
		<updated>2025-10-29T03:23:52Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah, Vatsalkumar Patel, Smit Raval '''&lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
[[File:Heatgrid_3.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_toggle.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_showallreviews.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166881</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166881"/>
		<updated>2025-10-29T03:19:22Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah, Vatsalkumar Patel, Smit Raval '''&lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
[[File:Heatgrid_3.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_toggle.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_showallreviews.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_tooltip.png|thumb|center|500px|Tooltip shown when hovering over item weight label.]]&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_tooltip.png || Tooltip on item weight || Testing and Validation&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166880</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166880"/>
		<updated>2025-10-29T03:19:02Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah, Vatsalkumar Patel, Smit Raval '''&lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails (Api::V1::GradesController)  &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
[[File:Heatgrid_3.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_toggle.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_showallreviews.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_tooltip.png|thumb|center|500px|Tooltip shown when hovering over item weight label.]]&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_tooltip.png || Tooltip on item weight || Testing and Validation&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166879</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166879"/>
		<updated>2025-10-29T03:16:21Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah ''(and team, if applicable)''  &lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails (Api::V1::GradesController)  &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
[[File:Heatgrid_3.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_toggle.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_showallreviews.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_tooltip.png|thumb|center|500px|Tooltip shown when hovering over item weight label.]]&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_tooltip.png || Tooltip on item weight || Testing and Validation&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_3.png&amp;diff=166878</id>
		<title>File:Heatgrid 3.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_3.png&amp;diff=166878"/>
		<updated>2025-10-29T03:16:02Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_showallreviews.png&amp;diff=166875</id>
		<title>File:Heatgrid showallreviews.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_showallreviews.png&amp;diff=166875"/>
		<updated>2025-10-29T03:14:13Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_toggle.png&amp;diff=166874</id>
		<title>File:Heatgrid toggle.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_toggle.png&amp;diff=166874"/>
		<updated>2025-10-29T03:12:39Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166873</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166873"/>
		<updated>2025-10-29T03:11:20Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah ''(and team, if applicable)''  &lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails (Api::V1::GradesController)  &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;border: none; margin: 0 auto; text-align: center;&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Heatgrid_1.png|800px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
| [[File:Heatgrid_2.png|800px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* '''Framework:''' React + TypeScript (TSX components under 'src/')&lt;br /&gt;
* '''Styling:''' 'grades.scss' (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* '''Components:'''&lt;br /&gt;
** 'ReviewTable.tsx' — main heatgrid rendering component&lt;br /&gt;
** 'ReviewTableRow.tsx' — row logic and item expansion&lt;br /&gt;
** 'RoundSelector.tsx' — UI for selecting review rounds&lt;br /&gt;
** 'Statistics.tsx' — displays score summaries and analytics&lt;br /&gt;
* '''Data Flow:''' Previously used 'heatMapData.json' (dummy). Now retrieves real data from backend API via 'gradesService.ts'.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* '''Framework:''' Ruby on Rails ('Api::V1' namespace)&lt;br /&gt;
* '''Controller:''' 'grades_controller.rb'&lt;br /&gt;
* '''Purpose:''' Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* '''Data Source:''' Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_goals.png|thumb|center|750px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
** “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_itemlist.png|thumb|center|500px|“Show item prompts” toggle with item list displayed above table.]]&lt;br /&gt;
[[File:Heatgrid_expand_review.png|thumb|center|500px|Expanded review view using “+” button or “Show all reviews”.]]&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_tooltip.png|thumb|center|500px|Tooltip shown when hovering over item weight label.]]&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* 'app/controllers/api/v1/grades_controller.rb'&lt;br /&gt;
* 'frontend/src/pages/ViewTeamGrades/'&lt;br /&gt;
* 'frontend/src/services/gradesService.ts'&lt;br /&gt;
* 'frontend/src/styles/grades.scss'&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_tooltip.png || Tooltip on item weight || Testing and Validation&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_goals.png&amp;diff=166872</id>
		<title>File:Heatgrid goals.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_goals.png&amp;diff=166872"/>
		<updated>2025-10-29T03:10:51Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_2.png&amp;diff=166822</id>
		<title>File:Heatgrid 2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_2.png&amp;diff=166822"/>
		<updated>2025-10-29T00:02:03Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_1.png&amp;diff=166821</id>
		<title>File:Heatgrid 1.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=File:Heatgrid_1.png&amp;diff=166821"/>
		<updated>2025-10-29T00:00:37Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
	<entry>
		<id>https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166793</id>
		<title>CSC/ECE 517 Fall 2025 - E2553. Heatgrid and Grades Controller (Frontend + Backend)</title>
		<link rel="alternate" type="text/html" href="https://wiki.expertiza.ncsu.edu/index.php?title=CSC/ECE_517_Fall_2025_-_E2553._Heatgrid_and_Grades_Controller_(Frontend_%2B_Backend)&amp;diff=166793"/>
		<updated>2025-10-28T22:56:41Z</updated>

		<summary type="html">&lt;p&gt;Dshah24: Created page with &amp;quot;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =  '''Mentor:''' Vihar Manojkumar Shah   '''Contributors:''' Devyash Shah ''(and team, if applicable)''   '''Frontend Framework:''' React + TypeScript   '''Backend Framework:''' Ruby on Rails (Api::V1::GradesController)    == Overview == The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and in...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= E2553: Heatgrid and Grades Controller (Frontend + Backend) =&lt;br /&gt;
&lt;br /&gt;
'''Mentor:''' Vihar Manojkumar Shah  &lt;br /&gt;
'''Contributors:''' Devyash Shah ''(and team, if applicable)''  &lt;br /&gt;
'''Frontend Framework:''' React + TypeScript  &lt;br /&gt;
'''Backend Framework:''' Ruby on Rails (Api::V1::GradesController)  &lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The '''Heatgrid''' is a key component of the ''View Team Grades'' page in Expertiza. It visualizes a matrix of peer review scores for each rubric item, enabling students and instructors to understand feedback and performance across multiple review rounds.&lt;br /&gt;
&lt;br /&gt;
Originally, this view rendered dummy data and did not conform to the system’s visual design guidelines. The purpose of this project is to redesign the interface for improved usability and integrate it fully with the backend so that it displays real grading data dynamically.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_before.png|thumb|center|500px|Current Heatgrid View (Before Redesign)]]&lt;br /&gt;
&lt;br /&gt;
== Technical Background ==&lt;br /&gt;
&lt;br /&gt;
=== Frontend Implementation ===&lt;br /&gt;
* **Framework:** React + TypeScript (TSX components under `src/`)&lt;br /&gt;
* **Styling:** `grades.scss` (defines colors, table layout, and heat intensity mapping)&lt;br /&gt;
* **Components:**&lt;br /&gt;
** `ReviewTable.tsx` — main heatgrid rendering component&lt;br /&gt;
** `ReviewTableRow.tsx` — row logic and item expansion&lt;br /&gt;
** `RoundSelector.tsx` — UI for selecting review rounds&lt;br /&gt;
** `Statistics.tsx` — displays score summaries and analytics&lt;br /&gt;
* **Data Flow:** Previously used `heatMapData.json` (dummy). Now retrieves real data from backend API via `gradesService.ts`.&lt;br /&gt;
&lt;br /&gt;
=== Backend Implementation ===&lt;br /&gt;
* **Framework:** Ruby on Rails (`Api::V1` namespace)&lt;br /&gt;
* **Controller:** `grades_controller.rb`&lt;br /&gt;
* **Purpose:** Exposes JSON endpoints for fetching review and grade data&lt;br /&gt;
* **Data Source:** Database models for Assignments, Participants, Reviews, and Scores&lt;br /&gt;
&lt;br /&gt;
=== Example Endpoint ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
GET /api/v1/grades/:assignment_id/view_our_scores&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;Review-Round-1&amp;quot;: [&lt;br /&gt;
    [&lt;br /&gt;
      {&lt;br /&gt;
        &amp;quot;id&amp;quot;: 42,&lt;br /&gt;
        &amp;quot;item_id&amp;quot;: 7,&lt;br /&gt;
        &amp;quot;txt&amp;quot;: &amp;quot;Clarity of explanation&amp;quot;,&lt;br /&gt;
        &amp;quot;answer&amp;quot;: 4,&lt;br /&gt;
        &amp;quot;comments&amp;quot;: &amp;quot;Well-structured response&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewer_name&amp;quot;: &amp;quot;Alice Johnson&amp;quot;,&lt;br /&gt;
        &amp;quot;reviewee_name&amp;quot;: &amp;quot;Team Alpha&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    ]&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Project Goals ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Goal !! Description&lt;br /&gt;
|-&lt;br /&gt;
| UI Consistency || Ensure all fonts, buttons, and table elements match Expertiza’s design language.&lt;br /&gt;
|-&lt;br /&gt;
| Dynamic Data Integration || Replace dummy JSON with backend-driven API calls.&lt;br /&gt;
|-&lt;br /&gt;
| Readability Improvements || Adjust font size, reduce whitespace, and improve table density.&lt;br /&gt;
|-&lt;br /&gt;
| Interactivity || Add toggles, tooltips, and expandable reviews.&lt;br /&gt;
|-&lt;br /&gt;
| Compatibility || Ensure proper rendering across all 10 rubric item types.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_after.png|thumb|center|500px|Updated Heatgrid Rendering Real Data (After Integration)]]&lt;br /&gt;
&lt;br /&gt;
== Backend API Endpoints ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Endpoint !! Method !! Description&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_all_scores || GET || Returns all team review scores (for instructors/admins)&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_our_scores || GET || Returns team-scoped scores for current participant&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:assignment_id/view_my_scores || GET || Returns data for reviews received, given, and feedback&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/edit || GET || Retrieves participant and item data for grade editing&lt;br /&gt;
|-&lt;br /&gt;
| /api/v1/grades/:participant_id/update || PATCH || Updates grade and submission comment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Key Features Implemented ==&lt;br /&gt;
* Use the term '''items''' instead of “questions”.&lt;br /&gt;
* Increased font size and reduced blank space in table cells.&lt;br /&gt;
* Left-aligned prompts in the rubric display.&lt;br /&gt;
* Added the option to display an item list above the table.&lt;br /&gt;
* Ensured consistent font and button styling.&lt;br /&gt;
* Display reviewer names instead of placeholders (e.g., “Reviewer 1”).&lt;br /&gt;
* Enabled clicking on review numbers or reviewer names to open that review.&lt;br /&gt;
* Added “Show all reviews” and per-review “+” expand buttons.&lt;br /&gt;
* Added hover tooltip on item weight (e.g., “This is the weight of this item”).&lt;br /&gt;
* Centered item numbers.&lt;br /&gt;
* Verified rendering for all 10 item types (criterion, text field, checkbox, etc.).&lt;br /&gt;
* For text-field items: Displayed preview with “...” to expand full text.&lt;br /&gt;
* Adjusted column widths to match longest responses.&lt;br /&gt;
* Renamed toggle button:&lt;br /&gt;
  * “Toggle question list” → “Show item prompts” / “Hide item prompts”&lt;br /&gt;
* Displayed both team member names and usernames.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_itemlist.png|thumb|center|500px|“Show item prompts” toggle with item list displayed above table.]]&lt;br /&gt;
[[File:Heatgrid_expand_review.png|thumb|center|500px|Expanded review view using “+” button or “Show all reviews”.]]&lt;br /&gt;
&lt;br /&gt;
== Testing and Validation ==&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
* Verified across multiple item types, review rounds, and team configurations.&lt;br /&gt;
* Confirmed responsive layout and tooltip functionality.&lt;br /&gt;
* Tested with Chrome, Firefox, and Safari for UI consistency.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
* Confirmed endpoint connectivity and response structures.&lt;br /&gt;
* Verified role-based access: anonymized reviewer names for non-staff users.&lt;br /&gt;
* Validated data across multiple assignments and rounds.&lt;br /&gt;
&lt;br /&gt;
[[File:Heatgrid_tooltip.png|thumb|center|500px|Tooltip shown when hovering over item weight label.]]&lt;br /&gt;
&lt;br /&gt;
== Future Work ==&lt;br /&gt;
* Enable sorting by reviewer or average score.&lt;br /&gt;
* Add export functionality (CSV or Excel format).&lt;br /&gt;
* Integrate instructor-level analytics and visualization.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
* [https://github.com/expertiza/expertiza Expertiza GitHub Repository]&lt;br /&gt;
* `app/controllers/api/v1/grades_controller.rb`&lt;br /&gt;
* `frontend/src/pages/ViewTeamGrades/`&lt;br /&gt;
* `frontend/src/services/gradesService.ts`&lt;br /&gt;
* `frontend/src/styles/grades.scss`&lt;br /&gt;
&lt;br /&gt;
== Screenshot Summary ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Screenshot !! Description !! Section&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_before.png || Old dummy-data Heatgrid || Overview&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_after.png || Backend-connected Heatgrid || Technical Background&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_itemlist.png || “Show item prompts” toggle view || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_expand_review.png || Expanded review example || Key Features&lt;br /&gt;
|-&lt;br /&gt;
| Heatgrid_tooltip.png || Tooltip on item weight || Testing and Validation&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dshah24</name></author>
	</entry>
</feed>