To boldly go where no one has gone before

Tag: Self Hosted

Database per collezionisti gratuito Parte 3

Prossimo passo: progettare il database. Prima di creare le tabelle voglio che decidiamo bene come gestire la collezione. Una proposta di base è:

Catalogo Ufficiale (contiene tutte le carte esistenti)
- sets
- cards

Collezione Paolo (Marco.. Giada etc..) contiene solo quelle possedute
- collection

Carta: Charizard ex 125/197
- Quantità: 3
- Lingua: ITA
- Condizione: Near Mint

e una seconda riga (per le carte aggiuntive con lingua diversa)
- Quantità: 1
- Lingua: ENG
- Condizione: Mint

Il DB sarà anche multiutente

Creazione schema database V1

Entriamo in PostgreSQL:
docker exec -it pokemon-postgres psql -U pokemon -d pokemon

Creiamo la Tabella utenti digitando:
CREATE TABLE users (
    id              BIGSERIAL PRIMARY KEY,
    username        VARCHAR(50) UNIQUE NOT NULL,
    email           VARCHAR(255) UNIQUE NOT NULL,
    password_hash   VARCHAR(255) NOT NULL,
    display_name    VARCHAR(100),
    is_admin        BOOLEAN DEFAULT FALSE,
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Per la Tabella Espansioni digitare:
CREATE TABLE sets (
    id              VARCHAR(50) PRIMARY KEY,
    name            VARCHAR(255) NOT NULL,
    series          VARCHAR(255),
    release_date    DATE,
    total_cards     INTEGER,
    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Per la Tabella Carte digitare:
CREATE TABLE cards (
    id              VARCHAR(100) PRIMARY KEY,
    set_id          VARCHAR(50) NOT NULL,

    card_number     VARCHAR(20),
    name            VARCHAR(255) NOT NULL,

    rarity          VARCHAR(100),
    supertype       VARCHAR(100),

    image_small     TEXT,
    image_large     TEXT,

    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_cards_set
      FOREIGN KEY (set_id)
      REFERENCES sets(id)
);

Per la Tabella Collezioni digitare:
CREATE TABLE collection (
    id              BIGSERIAL PRIMARY KEY,

    user_id         BIGINT NOT NULL,
    card_id         VARCHAR(100) NOT NULL,

    quantity        INTEGER NOT NULL DEFAULT 1,

    language        VARCHAR(10) NOT NULL,
    condition       VARCHAR(30) NOT NULL,

    variant         VARCHAR(30) NOT NULL,

    graded          BOOLEAN DEFAULT FALSE,

    notes           TEXT,

    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_collection_user
      FOREIGN KEY (user_id)
      REFERENCES users(id),

    CONSTRAINT fk_collection_card
      FOREIGN KEY (card_id)
      REFERENCES cards(id)
);

Per la Tabella Wishlist digitare:
CREATE TABLE wishlist (
    id              BIGSERIAL PRIMARY KEY,

    user_id         BIGINT NOT NULL,
    card_id         VARCHAR(100) NOT NULL,

    priority        SMALLINT DEFAULT 1,
    notes           TEXT,

    created_at      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_wishlist_user
      FOREIGN KEY (user_id)
      REFERENCES users(id),

    CONSTRAINT fk_wishlist_card
      FOREIGN KEY (card_id)
      REFERENCES cards(id)
);

Per la Tabella Indici digitare:
CREATE INDEX idx_cards_name
ON cards(name);

CREATE INDEX idx_cards_set
ON cards(set_id);

CREATE INDEX idx_collection_user
ON collection(user_id);

CREATE INDEX idx_collection_card
ON collection(card_id);

Primo utente amministratore:
INSERT INTO users (
    username,
    email,
    password_hash,
    display_name,
    is_admin
)
VALUES (
    'paolo',
    'paolo@local',
    'TEMP',
    'Paolo',
    TRUE
);

Verifica finale:
\dt

Dovresti vedere:
cards
collection
sets
users
wishlist

Per verifica l'utente:
SELECT * FROM users;

Prossimo passo: Backend FastAPI

/opt/pokemon

├── backend
├── frontend
├── postgres
├── imports
└── docker-compose.yml

Creiamo il backend:
cd /opt/pokemon/backend
mkdir app
cd app
touch main.py

Verifica: (se non hai tree: apt install tree -y)
tree /opt/pokemon/backend

Dovresti vedere:
backend
└── app
    └── main.py

Esegui:
mkdir -p /opt/pokemon/backend/app
touch /opt/pokemon/backend/app/main.py

Adesso creiamo il primo backend minimale e lo facciamo girare in Docker. Passo 1: file Python

nano /opt/pokemon/backend/app/main.py

e inserisci:
from fastapi import FastAPI

app = FastAPI(
    title="Pokemon Collection API"
)


@app.get("/")
def root():
    return {
        "app": "Pokemon Collection",
        "status": "running"
    }


@app.get("/health")
def health():
    return {
        "status": "ok"
    }

Salva ed esci con CTRL X e poi Y e invio

nano /opt/pokemon/backend/requirements.txt

e inserisci:
fastapi
uvicorn[standard]

Salva ed esci con CTRL X e poi Y e invio

nano /opt/pokemon/backend/Dockerfile

e inserisci:
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Salva ed esci con CTRL X e poi Y e invio

nano /opt/pokemon/docker-compose.yml

e sostituisci il contenuto con:
services:

  postgres:
    image: postgres:17
    container_name: pokemon-postgres
    restart: unless-stopped

    environment:
      POSTGRES_DB: pokemon
      POSTGRES_USER: pokemon
      POSTGRES_PASSWORD: pokemon123

    ports:
      - "5432:5432"

    volumes:
      - ./postgres:/var/lib/postgresql/data

  api:
    build: ./backend
    container_name: pokemon-api
    restart: unless-stopped

    ports:
      - "8000:8000"

    depends_on:
      - postgres

Salva ed esci con CTRL X e poi Y e invio

Build e avvio

cd /opt/pokemon
docker-compose up -d --build

partirà la compilazione, se è andato tutto bene vedrai:
Container pokemon-postgres  Running                                                                                                          Container pokemon-api       Started

Per ulteriore verifixa comando:
curl http://localhost:8000/health
la risposta dovrebbe essere {"status":"ok"}

Se tutto funziona sarà già visibile l'accesso web al db dall'indirizzo http://ip vm:8000/docs dove ip vm è l'indirizzo ip della tua virtual machine o PC, dovrebbe comparire la pagina "Pokemon Collection API"

Creiamo il file database.py

nano /opt/pokemon/backend/app/database.py

con dentro:
from sqlalchemy import create_engine

DATABASE_URL = "postgresql://pokemon:pokemon123@postgres:5432/pokemon"

engine = create_engine(DATABASE_URL)

Salva ed esci con CTRL X e poi Y e invio

Creiamo il file requirements.txt

nano /opt/pokemon/backend/requirements.txt

Sostituisci il contenuto con:
fastapi
uvicorn[standard]
sqlalchemy
psycopg2-binary

Ricostruisci il container

cd /opt/pokemon
docker-compose down
docker-compose build --no-cache
docker-compose up -d

Verifichiamo aprendo la pagina web http://vm ip:8000/db dovrebbe comparire {"users":1}

Creiamo le API reali

nano /opt/pokemon/backend/app/main.py

Aggiungi in calce:
@app.get("/users")
def get_users():

    with engine.connect() as conn:

        result = conn.execute(
            text("""
                SELECT
                    id,
                    username,
                    display_name,
                    is_admin
                FROM users
                ORDER BY id
            """)
        )

        rows = result.mappings().all()

    return rows


@app.get("/sets")
def get_sets():

    with engine.connect() as conn:

        result = conn.execute(
            text("""
                SELECT *
                FROM sets
                ORDER BY name
            """)
        )

        rows = result.mappings().all()

    return rows


@app.get("/cards")
def get_cards():

    with engine.connect() as conn:

        result = conn.execute(
            text("""
                SELECT *
                FROM cards
                ORDER BY name
                LIMIT 100
            """)
        )

        rows = result.mappings().all()

    return rows

Salva ed esci con CTRL X e poi Y e invio
Ricostruzione solita con:
cd /opt/pokemon
docker-compose up -d --build

finita la ricompilazione dovrebbe dare nuovamente "Runnig" e "Started" Verifichiamo aprendo la pagina web http://vm ip:8000/db dovrebbe comparire:
[
  {
    "id": 1,
    "username": "paolo",
    "display_name": "Paolo",
    "is_admin": true
  }
]

<—– Articolo Parte 2 | Articolo Parte 4 —–>

Database per collezionisti gratuito Parte 2

Creare la struttura del progetto:

mkdir -p /opt/pokemon
cd /opt/pokemon

verifica con:
pwd

deve mostrare:
/opt/pokemon

Installa quindi Docker-Compose:
apt install -y docker.io docker-compose git curl nano

Poi verifica:
systemctl enable --now docker
systemctl status docker

Dovrebbe comparire:
active (running)

Creare le cartelle che servono:

mkdir postgres backend frontend backups imports
ls -l

Dovresti vedere:
backend
backups
frontend
imports
postgres

Creare il file docker-compose.yml

cd /opt/pokemon
nano docker-compose.yml

Aperto il file inserisci:
services:
  postgres:
    image: postgres:17
    container_name: pokemon-postgres
    restart: unless-stopped

    environment:
      POSTGRES_DB: pokemon
      POSTGRES_USER: pokemon
      POSTGRES_PASSWORD: pokemon123

    ports:
      - "5432:5432"

    volumes:
      - ./postgres:/var/lib/postgresql/data

Per salvare CTRL+X e poi Y e invio

Avvio del database

docker-compose up -d
docker ps

Dovresti vedere qualcosa simile a:
pokemon-postgres
postgres:17
Up

Per verificare PostGres sempre da Root esegui:

docker exec -it pokemon-postgres psql -U pokemon -d pokemon

Se tutto è corretto vedrai:
pokemon=#

digita:
SELECT version();

Vedrai una struttura vuota che però indica che il DB è funzionante e pronto ad essere riempito.

per uscire digita il comando:
q

e torni al prompt PostgreSQL:
pokemon=#

Poi esci da PostgreSQL con il comando:
\q

Abbiamo completato tutta la parte infrastrutturale. Prossimo passo: progettare il database.

<—– Articolo Parte 1 | Articolo Parte 3 —–>

Database per collezionisti gratuito

Parte 1

Per la mia collezione di carte Pokemon (poteva essere di monete o altro) uso un App a pagamento sfruttabile via PC, Tablet etc.. per catalogare tutte le carte. Il costo però è piuttosto alto, circa 50 euro ad anno, ho deciso quindi di creare qualcosa di “home made”. Con un catalogo gestito in autonomia, si evita anche di condividere le proprie informazioni, con persone di cui spesso non si sa nulla.

In pochi passi si otterrà un risultato come quello dell’immagine sotto riportata dove le carte sono tutte in bianco e nero perchè il DB è vuoto, sarebbe a colori se posseduta.

Stato progetto (14/09/2026)

✅ VM Debian

✅ Docker

✅ PostgreSQL

✅ FastAPI

✅ Import catalogo Pokémon

✅ 20.479 carte

✅ Ricerca carte

✅ Collezione personale

✅ Dashboard Web

✅ Carte possedute evidenziate

⬜ Login utenti

⬜ Wishlist

⬜ Gestione set multipli

Anticipo già per chi ha intenzione di seguire questa guida, che i primi due o tre articoli saranno di pubblico accesso, per i successivi, bisognerà appartenere al gruppo Contributor. Fanno parte di questo gruppo gli utenti che hanno fatto una donazione per un’importo non inferiore ad 1 euro.

Avendo un PC su cui ho installato il sistema di virtualizzazione Proxmox “open source”, posso creare facilmente virtual machine, di seguito semplicemente “VM”. Avendo anche una linea internet in Fibra da 2.5Gbit, anche la parte di connettività è eccellente.

Partiamo quindi con la creazione di una VM, va bene anche un PC dedicato, un raspberry etc.. l’importante è che abbia un minimo di caratteristiche HW come 2 CPU, Ram 4 gb e un disco da 40 gb o superiore. Nel mio caso ho creato una nuova VM linux su base Debian ultima release, dando 2 CPU, 4G di Ram e 40Gb di disco.

Installazione Debian

Non essendo una guida su come si installa un sistema operativo sorvolerò sulle fasi iniziali, descrivendo solo le parti che ci permettano di avere un server minimale. Durante l’installazione dare come nome macchina “pokemon” o quello che volete, nella sezione dominio non mettere nulla e infine alla schermata “Software selection” lascia selezionate solo le opzioni sotto riportate:

✅ SSH server

✅ Standard system utilities

e deseleziona:

❌ Debian desktop environment

❌ GNOME

❌ KDE

❌ XFCE

❌ Web server

❌ Tutto il resto

Conclusa l’installazione eseguire i canonici aggiornamenti del S.O .

Da root esegui:
apt update
apt full-upgrade -y

al termine:
reboot

Rientra poi nella VM e verifica che sia tutto ok:

uname -a
apt list --upgradable

Se il secondo comando non mostra pacchetti da aggiornare (o ne mostra pochissimi), siamo pronti per il passo successivo.

Da root esegui:
apt install -y docker.io docker-compose-v2 git curl nano

Al termine verifica: 
docker --version

e anche:
docker compose version

Dovresti ottenere qualcosa di simile: Docker version 28.x, Docker Compose version v2.x

<—– | Articolo Parte 2 —–>

© 2026 Paolo Tuttoilmondo

Tema di Anders Noren — Su ↑