gabraken commited on
Commit
98da277
·
1 Parent(s): 29a88f8

Add nice tut

Browse files
backend/config.py CHANGED
@@ -3,6 +3,8 @@ from dotenv import load_dotenv
3
 
4
  load_dotenv()
5
 
 
 
6
  MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
7
  SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-secret-key-change-me")
8
 
 
3
 
4
  load_dotenv()
5
 
6
+ DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true"
7
+
8
  MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
9
  SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-secret-key-change-me")
10
 
backend/main.py CHANGED
@@ -44,7 +44,7 @@ from pathlib import Path
44
  from typing import Any, Optional
45
 
46
  import socketio
47
- from fastapi import APIRouter, Body, FastAPI, HTTPException
48
  from fastapi.middleware.cors import CORSMiddleware
49
  from fastapi.staticfiles import StaticFiles
50
  from fastapi.responses import FileResponse
@@ -56,8 +56,15 @@ from game.state import GameState, TUTORIAL_DUMMY_ID
56
  from lobby.manager import LobbyManager
57
  from lobby.safe_name import sanitize_player_name
58
  from voice import command_parser, stt
 
59
  import stats as _stats
60
 
 
 
 
 
 
 
61
  BOT_OFFER_DELAY = 10 # seconds before offering bot opponent
62
 
63
  logging.basicConfig(level=logging.INFO)
@@ -180,6 +187,10 @@ def get_stats_recent():
180
 
181
  fastapi_app.include_router(_stats_router)
182
 
 
 
 
 
183
  # Unit sound effects (generated by scripts/generate_unit_sounds.py)
184
  _SOUNDS_DIR = Path(__file__).parent / "static" / "sounds"
185
  _UNITS_SOUNDS_DIR = _SOUNDS_DIR / "units"
@@ -290,7 +301,7 @@ def _merged_walkable_polygon(polygons: list) -> list:
290
  return polygons[0]
291
 
292
 
293
- @fastapi_app.put("/api/map/walkable")
294
  async def save_walkable_polygon(body: dict = Body(...)):
295
  """Enregistre les polygones marchables (liste de polygones [x, y] en 0-100). Le jeu reçoit la fusion via GET."""
296
  polygons = body.get("polygons")
@@ -353,7 +364,7 @@ def _generate_geysers_around(cx: int, cy: int, count: int = 1, radius: float = 4
353
  return out
354
 
355
 
356
- @fastapi_app.put("/api/map/positions")
357
  async def save_map_positions(body: dict = Body(...)):
358
  """Enregistre positions de départ (2 ou 3, si 3 alors 2 seront tirées au sort par partie) et expansions."""
359
  import json
@@ -409,7 +420,7 @@ async def save_map_positions(body: dict = Body(...)):
409
  return {"status": "ok", "minerals_count": total_minerals, "geysers_count": total_geysers}
410
 
411
 
412
- @fastapi_app.put("/api/map/locations")
413
  async def save_map_locations(body: dict = Body(...)):
414
  """Enregistre les zones clés (locations) dans map.json."""
415
  import json
@@ -451,7 +462,7 @@ async def list_unit_sounds():
451
  return {"sounds": sounds}
452
 
453
 
454
- @fastapi_app.delete("/api/sounds/units/{unit}/{kind}")
455
  async def delete_unit_sound(unit: str, kind: str):
456
  """Supprime un son. Relancer le script régénère uniquement les manquants."""
457
  if not unit.replace("-", "").isalnum() or not kind.replace("-", "").isalnum():
@@ -485,7 +496,7 @@ async def list_building_sprites():
485
  return {"sprites": _list_sprites(_SPRITES_DIR / "buildings")}
486
 
487
 
488
- @fastapi_app.post("/api/sprites/generate")
489
  async def generate_sprites():
490
  """Lance la génération des sprites (unités + bâtiments) via l'API Mistral. Peut prendre plusieurs minutes."""
491
  import subprocess
@@ -522,7 +533,7 @@ def _run_sprite_script(extra_args: list[str]) -> subprocess.CompletedProcess:
522
  )
523
 
524
 
525
- @fastapi_app.post("/api/sprites/generate/units/{unit_id}")
526
  async def generate_one_unit_sprite(unit_id: str):
527
  """Régénère une seule image d'unité."""
528
  unit_id = unit_id.strip().lower()
@@ -536,7 +547,7 @@ async def generate_one_unit_sprite(unit_id: str):
536
  return {"status": "ok", "id": unit_id}
537
 
538
 
539
- @fastapi_app.post("/api/sprites/generate/buildings/{building_id}")
540
  async def generate_one_building_sprite(building_id: str):
541
  """Régénère une seule image de bâtiment."""
542
  building_id = building_id.strip().lower().replace(" ", "_")
@@ -556,7 +567,7 @@ async def list_resource_sprites():
556
  return {"sprites": _list_sprites(_SPRITES_DIR / "resources")}
557
 
558
 
559
- @fastapi_app.post("/api/sprites/generate/resources/{resource_id}")
560
  async def generate_one_resource_sprite(resource_id: str):
561
  """Régénère une seule icône de ressource (mineral, geyser)."""
562
  resource_id = resource_id.strip().lower()
@@ -579,7 +590,7 @@ async def list_icons():
579
  return {"icons": _list_sprites(_ICONS_DIR)}
580
 
581
 
582
- @fastapi_app.post("/api/sprites/rotate/{kind}/{sprite_id}")
583
  async def rotate_sprite(kind: str, sprite_id: str):
584
  """Fait pivoter un sprite de 90° dans le sens antihoraire (rotate CCW = tourne l'arme vers le haut)."""
585
  valid_kinds = {"units": _SPRITES_DIR / "units", "buildings": _SPRITES_DIR / "buildings", "resources": _SPRITES_DIR / "resources"}
@@ -600,7 +611,7 @@ async def rotate_sprite(kind: str, sprite_id: str):
600
  return {"status": "ok", "id": sprite_id, "kind": kind}
601
 
602
 
603
- @fastapi_app.post("/api/icons/rotate/{icon_id}")
604
  async def rotate_icon(icon_id: str):
605
  """Fait pivoter une icône UI de 90° dans le sens antihoraire."""
606
  icon_id = icon_id.strip().lower()
@@ -617,7 +628,7 @@ async def rotate_icon(icon_id: str):
617
  return {"status": "ok", "id": icon_id}
618
 
619
 
620
- @fastapi_app.post("/api/icons/generate/{icon_id}")
621
  async def generate_one_icon(icon_id: str):
622
  """Génère une icône UI symbolique (mineral, gas, supply)."""
623
  icon_id = icon_id.strip().lower()
 
44
  from typing import Any, Optional
45
 
46
  import socketio
47
+ from fastapi import APIRouter, Body, Depends, FastAPI, HTTPException
48
  from fastapi.middleware.cors import CORSMiddleware
49
  from fastapi.staticfiles import StaticFiles
50
  from fastapi.responses import FileResponse
 
56
  from lobby.manager import LobbyManager
57
  from lobby.safe_name import sanitize_player_name
58
  from voice import command_parser, stt
59
+ import config as _config
60
  import stats as _stats
61
 
62
+
63
+ def require_debug():
64
+ """FastAPI dependency — raises 403 in production (DEBUG=False)."""
65
+ if not _config.DEBUG:
66
+ raise HTTPException(status_code=403, detail="Not available in production")
67
+
68
  BOT_OFFER_DELAY = 10 # seconds before offering bot opponent
69
 
70
  logging.basicConfig(level=logging.INFO)
 
187
 
188
  fastapi_app.include_router(_stats_router)
189
 
190
+ @fastapi_app.get("/api/config")
191
+ def get_config():
192
+ return {"debug": _config.DEBUG}
193
+
194
  # Unit sound effects (generated by scripts/generate_unit_sounds.py)
195
  _SOUNDS_DIR = Path(__file__).parent / "static" / "sounds"
196
  _UNITS_SOUNDS_DIR = _SOUNDS_DIR / "units"
 
301
  return polygons[0]
302
 
303
 
304
+ @fastapi_app.put("/api/map/walkable", dependencies=[Depends(require_debug)])
305
  async def save_walkable_polygon(body: dict = Body(...)):
306
  """Enregistre les polygones marchables (liste de polygones [x, y] en 0-100). Le jeu reçoit la fusion via GET."""
307
  polygons = body.get("polygons")
 
364
  return out
365
 
366
 
367
+ @fastapi_app.put("/api/map/positions", dependencies=[Depends(require_debug)])
368
  async def save_map_positions(body: dict = Body(...)):
369
  """Enregistre positions de départ (2 ou 3, si 3 alors 2 seront tirées au sort par partie) et expansions."""
370
  import json
 
420
  return {"status": "ok", "minerals_count": total_minerals, "geysers_count": total_geysers}
421
 
422
 
423
+ @fastapi_app.put("/api/map/locations", dependencies=[Depends(require_debug)])
424
  async def save_map_locations(body: dict = Body(...)):
425
  """Enregistre les zones clés (locations) dans map.json."""
426
  import json
 
462
  return {"sounds": sounds}
463
 
464
 
465
+ @fastapi_app.delete("/api/sounds/units/{unit}/{kind}", dependencies=[Depends(require_debug)])
466
  async def delete_unit_sound(unit: str, kind: str):
467
  """Supprime un son. Relancer le script régénère uniquement les manquants."""
468
  if not unit.replace("-", "").isalnum() or not kind.replace("-", "").isalnum():
 
496
  return {"sprites": _list_sprites(_SPRITES_DIR / "buildings")}
497
 
498
 
499
+ @fastapi_app.post("/api/sprites/generate", dependencies=[Depends(require_debug)])
500
  async def generate_sprites():
501
  """Lance la génération des sprites (unités + bâtiments) via l'API Mistral. Peut prendre plusieurs minutes."""
502
  import subprocess
 
533
  )
534
 
535
 
536
+ @fastapi_app.post("/api/sprites/generate/units/{unit_id}", dependencies=[Depends(require_debug)])
537
  async def generate_one_unit_sprite(unit_id: str):
538
  """Régénère une seule image d'unité."""
539
  unit_id = unit_id.strip().lower()
 
547
  return {"status": "ok", "id": unit_id}
548
 
549
 
550
+ @fastapi_app.post("/api/sprites/generate/buildings/{building_id}", dependencies=[Depends(require_debug)])
551
  async def generate_one_building_sprite(building_id: str):
552
  """Régénère une seule image de bâtiment."""
553
  building_id = building_id.strip().lower().replace(" ", "_")
 
567
  return {"sprites": _list_sprites(_SPRITES_DIR / "resources")}
568
 
569
 
570
+ @fastapi_app.post("/api/sprites/generate/resources/{resource_id}", dependencies=[Depends(require_debug)])
571
  async def generate_one_resource_sprite(resource_id: str):
572
  """Régénère une seule icône de ressource (mineral, geyser)."""
573
  resource_id = resource_id.strip().lower()
 
590
  return {"icons": _list_sprites(_ICONS_DIR)}
591
 
592
 
593
+ @fastapi_app.post("/api/sprites/rotate/{kind}/{sprite_id}", dependencies=[Depends(require_debug)])
594
  async def rotate_sprite(kind: str, sprite_id: str):
595
  """Fait pivoter un sprite de 90° dans le sens antihoraire (rotate CCW = tourne l'arme vers le haut)."""
596
  valid_kinds = {"units": _SPRITES_DIR / "units", "buildings": _SPRITES_DIR / "buildings", "resources": _SPRITES_DIR / "resources"}
 
611
  return {"status": "ok", "id": sprite_id, "kind": kind}
612
 
613
 
614
+ @fastapi_app.post("/api/icons/rotate/{icon_id}", dependencies=[Depends(require_debug)])
615
  async def rotate_icon(icon_id: str):
616
  """Fait pivoter une icône UI de 90° dans le sens antihoraire."""
617
  icon_id = icon_id.strip().lower()
 
628
  return {"status": "ok", "id": icon_id}
629
 
630
 
631
+ @fastapi_app.post("/api/icons/generate/{icon_id}", dependencies=[Depends(require_debug)])
632
  async def generate_one_icon(icon_id: str):
633
  """Génère une icône UI symbolique (mineral, gas, supply)."""
634
  icon_id = icon_id.strip().lower()
dev.sh CHANGED
@@ -46,7 +46,7 @@ log "Démarrage backend → http://localhost:8000"
46
  (
47
  cd "$ROOT/backend"
48
  set -a; source "$ROOT/.env"; set +a
49
- uvicorn main:app --reload --host 0.0.0.0 --port 8000 2>&1 | sed 's/^/\033[0;33m[back]\033[0m /'
50
  ) &
51
  BACKEND_PID=$!
52
 
 
46
  (
47
  cd "$ROOT/backend"
48
  set -a; source "$ROOT/.env"; set +a
49
+ DEBUG=true uvicorn main:app --reload --host 0.0.0.0 --port 8000 2>&1 | sed 's/^/\033[0;33m[back]\033[0m /'
50
  ) &
51
  BACKEND_PID=$!
52
 
frontend/.svelte-kit/types/route_meta_data.json CHANGED
@@ -2,15 +2,23 @@
2
  "/": [
3
  "src/routes/+page.js"
4
  ],
5
- "/admin": [],
 
 
6
  "/admin/compiled-map": [
7
- "src/routes/admin/compiled-map/+page.ts"
 
8
  ],
9
  "/admin/map": [
10
- "src/routes/admin/map/+page.ts"
 
 
 
 
 
 
 
11
  ],
12
- "/admin/sounds": [],
13
- "/admin/sprites": [],
14
  "/game": [
15
  "src/routes/game/+page.js"
16
  ],
 
2
  "/": [
3
  "src/routes/+page.js"
4
  ],
5
+ "/admin": [
6
+ "src/routes/admin/+layout.ts"
7
+ ],
8
  "/admin/compiled-map": [
9
+ "src/routes/admin/compiled-map/+page.ts",
10
+ "src/routes/admin/+layout.ts"
11
  ],
12
  "/admin/map": [
13
+ "src/routes/admin/map/+page.ts",
14
+ "src/routes/admin/+layout.ts"
15
+ ],
16
+ "/admin/sounds": [
17
+ "src/routes/admin/+layout.ts"
18
+ ],
19
+ "/admin/sprites": [
20
+ "src/routes/admin/+layout.ts"
21
  ],
 
 
22
  "/game": [
23
  "src/routes/game/+page.js"
24
  ],
frontend/.svelte-kit/types/src/routes/admin/$types.d.ts CHANGED
@@ -16,5 +16,7 @@ type LayoutParams = RouteParams & { }
16
  type LayoutParentData = EnsureDefined<import('../$types.js').LayoutData>;
17
 
18
  export type LayoutServerData = null;
19
- export type LayoutData = Expand<LayoutParentData>;
 
 
20
  export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet }
 
16
  type LayoutParentData = EnsureDefined<import('../$types.js').LayoutData>;
17
 
18
  export type LayoutServerData = null;
19
+ export type LayoutLoad<OutputData extends OutputDataShape<LayoutParentData> = OutputDataShape<LayoutParentData>> = Kit.Load<LayoutParams, LayoutServerData, LayoutParentData, OutputData, LayoutRouteId>;
20
+ export type LayoutLoadEvent = Parameters<LayoutLoad>[0];
21
+ export type LayoutData = Expand<Omit<LayoutParentData, keyof Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>> & OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>>>>;
22
  export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet }
frontend/.svelte-kit/types/src/routes/admin/proxy+layout.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // @ts-nocheck
2
+ import { redirect } from '@sveltejs/kit';
3
+ import type { LayoutLoad } from './$types';
4
+
5
+ export const ssr = false;
6
+
7
+ export const load = async ({ fetch }: Parameters<LayoutLoad>[0]) => {
8
+ const res = await fetch('/api/config');
9
+ if (!res.ok) throw redirect(302, '/');
10
+ const { debug } = await res.json();
11
+ if (!debug) throw redirect(302, '/');
12
+ };
frontend/src/routes/admin/+layout.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { redirect } from '@sveltejs/kit';
2
+ import type { LayoutLoad } from './$types';
3
+
4
+ export const ssr = false;
5
+
6
+ export const load: LayoutLoad = async ({ fetch }) => {
7
+ const res = await fetch('/api/config');
8
+ if (!res.ok) throw redirect(302, '/');
9
+ const { debug } = await res.json();
10
+ if (!debug) throw redirect(302, '/');
11
+ };