├── .env ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── migrations └── 20240725050129_create_member_table.sql ├── scripts └── init_db.sh ├── src ├── lib.rs └── main.rs └── tests └── health_check.rs /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgres://postgres:password@localhost:5432/coduck" 2 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | rustfmt: 13 | name: 🗊 Formatting 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: nightly 22 | override: true 23 | components: rustfmt 24 | - name: Run cargo fmt 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: fmt 28 | args: --all -- --check 29 | 30 | lint: 31 | name: 🚨 Lint 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Install packages 36 | run: sudo apt-get install xorg-dev libglu1-mesa-dev 37 | - name: Install toolchain 38 | uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: stable 41 | override: true 42 | components: clippy 43 | - uses: giraffate/clippy-action@v1 44 | with: 45 | reporter: 'github-pr-review' 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | test: 49 | name: ✅ Test Suite 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | - name: Install packages 54 | run: sudo apt-get install xorg-dev libglu1-mesa-dev 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | toolchain: stable 58 | override: true 59 | - uses: Swatinem/rust-cache@v1 60 | - name: Install cargo-nextest 61 | uses: baptiste0928/cargo-install@v1 62 | with: 63 | crate: cargo-nextest 64 | locked: true 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: nextest 68 | args: run --retries 5 --workspace --failure-output final 69 | 70 | security_audit: 71 | name: 👮 Audit 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v4 75 | - name: Cache cargo bin 76 | uses: actions/cache@v4 77 | with: 78 | path: ~/.cargo/bin 79 | key: ${{ runner.os }}-cargo-audit 80 | - uses: actions-rs/audit-check@v1 81 | with: 82 | token: ${{ secrets.GITHUB_TOKEN }} 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # Per Editor Configuration 17 | .idea/ 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coduck-backend" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | path = "src/lib.rs" 8 | 9 | [[bin]] 10 | path = "src/main.rs" 11 | name = "coduck-backend" 12 | 13 | [dependencies] 14 | axum = "0.8.4" 15 | tokio = { version = "1.45.1", features = ["full"] } 16 | 17 | [dev-dependencies] 18 | reqwest = { version = "0.12.19", features = ["json", "rustls-tls"] } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Chris Ohk 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coduck 2 | 3 | A platform for the creation of programming contest problems heavily inspired by Polygon 4 | -------------------------------------------------------------------------------- /migrations/20240725050129_create_member_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE member ( 2 | id uuid NOT NULL PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT NOT NULL UNIQUE, 5 | created_at timestamptz NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /scripts/init_db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | set -eo pipefail 4 | 5 | # Check that both psql and sqlx-cli are installed 6 | if ! [ -x "$(command -v psql)" ]; then 7 | echo "Error: psql is not installed." 8 | exit 1 9 | fi 10 | 11 | if ! [ -x "$(command -v sqlx)" ]; then 12 | echo "Error: sqlx is not installed." 13 | echo >&2 "Use:" 14 | echo >&2 " cargo install --version=\"0.8.0\" sqlx-cli --no-default-features --features rustls,postgres" 15 | echo >&2 " to install it." 16 | exit 1 17 | fi 18 | 19 | DB_USER=${POSTGRES_USER:=postgres} 20 | DB_PASSWORD="${POSTGRES_PASSWORD:=password}" 21 | DB_NAME="${POSTGRES_DB:=coduck}" 22 | DB_PORT="${POSTGRES_PORT:=5432}" 23 | DB_HOST="${POSTGRES_HOST:=localhost}" 24 | 25 | # Launch Postgres using Docker 26 | # Allow skipping Docker if a dockerized Postgres database is already running 27 | if [[ -z "${SKIP_DOCKER}" ]] 28 | then 29 | docker run \ 30 | -e POSTGRES_USER=${DB_USER} \ 31 | -e POSTGRES_PASSWORD=${DB_PASSWORD} \ 32 | -e POSTGRES_DB=${DB_NAME} \ 33 | -p "${DB_PORT}":5432 \ 34 | -d postgres \ 35 | postgres -N 1000 36 | fi 37 | 38 | export PGPASSWORD="${DB_PASSWORD}" 39 | until psql -h "${DB_HOST}" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do 40 | >&2 echo "Postgres is still unavailable - sleeping" 41 | sleep 1 42 | done 43 | 44 | >&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!" 45 | 46 | DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} 47 | export DATABASE_URL 48 | sqlx database create 49 | sqlx migrate run 50 | 51 | >&2 echo "Postgres has been migrated, ready to go!" 52 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | 3 | async fn health_check() -> &'static str { 4 | "OK" 5 | } 6 | 7 | pub fn build_router() -> Router { 8 | Router::new().route("/health", get(health_check)) 9 | } 10 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() { 3 | let app = coduck_backend::build_router(); 4 | 5 | let listener = tokio::net::TcpListener::bind("127.0.0.1:8080") 6 | .await 7 | .unwrap(); 8 | println!("Listening on {}", listener.local_addr().unwrap()); 9 | axum::serve(listener, app) 10 | .await 11 | .expect("Server failed to start"); 12 | } 13 | -------------------------------------------------------------------------------- /tests/health_check.rs: -------------------------------------------------------------------------------- 1 | #[tokio::test] 2 | async fn health_check_works() { 3 | let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); 4 | let port = listener.local_addr().unwrap().port(); 5 | 6 | tokio::spawn(async move { 7 | let app = coduck_backend::build_router(); 8 | axum::serve(listener, app) 9 | .await 10 | .expect("Server failed to start"); 11 | }); 12 | 13 | let client = reqwest::Client::new(); 14 | let response = client 15 | .get(&format!("http://127.0.0.1:{port}/health")) 16 | .send() 17 | .await 18 | .unwrap(); 19 | 20 | assert_eq!(response.status(), reqwest::StatusCode::OK); 21 | let body = response.text().await.unwrap(); 22 | assert_eq!(body, "OK"); 23 | } 24 | --------------------------------------------------------------------------------