Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6979e73307 | |||
| 97e21bcb9b | |||
| f4c7f4dfc2 | |||
| fb2c6368d8 | |||
| a3b201fa09 | |||
| 44c9f0e705 | |||
| e5d0219df8 | |||
| a2c678cdba | |||
| 17d0709ed6 |
12 changed files with 383 additions and 36 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
name: Publish Cargo Package
|
name: Publish Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -8,11 +8,9 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: docker
|
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
|
||||||
|
|
@ -29,17 +27,6 @@ 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: |
|
||||||
|
|
@ -56,12 +43,17 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Check package can be published
|
- name: Build release binary
|
||||||
run: |
|
run: |
|
||||||
. "$HOME/.cargo/env"
|
. "$HOME/.cargo/env"
|
||||||
cargo publish --dry-run --locked --registry forgejo
|
cargo build --release --locked
|
||||||
|
mkdir -p dist/release
|
||||||
|
mv target/release/upvoters dist/release/upvoters-linux-x86_64
|
||||||
|
|
||||||
- name: Publish package
|
- name: Create release
|
||||||
run: |
|
uses: https://data.forgejo.org/actions/forgejo-release@v2
|
||||||
. "$HOME/.cargo/env"
|
with:
|
||||||
cargo publish --locked --registry forgejo
|
direction: upload
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
release-notes: "Release ${{ github.ref_name }}"
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -1961,8 +1961,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uprs"
|
name = "upvoters"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "uprs"
|
name = "upvoters"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
@ -19,7 +20,7 @@ sqlx = { version = "0.8.6", features = [
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tower = "0.5"
|
tower = "0.5"
|
||||||
tower-http = { version = "0.6", features = ["trace"] }
|
tower-http = { version = "0.6", features = ["trace", "cors"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
uuid = { version = "1.22.0", features = ["v4"] }
|
uuid = { version = "1.22.0", features = ["v4"] }
|
||||||
|
|
|
||||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
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
Normal file
21
LICENSE
Normal file
|
|
@ -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.
|
||||||
276
README.md
276
README.md
|
|
@ -1,3 +1,275 @@
|
||||||
# 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 so that I can use it with my Hugo static site.
|
||||||
|
It's directly integrated in [my theme]("https://forge.alexselimov.com/aselimov/hugo-bearcub") which attempts to mimic Herman Martinus' bear blog style.
|
||||||
|
I've added steps on how I set it up in my theme [at the bottom of this README](#setting-it-up-with-hugo).
|
||||||
|
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Running locally](#running-locally)
|
||||||
|
- [API](#api)
|
||||||
|
- [Running tests](#running-tests)
|
||||||
|
- [Actual deployment](#actual-deployment)
|
||||||
|
- [Setting it up with Hugo](#setting-it-up-with-hugo)
|
||||||
|
- [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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting it up with Hugo
|
||||||
|
|
||||||
|
I set this up in my [Voting Bear Cub Theme](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main).
|
||||||
|
The components are:
|
||||||
|
1. Javascript file to handle upvoting. This function is automatically called on page loads
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
// Define the slug
|
||||||
|
const slug = location.pathname.replace(/^\/|\/$/g, "").replaceAll("/", "-");
|
||||||
|
const API = window.UPVOTE_API;
|
||||||
|
|
||||||
|
const btn = document.getElementById("upvote-btn");
|
||||||
|
const count = document.getElementById("upvote-count");
|
||||||
|
|
||||||
|
// On load fetch curent vote count and whether the button should be clicked on or off
|
||||||
|
const res = await fetch(`${API}/posts/${slug}/votes`, {
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
count.textContent = data.vote_count;
|
||||||
|
if (data.voted) btn.setAttribute("voted", true);
|
||||||
|
|
||||||
|
btn.addEventListener("click", async () => {
|
||||||
|
const alreadyVoted = btn.hasAttribute("voted");
|
||||||
|
|
||||||
|
const method = alreadyVoted ? "DELETE" : "POST";
|
||||||
|
const r = await fetch(`${API}/posts/${slug}/vote`, {
|
||||||
|
method,
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
const updated = await fetch(`${API}/posts/${slug}/votes`, {
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
const d = await updated.json();
|
||||||
|
count.textContent = d.vote_count;
|
||||||
|
if (d.voted) btn.setAttribute("voted", false);
|
||||||
|
else btn.removeAttribute("voted");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
2. Button component in single.html template placed after the main post body. **Note:** I guard this behind a config check so users can use my theme without setting up upvoters.
|
||||||
|
```html
|
||||||
|
{{ if .Site.Params.upvotes }}
|
||||||
|
<div class="upvote">
|
||||||
|
<button id="upvote-btn" aria-label="Upvote">
|
||||||
|
<span id="upvote-chevron">⌃</span>
|
||||||
|
<span id="upvote-count">—</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<script>window.UPVOTE_API = "{{ .Site.Params.upvoteApi }}";</script>
|
||||||
|
<script src="/upvote.js"></script>
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
3. Styles for the button:
|
||||||
|
```css
|
||||||
|
.upvote {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
gap: 0.2rem;
|
||||||
|
margin-block: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#upvote-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: currentColor;
|
||||||
|
font-size: calc(var(--size) * 4);
|
||||||
|
line-height: 0.8;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
opacity: 0.4;
|
||||||
|
transition: opacity 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upvote-btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upvote-btn[voted] {
|
||||||
|
color: var(--color-primary);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upvote-chevron {
|
||||||
|
display: block;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: -0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upvote-count {
|
||||||
|
font-size: calc(var(--size) * 1.6);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see [LICENSE](LICENSE).
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:16
|
image: postgres:16
|
||||||
container_name: uprs_db
|
container_name: upvoters_db
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: uprs
|
POSTGRES_USER: upvoters
|
||||||
POSTGRES_PASSWORD: password123
|
POSTGRES_PASSWORD: password123
|
||||||
POSTGRES_DB: uprs
|
POSTGRES_DB: upvoters
|
||||||
volumes:
|
volumes:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
- ./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: upvoters_app
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_CONNECTION_STRING: postgres://upvoters:password123@db:5432/upvoters
|
||||||
|
ALLOWED_ORIGINS: http://localhost:1313
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|
|
||||||
13
src/env.rs
13
src/env.rs
|
|
@ -3,6 +3,8 @@ use std::env;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Env {
|
pub struct Env {
|
||||||
pub postgres_connection_string: String,
|
pub postgres_connection_string: String,
|
||||||
|
pub allowed_origins: Vec<String>,
|
||||||
|
pub port: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Env {
|
impl Env {
|
||||||
|
|
@ -10,8 +12,19 @@ impl Env {
|
||||||
let postgres_connection_string = env::var("POSTGRES_CONNECTION_STRING")
|
let postgres_connection_string = env::var("POSTGRES_CONNECTION_STRING")
|
||||||
.expect("Missing POSTGRES_CONNECTION_STRING as an environment variable");
|
.expect("Missing POSTGRES_CONNECTION_STRING as an environment variable");
|
||||||
|
|
||||||
|
let allowed_origins = env::var("ALLOWED_ORIGINS")
|
||||||
|
.expect("Missing ALLOWED_ORIGINS as an environment variable")
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let port = env::var("PORT")
|
||||||
|
.unwrap_or_else(|_| "3000".to_string());
|
||||||
|
|
||||||
Env {
|
Env {
|
||||||
postgres_connection_string,
|
postgres_connection_string,
|
||||||
|
allowed_origins,
|
||||||
|
port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -5,9 +5,25 @@ pub mod test_helpers;
|
||||||
pub mod votes;
|
pub mod votes;
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use axum::http::{HeaderValue, Method, header};
|
||||||
use state::AppState;
|
use state::AppState;
|
||||||
|
use tower_http::cors::CorsLayer;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
pub fn app(state: AppState) -> Router {
|
pub fn app(state: AppState) -> Router {
|
||||||
routes::router(state).layer(TraceLayer::new_for_http())
|
let origins: Vec<HeaderValue> = state
|
||||||
|
.env
|
||||||
|
.allowed_origins
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.parse().expect("Invalid origin in ALLOWED_ORIGINS"))
|
||||||
|
.collect();
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.allow_origin(origins)
|
||||||
|
.allow_methods([Method::GET, Method::POST, Method::DELETE])
|
||||||
|
.allow_headers([header::CONTENT_TYPE])
|
||||||
|
.allow_credentials(true);
|
||||||
|
|
||||||
|
routes::router(state)
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.layer(cors)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
use uprs::state::AppState;
|
use upvoters::state::AppState;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
|
@ -12,9 +12,11 @@ async fn main() {
|
||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let state = AppState::new().await;
|
||||||
|
let port = &state.env.port;
|
||||||
|
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap();
|
||||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||||
axum::serve(listener, uprs::app(AppState::new().await))
|
axum::serve(listener, upvoters::app(state))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ pub mod db {
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
const DEFAULT_CONNECTION_STRING: &str =
|
const DEFAULT_CONNECTION_STRING: &str =
|
||||||
"postgres://uprs:password123@localhost:5432/uprs";
|
"postgres://upvoters:password123@localhost:5432/upvoters";
|
||||||
|
|
||||||
pub async fn test_pool() -> PgPool {
|
pub async fn test_pool() -> PgPool {
|
||||||
let conn = std::env::var("POSTGRES_CONNECTION_STRING")
|
let conn = std::env::var("POSTGRES_CONNECTION_STRING")
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,8 @@ mod tests {
|
||||||
db,
|
db,
|
||||||
env: Env {
|
env: Env {
|
||||||
postgres_connection_string: String::new(),
|
postgres_connection_string: String::new(),
|
||||||
|
allowed_origins: vec![],
|
||||||
|
port: "3000".to_string(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue