A social network for students to track course grades, share grade calculation templates, and manage course prerequisites, created by our group for CMPT 370 at the University of Saskatchewan. Built using Svelte, complete with a GraphQL API and Grafana Dashboard.
Watch the Overview
Check out our video overview - one of the deliverables required for the class. In it, we discuss project architecture and design decisions as well as core features.
The easiest way to run the application is with Docker Compose:
Make sure you have Docker and Docker Compose installed on your system.
Create a .env
file with the required environment variables:
NODE_ENV=development # options: production, development, test
# Note: don't use NODE_ENV=production unless deploying on a site with valid SSL certificates, etc.
PORT=3000
SESSION_SECRET=your-secret-key-here
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=your-grafana-password-here
An example .env file is given (env.example)
Note: The NODE_ENV variable determines which database file is used:
- development: Uses dev.db
- production: Uses prod.db
- test: Uses test.db
Build and start the complete stack (app, Prometheus, and Grafana):
docker-compose up
or in detached mode:
docker-compose up -d
Access the application at http://localhost:3000
Access Prometheus at http://localhost:9090
Access Grafana at http://localhost:3456
.env
fileShut down docker containers using:
docker-compose down
To run only the monitoring tools (Prometheus and Grafana) without the app:
docker-compose -f docker-compose.metrics.yml up -d
If you want to work on the code directly without Docker:
Install dependencies:
npm install
Create a .env
file with the same variables as in the Docker setup.
Start development server:
npm run dev
This will build the frontend and start the server.
Access the app at http://localhost:3000
See self-documented API sandbox at http://localhost:3000/graphql
For monitoring, you can start just the monitoring stack:
docker-compose -f docker-compose.metrics.yml up -d
The application is set up to export Prometheus metrics that can be visualized in Grafana. The metrics cover HTTP requests, GraphQL operations, active sessions, and system resources.
Note: The
/metrics
endpoint does not require authentication. The standard for securing this endpoint in production is via adding authentication to it through your reverse proxy of choice.
The preconfigured Grafana dashboard includes panels for:
You can add custom metrics to the application by creating new metrics in server/server.js
using the prom-client library.
Note: Running development tests requires building through the development setup, not just through the Docker quickstart.
The application includes a comprehensive test suite utilizing Jest unit tests and Cypress E2E integration tests. The tests operate a separate test database to avoid affecting production data.
Run the Jest tests:
npm test
Run the Cypress tests:
npm run cypress:run
Optionally, you can view the Cypress tests run yourself:
npm run cypress:open
Then, open E2E test specs, letting you watch each test spec run
You're also able to run the project locally but using the test database:
npm run dev:test
The test database clears all data each run so it's easy to test scenarios. Additionally, when on the test database you can go to /reset
to reset the database, and /seed
to seed the database with some dummy data.
server/
__tests__/ # Backend test files
auth.test.js # Authentication endpoint tests
calculators.test.js # Calculator endpoint tests
courses.test.js # Course endpoint tests
templates.test.js # Template endpoint tests
users.test.js # User endpoint tests
testHelpers.js # Helper functions for tests
setup.js # Setup for individual test files
frontend/
__tests__/ # Frontend test files
utils/
gradeCalculations.test.js # Tests for grade calculation utilities
courseSorting.test.js # Tests for course sorting utilities
cypress/
e2e/ # Cypress E2E integration test specs
auth.cy.js # Tests the authentication flow
calculators.cy.js # Tests all user stories related to calculators
commandPalette.cy.js # Tests all user stories related to the command palette
courses.cy.js # Tests all user stories related to the course tracker
templates.cy.js # Tests all user stories related to shared templates
jest.config.js # Jest configuration
jest.setup.js # Global test setup and teardown
cypress.config.js # Cypress configuration
babel.config.js # Babel configuration for testing
The test suite covers both backend API endpoints and frontend utility functions.
It's important that course tracking is a separate feature from grade calculators as users may join the platform partially through university and it would be tedious to retroactively create grade calculators for classes you have already completed just to get accurate credit tracking
App.svelte
: Main router and authentication flowAppShell.svelte
: Main layout component that wraps the applicationCalculatorCard.svelte
: Calculator item display componentCourseCard.svelte
: Course card display componentTemplateCard.svelte
: Template display componentVoteButtons.svelte
: Template voting interfaceComments.svelte
: Template comment systemCommentCard.svelte
: Individual comment display componentCommentsSheet.svelte
: Slide-out comment panel componentCommandPalette.svelte
: Command interface for quick navigation and actionsIndex.svelte
: Landing page componentLogin.svelte
: User login pageRegister.svelte
: User registration pageCalculator.svelte
: Calculator editing/viewing pageCalculators.svelte
: List of user's calculatorsCourses.svelte
: Course management and visualizationSearch.svelte
: Template search interfaceTemplatePreview.svelte
: Template details viewUser.svelte
: User profile pageUsers and Authentication:
Grade Calculators:
Templates:
Course Tracking:
The application uses GraphQL for its API, providing a single endpoint for all operations:
POST /graphql
GraphQL Schema includes:
Queries:
me
: Get the current logged-in user's ID and usernameuser(id: ID!)
: Get a specific user by ID, returns user detailscalculator(id: ID!)
: Get a specific calculator with its assessments, min_desired_grade, and associated templatetemplate(id: ID!)
: Get a specific template with assessments, creator details, and voting informationallTemplates(query: String, term: String, year: Int, institution: String, page: Int, limit: Int)
: Search and filter templates with paginationcourse(id: ID!)
: Get a specific course with its name, credits, completion status, and prerequisitestemplateComments(templateId: ID!)
: Get all comments for a specific template with author informationhealth
: Simple health check endpointMutations:
register(username: String!, password: String!)
: Register a new user, returns user detailslogin(username: String!, password: String!)
: Log in a user, returns user detailslogout
: Log out the current user, returns success booleancreateCalculator(name: String!, min_desired_grade: Float)
: Create a calculator with optional minimum gradeupdateCalculator(id: ID!, name: String, min_desired_grade: Float, assessments: [AssessmentInput!])
: Update calculator details and assessmentsdeleteCalculator(id: ID!)
: Delete a calculator, returns success booleancreateTemplate(name: String!, term: String!, year: Int!, institution: String!, assessments: [TemplateAssessmentInput!]!)
: Create a shareable calculator templatedeleteTemplate(id: ID!)
: Soft delete a template (only owner can delete)useTemplate(templateId: ID!)
: Create a personal calculator from a templatevoteTemplate(templateId: ID!, vote: Int!)
: Vote on a template (1 for upvote, -1 for downvote)removeTemplateVote(templateId: ID!)
: Remove vote from a templatecreateCourse(name: String!, credits: Float!, prerequisiteIds: [ID!])
: Create a course with optional prerequisitesupdateCourse(id: ID!, name: String, credits: Float, completed: Boolean, prerequisiteIds: [ID!])
: Update course details and prerequisitesdeleteCourse(id: ID!)
: Delete a course, returns success booleanaddTemplateComment(templateId: ID!, content: String!)
: Add a comment to a templateupdateTemplateComment(commentId: ID!, content: String!)
: Update a comment (only author can update)deleteTemplateComment(commentId: ID!)
: Delete a comment (only author can delete)This project relies on several external dependencies:
Templates on the search page are not simply filtered (meaning, once you create a template you should never have an empty search), they always "show" all the templates paginated, it's just that they are ordered according to the search parameters via the following classifications: