├── docs ├── static │ ├── .nojekyll │ └── img │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── docusaurus.png │ │ ├── social-card.jpg │ │ └── logo.svg ├── docs │ ├── usage │ │ ├── assets │ │ │ ├── email.png │ │ │ ├── expo.png │ │ │ ├── login.png │ │ │ ├── checkboxes.png │ │ │ ├── judging-map.png │ │ │ ├── view-project.png │ │ │ ├── judge-dashboard.png │ │ │ ├── judged-project.png │ │ │ └── ranked-projects.png │ │ ├── admin │ │ │ ├── assets │ │ │ │ ├── qr-code.png │ │ │ │ ├── batch-ops.png │ │ │ │ ├── dashboard.png │ │ │ │ ├── add-judges.png │ │ │ │ ├── add-projects.png │ │ │ │ ├── csv-preview.png │ │ │ │ ├── flagged-row.png │ │ │ │ ├── move-groups.png │ │ │ │ ├── ranking-star.png │ │ │ │ ├── admin-actions.png │ │ │ │ ├── admin-settings.png │ │ │ │ ├── devpost-export.png │ │ │ │ ├── flagged-popup.png │ │ │ │ ├── group-settings.png │ │ │ │ ├── track-qr-code.png │ │ │ │ ├── track-settings.png │ │ │ │ ├── admin-dashboard.png │ │ │ │ ├── judge-popup-star.png │ │ │ │ └── reassign-groups.png │ │ │ ├── index.md │ │ │ ├── add-projects.md │ │ │ └── scoring.md │ │ ├── expo.md │ │ ├── index.md │ │ ├── judging-setup.md │ │ └── tips.md │ ├── reference │ │ ├── public-api.md │ │ ├── _category_.json │ │ └── envs.md │ └── details │ │ ├── backend │ │ ├── index.md │ │ ├── logging.md │ │ └── structure.md │ │ ├── frontend │ │ ├── index.md │ │ ├── routing.md │ │ ├── styling.md │ │ └── components.md │ │ ├── development │ │ ├── index.md │ │ ├── docs.md │ │ └── github.md │ │ └── principles.md ├── babel.config.js ├── compress ├── tsconfig.json ├── .gitignore ├── src │ ├── components │ │ └── CalloutButton.tsx │ ├── pages │ │ ├── index.module.css │ │ └── discord.svg │ └── css │ │ └── custom.css ├── sidebars.ts ├── package.json └── README.md ├── client ├── src │ ├── pages │ │ ├── Print.tsx │ │ ├── Info.tsx │ │ ├── judge │ │ │ └── hidden.tsx │ │ ├── admin │ │ │ ├── AddProjects.tsx │ │ │ ├── AddJudges.tsx │ │ │ └── log.tsx │ │ └── Home.tsx │ ├── assets │ │ ├── alarm.mp3 │ │ ├── star.svg │ │ ├── star-grey.svg │ │ ├── logout.svg │ │ └── arrow.svg │ ├── react-app-env.d.ts │ ├── enums.ts │ ├── components │ │ ├── qr.d.ts │ │ ├── admin │ │ │ ├── tables │ │ │ │ ├── AdminTable.tsx │ │ │ │ ├── HeaderEntry.tsx │ │ │ │ ├── JudgeRanksPopup.tsx │ │ │ │ ├── DeletePopup.tsx │ │ │ │ ├── EditJudgePopup.tsx │ │ │ │ ├── HidePopup.tsx │ │ │ │ └── MovePopup.tsx │ │ │ ├── add-judges │ │ │ │ ├── AddJudgeStat.tsx │ │ │ │ ├── AddJudgeStatsPanel.tsx │ │ │ │ └── JudgeQrCodes.tsx │ │ │ ├── add-projects │ │ │ │ └── AddProjectsStatsPanel.tsx │ │ │ ├── AdminStatsPanelMobile.tsx │ │ │ ├── ChallengeBlock.tsx │ │ │ ├── AdminStatsPanel.tsx │ │ │ ├── AdminHeader.tsx │ │ │ ├── AdminHeaderMobile.tsx │ │ │ ├── PauseButton.tsx │ │ │ ├── AdminClock.tsx │ │ │ ├── CSVPreview.tsx │ │ │ └── AdminToggleSwitch.tsx │ │ ├── Card.tsx │ │ ├── Back.tsx │ │ ├── Loading.tsx │ │ ├── StatBlock.tsx │ │ ├── judge │ │ │ ├── dnd │ │ │ │ ├── DragHamburger.tsx │ │ │ │ ├── CustomPointerSensor.tsx │ │ │ │ ├── SortableItem.tsx │ │ │ │ ├── RankItem.tsx │ │ │ │ └── Droppable.tsx │ │ │ ├── JudgeInfoPage.tsx │ │ │ ├── ProjectDisplay.tsx │ │ │ └── popups │ │ │ │ └── FinishPopup.tsx │ │ ├── Container.tsx │ │ ├── Paragraph.tsx │ │ ├── TextArea.tsx │ │ ├── ToTopButton.tsx │ │ ├── RadioSelect.tsx │ │ ├── InfoPopup.tsx │ │ ├── Dropdown.tsx │ │ ├── Checkbox.tsx │ │ ├── home │ │ │ └── HomeHub.tsx │ │ ├── RadioButton.tsx │ │ ├── ConfirmPopup.tsx │ │ ├── TextInput.tsx │ │ ├── SelectionButton.tsx │ │ ├── Popup.tsx │ │ ├── ActionsDropdown.tsx │ │ ├── PasswordInput.tsx │ │ └── JuryHeader.tsx │ ├── index.css │ └── data.json ├── .env.template ├── public │ └── favicon.ico ├── postcss.config.js ├── dev.Dockerfile ├── vite.config.js ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── index.html ├── tailwind.config.js ├── package.json └── README.md ├── init-mongo.js ├── server ├── public │ ├── favicon.ico │ └── index.html ├── funcs │ ├── email_test.go │ └── proj_nums.go ├── models │ ├── log.go │ ├── types.go │ ├── clock.go │ └── flag.go ├── router │ ├── state.go │ ├── limiter.go │ └── middleware.go ├── util │ ├── structs.go │ └── slices.go ├── main.go ├── .air.toml ├── database │ ├── init.go │ └── util.go ├── config │ └── env.go └── go.mod ├── .prettierrc ├── CONTRIBUTING.md ├── scripts ├── deploy.sh ├── run.sh └── test-runner.sh ├── tests ├── src │ ├── context.go │ ├── result.go │ ├── util.go │ ├── logging.go │ └── db.go ├── Dockerfile ├── go.mod ├── main.go └── tests │ ├── all.go │ └── unittests.go ├── test-data └── judges.csv ├── .env.template ├── .gitignore ├── dev.Dockerfile ├── mongo.Dockerfile ├── .github ├── workflows │ ├── test.yml │ └── deploy.yml ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── question.yml │ ├── feature_request.yml │ └── bug_report.yml ├── README.md ├── docker-compose.yml ├── .do └── deploy.template.yaml ├── LICENSE ├── init-mongo-rs.sh ├── docker-compose.dev.yml ├── Dockerfile ├── docker-compose.test.yml └── docker-compose-mongo.dev.yml /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/pages/Print.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /init-mongo.js: -------------------------------------------------------------------------------- 1 | // TODO: Create indices -------------------------------------------------------------------------------- /client/.env.template: -------------------------------------------------------------------------------- 1 | VITE_JURY_URL= 2 | VITE_JURY_NAME= 3 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/static/img/logo.png -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/server/public/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/alarm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/client/src/assets/alarm.mp3 -------------------------------------------------------------------------------- /client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.mp3'; 3 | -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/email.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/expo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/expo.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/login.png -------------------------------------------------------------------------------- /docs/static/img/social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/static/img/social-card.jpg -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/compress: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mogrify -strip -interlace Plane -sampling-factor 4:2:0 -quality 85 -resize 1000 $1 4 | -------------------------------------------------------------------------------- /docs/docs/usage/assets/checkboxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/checkboxes.png -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/qr-code.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/judging-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/judging-map.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/view-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/view-project.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/batch-ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/batch-ops.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/dashboard.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/judge-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/judge-dashboard.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/judged-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/judged-project.png -------------------------------------------------------------------------------- /docs/docs/usage/assets/ranked-projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/assets/ranked-projects.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsxSingleQuote": false, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/add-judges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/add-judges.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/add-projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/add-projects.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/csv-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/csv-preview.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/flagged-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/flagged-row.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/move-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/move-groups.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/ranking-star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/ranking-star.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | See the [contributing guide](https://jury.mikz.dev/docs/contributing) on our documentation site! 4 | -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/admin-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/admin-actions.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/admin-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/admin-settings.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/devpost-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/devpost-export.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/flagged-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/flagged-popup.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/group-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/group-settings.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/track-qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/track-qr-code.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/track-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/track-settings.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/admin-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/admin-dashboard.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/judge-popup-star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/judge-popup-star.png -------------------------------------------------------------------------------- /docs/docs/usage/admin/assets/reassign-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackutd/jury/HEAD/docs/docs/usage/admin/assets/reassign-groups.png -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker compose build 4 | docker tag jury-main ghcr.io/hackutd/jury:latest 5 | docker push ghcr.io/hackutd/jury:latest 6 | -------------------------------------------------------------------------------- /tests/src/context.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import "go.mongodb.org/mongo-driver/mongo" 4 | 5 | type Context struct { 6 | Db *mongo.Database 7 | Logger *Logger 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/reference/public-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Public API 6 | 7 | :::warning[WIP] 8 | 🚧 The public API is still under construction 🚧 9 | ::: 10 | -------------------------------------------------------------------------------- /test-data/judges.csv: -------------------------------------------------------------------------------- 1 | Egor Al,hello@gmail.com,,Catboy | actually a furry 2 | John Smith,alecbrightman@gmail.com,,This is a fake account btw 3 | Bob Joe,bob@gmail.com,,Bob is a cool name !! 4 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 2 | WORKDIR /jury 3 | 4 | COPY tests/go.mod tests/go.sum ./ 5 | 6 | RUN go mod download 7 | 8 | ENV MONGODB_URI=$MONGODB_URI 9 | 10 | CMD ["go", "run", "."] 11 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker pull ghcr.io/hackutd/jury:latest 4 | docker stop jury-main && sleep 2 && docker run --rm -d --name jury-main --env-file ./jury.env -p 8083:8080 ghcr.io/hackutd/jury:latest 5 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | JURY_NAME= 2 | JURY_ADMIN_PASSWORD= 3 | 4 | MONGODB_URI= 5 | 6 | EMAIL_HOST= 7 | EMAIL_PORT= 8 | EMAIL_FROM= 9 | EMAIL_FROM_NAME= 10 | EMAIL_PASSWORD= 11 | SENDGRID_API_KEY= 12 | 13 | PORT= 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .target 3 | *.env 4 | data 5 | .cargo 6 | .vscode 7 | server/tmp 8 | test-log.txt 9 | .hintrc 10 | **/*/log.txt 11 | 12 | # If someone accidentally makes these in root 13 | node_modules 14 | yarn.lock 15 | -------------------------------------------------------------------------------- /client/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-hydrogen AS client-builder 2 | WORKDIR /client 3 | 4 | ENV VITE_JURY_NAME=$VITE_JURY_NAME 5 | ENV VITE_JURY_URL=$VITE_JURY_URL 6 | ENV VITE_HUB=$VITE_HUB 7 | 8 | CMD [ "yarn", "run", "docker" ] 9 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/docs/details/backend/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Backend 4 | description: The backend. 5 | --- 6 | 7 | # Backend 8 | 9 | 10 | 11 | import DocCardList from '@theme/DocCardList'; 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/details/frontend/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Frontend 4 | description: The frontend. 5 | --- 6 | 7 | # Frontend 8 | 9 | 10 | 11 | import DocCardList from '@theme/DocCardList'; 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/assets/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/star-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/docs/usage/admin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Jury Admin 4 | description: Guide on how to use the admin (organizer) side of Jury 5 | --- 6 | 7 | # Using the Admin Interface 8 | 9 | 10 | 11 | import DocCardList from '@theme/DocCardList'; 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/details/development/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Development Notes 4 | description: Specifics on how to develop Jury and its deployment process. 5 | --- 6 | 7 | # Development Notes 8 | 9 | 10 | 11 | import DocCardList from '@theme/DocCardList'; 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 2 | WORKDIR /jury 3 | 4 | RUN curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b /usr/local/bin 5 | 6 | COPY server/go.mod server/go.sum ./ 7 | 8 | RUN go mod download 9 | 10 | ENV MONGODB_URI=$MONGODB_URI 11 | ENV JURY_ADMIN_PASSWORD=$JURY_ADMIN_PASSWORD 12 | 13 | CMD ["air"] 14 | -------------------------------------------------------------------------------- /docs/docs/reference/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 4, 3 | "label": "Reference", 4 | "collapsible": true, 5 | "collapsed": true, 6 | "link": { 7 | "type": "generated-index", 8 | "title": "Reference Pages", 9 | "description": "Reference pages provide technical details about the public and internal APIs." 10 | } 11 | } -------------------------------------------------------------------------------- /tests/src/result.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | type Result struct { 4 | Success bool 5 | Message string 6 | } 7 | 8 | func NewResult(success bool, message string) Result { 9 | return Result{ 10 | Success: success, 11 | Message: message, 12 | } 13 | } 14 | 15 | func ResultOk() Result { 16 | return Result{ 17 | Success: true, 18 | Message: "", 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/assets/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum JudgeSortField { 2 | Name, 3 | Code, 4 | Track, 5 | Group, 6 | Seen, 7 | Updated, 8 | Curr, 9 | None, 10 | } 11 | 12 | export enum ProjectSortField { 13 | Name, 14 | Flagged, 15 | TableNumber, 16 | Group, 17 | Score, 18 | Stars, 19 | Seen, 20 | Updated, 21 | None, 22 | } 23 | -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig(() => { 5 | return { 6 | build: { 7 | outDir: 'build', 8 | }, 9 | plugins: [react()], 10 | server: { 11 | host: true, 12 | port: 3000, 13 | } 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/src/pages/Info.tsx: -------------------------------------------------------------------------------- 1 | import Container from '../components/Container'; 2 | import JuryHeader from '../components/JuryHeader'; 3 | 4 | const Info = ({ children }: { children: React.ReactNode }) => { 5 | return ( 6 | <> 7 | 8 | 9 |

