A project designed to create a new pairwise judging system using modern technologies aimed at optimizing the user experience of judges and admin users. See the inspiration for this project: Gavel by anishathalye. Refer to this excellent article for more details on the underlying formulas of Gavel! The majority of our algorithm will be based off of research done in the field of pairwise comparison.
MongoDB is a free NoSQL database that is hosted in the cloud! Their website has great instructions on how to quickly create an account and 100% lifetime free database, perfect for Jury: https://www.mongodb.com/docs/atlas/getting-started/.
Follow the instructions up to step 5: connect to your cluster. You will get a URI string that looks like mongodb+srv://<username>:<password>@<something>.mongodb.net/?retryWrites=true&w=majority
, except <username>, <password>, and <something> should be filled in. You will need that for the environmental variables.
- Click Edit Plan
- Change plan to "Basic"
- Change Instance Size to "$5.00/mo -- Basic (512 MB RAM | 1 vCPU)"
Note that since Digital Ocean charges prorated, this will literally be like less than 20 cents a day.
Click on "edit" next to "jury-service" and fill in the env as follows:
- REACT_APP_JURY_NAME - Name of the app to display to the user (eg. HackUTD X Judging)
- JURY_ADMIN_PASSWORD - Password for the admin portal (suggestion: use 2 random words)
- MONGODB_URI - URI of MongoDB Atlas instance (see step 0)
If using Gmail SMTP:
- EMAIL_HOST = smtp.gmail.com
- EMAIL_FROM = gmail username
- EMAIL_USERNAME = gmail username
- EMAIL_PASSWORD = google app password
If using SendGrid:
- SENDGRID_API_KEY = API key provided by SendGrid
- EMAIL_FROM = email to send from
- EMAIL_FROM_NAME = display name of sender
If using AWS SES:
- EMAIL_HOST = email-smtp..amazonaws.com
- EMAIL_FROM = verified email to send from
- EMAIL_USERNAME = SES SMTP username
- EMAIL_PASSWORD = SES SMTP password
NOTE: Only set SENDGRID_API_KEY
if using Sendgrid!!
You can change the start of the URL if you want:
- App Info > edit
- Change app name (URL will be <app_name><some_characters>.ondigitalocean.app)
- Click Next
Copy .env.template
into .env
and fill in the environmental variables
Environmental Variables:
JURY_NAME="Name of the jury app [Displays on the app!]"
JURY_ADMIN_PASSWORD="Password used to log into the admin dashboard"
MONGODB_URI="MongoDB connection URI string [ONLY for MongoDB Atlas]"
MONGODB_USER="Username for local mongo container [ONLY use if running local mongo instance]"
MONGODB_PASS="Password for local mongo container [ONLY use if running local mongo instance]"
I suggest you run the app with MongoDB Atlas! Create a free account and database here. It should provide you a URI string to fill into the .env
file.
If you would rather use a local instance deployed with docker-compose, you can simply fill in the username and password you want to use with that database. You will use the files
docker-compose-mongo.yml
for production anddocker-compose-mongo.dev.yml
for development. This is not recommended as atlas provides a much easier interface!!
Run docker compose up
after configuring the .env
file.
To run the dev environment:
Copy .env.template
into .env
and fill in the environmental variables (see above).
Simply run docker compose -f docker-compose.dev.yml up
and open the page at localhost:3000
.
Use mongodb://{MONGODB_USER}:{MONGODB_PASS}@0.0.0.0:27107/?directConnection=true
as the connection string. This works with both MongoDB Compass and through mongosh
.
To reset the database and reload the sample data, completely remove the data
folder that the Docker compose creates.
Requirements:
Copy .env.template
into .env
and fill in the environmental variables. You will also need a MONGODB_URI
field.
Additionally, copy client/.env.template
into client/.env
and copy over the relavent environmental variables from .env
.
This is used to expose the correct environmental variables to the running instance of CRA.
Client dev server (PORT 3000):
cd client
yarn install
yarn start
Backend dev server (PORT 8000):
cargo run
Here is the Figma design file: https://www.figma.com/file/qwBWs4i7pJMpFbcjMffDZU/Jury-(Gavel-Plus)?node-id=8%3A100&t=xYwfPwRAUeJw9jNr-1
All routes are found at the http://localhost:8000/api
endpoint, which can be accessed on the frontend through the REACT_APP_JURY_URL
environmental variable.
An example fetch request (The credentials: include
allow for authentication cookies to be sent over):
const fetchedProjects = await fetch(`${process.env.REACT_APP_JURY_URL}/project/list`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
}).then((data) => data.json());
Path | Method | Auth | Description |
---|---|---|---|
/judge/list | GET | admin | get list of all judges |
/judge/new | POST | admin | Add a new judge |
/judge/csv | POST | admin | Preview a judges csv |
/judge/csv/upload | POST | admin | Upload judges using csv |
/judge/stats | GET | admin | Get the stats for the add judges page |
/judge/login | POST | Login request for a judge | |
/judge/auth | POST | token | Checks to see if judge is logged in |
/judge/welcome | GET | token | Checks for read_welcome for a judge |
/judge/welcome | PUT | token | Set read_welcome to true for a judge |
/project/list | GET | admin | get list of all projects |
/project/new | POST | admin | Add a new project |
/project/csv | POST | admin | Preview a projects csv |
/project/csv/upload | POST | admin | Upload projects using csv |
/project/stats | GET | admin | Get the stats for the add projects page |
/project/devpost | POST | admin | Upload a Devpost CSV |
/project/:id | GET | token | Get project by ID |
/admin/login | POST | Log into the admin dashboard | |
/admin/stats | GET | admin | Get all stats |
/admin/sync | GET | admin | Establish event stream with server |
/admin/clock | GET | admin | Gets the current clock state |
/admin/clock/pause | POST | admin | Pauses the clock |
/admin/clock/unpause | POST | admin | Resumes the clock |
/admin/clock/reset | POST | admin | Resets the clock |
Get a list of all judges
Response
[
{
"id": "ObjectId",
"token": "String",
"code": "String",
"name": "String",
"email": "String",
"active": "bool",
"last_activity": "DateTime",
"read_welcome": "bool",
"notes": "String",
"next": "ObjectId",
"prev": "ObjectId",
"alpha": "f64",
"beta": "f64"
}
// ...
]
Requires admin password | Add a singular new judge
Request Body
{
"name": "String",
"email": "String",
"notes": "String"
}
Response
200 Ok
- Error + String
Requires admin password | PREVIEW judge CSV file (does not upload)
Request Body
Content-Type: multipart/form-data
Fields:
- csv: CSV file upload
- hasHeader: Bool that is true if csv contains header
Response
[
{
"name": "String",
"email": "String",
"notes": "String"
}
// ...
]
or Error + String
Requires admin password | Upload judge CSV file
Request Body
Content-Type: multipart/form-data
Fields:
- csv: CSV file upload
- hasHeader: Bool that is true if csv contains header
Response
200 Ok
+ Number of added judges as String- Error + String
Requires admin password | Get the stats to display on the add judges page
Response
{
"num": 0, // u64 - Total # of judges
"alpha": 0.0, // f64 - Average alpha of judges
"beta": 0.0, // f64 - Average beta of judges
},
or Error + String
Login request for a judge
Request Body
{
"code": "String (6-digit login code for judge)"
}
Response
200 Ok
+ Token as String- Error + String
Requires judge token | Checks to see if judge is logged in
Response
200 Ok
if token is valid401 Unauthorized
if token is invalid500 Internal Server Error
Requires judge token | Checks to see if a judge has read the welcome page
Response
{
"ok": true // Boolean, true or false
}
or Error
Requires judge token | Set the read_welcome
field for a judge to true
Response
202 Accepted
- Error + String
Get a list of all projects
Response
[
{
"id": "ObjectId",
"name": "String",
"location": "u64",
"description": "String",
"try_link": "String",
"video_link": "String",
"challenge_list": ["String"],
"seen": "u64",
"votes": "u64",
"mu": "f64",
"sigma_sq": "f64",
"active": "bool",
"prioritized": "bool",
"last_activity": "DateTime"
}
// ...
]
Requires admin password | Add a singular new project
Request Body
{
"name": "String",
"description": "String",
"try_link": "String (Optional)",
"video_link": "String (Optional)",
"challenge_list": "String (Optional)"
}
Response
200 Ok
- Error + String
Requires admin password | PREVIEW project CSV file (does not upload)
Request Body
Content-Type: multipart/form-data
Fields:
- csv: CSV file upload
- hasHeader: Bool that is true if csv contains header
Response
[
{
"name": "String",
"location": "String",
"description": "String"
}
// ...
]
or Error + String
Requires admin password | Upload project CSV file
Request Body
Content-Type: multipart/form-data
Fields:
- csv: CSV file upload
- hasHeader: Bool that is true if csv contains header
Response
200 Ok
+ Number of added judges as String- Error + String
Requires admin password | Upload CSV from devpost
Request Body
Content-Type: multipart/form-data
Fields:
- csv: CSV file upload
Response
- Ok 200
- Error + String
Requires judge token | Gets a project by ID
- id - ID of the project
Response
- Ok 200 with project data as JSON
- Error
Login request for admins
Request Body
{
"password": "String (admin password)"
}
Response
200 Ok
+ Password as String- Error + String
Requires admin password | Get the stats to display on the admin dashboard
Response
{
"projects": 0, // u64 - Total # of projects
"seen": 0.0, // f64 - Average seen # of projects
"votes": 0.0, // f64 - Average votes of projects
"time": 0, // u64 - Current judging time in milliseconds
"avg_mu": 0.0, // f64 - Average mu value of projects
"avg_sigma": 0.0, // f64 - Average sigma^2 value of projects
"judges": 0 // u64 - Total # of judges
},
or Error
Requires admin password | Establish event stream with server
Request
Response
Content-Type: text/event-stream
Requires admin password | Get the current clock state
Response
{
"start": 0, // Start time in millis
"prev": 0, // Previously elapsed time, relavent if paused/previously paused
"paused": true // Bool, true if paused
}
or Error
Requires admin password | Pauses the clock
Response
- Ok 200
- InternalServerError 500 + String
Requires admin password | Resumes the clock
Response
- Ok 200
- InternalServerError 500 + String
Requires admin password | Resets the clock
Response
- Ok 200
- InternalServerError 500 + String