Skip to content

Express/Mongo: Create Application Retrieval Endpoints #85

@smalex-z

Description

@smalex-z

Description:
Create endpoints for retrieving applications. Support different views for applicants (own application) and officers (committee-filtered applications).

Acceptance Criteria:

  • Create GET /api/v1/internship/applications/me - Get current user's application
    • Returns full application with all fields
    • 404 if no application exists
  • Create GET /api/v1/internship/applications - Get all applications (OFFICER only)
    • Query params:
      • ?committee=hack - Filter by committee (any choice position)
      • ?status=pending - Filter by status
      • ?page=1&limit=20 - Pagination (default 20, max 100)
    • Returns paginated list with total count
  • Create GET /api/v1/internship/applications/:id - Get single application (OFFICER only)
    • Returns full application details
    • Populate committee information
  • Add authorization checks:
    • Applicants can only see their own application
    • Officers can see applications for their committee(s)
    • Admins can see all applications
  • Add rate limiting: 100 requests per 15 minutes

Example Request/Response:

# Applicant view
GET /api/v1/internship/applications/me

Response:
{
  "error": null,
  "application": {
    "_id": "...",
    "userId": "user-uuid",
    "firstName": "Alice",
    "lastName": "Smith",
    "email": "alice@ucla.edu",
    "firstChoice": {
      "_id": "...",
      "name": "hack",
      "displayName": "Hack"
    },
    "firstChoiceStatus": "pending",
    "firstChoiceResponses": [...],
    "submittedAt": "2025-01-15T10:30:00.000Z"
  }
}

# Officer view
GET /api/v1/internship/applications?committee=hack&status=pending&page=1&limit=20

Response:
{
  "error": null,
  "applications": [...],
  "total": 45,
  "page": 1,
  "limit": 20,
  "pages": 3
}

Technical Notes:

exports.getMyApplication = async (req, res, next) => {
  try {
    const application = await Application.findOne({ userId: req.user.uuid })
      .populate('firstChoice secondChoice thirdChoice');
    
    if (!application) {
      return res.status(404).json({ error: 'No application found' });
    }
    
    res.json({ error: null, application });
  } catch (error) {
    next(error);
  }
};

exports.getAllApplications = async (req, res, next) => {
  try {
    // Only officers/admins
    if (!req.user.isAdmin()) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    
    const { committee, status, page = 1, limit = 20 } = req.query;
    const query = {};
    
    // Filter by committee (check all three choice fields)
    if (committee) {
      const comm = await Committee.findOne({ name: committee });
      if (comm) {
        query.$or = [
          { firstChoice: comm._id },
          { secondChoice: comm._id },
          { thirdChoice: comm._id },
        ];
      }
    }
    
    // Filter by status (for first choice only for now)
    if (status) {
      query.firstChoiceStatus = status;
    }
    
    const skip = (page - 1) * Math.min(limit, 100);
    const limitNum = Math.min(limit, 100);
    
    const [applications, total] = await Promise.all([
      Application.find(query)
        .populate('firstChoice secondChoice thirdChoice')
        .sort({ submittedAt: -1 })
        .skip(skip)
        .limit(limitNum),
      Application.countDocuments(query),
    ]);
    
    res.json({
      error: null,
      applications,
      total,
      page: parseInt(page),
      limit: limitNum,
      pages: Math.ceil(total / limitNum),
    });
  } catch (error) {
    next(error);
  }
};

Dependencies: Issue #82, Issue #84

Estimated Effort: 4-5 hours

Metadata

Metadata

Assignees

No one assigned

    Labels

    internship-portalFor supporting the internship-portal. Probably uses MongoDBnew feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions