166 lines
4.8 KiB
Markdown
166 lines
4.8 KiB
Markdown
# upvoters ☝️
|
|
|
|
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.
|
|
|
|
|
|
## Table of Contents
|
|
|
|
- [Requirements](#requirements)
|
|
- [Configuration](#configuration)
|
|
- [Running locally](#running-locally)
|
|
- [API](#api)
|
|
- [Running tests](#running-tests)
|
|
- [Actual deployment](#actual-deployment)
|
|
- [License](#license)
|
|
|
|
## 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`) |
|
|
| `ALLOWED_ORIGINS` | Comma-separated list of allowed CORS origins (e.g. `localhost:1313,example.com`) |
|
|
| `PORT` | Port to listen on (default: `3000`) |
|
|
|
|
## 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
|
|
```
|
|
|
|
## Actual deployment
|
|
|
|
Here are the instructions for how I deployed this service. You might be able to just run it through the docker-compose on a VPS but I already had a postgres server set up:
|
|
|
|
1. Install dependencies (rust/postgres) (For debian server).
|
|
```bash
|
|
sudo apt update
|
|
sudo apt install -y postgresql postgresql-contrib
|
|
```
|
|
2. Create `postgres` user to manage postgres and `upvoters` user to run the app:
|
|
```bash
|
|
# Create the system user that will run the app
|
|
sudo useradd --system --home /opt/upvoters --create-home upvoters
|
|
|
|
# Create the PostgreSQL role with peer auth (no password needed)
|
|
sudo -u postgres psql -c "CREATE USER upvoters;"
|
|
sudo -u postgres psql -c "CREATE DATABASE upvoters OWNER upvoters;"
|
|
|
|
# Enable peer auth for the upvoters role (append to pg_hba.conf)
|
|
echo "local upvoters upvoters peer" | sudo tee -a /etc/postgresql/16/main/pg_hba.conf
|
|
sudo systemctl reload postgresql
|
|
```
|
|
3. Create the votes table as the `upvoters` user
|
|
```bash
|
|
sudo -u upvoters psql upvoters
|
|
```
|
|
```sql
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TABLE IF NOT EXISTS votes (
|
|
slug text not null,
|
|
voter_id uuid not null,
|
|
created_at timestamptz NOT NULL DEFAULT timezone('utc'::text, now()),
|
|
primary key (slug, voter_id)
|
|
);
|
|
```
|
|
4. Download the binary (You can also install rust and build yourself if you prefer). Replace `v0.2.0` with the latest release tag.
|
|
```bash
|
|
sudo curl https://forge.alexselimov.com/aselimov/upvoters/releases/download/v0.2.0/upvoters-linux-x86_64 --output /opt/upvoters/upvoters
|
|
sudo chmod +x /opt/upvoters/upvoters
|
|
sudo chown upvoters /opt/upvoters/upvoters
|
|
```
|
|
5. Create a systemd service at /etc/systemd/system/upvoters.service
|
|
```
|
|
[Unit]
|
|
Description=upvoters rust backend
|
|
After=network.target postgresql.service
|
|
|
|
[Service]
|
|
User=upvoters
|
|
Group=upvoters
|
|
WorkingDirectory=/opt/upvoters
|
|
ExecStart=/opt/upvoters/upvoters
|
|
Restart=on-failure
|
|
# The below connection string works if you have peer auth enabled and have postgresql running on
|
|
# a socket
|
|
Environment='POSTGRES_CONNECTION_STRING=postgresql:///upvoters?host=/var/run/postgresql'
|
|
Environment='ALLOWED_ORIGINS=https://example.com'
|
|
# Specify a PORT if the default 3000 is already taken
|
|
Environment='PORT=3000'
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
6. Enable and start the service
|
|
```bash
|
|
sudo systemctl enable --now upvoters
|
|
```
|
|
7. Test from VPS using curl
|
|
```bash
|
|
curl https://example.com/api/posts/tests/votes
|
|
```
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|