Compare commits

...

13 Commits

Author SHA1 Message Date
Matteo Rosati
fe4f135b92 add cursor style 2026-02-11 22:16:55 +01:00
Matteo Rosati
f05e91c2ce add styles 2026-02-11 21:52:15 +01:00
Matteo Rosati
9587995354 simplify apps 2026-02-11 21:05:49 +01:00
Matteo Rosati
93dafb6a81 add django-stubs 2026-02-11 20:52:23 +01:00
Matteo Rosati
c116f8dcb1 remove unused stuff. add player energy 2026-02-11 18:03:59 +01:00
Matteo Rosati
24a26bd092 remove unused stuff. add player energy 2026-02-11 18:03:54 +01:00
Matteo Rosati
6acc708f46 add .env 2026-02-11 17:24:31 +01:00
Matteo Rosati
4c67b10f75 add buildings hover. add favicon 2026-02-11 17:09:06 +01:00
Matteo Rosati
335299c670 remove old CDN maplibre 2026-02-11 15:38:05 +01:00
Matteo Rosati
af95eea265 uniform syntax 2026-02-11 15:35:21 +01:00
Matteo Rosati
3884864d30 setup ts 2026-02-11 15:34:53 +01:00
Matteo Rosati
787bdede87 add minimal TS bundler 2026-02-11 15:15:27 +01:00
Matteo Rosati
a3ef6cf84c add js types 2026-02-11 12:45:04 +01:00
41 changed files with 510 additions and 316 deletions

2
.env.dist Normal file
View File

@@ -0,0 +1,2 @@
DEBUG=True
SECRET_KEY=1234567890

7
.gitignore vendored
View File

@@ -9,4 +9,11 @@ wheels/
# Virtual environments
.venv
# Environment files
.env
# Node modules (only for types definitions)
node_modules/
# Collected static files
staticfiles/

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
name = 'api'

View File

@@ -1,22 +0,0 @@
# Generated by Django 6.0.2 on 2026-02-10 18:07
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Building',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geojson_id', models.IntegerField()),
('health', models.FloatField()),
],
),
]

View File

@@ -1,6 +0,0 @@
from django.db import models
class Building(models.Model):
geojson_id = models.IntegerField()
health = models.FloatField()

View File

@@ -1,75 +0,0 @@
from django.contrib.auth import authenticate, get_user_model
from django.core.validators import validate_email
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework_simplejwt.tokens import RefreshToken
User = get_user_model()
class RegistrationSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField(min_length=8, write_only=True)
def validate_email(self, value):
validate_email(value)
if User.objects.filter(email__iexact=value).exists():
raise serializers.ValidationError(_("Email is already registered."))
return value
def create(self, validated_data):
email = validated_data["email"].lower()
password = validated_data["password"]
user = User.objects.create_user(
username=email,
email=email,
password=password,
)
return user
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField(write_only=True)
def validate(self, attrs):
email = attrs.get("email", "").lower()
password = attrs.get("password")
if not email or not password:
raise serializers.ValidationError(_("Email and password are required."))
user = authenticate(
request=self.context.get("request"),
username=email,
password=password,
)
if user is None:
raise serializers.ValidationError(_("Invalid email or password."))
if not user.is_active:
raise serializers.ValidationError(_("User account is disabled."))
attrs["user"] = user
return attrs
def create(self, validated_data):
user = validated_data["user"]
refresh = RefreshToken.for_user(user)
return {
"refresh": str(refresh),
"access": str(refresh.access_token),
}
class LogoutSerializer(serializers.Serializer):
refresh = serializers.CharField()
def validate(self, attrs):
refresh = attrs.get("refresh")
if not refresh:
raise serializers.ValidationError(_("Refresh token is required."))
return attrs
def create(self, validated_data):
refresh = RefreshToken(validated_data["refresh"])
refresh.blacklist()
return {}

View File

@@ -1,9 +0,0 @@
from django.urls import path
from .views import LoginView, LogoutView, RegisterView
urlpatterns = [
path("auth/register", RegisterView.as_view(), name="auth-register"),
path("auth/login", LoginView.as_view(), name="auth-login"),
path("auth/logout", LogoutView.as_view(), name="auth-logout"),
]

View File

@@ -1,67 +0,0 @@
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import LoginSerializer, LogoutSerializer, RegistrationSerializer
class RegisterView(APIView):
permission_classes = [permissions.AllowAny]
throttle_scope = "auth"
def post(self, request):
serializer = RegistrationSerializer(data=request.data)
if not serializer.is_valid():
return Response(
{"errors": serializer.errors},
status=status.HTTP_400_BAD_REQUEST,
)
user = serializer.save()
login_serializer = LoginSerializer(
data={
"email": user.email,
"password": request.data.get("password"),
},
context={"request": request},
)
login_serializer.is_valid(raise_exception=True)
tokens = login_serializer.save()
return Response(
{
"user": {
"id": user.id,
"email": user.email,
},
"tokens": tokens,
},
status=status.HTTP_201_CREATED,
)
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
throttle_scope = "auth"
def post(self, request):
serializer = LoginSerializer(data=request.data, context={"request": request})
if not serializer.is_valid():
return Response(
{"errors": serializer.errors},
status=status.HTTP_400_BAD_REQUEST,
)
tokens = serializer.save()
return Response({"tokens": tokens}, status=status.HTTP_200_OK)
class LogoutView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
serializer = LogoutSerializer(data=request.data)
if not serializer.is_valid():
return Response(
{"errors": serializer.errors},
status=status.HTTP_400_BAD_REQUEST,
)
serializer.save()
return Response({"detail": "Logged out."}, status=status.HTTP_200_OK)

31
build.js Normal file
View File

@@ -0,0 +1,31 @@
// build.js
import * as esbuild from "esbuild";
const isDev = process.argv.includes("--dev");
const config = {
entryPoints: ["frontend/static/frontend/ts/main.ts"], // il tuo entry point
bundle: true,
outdir: "frontend/static/frontend/dist/js",
format: "iife", // o 'iife' se serve per un tag <script> classico
target: "es2020",
sourcemap: isDev,
minify: true,
logLevel: "info",
};
if (isDev) {
const ctx = await esbuild.context(config);
await ctx.watch();
console.log("Watching...");
const shutdown = async () => {
await ctx.dispose();
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
} else {
await esbuild.build(config);
}

128
bun.lock Normal file
View File

@@ -0,0 +1,128 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"dependencies": {
"@types/maplibre-gl": "^1.14.0",
"esbuild": "^0.27.3",
"maplibre-gl": "^5.18.0",
},
},
},
"packages": {
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@mapbox/geojson-rewind": ["@mapbox/geojson-rewind@0.5.2", "", { "dependencies": { "get-stream": "^6.0.1", "minimist": "^1.2.6" }, "bin": { "geojson-rewind": "geojson-rewind" } }, "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA=="],
"@mapbox/jsonlint-lines-primitives": ["@mapbox/jsonlint-lines-primitives@2.0.2", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="],
"@mapbox/point-geometry": ["@mapbox/point-geometry@1.1.0", "", {}, "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ=="],
"@mapbox/tiny-sdf": ["@mapbox/tiny-sdf@2.0.7", "", {}, "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug=="],
"@mapbox/unitbezier": ["@mapbox/unitbezier@0.0.1", "", {}, "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="],
"@mapbox/vector-tile": ["@mapbox/vector-tile@2.0.4", "", { "dependencies": { "@mapbox/point-geometry": "~1.1.0", "@types/geojson": "^7946.0.16", "pbf": "^4.0.1" } }, "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg=="],
"@mapbox/whoots-js": ["@mapbox/whoots-js@3.1.0", "", {}, "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="],
"@maplibre/geojson-vt": ["@maplibre/geojson-vt@5.0.4", "", {}, "sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ=="],
"@maplibre/maplibre-gl-style-spec": ["@maplibre/maplibre-gl-style-spec@24.4.1", "", { "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", "quickselect": "^3.0.0", "rw": "^1.3.3", "tinyqueue": "^3.0.0" }, "bin": { "gl-style-migrate": "dist/gl-style-migrate.mjs", "gl-style-validate": "dist/gl-style-validate.mjs", "gl-style-format": "dist/gl-style-format.mjs" } }, "sha512-UKhA4qv1h30XT768ccSv5NjNCX+dgfoq2qlLVmKejspPcSQTYD4SrVucgqegmYcKcmwf06wcNAa/kRd0NHWbUg=="],
"@maplibre/mlt": ["@maplibre/mlt@1.1.6", "", { "dependencies": { "@mapbox/point-geometry": "^1.1.0" } }, "sha512-rgtY3x65lrrfXycLf6/T22ZnjTg5WgIOsptOIoCaMZy4O4UAKTyZlYY0h6v8le721pTptF94U65yMDQkug+URw=="],
"@maplibre/vt-pbf": ["@maplibre/vt-pbf@4.2.1", "", { "dependencies": { "@mapbox/point-geometry": "^1.1.0", "@mapbox/vector-tile": "^2.0.4", "@maplibre/geojson-vt": "^5.0.4", "@types/geojson": "^7946.0.16", "@types/supercluster": "^7.1.3", "pbf": "^4.0.1", "supercluster": "^8.0.1" } }, "sha512-IxZBGq/+9cqf2qdWlFuQ+ZfoMhWpxDUGQZ/poPHOJBvwMUT1GuxLo6HgYTou+xxtsOsjfbcjI8PZaPCtmt97rA=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/maplibre-gl": ["@types/maplibre-gl@1.14.0", "", { "dependencies": { "maplibre-gl": "*" } }, "sha512-I6ibscT7UdL1oOqqCz9s1gjcolLaUPkHIIfMLusczTvlsMhjORyS6sE1g4V/NESAOL5KhNQX3/31LJH+OCGjkg=="],
"@types/supercluster": ["@types/supercluster@7.1.3", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA=="],
"earcut": ["earcut@3.0.2", "", {}, "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"gl-matrix": ["gl-matrix@3.4.4", "", {}, "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ=="],
"json-stringify-pretty-compact": ["json-stringify-pretty-compact@4.0.0", "", {}, "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="],
"kdbush": ["kdbush@4.0.2", "", {}, "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="],
"maplibre-gl": ["maplibre-gl@5.18.0", "", { "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", "@mapbox/point-geometry": "^1.1.0", "@mapbox/tiny-sdf": "^2.0.7", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^2.0.4", "@mapbox/whoots-js": "^3.1.0", "@maplibre/geojson-vt": "^5.0.4", "@maplibre/maplibre-gl-style-spec": "^24.4.1", "@maplibre/mlt": "^1.1.6", "@maplibre/vt-pbf": "^4.2.1", "@types/geojson": "^7946.0.16", "@types/supercluster": "^7.1.3", "earcut": "^3.0.2", "gl-matrix": "^3.4.4", "kdbush": "^4.0.2", "murmurhash-js": "^1.0.0", "pbf": "^4.0.1", "potpack": "^2.1.0", "quickselect": "^3.0.0", "supercluster": "^8.0.1", "tinyqueue": "^3.0.0" } }, "sha512-UtWxPBpHuFvEkM+5FVfcFG9ZKEWZQI6+PZkvLErr8Zs5ux+O7/KQ3JjSUvAfOlMeMgd/77qlHpOw0yHL7JU5cw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"murmurhash-js": ["murmurhash-js@1.0.0", "", {}, "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="],
"pbf": ["pbf@4.0.1", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA=="],
"potpack": ["potpack@2.1.0", "", {}, "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ=="],
"protocol-buffers-schema": ["protocol-buffers-schema@3.6.0", "", {}, "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="],
"quickselect": ["quickselect@3.0.0", "", {}, "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="],
"resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="],
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
"supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="],
"tinyqueue": ["tinyqueue@3.0.0", "", {}, "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="],
}
}

Binary file not shown.

View File

@@ -10,34 +10,41 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
import os
from datetime import timedelta
from pathlib import Path
from dotenv import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-nows$x_3#k9x7j09f*o4xmx#p6zeb%ak+#ew2pc01yx2bm+hwq"
SECRET_KEY = os.getenv(
"SECRET_KEY",
"django-insecure-nows$x_3#k9x7j09f*o4xmx#p6zeb%ak+#ew2pc01yx2bm+hwq",
)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv("DEBUG", "False").strip().lower() in {"1", "true", "yes", "on"}
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
# Application definition
INSTALLED_APPS = [
"api",
"game",
"frontend",
"rest_framework",
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",

View File

@@ -15,11 +15,8 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("", include("frontend.urls")),
path("admin/", admin.site.urls),
path("api/", include("api.urls")),
]

View File

@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@@ -1,8 +0,0 @@
from django.apps import AppConfig
class FrontendConfig(AppConfig):
name = "frontend"
def ready(self) -> None:
from . import models # noqa: F401

View File

@@ -1,37 +0,0 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="UserProfile",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("display_name", models.CharField(max_length=150)),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@@ -1,15 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("frontend", "0001_userprofile"),
]
operations = [
migrations.AlterField(
model_name="userprofile",
name="display_name",
field=models.CharField(max_length=150, unique=True),
),
]

View File

@@ -1,6 +1,52 @@
:root {
color-scheme: dark;
--map-ink: #041005;
--map-void: #020a03;
--map-forest: #0b1f0e;
--map-grid: #0f2f17;
--map-road: #1de85b;
--map-road-soft: #119a3b;
--map-node: #6dff9d;
--map-label: #7dffa6;
--map-glow: rgba(19, 240, 97, 0.35);
--map-glow-strong: rgba(19, 240, 97, 0.65);
--map-surface: rgba(6, 20, 9, 0.85);
--map-surface-strong: rgba(6, 24, 10, 0.96);
--map-border: rgba(29, 232, 91, 0.35);
--map-border-strong: rgba(29, 232, 91, 0.6);
--map-text: #b5ffd0;
--map-muted: #6bbf83;
--map-accent: #1de85b;
--map-accent-hot: #9bff5c;
}
body {
background: radial-gradient(
circle at top,
#0b1f0e 0%,
#041005 45%,
#020a03 100%
);
color: var(--map-text);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
}
strong {
font-weight: 800;
}
#map {
width: 100%;
height: 100vh;
background:
radial-gradient(
circle at 20% 10%,
rgba(12, 48, 20, 0.85),
rgba(4, 14, 7, 0.9)
),
linear-gradient(140deg, rgba(5, 24, 10, 0.95), rgba(2, 8, 4, 0.98));
}
.site-header {
@@ -15,14 +61,20 @@
justify-content: space-between;
padding: 0.85rem 1.5rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.85);
background: linear-gradient(
120deg,
rgba(7, 24, 11, 0.9),
rgba(6, 20, 9, 0.75)
);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 18px 40px rgba(24, 31, 59, 0.16);
border: 1px solid var(--map-border);
box-shadow:
0 18px 40px rgba(0, 0, 0, 0.45),
0 0 24px var(--map-glow);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
color: #111827;
color: var(--map-text);
}
.site-brand {
@@ -32,6 +84,7 @@
color: inherit;
text-decoration: none;
font-weight: 600;
text-shadow: 0 0 12px var(--map-glow);
}
.site-actions {
@@ -42,16 +95,16 @@
}
.site-user {
color: #4b5563;
color: var(--map-muted);
font-weight: 500;
}
.site-link {
text-decoration: none;
color: #111827;
color: var(--map-text);
padding: 0.45rem 0.9rem;
border-radius: 999px;
border: 1px solid rgba(17, 24, 39, 0.12);
border: 1px solid rgba(29, 232, 91, 0.25);
transition:
transform 0.2s ease,
box-shadow 0.2s ease,
@@ -60,15 +113,18 @@
.site-link:hover {
transform: translateY(-1px);
border-color: rgba(17, 24, 39, 0.28);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.12);
border-color: var(--map-border-strong);
box-shadow:
0 10px 20px rgba(0, 0, 0, 0.35),
0 0 16px var(--map-glow);
}
.site-link--accent {
border: none;
background: linear-gradient(130deg, #f97316, #facc15);
color: #1f2937;
background: linear-gradient(130deg, #0fd64f, #9bff5c);
color: #041005;
font-weight: 600;
box-shadow: 0 0 18px rgba(15, 214, 79, 0.45);
}
@media (max-width: 720px) {
@@ -97,12 +153,12 @@
padding: 4rem 1.5rem;
background: radial-gradient(
circle at top,
#eef5ff,
#f7f0ff 35%,
#f7f6ff 65%,
#fdf6ef
#0e2613,
#07180c 35%,
#041106 65%,
#020a03
);
color: #1f1f1f;
color: var(--map-text);
font-family:
"JetBrains Mono", "SFMono-Regular", "Menlo", "Monaco", "Consolas",
monospace;
@@ -110,11 +166,13 @@
.auth-card {
width: min(420px, 100%);
background: #ffffff;
background: var(--map-surface);
border-radius: 24px;
padding: 2.5rem;
box-shadow: 0 24px 60px rgba(24, 31, 59, 0.12);
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow:
0 24px 60px rgba(0, 0, 0, 0.5),
0 0 22px var(--map-glow);
border: 1px solid var(--map-border);
}
.auth-header {
@@ -125,28 +183,28 @@
text-transform: uppercase;
letter-spacing: 0.2em;
font-size: 0.7rem;
color: #57606a;
color: var(--map-muted);
margin-bottom: 0.75rem;
}
.auth-title {
font-size: 1.6rem;
margin-bottom: 0.5rem;
color: #111827;
color: var(--map-text);
}
.auth-subtitle {
font-size: 0.95rem;
color: #6b7280;
color: var(--map-muted);
}
.auth-messages {
margin-bottom: 1.5rem;
background: #fff4f4;
border: 1px solid #ffccd2;
background: rgba(18, 55, 26, 0.75);
border: 1px solid rgba(110, 255, 157, 0.35);
border-radius: 16px;
padding: 0.75rem 1rem;
color: #b42318;
color: var(--map-accent-hot);
}
.auth-message {
@@ -158,6 +216,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.auth-field {
@@ -165,20 +224,26 @@
flex-direction: column;
gap: 0.45rem;
font-size: 0.85rem;
color: #374151;
color: var(--map-muted);
width: 100%;
}
.auth-field input {
padding: 0.7rem 0.9rem;
border-radius: 12px;
border: 1px solid #d1d5db;
border: 1px solid rgba(109, 255, 157, 0.3);
font-size: 0.95rem;
font-family: inherit;
background: var(--map-surface-strong);
color: var(--map-text);
box-shadow: inset 0 0 0 1px rgba(3, 18, 7, 0.8);
width: 100%;
min-width: 0;
}
.auth-field input:focus {
outline: 2px solid rgba(76, 110, 245, 0.35);
border-color: #6d7bf7;
outline: 2px solid rgba(29, 232, 91, 0.35);
border-color: var(--map-accent);
}
.auth-submit {
@@ -186,8 +251,8 @@
padding: 0.8rem 1rem;
border-radius: 999px;
border: none;
background: linear-gradient(130deg, #f97316, #facc15);
color: #1f2937;
background: linear-gradient(130deg, #0fd64f, #9bff5c);
color: #041005;
font-weight: 600;
cursor: pointer;
transition:
@@ -197,17 +262,19 @@
.auth-submit:hover {
transform: translateY(-1px);
box-shadow: 0 12px 25px rgba(249, 115, 22, 0.3);
box-shadow:
0 12px 25px rgba(15, 214, 79, 0.35),
0 0 18px var(--map-glow-strong);
}
.auth-footer {
margin-top: 1.5rem;
font-size: 0.85rem;
color: #6b7280;
color: var(--map-muted);
}
.auth-footer a {
color: #111827;
color: var(--map-text);
font-weight: 600;
text-decoration: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -0,0 +1,13 @@
import { AddLayerObject } from "maplibre-gl";
export const HIGHLIGHT_LAYER = {
id: "buildings-highlight", // nome arbitrario del layer
type: "fill",
source: "buildings",
"source-layer": "building",
paint: {
"fill-color": "#41ff44",
"fill-opacity": 1,
},
filter: ["==", ["id"], ""],
} as AddLayerObject;

View File

@@ -0,0 +1,3 @@
export default function add(a: number, b: number): number {
return a + b;
}

View File

@@ -0,0 +1,41 @@
import { HIGHLIGHT_LAYER } from "./layers";
import { Map } from "maplibre-gl";
const map = new Map({
style:
"https://api.maptiler.com/maps/019be805-c88e-7c8b-9850-bc704d72e604/style.json?key=8nmgHEIZQiIgqQj3RZNa",
container: "map",
zoom: 17,
});
map.on("load", () => {
navigator.geolocation.getCurrentPosition((position) => {
map.panTo({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
});
// Il layer per gli highlight
map.addLayer(HIGHLIGHT_LAYER);
map.on("mousemove", (ev) => {
const features = map.queryRenderedFeatures(ev.point, {
layers: ["buildings"], // questo e' il layer che c'e' nello style di maptiler
});
if (features && features.length > 0) {
const feature = features[0];
const hoveredId = feature.id;
if (hoveredId) {
map.setFilter("buildings-highlight", ["==", ["id"], hoveredId]);
map.getCanvas().style.cursor = "pointer";
}
} else {
map.setFilter("buildings-highlight", ["==", ["id"], ""]);
map.getCanvas().style.cursor = "";
}
});
});

View File

@@ -6,22 +6,33 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}{% endblock %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'frontend/css/base.css' %}">
<link rel="stylesheet" href="{% static 'frontend/css/reset.css' %}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/maplibre-gl/5.7.3/maplibre-gl.min.css" integrity="sha512-/UDhp6sLyQgK2cEDHUYEyJFXI7+WgJ+nuxTqETJkT8XlfLiMSt2xQblBVnCjE5rGJMBr5sOWaEhflmtBL1Psww==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="{% static 'frontend/css/base.css' %}" />
<link rel="stylesheet" href="{% static 'frontend/css/reset.css' %}" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/maplibre-gl/5.7.3/maplibre-gl.min.css"
integrity="sha512-/UDhp6sLyQgK2cEDHUYEyJFXI7+WgJ+nuxTqETJkT8XlfLiMSt2xQblBVnCjE5rGJMBr5sOWaEhflmtBL1Psww=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link rel="icon" type="image/x-icon" href="{% static 'frontend/images/favicon.ico' %}">
{% block extra_css %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js" integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz" crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"
integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz"
crossorigin="anonymous"
></script>
{% block js_top %}{% endblock %}
</head>
<body>
<header>
{% include "frontend/partials/header.html" %}
</header>
<header>{% include "frontend/partials/header.html" %}</header>
{% block content %} {% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/maplibre-gl/5.7.3/maplibre-gl.min.js" integrity="sha512-Gx0xDElSrwjxjT9mjMg+OsoA0ekI8IkwuPurccWk5afkFBzXQHE0eQsQ7syopu9MJ0HD1EYGmVXjY8SPZt5FAg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% block js_bottom %}{% endblock %}
</body>
</html>

View File

@@ -1,18 +1,13 @@
{% extends 'frontend/base.html' %}
{% block title %}Home{% endblock %}
{% load static %}
{% block title %}ᑀ DroneWars ᑅ{% endblock %}
{% block content %}
<div id="map"></div>
{% endblock %}
{% block js_bottom %}
<script>
var map = new maplibregl.Map({
container: "map",
style: "https://api.maptiler.com/maps/019be805-c88e-7c8b-9850-bc704d72e604/style.json?key=8nmgHEIZQiIgqQj3RZNa",
center: [-74.5, 40],
zoom: 9,
});
</script>
<script src="{% static 'frontend/dist/js/main.js' %}"></script>
{% endblock %}

View File

@@ -3,7 +3,7 @@
<div class="site-actions">
{% if user.is_authenticated %}
<span class="site-user">
{{ user.profile.display_name|default:user.get_username }}
Welcome, <strong>{{ user.profile.display_name|default:user.get_username }}</strong>
</span>
<a class="site-link" href="{% url 'logout' %}">Logout</a>
{% else %}

View File

@@ -2,7 +2,6 @@ from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"),
path("auth/register/", views.register, name="register"),

View File

@@ -10,7 +10,7 @@ from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from django_ratelimit.decorators import ratelimit
from .models import UserProfile
from game.models import UserProfile
def home(request):

5
game/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class GameConfig(AppConfig):
name = 'game'

View File

@@ -0,0 +1,41 @@
# Generated by Django 6.0.2 on 2026-02-11 20:00
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Building',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geojson_id', models.IntegerField()),
('health', models.FloatField()),
],
),
migrations.CreateModel(
name='PlayerInfo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('energy', models.PositiveIntegerField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='player_info', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('display_name', models.CharField(max_length=150, unique=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -4,6 +4,20 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
class Building(models.Model):
geojson_id = models.IntegerField()
health = models.FloatField()
class PlayerInfo(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="player_info",
)
energy = models.PositiveIntegerField()
class UserProfile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
@@ -19,7 +33,8 @@ class UserProfile(models.Model):
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_user(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create( # type: ignore[attr-defined]
UserProfile.objects.create(
user=instance,
display_name=instance.username,
)
PlayerInfo.objects.create(user=instance, energy=10_000)

3
game/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

11
package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"dependencies": {
"@types/maplibre-gl": "^1.14.0",
"esbuild": "^0.27.3",
"maplibre-gl": "^5.18.0"
},
"scripts": {
"dev": "bun run build.js --dev",
"build": "bun run build.js"
}
}

View File

@@ -7,10 +7,12 @@ requires-python = ">=3.13"
dependencies = [
"django>=6.0.2",
"django-ratelimit>=4.1.0",
"django-stubs>=5.2.9",
"djangorestframework>=3.16.1",
"djangorestframework-simplejwt>=5.5.0",
"ipython>=9.10.0",
"pydantic>=2.12.5",
"python-dotenv>=1.2.1",
"uvicorn>=0.40.0",
"whitenoise>=6.11.0",
]

View File

@@ -5,6 +5,8 @@ click==8.3.1
decorator==5.2.1
django==6.0.2
django-ratelimit==4.1.0
django-stubs==5.2.9
django-stubs-ext==5.2.9
djangorestframework==3.16.1
djangorestframework-simplejwt==5.5.1
executing==2.2.1
@@ -22,9 +24,11 @@ pydantic==2.12.5
pydantic-core==2.41.5
pygments==2.19.2
pyjwt==2.11.0
python-dotenv==1.2.1
sqlparse==0.5.5
stack-data==0.6.3
traitlets==5.14.3
types-pyyaml==6.0.12.20250915
typing-extensions==4.15.0
typing-inspection==0.4.2
uvicorn==0.40.0

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"types": ["maplibre-gl"],
"lib": ["ES2020", "DOM"],
"noEmit": true
},
"include": ["frontend/static/frontend/ts/**/*.ts"]
}

50
uv.lock generated
View File

@@ -82,6 +82,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/78/2c59b30cd8bc8068d02349acb6aeed5c4e05eb01cdf2107ccd76f2e81487/django_ratelimit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d047a31cf94d83ef1465d7543ca66c6fc16695559b5f8d814d1b51df15110b92", size = 11608, upload-time = "2023-07-24T20:34:31.362Z" },
]
[[package]]
name = "django-stubs"
version = "5.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "django-stubs-ext" },
{ name = "types-pyyaml" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9c/01/86c921e0e19c9fa7e705bf795998dbf55eb183e7be0342a3027dc1bcbc9f/django_stubs-5.2.9.tar.gz", hash = "sha256:c192257120b08785cfe6f2f1c91f1797aceae8e9daa689c336e52c91e8f6a493", size = 257970, upload-time = "2026-01-20T23:59:27.018Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/05/4c9c419b7051eb4b350100b086be6df487f968ab672d3d370f8ccf7c3746/django_stubs-5.2.9-py3-none-any.whl", hash = "sha256:2317a7130afdaa76f6ff7f623650d7f3bf1b6c86a60f95840e14e6ec6de1a7cd", size = 508656, upload-time = "2026-01-20T23:59:25.12Z" },
]
[[package]]
name = "django-stubs-ext"
version = "5.2.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/03/9c2be939490d2282328db4611bc5956899f5ff7eabc3e88bd4b964a87373/django_stubs_ext-5.2.9.tar.gz", hash = "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9", size = 6497, upload-time = "2026-01-20T23:58:59.462Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/f7/0d5f7d7e76fe972d9f560f687fdc0cab4db9e1624fd90728ca29b4ed7a63/django_stubs_ext-5.2.9-py3-none-any.whl", hash = "sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71", size = 9974, upload-time = "2026-01-20T23:58:58.438Z" },
]
[[package]]
name = "djangorestframework"
version = "3.16.1"
@@ -115,10 +143,12 @@ source = { virtual = "." }
dependencies = [
{ name = "django" },
{ name = "django-ratelimit" },
{ name = "django-stubs" },
{ name = "djangorestframework" },
{ name = "djangorestframework-simplejwt" },
{ name = "ipython" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "uvicorn" },
{ name = "whitenoise" },
]
@@ -127,10 +157,12 @@ dependencies = [
requires-dist = [
{ name = "django", specifier = ">=6.0.2" },
{ name = "django-ratelimit", specifier = ">=4.1.0" },
{ name = "django-stubs", specifier = ">=5.2.9" },
{ name = "djangorestframework", specifier = ">=3.16.1" },
{ name = "djangorestframework-simplejwt", specifier = ">=5.5.0" },
{ name = "ipython", specifier = ">=9.10.0" },
{ name = "pydantic", specifier = ">=2.12.5" },
{ name = "python-dotenv", specifier = ">=1.2.1" },
{ name = "uvicorn", specifier = ">=0.40.0" },
{ name = "whitenoise", specifier = ">=6.11.0" },
]
@@ -347,6 +379,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
]
[[package]]
name = "sqlparse"
version = "0.5.5"
@@ -379,6 +420,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
]
[[package]]
name = "types-pyyaml"
version = "6.0.12.20250915"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"