{children}

10 |
11 | 12 | ); 13 | }; 14 | 15 | export default Info; 16 | -------------------------------------------------------------------------------- /client/src/components/qr.d.ts: -------------------------------------------------------------------------------- 1 | // qr.d.ts 2 | declare module 'qr.js' { 3 | export interface QRCode { 4 | modules: boolean[][]; 5 | } 6 | 7 | /** 8 | * Generates a QR code for a given input string. 9 | * 10 | * @param input The string to encode in the QR code. 11 | * @returns An object representing the QR code with a `modules` property. 12 | */ 13 | export default function qr(input: string): QRCode; 14 | } 15 | -------------------------------------------------------------------------------- /server/funcs/email_test.go: -------------------------------------------------------------------------------- 1 | package funcs 2 | 3 | import ( 4 | "server/models" 5 | "testing" 6 | 7 | "github.com/joho/godotenv" 8 | ) 9 | 10 | func TestSendJudgeEmail(t *testing.T) { 11 | godotenv.Load("../.env") 12 | 13 | judge := models.NewJudge("Michael Zhao", "michaelzhao314@gmail.com", "", "notes here", -1) 14 | err := SendJudgeEmail(judge, "http://localhost:3000") 15 | if err != nil { 16 | t.Errorf("%s\n", err.Error()) 17 | t.FailNow() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/components/admin/tables/AdminTable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AdminTable = ({ children }: { children: React.ReactNode }) => { 4 | return ( 5 |
6 | 7 | {children} 8 |
9 |
10 | ); 11 | }; 12 | 13 | export default AdminTable; 14 | -------------------------------------------------------------------------------- /mongo.Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM mongo:6.0.4 2 | FROM mongo:latest 3 | 4 | RUN openssl rand -base64 756 > /keyfile 5 | RUN chmod 400 /keyfile 6 | RUN chown 999:999 /keyfile 7 | 8 | ENV MONGO_INITDB_ROOT_USERNAME=$MONGO_INITDB_ROOT_USERNAME 9 | ENV MONGO_INITDB_ROOT_PASSWORD=$MONGO_INITDB_ROOT_PASSWORD 10 | ENV MONGO_INITDB_DATABASE=$MONGO_INITDB_DATABASE 11 | ENV MONGO_REPLICA_SET_NAME=$MONGO_REPLICA_SET_NAME 12 | 13 | CMD ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/keyfile"] 14 | -------------------------------------------------------------------------------- /docs/src/components/CalloutButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@docusaurus/Link'; 2 | 3 | interface CalloutButtonProps { 4 | label: string; 5 | to: string; 6 | } 7 | 8 | export default function CalloutButton(props: CalloutButtonProps) { 9 | return ( 10 | 15 | {props.label} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /client/src/components/admin/add-judges/AddJudgeStat.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | 3 | const AddJudgeStat = (props: { name: string; value: string | number; className?: string }) => { 4 | return ( 5 |
6 |
{props.value}
7 |
{props.name}
8 |
9 | ); 10 | }; 11 | 12 | export default AddJudgeStat; 13 | -------------------------------------------------------------------------------- /client/src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | 3 | interface CardProps { 4 | children: React.ReactNode; 5 | className?: string; 6 | } 7 | 8 | function Card(props: CardProps) { 9 | return ( 10 |
16 | {props.children} 17 |
18 | ); 19 | } 20 | 21 | export default Card; 22 | -------------------------------------------------------------------------------- /server/models/log.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | type Log struct { 6 | Id primitive.ObjectID `bson:"_id,omitempty" json:"id"` 7 | Time int64 `bson:"time" json:"time"` // First time entry was added to this log 8 | Count int64 `bson:"count" json:"count"` // Number of times this log has been updated 9 | Entries []string `bson:"entries" json:"entries"` 10 | } 11 | 12 | func NewLog() *Log { 13 | return &Log{ 14 | Time: GetCurrTime(), 15 | Count: 0, 16 | Entries: []string{}, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/components/Back.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | const Back = (props: { location: string; className?: string }) => { 5 | const navigate = useNavigate(); 6 | const back = () => { 7 | navigate(props.location); 8 | }; 9 | return ( 10 |
14 | {'<'} Back 15 |
16 | ); 17 | }; 18 | 19 | export default Back; 20 | -------------------------------------------------------------------------------- /client/src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | 3 | const Loading = (props: { disabled: boolean }) => { 4 | return ( 5 |
11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Loading; 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | backend-test: 9 | name: Run Backend Tests 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | 15 | - name: Run Test Script 16 | run: | 17 | cat > .env < 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/pages/judge/hidden.tsx: -------------------------------------------------------------------------------- 1 | import Back from '../../components/Back'; 2 | import Container from '../../components/Container'; 3 | import JuryHeader from '../../components/JuryHeader'; 4 | 5 | const Hidden = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 |

You are currently disabled

12 |

Please contact an organizer to enable your account to continue judging.

13 |
14 | 15 | ); 16 | }; 17 | 18 | export default Hidden; 19 | -------------------------------------------------------------------------------- /tests/go.mod: -------------------------------------------------------------------------------- 1 | module tests 2 | 3 | go 1.23.1 4 | 5 | require go.mongodb.org/mongo-driver v1.17.1 6 | 7 | require ( 8 | github.com/golang/snappy v0.0.4 // indirect 9 | github.com/klauspost/compress v1.13.6 // indirect 10 | github.com/montanaflynn/stats v0.7.1 // indirect 11 | github.com/valyala/fastjson v1.6.4 // indirect 12 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 13 | github.com/xdg-go/scram v1.1.2 // indirect 14 | github.com/xdg-go/stringprep v1.0.4 // indirect 15 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 16 | golang.org/x/crypto v0.26.0 // indirect 17 | golang.org/x/sync v0.8.0 // indirect 18 | golang.org/x/text v0.17.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Describe the issue that this fixes or the feature that this adds] 4 | 5 | ### Fixes #[Issue] 6 | 7 | ### Type of Change 8 | 9 | Delete options that do not apply: 10 | 11 | - Bug fix (change which fixes an issue) 12 | - New feature (non-breaking change which adds functionality) 13 | - Refactor (code changes that doesn't affect functionality) 14 | - DevOps (changes to the build system/pipeline) 15 | - Documentation (works on updating the documentation) 16 | - Test (write test cases (huge w if u do this voluntarily)) 17 | - Revert (oopsie, undo a commit or change) 18 | 19 | ### Is this a breaking change? 20 | 21 | - [ ] Yes 22 | - [ ] No 23 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /docs/docs/details/development/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Documentation 4 | description: Docs about the docs 5 | --- 6 | 7 | # Documentation 8 | 9 | Writing the documentation is extremely important yet probably the hardest part of development. The docs are hosted using [Docusarus](https://docusaurus.io/). All documentation is in the `docs/docs` directory, where each markdown file represents a single page in the documentation. The configuration can be found at `docs/docusaurus.config.ts`. 10 | 11 | ## Fun thing 12 | 13 | If you want to see how many words is in the documentation, run this command in the `docs/docs` directory: 14 | 15 | ```bash 16 | find . -type f -name "*.md" | xargs wc -w 17 | ``` 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Jury Banner](./docs/static/img/social-card.jpg) 2 | 3 | # Jury 4 | 5 | A project designed to create a new pairwise judging system using modern technologies, aimed at optimizing the user experience of judges, hackers, and organizers. See the inspiration for this project: [Gavel by anishathalye](https://github.com/anishathalye/gavel). This project uses an individual ranking and rank aggregation system to automate the judging process and help organizers get more data from less judges to decide the winners of your hackathon. 6 | 7 | To get started, check out the [Jury documentation](https://jury.mikz.dev). 8 | 9 | # Contributing 10 | 11 | Check out [our contributing docs](https://jury.mikz.dev/docs/contributing). 12 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root, 6 | #root, 7 | html, 8 | body { 9 | --background-color: #f5f5f5; 10 | --text-font: Rubik, 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; 11 | /* -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; */ 13 | background-color: theme('colors.background'); 14 | font-family: theme('fontFamily.text'); 15 | min-height: 100vh; 16 | margin: 0; 17 | display: flex; 18 | flex-direction: column; 19 | } 20 | 21 | button, 22 | input { 23 | font-family: theme('fontFamily.text'); 24 | } 25 | 26 | ::placeholder { 27 | color: theme('colors.lightest'); 28 | } 29 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:react/recommended', 10 | ], 11 | parser: '@typescript-eslint/parser', 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | sourceType: 'module', 15 | }, 16 | plugins: ['@typescript-eslint', 'react', 'unused-imports'], 17 | settings: { 18 | react: { 19 | version: 'detect', 20 | }, 21 | }, 22 | rules: { 23 | 'react/react-in-jsx-scope': 'off', 24 | "unused-imports/no-unused-imports": "error", 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /client/src/components/StatBlock.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | 3 | const StatBlock = (props: { name: string; value: string | number; className?: string }) => { 4 | function fixIfFloat(n: number): string { 5 | if (Math.round(n) === n) { 6 | return n.toString(); 7 | } 8 | return n.toFixed(3); 9 | } 10 | 11 | return ( 12 |
13 |
14 | {typeof props.value === 'number' ? fixIfFloat(props.value) : props.value} 15 |
16 |
{props.name}
17 |
18 | ); 19 | }; 20 | 21 | export default StatBlock; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask a question or open discussion on a topic 3 | labels: ["question"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: If you have any questions or want to create an open discussion about a feature, please enter it here! 8 | - type: textarea 9 | id: question 10 | attributes: 11 | label: What is your question? Describe in detail. 12 | description: A clear and concise description of what the question is. 13 | placeholder: ex. Should this screen be changed to...? 14 | - type: textarea 15 | id: context 16 | attributes: 17 | label: Additional context 18 | description: Put additional context for your question here. This can be screenshots, code, or anything else! 19 | -------------------------------------------------------------------------------- /server/router/state.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "server/judging" 5 | "server/logging" 6 | "server/models" 7 | 8 | "github.com/gin-gonic/gin" 9 | "go.mongodb.org/mongo-driver/mongo" 10 | ) 11 | 12 | type State struct { 13 | Db *mongo.Database 14 | Clock *models.SafeClock 15 | Comps *judging.Comparisons 16 | Logger *logging.Logger 17 | Limiter *Limiter 18 | } 19 | 20 | func NewState(db *mongo.Database, clock *models.SafeClock, comps *judging.Comparisons, logger *logging.Logger, limiter *Limiter) *State { 21 | return &State{ 22 | Db: db, 23 | Clock: clock, 24 | Comps: comps, 25 | Logger: logger, 26 | Limiter: limiter, 27 | } 28 | } 29 | 30 | func GetState(ctx *gin.Context) *State { 31 | state := ctx.MustGet("state").(*State) 32 | return state 33 | } 34 | -------------------------------------------------------------------------------- /client/src/components/judge/dnd/DragHamburger.tsx: -------------------------------------------------------------------------------- 1 | const DragHamburger = () => { 2 | return ( 3 |
4 | 12 | 13 | 14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default DragHamburger; 21 | -------------------------------------------------------------------------------- /tests/src/util.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | ) 8 | 9 | // GetEnv returns the value of the environmental variable or panics if it does not exist 10 | func GetEnv(key string) string { 11 | val, ok := os.LookupEnv(key) 12 | if !ok { 13 | log.Fatalf("ERROR: %s environmental variable not defined\n", key) 14 | return "" 15 | } 16 | return val 17 | } 18 | 19 | // GetOptEnv returns the value of the environmental variable or the default value if it does not exist 20 | func GetOptEnv(key string, defaultVal string) string { 21 | val, ok := os.LookupEnv(key) 22 | if !ok { 23 | return defaultVal 24 | } 25 | return val 26 | } 27 | 28 | // GetDateTime returns a formatted datetime string 29 | func GetDateTime() string { 30 | now := time.Now() 31 | return now.Format("January 2, 2006 @ 03:04 PM MST") 32 | } 33 | -------------------------------------------------------------------------------- /docs/docs/usage/expo.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | title: Project Expo 4 | description: Details about the Project Expo page 5 | --- 6 | 7 | # Project Expo 8 | 9 | The project expo page is the public project list, showing all projects with their table numbers and [groups](/docs/usage/admin/groups) (if enabled): 10 | 11 | ![Project Expo Page](./assets/expo.png) 12 | 13 | Clicking on the headers will sort by table number or name (alphabetically). You can click on the names of each project to go to the linked `url` of the project. 14 | 15 | The dropdown at the top lets you select which track to view. Note that ALL tracks will be listed here, not only the ones that are going to be judged by Jury. 16 | 17 | Click on **Print this page** to get a printable view of the page. Note that the selected track will be displayed on the print page as well. 18 | -------------------------------------------------------------------------------- /client/src/components/admin/add-judges/AddJudgeStatsPanel.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import StatBlock from '../../StatBlock'; 3 | import { useAdminStore } from '../../../store'; 4 | 5 | const AddJudgeStatsPanel = () => { 6 | const stats = useAdminStore((state) => state.judgeStats); 7 | const fetchStats = useAdminStore((state) => state.fetchJudgeStats); 8 | 9 | useEffect(() => { 10 | fetchStats(); 11 | }, []); 12 | 13 | return ( 14 |
15 |
16 | 17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default AddJudgeStatsPanel; 24 | -------------------------------------------------------------------------------- /client/src/components/admin/add-projects/AddProjectsStatsPanel.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import StatBlock from '../../StatBlock'; 3 | import { useAdminStore } from '../../../store'; 4 | 5 | const AddProjectsStatsPanel = () => { 6 | const stats = useAdminStore((state) => state.projectStats); 7 | const fetchStats = useAdminStore((state) => state.fetchProjectStats); 8 | 9 | useEffect(() => { 10 | fetchStats(); 11 | }, []); 12 | 13 | return ( 14 |
15 |
16 | 17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default AddProjectsStatsPanel; 24 | -------------------------------------------------------------------------------- /server/util/structs.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "go.mongodb.org/mongo-driver/bson/primitive" 4 | 5 | // Request structs for the API 6 | 7 | type HideRequest struct { 8 | Hide bool `json:"hide"` 9 | } 10 | 11 | type PrioritizeRequest struct { 12 | Prioritize bool `json:"prioritize"` 13 | } 14 | 15 | type HideSelectedRequest struct { 16 | Items []primitive.ObjectID `json:"items"` 17 | Hide bool `json:"hide"` 18 | } 19 | 20 | type PrioritizeSelectedRequest struct { 21 | Items []primitive.ObjectID `json:"items"` 22 | Prioritize bool `json:"prioritize"` 23 | } 24 | 25 | type MoveGroupRequest struct { 26 | Group int64 `json:"group"` 27 | } 28 | 29 | type MoveRequest struct { 30 | Location int64 `json:"location"` 31 | } 32 | 33 | type MoveSelectedRequest struct { 34 | Items []primitive.ObjectID `json:"items"` 35 | Group int64 `json:"group"` 36 | } 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | main: 3 | container_name: jury-main 4 | environment: 5 | - MONGODB_URI=${MONGODB_URI} 6 | - JURY_ADMIN_PASSWORD=${JURY_ADMIN_PASSWORD} 7 | - EMAIL_HOST=${EMAIL_HOST} 8 | - EMAIL_PORT=${EMAIL_PORT} 9 | - EMAIL_FROM=${EMAIL_FROM} 10 | - EMAIL_FROM_NAME=${EMAIL_FROM_NAME} 11 | - EMAIL_USERNAME=${EMAIL_USERNAME} 12 | - EMAIL_PASSWORD=${EMAIL_PASSWORD} 13 | - SENDGRID_API_KEY=${SENDGRID_API_KEY} 14 | - VITE_JURY_NAME=${JURY_NAME} 15 | - PORT=8000 16 | build: 17 | context: './' 18 | dockerfile: Dockerfile 19 | args: 20 | - VITE_JURY_NAME=${JURY_NAME} 21 | - VITE_JURY_URL=/api 22 | - VITE_HUB=${HEHE:-} 23 | ports: 24 | - ${PORT:-8000}:8000 25 | -------------------------------------------------------------------------------- /client/src/components/judge/dnd/CustomPointerSensor.tsx: -------------------------------------------------------------------------------- 1 | import { PointerSensor, PointerSensorOptions } from '@dnd-kit/core'; 2 | import { PointerEvent } from 'react'; 3 | 4 | class CustomPointerSensor extends PointerSensor { 5 | // I hate typescript 6 | static activators: { 7 | eventName: 'onPointerDown'; 8 | handler: ( 9 | { nativeEvent }: PointerEvent, 10 | { onActivation }: PointerSensorOptions 11 | ) => boolean; 12 | }[] = [ 13 | { 14 | eventName: 'onPointerDown', 15 | handler: ({ nativeEvent: event }) => { 16 | return ( 17 | event.isPrimary && 18 | event.button === 0 && 19 | (event.target as HTMLElement).classList.contains('drag-handle') 20 | ); 21 | }, 22 | }, 23 | ]; 24 | } 25 | 26 | export default CustomPointerSensor; 27 | -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "server/config" 7 | "server/database" 8 | "server/logging" 9 | "server/router" 10 | 11 | "github.com/joho/godotenv" 12 | ) 13 | 14 | func main() { 15 | // Load the env file 16 | err := godotenv.Load() 17 | if err != nil { 18 | fmt.Printf("Did not load .env file (%s). This is expected when running in a Docker container\n", err.Error()) 19 | } 20 | 21 | // Check for all necessary env variables) 22 | config.CheckEnv() 23 | 24 | // Connect to the database 25 | db := database.InitDb() 26 | 27 | // Load the logger 28 | logger, err := logging.NewLogger(db) 29 | if err != nil { 30 | panic(errors.New("failed to load logger: " + err.Error())) 31 | } 32 | logger.SystemLogf("Server restarted") 33 | 34 | // Create the router and attach variables 35 | r := router.NewRouter(db, logger) 36 | 37 | // Start the Gin server! 38 | r.Run(":" + config.GetOptEnv("PORT", "8080")) 39 | } 40 | -------------------------------------------------------------------------------- /tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "tests/src" 7 | "tests/tests" 8 | ) 9 | 10 | func main() { 11 | // Initialize the logger (make sure to change the .gitignore if this filename ever changes) 12 | logger := src.NewLogger("test-log.txt") 13 | 14 | // Log start message with date and time 15 | logger.LogLn(src.Info, "\n===============\nTESTING STARTED\n===============") 16 | logger.Log(src.Info, "Date/time: %s\n", src.GetDateTime()) 17 | 18 | // Initialize the database connection 19 | db := src.InitDb(logger) 20 | 21 | // Close the database connection 22 | defer db.Client().Disconnect(context.Background()) 23 | 24 | // Wait for backend to load 25 | err := src.WaitForBackend(logger) 26 | if err != nil { 27 | os.Exit(1) 28 | } 29 | 30 | // Create a context with the database and logger 31 | context := &src.Context{ 32 | Db: db, 33 | Logger: logger, 34 | } 35 | 36 | // Run all tests! 37 | tests.RunTests(context) 38 | } 39 | -------------------------------------------------------------------------------- /client/src/components/judge/JudgeInfoPage.tsx: -------------------------------------------------------------------------------- 1 | import Back from '../Back'; 2 | import Container from '../Container'; 3 | import JuryHeader from '../JuryHeader'; 4 | 5 | interface JudgeInfoPageProps { 6 | /* Title to show */ 7 | title: string; 8 | 9 | /* Description to show */ 10 | description: string; 11 | } 12 | 13 | /** 14 | * Page to display when judging cannot happen. This could be because 15 | * of multiple reasons such as a paused session or no more projects left. 16 | */ 17 | const JudgeInfoPage = (props: JudgeInfoPageProps) => { 18 | return ( 19 | <> 20 | 21 | 22 | 23 |

{props.title}

24 |

{props.description}

25 |
26 | 27 | ); 28 | }; 29 | 30 | export default JudgeInfoPage; 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | .heroTitle { 14 | color: #00ACE6; 15 | font-weight: bold; 16 | font-size: 5rem; 17 | } 18 | 19 | .heroTagline { 20 | color: #657985; 21 | font-size: 1.5rem; 22 | } 23 | 24 | @media screen and (max-width: 996px) { 25 | .heroBanner { 26 | padding: 2rem; 27 | } 28 | } 29 | 30 | .buttons { 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | .description { 37 | margin-top: 5rem; 38 | display: flex; 39 | flex-direction: column; 40 | align-items: center; 41 | } 42 | 43 | .list { 44 | text-align: left; 45 | color: #657985; 46 | } 47 | 48 | .subheading { 49 | color: #657985; 50 | font-weight: bold; 51 | } 52 | -------------------------------------------------------------------------------- /server/.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = [] 7 | bin = "./tmp/main" 8 | cmd = "go build -o ./tmp/main ." 9 | delay = 0 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"] 18 | include_file = [] 19 | kill_delay = "0s" 20 | log = "build-errors.log" 21 | poll = false 22 | poll_interval = 0 23 | rerun = false 24 | rerun_delay = 500 25 | send_interrupt = false 26 | stop_on_error = false 27 | 28 | [color] 29 | app = "" 30 | build = "yellow" 31 | main = "magenta" 32 | runner = "green" 33 | watcher = "cyan" 34 | 35 | [log] 36 | main_only = false 37 | time = false 38 | 39 | [misc] 40 | clean_on_exit = false 41 | 42 | [screen] 43 | clear_on_rebuild = false 44 | keep_scroll = true 45 | -------------------------------------------------------------------------------- /client/src/components/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | interface ContainerProps { 5 | /* Element children */ 6 | children?: React.ReactNode; 7 | 8 | /* Tailwind classes, will override defaults */ 9 | className?: string; 10 | 11 | /* If true, don't justify center the entire page */ 12 | noCenter?: boolean; 13 | } 14 | 15 | /** 16 | * This is the main container for the site, which is optimized for mobile. 17 | * On desktop devices, a set width will be imposed to mimic mobile displays. 18 | */ 19 | const Container = (props: ContainerProps) => { 20 | return ( 21 |
28 | {props.children} 29 |
30 | ); 31 | }; 32 | 33 | export default Container; 34 | -------------------------------------------------------------------------------- /client/src/pages/admin/AddProjects.tsx: -------------------------------------------------------------------------------- 1 | import JuryHeader from "../../components/JuryHeader"; 2 | import UploadCSVForm from "../../components/admin/UploadCSVForm"; 3 | import AddProjectsStatsPanel from "../../components/admin/add-projects/AddProjectsStatsPanel"; 4 | import NewProjectForm from "../../components/admin/add-projects/NewProjectForm"; 5 | 6 | const AddProjects = () => { 7 | return ( 8 | <> 9 | 10 |
11 |

Add Projects

12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | ); 21 | }; 22 | 23 | export default AddProjects; 24 | -------------------------------------------------------------------------------- /client/src/pages/admin/AddJudges.tsx: -------------------------------------------------------------------------------- 1 | import JuryHeader from '../../components/JuryHeader'; 2 | import AddJudgeStatsPanel from '../../components/admin/add-judges/AddJudgeStatsPanel'; 3 | import JudgeQrCodes from '../../components/admin/add-judges/JudgeQrCodes'; 4 | import NewJudgeForm from '../../components/admin/add-judges/NewJudgeForm'; 5 | import UploadCSVForm from '../../components/admin/UploadCSVForm'; 6 | 7 | const AddJudges = () => { 8 | return ( 9 | <> 10 | 11 |
12 |

Add Judges

13 | 14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default AddJudges; 25 | -------------------------------------------------------------------------------- /.do/deploy.template.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | name: jury 3 | services: 4 | - name: jury-service 5 | dockerfile_path: Dockerfile 6 | envs: 7 | - key: VITE_JURY_NAME 8 | scope: RUN_AND_BUILD_TIME 9 | - key: VITE_JURY_URL 10 | scope: RUN_AND_BUILD_TIME 11 | value: /api 12 | - key: JURY_ADMIN_PASSWORD 13 | scope: RUN_AND_BUILD_TIME 14 | - key: MONGODB_URI 15 | scope: RUN_AND_BUILD_TIME 16 | - key: EMAIL_HOST 17 | scope: RUN_AND_BUILD_TIME 18 | - key: EMAIL_PORT 19 | scope: RUN_AND_BUILD_TIME 20 | value: "587" 21 | - key: EMAIL_FROM 22 | scope: RUN_AND_BUILD_TIME 23 | - key: EMAIL_FROM_NAME 24 | scope: RUN_AND_BUILD_TIME 25 | - key: EMAIL_PASSWORD 26 | scope: RUN_AND_BUILD_TIME 27 | - key: EMAIL_USERNAME 28 | scope: RUN_AND_BUILD_TIME 29 | - key: SENDGRID_API_KEY 30 | scope: RUN_AND_BUILD_TIME 31 | git: 32 | branch: master 33 | repo_clone_url: https://github.com/hackutd/jury.git 34 | instance_size_slug: apps-s-1vcpu-0.5gb 35 | instance_count: 1 36 | -------------------------------------------------------------------------------- /client/src/components/Paragraph.tsx: -------------------------------------------------------------------------------- 1 | interface ParagraphProps { 2 | /** Text to display */ 3 | text: string; 4 | 5 | /** Style with className */ 6 | className?: string; 7 | } 8 | 9 | /** 10 | * Formats a paragraph of text. 11 | * This will split the text into its respective paragraphs, 12 | * adding a slight space between each body section. 13 | */ 14 | const Paragraph = (props: ParagraphProps) => { 15 | // If there is no text, return "no description" 16 | if (props.text === '') return

[No Description]

; 17 | 18 | // Split the pararaphs by the newline character 19 | const paragraphs = props.text.split('\\n'); 20 | 21 | // Create a list of paragraph elements 22 | const paragraphElements = paragraphs.map((p, i) => ( 23 |

24 | {p} 25 |

26 | )); 27 | 28 | // Return list of items in a div 29 | return
{paragraphElements}
; 30 | }; 31 | 32 | export default Paragraph; 33 | -------------------------------------------------------------------------------- /client/src/components/admin/AdminStatsPanelMobile.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import StatBlock from '../StatBlock'; 3 | import { useAdminStore } from '../../store'; 4 | import AdminClock from './AdminClock'; 5 | 6 | const AdminStatsPanelMobile = () => { 7 | const stats = useAdminStore((state) => state.stats); 8 | const fetchStats = useAdminStore((state) => state.fetchStats); 9 | 10 | // Fetch stats on load 11 | useEffect(() => { 12 | fetchStats(); 13 | }, []); 14 | 15 | return ( 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default AdminStatsPanelMobile; 29 | -------------------------------------------------------------------------------- /client/src/components/admin/ChallengeBlock.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { getRequest } from '../../api'; 3 | import { errorAlert } from '../../util'; 4 | 5 | const ChallengeBlock = () => { 6 | const [challenges, setChallenges] = useState([]); 7 | 8 | useEffect(() => { 9 | async function fetchData() { 10 | // Get challenges 11 | const challengesRes = await getRequest('/challenges', ''); 12 | if (challengesRes.status !== 200) { 13 | errorAlert(challengesRes); 14 | return; 15 | } 16 | setChallenges(challengesRes.data as string[]); 17 | } 18 | 19 | fetchData(); 20 | }, []); 21 | 22 | if (!challenges) return; 23 | 24 | return ( 25 |
26 |

Challenge List

27 |

{challenges.join(',')}

28 |
29 | ); 30 | }; 31 | 32 | export default ChallengeBlock; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Michael Zhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea or a feature 3 | labels: ["new feature"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for helping out and suggesting a new feature!! 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the feature you'd like 12 | description: A clear and concise description of what you want to happen or the feature you want. 13 | placeholder: ex. I would like to add a button... 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: justification 18 | attributes: 19 | label: Pre-existing Issue or Justification 20 | description: Is your feature request related to a problem? Please describe the current issue you are facing or reason this feature should be added. 21 | placeholder: ex. I'm always frustrated when [...] 22 | - type: textarea 23 | id: context 24 | attributes: 25 | label: Additional context 26 | description: Put additional context for your feature request here. This can be screenshots, code, or anything else! 27 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #0092c3; 10 | --ifm-color-primary-dark: #007197; 11 | --ifm-color-primary-darker: #005a79; 12 | --ifm-color-primary-darkest: #00465e; 13 | --ifm-color-primary-light: #18bcf2; 14 | --ifm-color-primary-lighter: #39cbfc; 15 | --ifm-color-primary-lightest: #5ed7ff; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #00ACE6; 23 | --ifm-color-primary-dark: #0092c3; 24 | --ifm-color-primary-darker: #007197; 25 | --ifm-color-primary-darkest: #005a79; 26 | --ifm-color-primary-light: #18bcf2; 27 | --ifm-color-primary-lighter: #39cbfc; 28 | --ifm-color-primary-lightest: #5ed7ff; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /client/src/components/judge/dnd/SortableItem.tsx: -------------------------------------------------------------------------------- 1 | import { useSortable } from '@dnd-kit/sortable'; 2 | import { CSS } from '@dnd-kit/utilities'; 3 | import RankItem from './RankItem'; 4 | 5 | interface SortableItemProps { 6 | item: SortableJudgedProject; 7 | ranking: number; 8 | children?: React.ReactNode; 9 | disabled?: boolean; 10 | } 11 | 12 | const SortableItem = (props: SortableItemProps) => { 13 | if (!props.item) { 14 | return null; 15 | } 16 | const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ 17 | id: props.item.id, 18 | disabled: props.disabled, 19 | }); 20 | 21 | const style = { 22 | transform: CSS.Transform.toString(transform), 23 | transition, 24 | touchAction: 'initial', 25 | }; 26 | 27 | return ( 28 | 36 | {props.children} 37 | 38 | ); 39 | }; 40 | 41 | export default SortableItem; 42 | -------------------------------------------------------------------------------- /client/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import Button from '../components/Button'; 2 | import Container from '../components/Container'; 3 | 4 | const App = () => { 5 | return ( 6 | 7 |

8 | 9 | Jury 10 | 11 |

12 |

13 | {import.meta.env.VITE_JURY_NAME} 14 |

15 | 18 | 21 | 24 |
25 | ); 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /client/src/components/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import { twMerge } from 'tailwind-merge'; 2 | 3 | interface TextAreaProps { 4 | /* Label of the field */ 5 | label: string; 6 | 7 | /* Placeholder for the field */ 8 | placeholder?: string; 9 | 10 | /* State variable for the field */ 11 | value: string; 12 | 13 | /* Setter function for the field */ 14 | setValue: React.Dispatch>; 15 | 16 | /* Custom class name */ 17 | className?: string; 18 | } 19 | 20 | const TextArea = (props: TextAreaProps) => { 21 | return ( 22 |
23 | 26 |