+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/README.md b/README.md
index 01ad769..e4935fc 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,84 @@
# lifenexp
+Application libre de gamification des tâches de la vie quotidienne.
+├── achievements/ # succès et titres
+└── federation/ # échanges entre instances
+```
+
+Les frontières entre applications pourront évoluer pendant la conception.
+
+## API
+
+L'API sera versionnée :
+
+```text
+/api/v1/
+```
+
+Une documentation OpenAPI sera ajoutée avec, par exemple :
+
+```text
+/api/schema/
+/api/docs/
+```
+
+Les changements incompatibles devront entraîner une nouvelle version majeure
+de l'API.
+
+## Fédération
+
+L'objectif est de permettre à une instance de consulter les profils et arbres
+de compétences rendus publics sur d'autres instances.
+
+Avant implémentation, il faudra notamment définir :
+
+- l'identité stable d'un joueur distant ;
+- le mécanisme de découverte des instances ;
+- les formats d'échange ;
+- la signature ou l'authentification des requêtes ;
+- la gestion des blocages et de la modération ;
+- la protection des données personnelles ;
+- la compatibilité entre versions.
+
+## Contribution
+
+1. Créer une branche à partir de `main`.
+2. Effectuer des commits courts et explicites.
+3. Ajouter ou modifier les tests.
+4. Ouvrir une pull request.
+5. Attendre la validation de la CI et une revue.
+
+Exemples de noms de branches :
+
+```text
+feat/skill-trees
+fix/xp-calculation
+docs/federation
+chore/ci
+```
+
+Le format [Conventional Commits](https://www.conventionalcommits.org/) est
+recommandé :
+
+```text
+feat(skills): add skill tree model
+fix(progression): prevent duplicate XP rewards
+docs(api): describe profile endpoint
+```
+
+## Sécurité
+
+Ne signalez pas publiquement une vulnérabilité exploitable.
+
+Consultez `SECURITY.md` pour connaître la procédure de signalement.
+
+Aucun secret, mot de passe, jeton ou fichier `.env` ne doit être ajouté au
+dépôt.
+
+## Licence
+
+Ce projet est distribué sous la licence indiquée dans le fichier `LICENSE`.
+
+## Mainteneurs
+
+- Dylan TIRARD
diff --git a/apps/__init__.py b/apps/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apps/accounts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py
new file mode 100644
index 0000000..efecfc9
--- /dev/null
+++ b/apps/accounts/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = 'apps.accounts'
diff --git a/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/accounts/models.py b/apps/accounts/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/apps/accounts/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/accounts/tests.py b/apps/accounts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apps/accounts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/accounts/views.py b/apps/accounts/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/apps/accounts/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/apps/achievements/__init__.py b/apps/achievements/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/achievements/admin.py b/apps/achievements/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apps/achievements/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/achievements/apps.py b/apps/achievements/apps.py
new file mode 100644
index 0000000..fb52af6
--- /dev/null
+++ b/apps/achievements/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AchievementsConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = 'apps.achievements'
diff --git a/apps/achievements/migrations/__init__.py b/apps/achievements/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/achievements/models.py b/apps/achievements/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/apps/achievements/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/achievements/tests.py b/apps/achievements/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apps/achievements/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/achievements/views.py b/apps/achievements/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/apps/achievements/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/apps/activities/__init__.py b/apps/activities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/activities/admin.py b/apps/activities/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apps/activities/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/activities/apps.py b/apps/activities/apps.py
new file mode 100644
index 0000000..e48b821
--- /dev/null
+++ b/apps/activities/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ActivitiesConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = 'apps.activities'
diff --git a/apps/activities/migrations/__init__.py b/apps/activities/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/activities/models.py b/apps/activities/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/apps/activities/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/activities/tests.py b/apps/activities/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apps/activities/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/activities/views.py b/apps/activities/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/apps/activities/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/apps/progression/__init__.py b/apps/progression/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/progression/admin.py b/apps/progression/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apps/progression/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/progression/apps.py b/apps/progression/apps.py
new file mode 100644
index 0000000..693bd73
--- /dev/null
+++ b/apps/progression/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ProgressionConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = 'apps.progression'
diff --git a/apps/progression/migrations/__init__.py b/apps/progression/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/progression/models.py b/apps/progression/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/apps/progression/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/progression/tests.py b/apps/progression/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apps/progression/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/progression/views.py b/apps/progression/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/apps/progression/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/apps/skills/__init__.py b/apps/skills/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/skills/admin.py b/apps/skills/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apps/skills/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apps/skills/apps.py b/apps/skills/apps.py
new file mode 100644
index 0000000..f76a116
--- /dev/null
+++ b/apps/skills/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SkillsConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = 'apps.skills'
diff --git a/apps/skills/migrations/__init__.py b/apps/skills/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apps/skills/models.py b/apps/skills/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/apps/skills/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/apps/skills/tests.py b/apps/skills/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apps/skills/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apps/skills/views.py b/apps/skills/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/apps/skills/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/config/__init__.py b/config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/config/asgi.py b/config/asgi.py
new file mode 100644
index 0000000..33a47a1
--- /dev/null
+++ b/config/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for config project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+
+application = get_asgi_application()
diff --git a/config/settings.py b/config/settings.py
new file mode 100644
index 0000000..1bc0462
--- /dev/null
+++ b/config/settings.py
@@ -0,0 +1,168 @@
+'''
+Django settings for config project.
+
+Generated by 'django-admin startproject' using Django 6.1.3.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/6.1/ref/settings/
+'''
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-0x!&cd(!j54se5&_9+!5$6m4ps97(-_#_w+gcl5737!!t&t5kk'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+
+ # Authentification
+ 'django.contrib.sites',
+ 'allauth',
+ 'allauth.account',
+
+ # API
+ 'rest_framework',
+ 'django_filters',
+ 'drf_spectacular',
+
+ # Interface
+ 'tailwind',
+]
+
+SITE_ID = 1
+
+AUTHENTICATION_BACKENDS = [
+ 'django.contrib.auth.backends.ModelBackend',
+ 'allauth.account.auth_backends.AuthenticationBackend',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'allauth.account.middleware.AccountMiddleware',
+]
+
+
+REST_FRAMEWORK = {
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
+ 'DEFAULT_FILTER_BACKENDS': [
+ 'django_filters.rest_framework.DjangoFilterBackend',
+ ],
+}
+
+SPECTACULAR_SETTINGS = {
+ 'TITLE': 'LifeNExp API',
+ 'DESCRIPTION': 'API de gamification des activités quotidiennes.',
+ 'VERSION': '0.1.0',
+}
+
+
+ROOT_URLCONF = 'config.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [BASE_DIR / 'templates'],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'config.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/6.1/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/6.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/6.1/topics/i18n/
+
+LANGUAGE_CODE = 'fr-fr'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/6.1/howto/static-files/
+
+STATIC_URL = 'static/'
+STATIC_ROOT = BASE_DIR / 'staticfiles'
+STATICFILES_DIRS = [BASE_DIR / 'static']
+
+MEDIA_URL = 'media/'
+MEDIA_ROOT = BASE_DIR / 'media'
+
+
+# Email
+# https://docs.djangoproject.com/en/6.1/topics/email/#topic-email-configuration
+
+MAILERS = {
+ 'default': {
+ 'BACKEND': 'django.core.mail.backends.console.EmailBackend',
+ },
+}
diff --git a/config/urls.py b/config/urls.py
new file mode 100644
index 0000000..77ff5bd
--- /dev/null
+++ b/config/urls.py
@@ -0,0 +1,22 @@
+"""
+URL configuration for config project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/6.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+]
diff --git a/config/wsgi.py b/config/wsgi.py
new file mode 100644
index 0000000..1b4cc01
--- /dev/null
+++ b/config/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for config project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+
+application = get_wsgi_application()
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..8e7ac79
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..8d6ddd0
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,40 @@
+arrow==1.4.0
+asgiref==3.13.0
+attrs==26.1.0
+binaryornot==0.6.0
+certifi==2026.10.23
+charset-normalizer==3.5.2
+click==8.5.0
+cookiecutter==2.7.1
+Django==6.1.3
+django-allauth==65.21.1
+django-environ==0.14.0
+django-filter==26.2
+django-tailwind==4.6.0
+djangorestframework==3.18.3
+drf-spectacular==0.30.0
+idna==3.20
+inflection==0.5.1
+Jinja2==3.1.6
+jsonschema==4.26.0
+jsonschema-specifications==2025.9.1
+markdown-it-py==4.2.0
+MarkupSafe==3.0.4
+mdurl==0.1.2
+psycopg==3.3.8
+psycopg-binary==3.3.8
+Pygments==2.21.0
+pytailwindcss==0.4.2
+python-dateutil==4.0.0
+python-slugify==9.1.3
+PyYAML==6.0.3
+referencing==0.37.0
+requests==2.34.2
+rich==15.0.0
+rpds-py==2026.9.1
+sqlparse==0.6.1
+text-unidecode==1.3
+typing_extensions==4.16.0
+tzdata==2026.6
+uritemplate==4.2.0
+urllib3==2.8.0
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/theme/__init__.py b/theme/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/theme/apps.py b/theme/apps.py
new file mode 100644
index 0000000..bec7464
--- /dev/null
+++ b/theme/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ThemeConfig(AppConfig):
+ name = 'theme'
diff --git a/theme/static_src/src/styles.css b/theme/static_src/src/styles.css
new file mode 100644
index 0000000..9bf3c00
--- /dev/null
+++ b/theme/static_src/src/styles.css
@@ -0,0 +1,10 @@
+@import "tailwindcss";
+
+/**
+ * A catch-all path to Django template files, JavaScript, and Python files
+ * that contain Tailwind CSS classes and will be scanned by Tailwind to generate the final CSS file.
+ *
+ * If your final CSS file is not being updated after code changes, you may want to broaden or narrow
+ * the scope of this path.
+ */
+@source "../../../**/*.{html,py,js}";
diff --git a/theme/templates/base.html b/theme/templates/base.html
new file mode 100644
index 0000000..a7dc35b
--- /dev/null
+++ b/theme/templates/base.html
@@ -0,0 +1,19 @@
+{% load static tailwind_tags %}
+
+
+
+ Django Tailwind
+
+
+ {% tailwind_css %}
+
+
+
+
+
+
+ Django + Tailwind = ❤️
+
+
+
+