Compare commits

..

No commits in common. "e5d0219df8da6d83b97f5b61eee679c1cfd603e3" and "17d0709ed61ce6306caa79010da25a6617e26bd4" have entirely different histories.

8 changed files with 26 additions and 150 deletions

View file

@ -1,4 +1,4 @@
name: Publish Release name: Publish Cargo Package
on: on:
push: push:
@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
env: env:
CARGO_TERM_COLOR: always 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: steps:
- name: Check out repository - name: Check out repository
uses: https://data.forgejo.org/actions/checkout@v4 uses: https://data.forgejo.org/actions/checkout@v4
@ -27,6 +29,17 @@ jobs:
rustup default stable rustup default stable
cargo --version cargo --version
- name: Configure Cargo registry
run: |
mkdir -p "$HOME/.cargo"
cat > "$HOME/.cargo/config.toml" <<EOF
[registries.forgejo]
index = "${FORGEJO_CARGO_INDEX}"
[net]
git-fetch-with-cli = true
EOF
- name: Verify tag matches package version - name: Verify tag matches package version
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
run: | run: |
@ -43,17 +56,12 @@ jobs:
exit 1 exit 1
fi fi
- name: Build release binary - name: Check package can be published
run: | run: |
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
cargo build --release --locked cargo publish --dry-run --locked --registry forgejo
mv target/release/upvoters upvoters-linux-x86_64
- name: Create release - name: Publish package
uses: https://data.forgejo.org/actions/forgejo-release@v2 run: |
with: . "$HOME/.cargo/env"
direction: upload cargo publish --locked --registry forgejo
tag: ${{ github.ref_name }}
release-notes: "Release ${{ github.ref_name }}"
files: upvoters-linux-x86_64
token: ${{ secrets.GITHUB_TOKEN }}

2
Cargo.lock generated
View file

@ -1961,7 +1961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "upvoters" name = "uprs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",

View file

@ -1,8 +1,7 @@
[package] [package]
name = "upvoters" name = "uprs"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
license = "MIT"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"

View file

@ -1,15 +0,0 @@
FROM rust:1.88-slim AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > 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"]

21
LICENSE
View file

@ -1,21 +0,0 @@
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.

View file

@ -1,86 +1,3 @@
# upvoters ☝️ # uprs
upvoters is a basic anonymous voting system that can be added to a blog. Simple Rust backend for supporting anonymous like/upvote functionality in hugo sites.
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).

View file

@ -12,17 +12,5 @@ services:
- ./db/migrations:/docker-entrypoint-initdb.d # run initial schema - ./db/migrations:/docker-entrypoint-initdb.d # run initial schema
ports: ports:
- "5432:5432" - "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: volumes:
pgdata: pgdata:

View file

@ -1,5 +1,5 @@
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use upvoters::state::AppState; use uprs::state::AppState;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -14,7 +14,7 @@ async fn main() {
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
tracing::info!("listening on {}", listener.local_addr().unwrap()); tracing::info!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, upvoters::app(AppState::new().await)) axum::serve(listener, uprs::app(AppState::new().await))
.await .await
.unwrap(); .unwrap();
} }