Skip to content

Feature: Implement course rating system#87

Open
charlottenguyen05 wants to merge 4 commits intotarinagarwal:mainfrom
charlottenguyen05:feature/rating
Open

Feature: Implement course rating system#87
charlottenguyen05 wants to merge 4 commits intotarinagarwal:mainfrom
charlottenguyen05:feature/rating

Conversation

@charlottenguyen05
Copy link
Contributor

📝 Description

Frontend Changes

client/src/components/ui/Star.tsx (New File): Created reusable Star component

client/src/components/ui/StarRating.tsx (New File)

  • Created StarRating component that displays 5-star rating interface with readonly (display) and interactive (input) modes
  • Implements hover preview functionality for interactive ratings

client/src/components/courses/CoursesPage.tsx

  • Added rating display to course cards showing average rating and rating count
  • Handles zero ratings gracefully (shows empty text)

client/src/components/courses/CourseDetailPage.tsx

  • Added rating state management (rating, averageRating, ratingCount)
  • Added toast notifications for rating submission feedback
  • Implemented handleRatingSubmit() function with validation
  • Implemented fetchUserRating() to load existing user ratings
  • Added rating UI in course completion section so that user can submit their rating after completion.
  • Shows average rating in course header with star display

client/src/types/index.ts

  • Added average_rating: number and rating_count: number to Course interface

client/src/utils/api.ts

  • Added rateCourse() API function to submit/update ratings
  • Added getCourseRatings() API function to fetch ratings data

Backend Changes

server/prisma/schema.prisma

  • Added CourseRating model with fields:
    • id: Unique identifier
    • courseId: Reference to course
    • userId: Reference to user
    • rating: Integer value (1-5)

server/routes/courses.js

  • NEW GET /api/courses/:id/rate: Fetch all ratings for a course (See API.md for clear explanation)
  • NEW POST /api/courses/:id/rate: Submit or update a course rating (See API.md for clear explanation)
  • Modified GET /api/courses/:
    • Calculates and return additionally two key average_rating and rating_count for each course (conform to the new interface of Course in index.ts)
  • Modified GET /api/courses/:id:
    • Calculates and return additionally two key average_rating and rating_count for the course with that id (conform to the new interface of Course in index.ts)

Documentation Changes

docs/API.md: Added documentation for GET /api/courses/:id/rate endpoint and POST /api/courses/:id/rate endpoint

🔗 Related Issue

Closes #5

🏷️ Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📝 Documentation update
  • 🎨 Style/UI update
  • ♻️ Code refactoring
  • ⚡ Performance improvement
  • 🧪 Test update

📸 Screenshots (if applicable)

image image

Video demo (this video is done before I swapped the position of sharing button and rating in header in the last commit):
cinnamon-2026-01-05T185512+0100.webm
cinnamon-2026-01-05T185402+0100.webm

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have tested my changes locally
  • Any dependent changes have been merged and published

🧪 Testing

  • Tested on Chrome
  • Tested on Firefox
  • Tested on mobile
  • Tested API endpoints (if applicable)

📋 Additional Notes

I'm SWOC 2026 participant. Can you add the label to this PR for me. Thank you so much.


SWOC 2026 Participant? Add swoc2026 label to your PR! 🎉

@tarinagarwal
Copy link
Owner

@charlottenguyen05 ill review it asap

@charlottenguyen05
Copy link
Contributor Author

@tarinagarwal Can you check my code please.

Copy link

@tarin-lgtm tarin-lgtm bot left a comment

Choose a reason for hiding this comment

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

Changes Requested 🐈

This PR introduces a course rating system with new UI components and backend API endpoints. While the implementation is functional and well-structured, there are significant security concerns regarding authorization checks, missing documentation on key functions, and opportunities for performance and validation improvements.

There are a few things I'd like to see addressed before we merge this:

Before merging

  1. Implement authorization checks on the server to ensure only permitted users can rate courses.
  2. Add comprehensive JSDoc comments to all new exported functions and components for maintainability.
  3. Improve input validation and optimize rating data fetching with caching or aggregation to enhance security and performance.
Findings breakdown (38 total)

5 high / 10 medium / 12 low / 11 info

Confidence: 90%


🔗 View Full Review Report — detailed findings, severity breakdown, and agent analysis

Reviewed by Looks Good To Meow — AI-powered code review

}
});

