Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# MQTT Mosquitto credentials
MQTT_USERNAME=pinball_user
MQTT_PASSWORD=change_me

# PostgreSQL
POSTGRES_DB=pinball_db
POSTGRES_USER=pinball_user
POSTGRES_PASSWORD=change_me
75 changes: 69 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,91 @@
# Pinball Three.js

Ce projet a pour ambition de recréer lexpérience dun flipper physique à travers une architecture web moderne, distribuée et interactive.
Lobjectif nest pas seulement de reproduire un jeu, mais de concevoir un système complet temps réel, combinant :
Ce projet a pour ambition de recréer l'expérience d'un flipper physique à travers une architecture web moderne, distribuée et interactive.
L'objectif n'est pas seulement de reproduire un jeu, mais de concevoir un système complet temps réel, combinant :

- Simulation physique 3D
- Communication distribuée (WebSockets + MQTT)
- Intégration IoT
- Expérience utilisateur immersive

Le projet vise à démontrer la capacité à concevoir une architecture logicielle complexe inspirée dun système réel (flipper mécanique) tout en exploitant les technologies web contemporaines.
Ce projet est réalisé dans le cadre validation de la troisième année de _Bachelor Développeur Web_ à HETIC, et sinscrit dans une démarche dapprentissage par la pratique, en appliquant les concepts de développement web, darchitecture logicielle et de communication temps réel.
Le projet vise à démontrer la capacité à concevoir une architecture logicielle complexe inspirée d'un système réel (flipper mécanique) tout en exploitant les technologies web contemporaines.
Ce projet est réalisé dans le cadre validation de la troisième année de _Bachelor Développeur Web_ à HETIC, et s'inscrit dans une démarche d'apprentissage par la pratique, en appliquant les concepts de développement web, d'architecture logicielle et de communication temps réel.

---

## Lancer le projet

Ce projet est contenu dans un conteneur Docker. Pour le lancer, utilisez la commande suivante dans le terminal à la racine du projet :

**Développement :**

```bash
docker compose -f compose.dev.yml up -d
```

Services principaux disponibles en développement :

- Frontend : `http://localhost:5173`
- Backend API : `http://localhost:3000`
- Drizzle Studio backend : `localhost:4983`
- PostgreSQL : `localhost:5432`
- Mosquitto : `localhost:1883`

---

## Base de données

La base de données du projet repose sur :

- PostgreSQL
- Drizzle ORM
- Drizzle Studio pour la visualisation en développement

Tables actuellement initialisées :

- `users`
- `games`
- `scores`

### Exécuter les migrations en développement

Démarrer les services nécessaires :

```bash
docker compose -f compose.dev.yml up -d postgres backend drizzle-studio
```

Appliquer les migrations Drizzle depuis le conteneur backend :

```bash
docker compose -f compose.dev.yml exec backend pnpm db:migrate
```

### Générer une nouvelle migration

Après modification du schéma dans `backend/src/db/schema.ts` :

```bash
docker compose -f compose.dev.yml exec backend pnpm db:generate
docker compose -f compose.dev.yml exec backend pnpm db:migrate
```

### Accéder à la base avec Drizzle Studio

Ouvrir `https://local.drizzle.studio`.

Le service `drizzle-studio` est disponible uniquement en développement.
Le port `4983` correspond au backend local utilisé par Drizzle Studio.

### Production

En production, les migrations sont exécutées par le service `migrate` avant le démarrage du backend.
Il n'y a pas d'interface d'administration de la base exposée.

Commande de déploiement :

```bash
docker compose -f compose.prod.yml up -d --build
```

---

## Tests
Expand Down
10 changes: 10 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
dist
.pnpm-store
.env
.git
.gitignore
README.md

coverage
*.log
4 changes: 4 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
.pnpm-store/*
.env
45 changes: 45 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# --- Étape 1 : Toutes les dépendances (pour le build) ---
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile

# --- Étape 2 : Construction du code (TypeScript -> JS) ---
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY package.json tsconfig.json drizzle.config.ts ./
COPY drizzle ./drizzle
COPY src ./src
RUN corepack enable && pnpm run build

# --- Étape 3 : Uniquement les dépendances de prod ---
FROM node:20-alpine AS prod-deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Le flag --prod ignore les devDependencies !
RUN corepack enable && pnpm install --prod --frozen-lockfile

# --- Étape 4 : Image finale (Runner) ---
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# On active corepack une bonne fois pour toutes dans l'image
RUN corepack enable

# Sécurité : On bascule sur l'utilisateur non privilégié
USER node

# On récupère le strict minimum
COPY package.json ./
COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
COPY --chown=node:node drizzle.config.ts ./
COPY --from=build --chown=node:node /app/drizzle ./drizzle

EXPOSE 3000

# Commande de démarrage épurée
CMD ["pnpm", "run", "start"]
76 changes: 76 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Backend

Le backend fournit :

- l'API Express
- la connexion PostgreSQL
- le schema Drizzle
- les migrations SQL

## Structure

- `src/server.ts` : point d'entree de l'API
- `src/db/client.ts` : client PostgreSQL et Drizzle
- `src/db/schema.ts` : definition des tables
- `drizzle/` : migrations generees
- `drizzle.config.ts` : configuration Drizzle Kit

## Scripts

Depuis `backend/` :

```bash
pnpm dev
pnpm build
pnpm db:generate
pnpm db:migrate
pnpm db:studio
```

## Workflow recommande

1. Modifier le schema dans `src/db/schema.ts`
2. Generer la migration
3. Appliquer la migration
4. Verifier le resultat dans Drizzle Studio

Exemple via Docker :

```bash
docker compose -f compose.dev.yml up -d postgres backend drizzle-studio
docker compose -f compose.dev.yml exec backend pnpm db:generate
docker compose -f compose.dev.yml exec backend pnpm db:migrate
```

## Tables actuelles

- `users`
- `games`
- `scores`

Relations :

- `games.user_id -> users.id`
- `scores.game_id -> games.id`

## Verification rapide

Verifier la sante de l'API :

```bash
curl http://localhost:3000/health
```

Lister les tables PostgreSQL :

```bash
docker compose -f compose.dev.yml exec postgres psql -U pinball_user -d pinball_db -c "\dt"
```

Ouvrir Drizzle Studio :

```text
https://local.drizzle.studio
```

Le port `4983` est expose localement pour le backend de Drizzle Studio.
19 changes: 19 additions & 0 deletions backend/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import "dotenv/config";
import { defineConfig } from "drizzle-kit";

const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
throw new Error("DATABASE_URL is required to generate or run migrations.");
}

export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: databaseUrl,
},
verbose: true,
strict: true,
});
30 changes: 30 additions & 0 deletions backend/drizzle/0000_thick_meltdown.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE "users" (
"id" serial PRIMARY KEY NOT NULL,
"username" varchar(255) NOT NULL,
"email" varchar(255) NOT NULL,
"password_hash" varchar(255) NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "users_username_unique" UNIQUE("username"),
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "games" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"played_duration_seconds" integer NOT NULL,
"final_score" integer NOT NULL,
"played_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "scores" (
"id" serial PRIMARY KEY NOT NULL,
"game_id" integer NOT NULL,
"points_earned" integer NOT NULL,
"collision_event" varchar(255) NOT NULL,
"game_timestamp" double precision NOT NULL
);
--> statement-breakpoint
ALTER TABLE "games" ADD CONSTRAINT "games_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
--> statement-breakpoint
ALTER TABLE "scores" ADD CONSTRAINT "scores_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action;
Loading
Loading