diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml index 41dcaf5..28230e8 100644 --- a/.forgejo/workflows/publish.yml +++ b/.forgejo/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish Cargo Package +name: Publish Release on: push: @@ -11,8 +11,6 @@ jobs: runs-on: ubuntu-22.04 env: CARGO_TERM_COLOR: always - CARGO_REGISTRIES_FORGEJO_TOKEN: Bearer ${{ secrets.FORGEJO_CARGO_TOKEN }} - FORGEJO_CARGO_INDEX: ${{ github.server_url }}/${{ github.repository_owner }}/_cargo-index.git steps: - name: Check out repository uses: https://data.forgejo.org/actions/checkout@v4 @@ -29,17 +27,6 @@ jobs: rustup default stable cargo --version - - name: Configure Cargo registry - run: | - mkdir -p "$HOME/.cargo" - cat > "$HOME/.cargo/config.toml" < src/main.rs && cargo build --release && rm -rf src + +COPY src ./src +RUN touch src/main.rs && cargo build --release --locked + +FROM debian:bookworm-slim + +COPY --from=builder /app/target/release/upvoters /usr/local/bin/upvoters + +CMD ["upvoters"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e6b2c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Alex Selimov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1b07d58..c6ce19b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,86 @@ -# uprs +# upvoters ☝️ -Simple Rust backend for supporting anonymous like/upvote functionality in hugo sites. +upvoters is a basic anonymous voting system that can be added to a blog. +I recently reworked my personal blog with the Hugo Bear blog theme. +I came across the [creator's (Herman Martinus) blogging site](https://herman.bearblog.dev/) which had this really cool anonymous upvote system. +I wanted something similar and decided to implement it myself. +The goal is maximum simplicity. + +## Important Notes + +This service does NOT have any authentication/authorization, so you should NOT serve this to the public web. +I'm using this by just serving it on the same VPS as my blog. + + +## Requirements + +- Rust (stable) +- PostgreSQL 16 + +## Configuration + +The following environment variables are required: + +| Variable | Description | +|------------------------------|----------------------------------------------------------------------------------------| +| `POSTGRES_CONNECTION_STRING` | PostgreSQL connection string (e.g. `postgres://user:password@localhost:5432/upvoters`) | + +## Running locally + +### With Docker Compose + +Builds and starts both the database and app: + +```sh +docker compose up --build +``` + +The server will be available at `http://localhost:3000`. + +### Without Docker + +Start the database: + +```sh +docker compose up -d db +``` + +Run the server: + +```sh +POSTGRES_CONNECTION_STRING=postgres://uprs:password123@localhost:5432/uprs cargo run +``` + +The server listens on port `3000`. + +## API + +| Method | Endpoint | Description | +|---|---|---| +| `GET` | `/health` | Health check | +| `POST` | `/posts/{slug}/vote` | Upvote a post | +| `DELETE` | `/posts/{slug}/vote` | Remove a vote from a post | +| `GET` | `/posts/{slug}/votes` | Get vote count and whether the current user has voted | + +Voter identity is tracked via a `voter_id` cookie. One is set automatically on first request. + +### `GET /posts/{slug}/votes` response + +```json +{ + "vote_count": 42, + "voted": true +} +``` + +## Running tests + +Tests require a running PostgreSQL instance at `postgres://uprs:password123@localhost:5432/uprs`. + +```sh +cargo test +``` + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/docker-compose.yml b/docker-compose.yml index 86f7a6f..5a55b89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,5 +12,17 @@ services: - ./db/migrations:/docker-entrypoint-initdb.d # run initial schema ports: - "5432:5432" + + app: + build: . + container_name: uprs_app + restart: always + environment: + POSTGRES_CONNECTION_STRING: postgres://uprs:password123@db:5432/uprs + ports: + - "3000:3000" + depends_on: + - db + volumes: pgdata: diff --git a/src/main.rs b/src/main.rs index 6350267..0514583 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use uprs::state::AppState; +use upvoters::state::AppState; #[tokio::main] async fn main() { @@ -14,7 +14,7 @@ async fn main() { let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); tracing::info!("listening on {}", listener.local_addr().unwrap()); - axum::serve(listener, uprs::app(AppState::new().await)) + axum::serve(listener, upvoters::app(AppState::new().await)) .await .unwrap(); }