router.post("/:id/rate", authenticateToken, async (req, res) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ High — The POST /api/courses/:id/rate endpoint allows authenticated users to submit or update a rating for a course. While it requires authentication, it does not explicitly verify that the user is authorized to rate the course (e.g., enrolled or has access). This could allow unauthorized users to rate courses they shouldn't access.

Add authorization checks to ensure the user is allowed to rate the course, such as verifying enrollment or course visibility before allowing rating submission.

security

navigate(`/courses/${course.id}/test/${testId}/results`);
};

const handleRatingSubmit = async (rating: number) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ High — Exported function 'handleRatingSubmit' lacks JSDoc documentation including description, parameters, and return type.

Add JSDoc comment for 'handleRatingSubmit' describing its purpose, parameters (rating: number), return type (Promise), and possible exceptions.

documentation

}
};

const fetchUserRating = async () => {
Copy link

Choose a reason for hiding this comment

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

⚠️ High — Exported function 'fetchUserRating' lacks JSDoc documentation including description, parameters, and return type.

Add JSDoc comment for 'fetchUserRating' describing its purpose, parameters (none), return type (Promise), and possible exceptions.

documentation

return response.data;
};

export const rateCourse = async (
Copy link

Choose a reason for hiding this comment

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

⚠️ High — Exported function 'rateCourse' lacks JSDoc documentation including description, parameters, return type, and possible errors.

Add JSDoc comment for 'rateCourse' describing its purpose, parameters (courseId: string, rating: number), return type (Promise with message and rating), and possible errors.

documentation

return response.data;
};

export const getCourseRatings = async (
Copy link

Choose a reason for hiding this comment

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

⚠️ High — Exported function 'getCourseRatings' lacks JSDoc documentation including description, parameters, return type, and possible errors.

Add JSDoc comment for 'getCourseRatings' describing its purpose, parameter (courseId: string), return type (Promise with ratings array and metadata), and possible errors.

documentation

</span>
</div>
</div>
<div className="mt-2 sm:mt-4 flex items-center space-x-2 text-sm text-gray-400">
Copy link

Choose a reason for hiding this comment

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

💡 Suggestion — The averageRating state is rounded with Math.round when passed to StarRating component, but displayed with toFixed(1) in text, causing slight inconsistency in displayed rating vs stars.

Use consistent rounding for both star display and numeric display to avoid confusion.

best-practices

<span className="text-gray-300">Rate this course:</span>
<StarRating readonly={false} rating={rating} onRatingChange={setRating} />
<div className="ml-4">
<button onClick={() => handleRatingSubmit(rating)} className="bg-alien-green text-royal-black px-3 py-2 rounded-lg font-semibold hover:bg-alien-green/90 transition-colors duration-300">
Copy link

Choose a reason for hiding this comment

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

💡 Suggestion — The Submit Rating button triggers handleRatingSubmit with current rating state, but the rating state is updated only on StarRating onRatingChange. There is potential for stale rating if user clicks submit without changing stars.

Consider disabling submit button when rating is zero or add immediate validation feedback to prevent submitting invalid rating.

best-practices

navigate(`/courses/${course.id}/test/${testId}/results`);
};

const handleRatingSubmit = async (rating: number) => {
Copy link

Choose a reason for hiding this comment

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

💡 Suggestion — The function 'handleRatingSubmit' uses the parameter name 'rating' which shadows the state variable 'rating'. This can cause confusion when reading the code.

Rename the parameter to something like 'selectedRating' to clearly distinguish it from the state variable.

readability

}
};

const fetchUserRating = async () => {
Copy link

Choose a reason for hiding this comment

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

💡 Suggestion — The function 'fetchUserRating' fetches ratings and updates multiple state variables. The logic to update 'rating' state is conditional on 'response.hasRated' and 'response.userRating !== null'. This logic is clear but could benefit from a comment explaining the purpose.

Add a comment explaining that the rating state is set only if the user has rated the course, otherwise it remains 0.

readability

</span>
</div>
</div>
<div className="mt-2 sm:mt-4 flex items-center space-x-2 text-sm text-gray-400">
Copy link

Choose a reason for hiding this comment

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

💡 Suggestion — The JSX for the course completion certificate and rating UI is nested more than 3 levels deep, which can reduce readability.

Consider using early returns or extracting nested JSX into smaller components to reduce nesting depth.

readability

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement course rating system

2 participants