commit 74b43cd8faf952cfbddc19f3ead6231d11066ac5 Author: Nell Date: Sat Jul 20 11:36:35 2024 +0200 init diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..a254ffe --- /dev/null +++ b/.env.sample @@ -0,0 +1,30 @@ +# should be prod or dev +ENV='dev' + +# secret +SECRET='CHANGE_ME' + +# Allowed hosts, domain's allowed, comma separated +ALLOWED_HOSTS='127.0.0.1, localhost' + +# CSRF Trusted origins +CSRF_TRUSTED_ORIGINS='http://127.0.0.1, http://localhost' + +### DEV RELATED +# VITE +DEV_SERVER_HOST='127.0.0.1' +DEV_SERVER_PORT='8080' + +### DB related +# db engine, should be 'sqlite', 'pgsql' (psycopg[binary] required) +DB_ENGINE='sqlite3' + +# SQLITE +SQLITE_REL_PATH='db.sqlite3' + +# PostgreSQL, docker compose .env file convention +#POSTGRES_HOST=localhost +#POSTGRES_PORT=5432 +#POSTGRES_DB=changeme +#POSTGRES_USER=changeme +#POSTGRES_PASSWORD=changeme diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..383aaa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ + +db.sqlite3 +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0932c2 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +## First run +* install https://nodejs.org/en (tested with version 20) +* install https://www.python.org/ (tested with version 3.12) +* `cp .env.sample .env` +* modify .env +* follow requirements sections + +## Requirements python +* ### for dev : `pip install -r requirements/dev.txt` +* ### for prod : `pip install -r requirements/prod.txt` + +## Requirements nodejs +* ### Linux `cd frontend && npm install && cd -` +* ### Windows `pushd .; cd frontend; npm install; popd` + + +## Run project +* ### for dev : + * in base dir `python manage.py runserver` + * in frontend dir `npm run dev` +* ### for prod : in frontend dir `npm run build` \ No newline at end of file diff --git a/_static/images/no_image.png b/_static/images/no_image.png new file mode 100644 index 0000000..ef9fa02 Binary files /dev/null and b/_static/images/no_image.png differ diff --git a/_static/images/no_mage_600_x_400.svg b/_static/images/no_mage_600_x_400.svg new file mode 100644 index 0000000..be82cd0 --- /dev/null +++ b/_static/images/no_mage_600_x_400.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/asgi.py b/app/asgi.py new file mode 100644 index 0000000..de17191 --- /dev/null +++ b/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app 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/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/app/settings.py b/app/settings.py new file mode 100644 index 0000000..35386fe --- /dev/null +++ b/app/settings.py @@ -0,0 +1,166 @@ +""" +Django settings for app project. + +Generated by 'django-admin startproject' using Django 5.0.4. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path +from dotenv import load_dotenv +from os import getenv + +# 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/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = getenv("SECRET", "not_secure") + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = getenv("ENV", "prod") == "dev" + +ALLOWED_HOSTS = [host.strip() for host in getenv("ALLOWED_HOSTS").split(",") if host] +CSRF_TRUSTED_ORIGINS = [host.strip() for host in getenv("CSRF_TRUSTED_ORIGINS").split(",") if host] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + "corsheaders", + "django_vite", + + "user", + "home", + "blog" +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + "corsheaders.middleware.CorsMiddleware", + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +match getenv("DB_ENGINE"): + case "pgsql": + DB = { + "ENGINE": "django.db.backends.postgresql", + "NAME": getenv("DB_NAME"), + "USER": getenv("DB_USER"), + "PASSWORD": getenv("DB_PASSWORD"), + "HOST": getenv("DB_HOST", "localhost"), + "PORT": int(getenv("DB_PORT", 5432)), + } + case _: + DB = { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / getenv("SQLITE_REL_PATH", "db.sqlite3"), + } + +DATABASES = { + 'default': DB +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/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/5.0/topics/i18n/ + +LANGUAGE_CODE = 'fr-fr' + +TIME_ZONE = 'Europe/Paris' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' +STATICFILES_DIRS = [] + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +AUTH_USER_MODEL = "user.User" +LOGIN_REDIRECT_URL = "/" +LOGIN_URL = "/user/login/" +LOGOUT_REDIRECT_URL = "/user/login/" +SESSION_COOKIE_AGE = 60 * 60 * 24 * 30 + +DJANGO_VITE = { + "default": { + "dev_mode": DEBUG, + "manifest_path": BASE_DIR / "frontend/dist/manifest.json", + "dev_server_host": getenv("DEV_SERVER_HOST", "localhost"), + "dev_server_port": int(getenv("DEV_SERVER_PORT", 8080)), + } +} +STATICFILES_DIRS += BASE_DIR / "frontend/dist", + diff --git a/app/urls.py b/app/urls.py new file mode 100644 index 0000000..c476adb --- /dev/null +++ b/app/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for app project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/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, include + +urlpatterns = [ + path('admin/', admin.site.urls), + + path("", include("home.urls", namespace="home")), + path("blog/", include("blog.urls", namespace="blog")), +] diff --git a/app/wsgi.py b/app/wsgi.py new file mode 100644 index 0000000..eb514ab --- /dev/null +++ b/app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for app 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/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_wsgi_application() diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000..40ef74c --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,26 @@ +from django.contrib import admin + +from ordered_model.admin import OrderedModelAdmin + +from .models import Article, Tag, Category +from .forms import ArticleForm + + +@admin.register(Article) +class ArticleAdmin(admin.ModelAdmin): + form = ArticleForm + list_display = ('title', 'description', 'author') + exclude = ["author"] + + def save_model(self, request, obj, form, change): + obj.author = request.user + return super().save_model(request, obj, form, change) + + +@admin.register(Category) +class CategoryAdmin(OrderedModelAdmin): + list_display = ('name', 'move_up_down_links') + + +admin.site.register(Tag) + diff --git a/blog/apps.py b/blog/apps.py new file mode 100644 index 0000000..94788a5 --- /dev/null +++ b/blog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blog' diff --git a/blog/forms.py b/blog/forms.py new file mode 100644 index 0000000..8010b59 --- /dev/null +++ b/blog/forms.py @@ -0,0 +1,13 @@ +from django import forms +from tinymce.widgets import TinyMCE + +from blog.models import Article + + +class ArticleForm(forms.ModelForm): + class Meta: + model = Article + exclude = "__all__" + widgets = { + "content": TinyMCE() + } diff --git a/blog/jinja2/blog/article_detail.html b/blog/jinja2/blog/article_detail.html new file mode 100644 index 0000000..26eb541 --- /dev/null +++ b/blog/jinja2/blog/article_detail.html @@ -0,0 +1,32 @@ +{% extends "blog/layout.html" %} + +{% block css %} + {{ super() }} + + + +{% endblock %} + +{% block js %} + {{ super() }} + + + + + + +{% endblock %} + +{% block content %} +
+
+

+ {{ article.title }} +

+
+

{{ article.description.replace("\n", "
")|safe }}

+
+ {{ article.content|safe }} +
+
+{% endblock %} \ No newline at end of file diff --git a/blog/jinja2/blog/article_list.html b/blog/jinja2/blog/article_list.html new file mode 100644 index 0000000..fb55ad7 --- /dev/null +++ b/blog/jinja2/blog/article_list.html @@ -0,0 +1,34 @@ +{% extends "blog/layout.html" %} + +{% block content %} + +
+ {% for article in articles %} + +
+ {% set article_image %} +
+{# #} + No image +
+ {% endset %} + + {% set article_text %} +
+
+

{{ article.title }}

+
+

{{ article.description.replace("\n", "
")|safe }}

+
+ {% endset %} + + {{ article_image }} + {{ article_text }} +
+
+ + {% else %} +

Aucun article

+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/blog/jinja2/blog/layout.html b/blog/jinja2/blog/layout.html new file mode 100644 index 0000000..d4dee60 --- /dev/null +++ b/blog/jinja2/blog/layout.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block css %} + {{ super() }} + {{ stylesheet_pack("blog") }} +{% endblock %} + +{% block js %} + {{ super() }} + {{ javascript_pack("blog") }} +{% endblock %} \ No newline at end of file diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 0000000..208c1c1 --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# Generated by Django 5.0.7 on 2024-07-19 08:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField()), + ('content', models.TextField()), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_modified', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')), + ('name', models.CharField(max_length=255)), + ], + options={ + 'verbose_name_plural': 'Categories', + 'ordering': ('order',), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_modified', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + ] diff --git a/blog/migrations/0002_initial.py b/blog/migrations/0002_initial.py new file mode 100644 index 0000000..56ef9ab --- /dev/null +++ b/blog/migrations/0002_initial.py @@ -0,0 +1,48 @@ +# Generated by Django 5.0.7 on 2024-07-19 08:31 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('blog', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='article', + name='author', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='article', + name='category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category'), + ), + migrations.AddField( + model_name='comment', + name='article', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article'), + ), + migrations.AddField( + model_name='comment', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='comment', + name='response_to', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.comment'), + ), + migrations.AddField( + model_name='article', + name='tags', + field=models.ManyToManyField(blank=True, to='blog.tag'), + ), + ] diff --git a/blog/migrations/__init__.py b/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 0000000..207f23f --- /dev/null +++ b/blog/models.py @@ -0,0 +1,44 @@ +from django.db import models + +from ordered_model.models import OrderedModel + + +class Article(models.Model): + title = models.CharField(max_length=255) + description = models.TextField() + content = models.TextField() + date_created = models.DateTimeField(auto_now_add=True) + date_modified = models.DateTimeField(auto_now=True) + category = models.ForeignKey("Category", on_delete=models.CASCADE, null=True, blank=True) + tags = models.ManyToManyField("Tag", blank=True) + author = models.ForeignKey("user.User", on_delete=models.CASCADE, null=True, blank=True) + + + def __str__(self): + return self.title + + +class Category(OrderedModel): + name = models.CharField(max_length=255) + + class Meta(OrderedModel.Meta): + verbose_name_plural = "Categories" + + def __str__(self): + return self.name + + +class Tag(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + +class Comment(models.Model): + author = models.ForeignKey("user.User", on_delete=models.CASCADE) + article = models.ForeignKey("Article", on_delete=models.CASCADE) + content = models.TextField() + date_created = models.DateTimeField(auto_now_add=True) + date_modified = models.DateTimeField(auto_now=True) + response_to = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True) diff --git a/blog/templates/blog/article_detail.html b/blog/templates/blog/article_detail.html new file mode 100644 index 0000000..26eb541 --- /dev/null +++ b/blog/templates/blog/article_detail.html @@ -0,0 +1,32 @@ +{% extends "blog/layout.html" %} + +{% block css %} + {{ super() }} + + + +{% endblock %} + +{% block js %} + {{ super() }} + + + + + + +{% endblock %} + +{% block content %} +
+
+

+ {{ article.title }} +

+
+

{{ article.description.replace("\n", "
")|safe }}

+
+ {{ article.content|safe }} +
+
+{% endblock %} \ No newline at end of file diff --git a/blog/templates/blog/article_list.html b/blog/templates/blog/article_list.html new file mode 100644 index 0000000..1fae5cf --- /dev/null +++ b/blog/templates/blog/article_list.html @@ -0,0 +1,34 @@ +{% extends "blog/layout.html" %} +{% load static %} + +{% block content %} + +
+ {% for article in articles %} + +
+{# {% set article_image %}#} +{#
#} +{# #} +{# No image#} +{#
#} +{# {% endset %}#} +{##} +{# {% set article_text %}#} +{#
#} +{#
#} +{#

{{ article.title }}

#} +{#
#} +{#

{{ article.description|safe|linebreaks }}

#} +{#
#} +{# {% endset %}#} + +{# {{ article_image }}#} +{# {{ article_text }}#} +
+
+ {% empty %} +

Aucun article

+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/blog/templates/blog/layout.html b/blog/templates/blog/layout.html new file mode 100644 index 0000000..d99f27e --- /dev/null +++ b/blog/templates/blog/layout.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block css %} + {{ super }} +{# {{ stylesheet_pack("blog") }}#} +{% endblock %} + +{% block js %} + {{ super }} +{# {{ javascript_pack("blog") }}#} +{% endblock %} \ No newline at end of file diff --git a/blog/tests.py b/blog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blog/urls.py b/blog/urls.py new file mode 100644 index 0000000..343cedd --- /dev/null +++ b/blog/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = 'blog' +urlpatterns = [ + path("", views.ArticleListView.as_view(), name="article_list"), + path("-/", views.ArticleDetailView.as_view(), name="article_detail"), +] diff --git a/blog/views.py b/blog/views.py new file mode 100644 index 0000000..ad5a018 --- /dev/null +++ b/blog/views.py @@ -0,0 +1,15 @@ +from django.views.generic import ListView, DetailView + +from blog.models import Article + + +class ArticleListView(ListView): + model = Article + template_name = 'blog/article_list.html' + context_object_name = 'articles' + + +class ArticleDetailView(DetailView): + model = Article + template_name = 'blog/article_detail.html' + context_object_name = 'article' diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..571254f --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1098 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "devDependencies": { + "autoprefixer": "^10.4.19", + "normalize.css": "^8.0.1", + "postcss": "^8.4.38", + "postcss-import": "^16.1.0", + "postcss-simple-vars": "^7.0.1", + "vite": "^5.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001617", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", + "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/electron-to-chromium": { + "version": "1.4.762", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz", + "integrity": "sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", + "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", + "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..ce009b1 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "autoprefixer": "^10.4.19", + "normalize.css": "^8.0.1", + "postcss": "^8.4.38", + "postcss-import": "^16.1.0", + "postcss-simple-vars": "^7.0.1", + "vite": "^5.2.0" + } +} diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..5bf2493 --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,7 @@ +module.exports = (ctx) => ({ + plugins: [ + require('postcss-import')(), + require('postcss-simple-vars')(), + require("autoprefixer")(), + ] +}) \ No newline at end of file diff --git a/frontend/src/application/app.js b/frontend/src/application/app.js new file mode 100644 index 0000000..977eff2 --- /dev/null +++ b/frontend/src/application/app.js @@ -0,0 +1,35 @@ +import 'vite/modulepreload-polyfill' +// import "../styles/app.css" + +function getCookie(name) { + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} + +(function(){ + // DOM is ready. + setTimeout(async () => { + const response = await fetch("/hit/", { + method: "POST", + headers: { + "Content-Type": "application/json", + 'X-CSRFToken': getCookie("csrftoken") + }, + mode: 'same-origin', + body: JSON.stringify({ + + }) + }); + }, 1000) +})(); diff --git a/frontend/src/application/app2.js b/frontend/src/application/app2.js new file mode 100644 index 0000000..2cbe41b --- /dev/null +++ b/frontend/src/application/app2.js @@ -0,0 +1,5 @@ +import "../components/sidebar"; + +window.document.addEventListener("DOMContentLoaded", function () { + window.console.log("dom ready 2"); +}); diff --git a/frontend/src/application/blog.js b/frontend/src/application/blog.js new file mode 100644 index 0000000..d78626a --- /dev/null +++ b/frontend/src/application/blog.js @@ -0,0 +1,2 @@ +import "../styles/blog.css" + diff --git a/frontend/src/application/cv.js b/frontend/src/application/cv.js new file mode 100644 index 0000000..ff8b964 --- /dev/null +++ b/frontend/src/application/cv.js @@ -0,0 +1 @@ +// import "../styles/cv.css" \ No newline at end of file diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js new file mode 100644 index 0000000..0c1bb90 --- /dev/null +++ b/frontend/src/components/sidebar.js @@ -0,0 +1 @@ +window.console.log("sidebar is loaded"); diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css new file mode 100644 index 0000000..e3454c3 --- /dev/null +++ b/frontend/src/styles/app.css @@ -0,0 +1,48 @@ +@import "normalize.css"; + +html::-webkit-scrollbar { + display: none; +} + +html{ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +body{ + background-color: rgba(43, 43, 42, 0.8); + overflow-y: scroll; +} + +ul{ + list-style-type: none; +} + +#main_header{ + background-color: white; + margin-top: 0; +} + +#main_header nav{ + max-width: 90%; + margin: 0 auto; + padding: 0; +} + +#main_header ul{ + margin-top: 0; + display: flex; + justify-content: center; +} + +#main_header li { + padding: 1rem; +} + +#main_app{ + max-width: 90%; + min-height: 50rem; + margin: 0 auto; + padding: 1rem; + background-color: white; +} \ No newline at end of file diff --git a/frontend/src/styles/blog.css b/frontend/src/styles/blog.css new file mode 100644 index 0000000..56ffa24 --- /dev/null +++ b/frontend/src/styles/blog.css @@ -0,0 +1,38 @@ +/* hello */ + +#article_list{ + /*background-color: grey;*/ +} + +#article_list article{ + /*background-color: green;*/ + display: flex; + border: 0.1rem solid green; + margin: 1rem; + /*height: 10rem;*/ +} + +#article_list a{ + text-decoration: none; + color: black; +} + +.article_image{ + width: 30%; + background-color: grey; +} + +.article_image img{ + /*image-;*/ + width: 100%; + height: 100%; +} + +.article_text{ + width: 70%; + margin: 1rem; +} + +.article_text h2{ + text-align: center; +} \ No newline at end of file diff --git a/frontend/src/styles/cv.css b/frontend/src/styles/cv.css new file mode 100644 index 0000000..2c75382 --- /dev/null +++ b/frontend/src/styles/cv.css @@ -0,0 +1,150 @@ +#cv_main{ + display: flex; + justify-content: normal; + flex-direction: row; +} + +#cv_main > #cv_left{ + background-color: beige; + max-width: 30%; + padding: 1rem; +} + +#cv_main > #cv_right{ + background-color: cadetblue; + min-width: 70%; + padding: 1rem; +} + +article header{ + display: flex; + flex-direction: row; + align-items: center; + background-color: #e0e0e0; + justify-content: center; + position: relative; +} + +article header>div:first-child{ + margin-left: 1rem; + position: absolute; + left: 0; + text-align: center; +} + +.cv_header_title{ + margin: auto; + + display: flex; + flex-direction: column; + align-items: center; +} + +.cv_header_title>*{ + padding: 0; + margin: 0.1rem; +} + +.cv_content_competence{ + max-width: 30%; + display: grid; + grid-template-columns: repeat(4, 2fr); + column-gap: 1rem; + row-gap: 1rem; +} + + + +/*article header{*/ +/* display: flex;*/ +/* flex-direction: row;*/ +/* align-items: center;*/ +/* background-color: #e0e0e0;*/ +/* justify-content: center;*/ +/* position: relative;*/ +/*}*/ +/*article header>div:first-child{*/ +/* margin-left: 1rem;*/ +/*}*/ + +/*.cv_header_title{*/ +/* margin: auto;*/ +/* display: flex;*/ +/* flex-direction: column;*/ +/* align-items: center;*/ + +/*}*/ + +/*.cv_header_title>*{*/ +/* padding: 0;*/ +/* margin: 0.1rem;*/ +/*}*/ +/*.g_pas_idee {*/ +/* position: absolute;*/ +/* left: 0;*/ +/* background-color: red;*/ +/* text-align: center;*/ +/*}*/ + +/*@media screen and (max-width: 700px) {*/ +/* .g_pas_idee {*/ +/* position: inherit;*/ +/* width: 100%;*/ +/* }*/ +/* article header{*/ +/* flex-direction: column;*/ +/* }*/ +/*}*/ + + + +/*article header{*/ +/* display: grid;*/ +/* grid-template-columns: 3fr 5fr;*/ +/* align-items: center;*/ +/* background-color: #e0e0e0;*/ +/*}*/ + +/*article header>div:first-child{*/ +/* margin-left: 1rem;*/ +/*}*/ + +/*.cv_header_title{*/ +/* text-align: center;*/ +/* margin-right: auto;*/ +/*}*/ + +/*.cv_header_title>h2,h3{*/ +/* margin: .5rem;*/ +/*}*/ + +/*article header>div{*/ +/* max-width: 60%;*/ +/* text-align: center;*/ +/*}*/ + +/*.cv_header_title{*/ +/* display: flex;*/ +/* flex-direction: column;*/ +/*}*/ + +.cv_content h4 { + text-decoration: underline; +} + + + +@media (max-width: 800px) { + #cv_main{ + flex-direction: column; + } + + #cv_main > #cv_left{ + max-width: 90%; + } + + #cv_main > #cv_right{ + min-width: 90%; + padding: 1rem; + } +} \ No newline at end of file diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss new file mode 100644 index 0000000..923ad00 --- /dev/null +++ b/frontend/src/styles/index.scss @@ -0,0 +1,17 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +//.jumbotron { +// // should be relative path of the entry scss file +// background-image: url("../../vendors/images/sample.jpg"); +// background-size: cover; +//} +// +//.btn-blue { +// @apply inline-block px-4 py-2; +// @apply font-semibold rounded-lg shadow-md; +// @apply bg-blue-500 text-white; +// @apply hover:bg-blue-700 focus:outline-none; +// @apply focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75; +//} \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..77b0a88 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,49 @@ +import {defineConfig, loadEnv} from "vite"; +import {resolve, join} from "path"; + +// const postcssConfig = { +// plugins: [ +// require('postcss-import')(), +// require('postcss-simple-vars')(), +// require('autoprefixer')(), +// ], +// }; + +export default defineConfig((mode) => { + const env = loadEnv(mode, "..", ""), + SRC_DIR = resolve("./src"), + OUT_DIR = resolve("./dist") + + return { + plugins: [ + + ], + resolve: { + alias: { + "@": resolve(SRC_DIR) + } + }, + root: SRC_DIR, + base: "/static/", + // css: { + // postcss: postcssConfig + // }, + server: { + host: env.DEV_SERVER_HOST, + port: env.DEV_SERVER_PORT + }, + build: { + manifest: "manifest.json", + emptyOutDir: true, + outDir: OUT_DIR, + rollupOptions: { + input: { + app: join(SRC_DIR, "application/app.js"), + app2: join(SRC_DIR, "application/app2.js"), + blog: join(SRC_DIR, "application/blog.js"), + cv: join(SRC_DIR, "application/cv.js") + } + } + } + } +}) \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock new file mode 100644 index 0000000..895a054 --- /dev/null +++ b/frontend/yarn.lock @@ -0,0 +1,241 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@rollup/rollup-win32-x64-msvc@4.17.2": + version "4.17.2" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz" + integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w== + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +autoprefixer@^10.4.19: + version "10.4.19" + resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +browserslist@^4.23.0, "browserslist@>= 4.21.0": + version "4.23.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: + version "1.0.30001617" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz" + integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== + +electron-to-chromium@^1.4.668: + version "1.4.762" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz" + integrity sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ== + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +postcss-import@^16.1.0: + version "16.1.0" + resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz" + integrity sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-simple-vars@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz" + integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.1, postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +resolve@^1.1.7: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rollup@^4.13.0: + version "4.17.2" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz" + integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.17.2" + "@rollup/rollup-android-arm64" "4.17.2" + "@rollup/rollup-darwin-arm64" "4.17.2" + "@rollup/rollup-darwin-x64" "4.17.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.17.2" + "@rollup/rollup-linux-arm-musleabihf" "4.17.2" + "@rollup/rollup-linux-arm64-gnu" "4.17.2" + "@rollup/rollup-linux-arm64-musl" "4.17.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.17.2" + "@rollup/rollup-linux-riscv64-gnu" "4.17.2" + "@rollup/rollup-linux-s390x-gnu" "4.17.2" + "@rollup/rollup-linux-x64-gnu" "4.17.2" + "@rollup/rollup-linux-x64-musl" "4.17.2" + "@rollup/rollup-win32-arm64-msvc" "4.17.2" + "@rollup/rollup-win32-ia32-msvc" "4.17.2" + "@rollup/rollup-win32-x64-msvc" "4.17.2" + fsevents "~2.3.2" + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +update-browserslist-db@^1.0.13: + version "1.0.15" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz" + integrity sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.0" + +vite@^5.2.0: + version "5.2.11" + resolved "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz" + integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" diff --git a/home/__init__.py b/home/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/home/admin.py b/home/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/home/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/home/apps.py b/home/apps.py new file mode 100644 index 0000000..e5ea0af --- /dev/null +++ b/home/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HomeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'home' diff --git a/home/forms.py b/home/forms.py new file mode 100644 index 0000000..4cf270e --- /dev/null +++ b/home/forms.py @@ -0,0 +1,8 @@ +from django import forms + + +class ContactForm(forms.Form): + full_name = forms.CharField(max_length=255, required=True, label="Nom/Prénom ou entreprise") + email = forms.EmailField(required=True) + subject = forms.CharField(max_length=255, required=True, label="Sujet") + message = forms.CharField(widget=forms.Textarea, required=True) diff --git a/home/middleware.py b/home/middleware.py new file mode 100644 index 0000000..6d83a3f --- /dev/null +++ b/home/middleware.py @@ -0,0 +1,24 @@ +from django.middleware.csrf import rotate_token + + +class SessionOnHitMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if not request.user.is_authenticated and not request.session.session_key: + request.session.save() + response = self.get_response(request) + return response + + +class CsrfOnHitMiddleWare: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if 'CSRF_COOKIE' not in request.META: + rotate_token(request) + response = self.get_response(request) + + return response diff --git a/home/migrations/0001_initial.py b/home/migrations/0001_initial.py new file mode 100644 index 0000000..386ffd7 --- /dev/null +++ b/home/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.7 on 2024-07-19 08:31 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Hit', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('path', models.CharField(max_length=255)), + ('parameters', models.JSONField(default=dict)), + ('session_id', models.CharField(max_length=255)), + ('ip_address', models.GenericIPAddressField()), + ('user_agent', models.CharField(max_length=255)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/home/migrations/__init__.py b/home/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/home/models.py b/home/models.py new file mode 100644 index 0000000..276876a --- /dev/null +++ b/home/models.py @@ -0,0 +1,15 @@ +from django.db import models + +import uuid + + +class Hit(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + path = models.CharField(max_length=255) + parameters = models.JSONField(default=dict) + session_id = models.CharField(max_length=255) + ip_address = models.GenericIPAddressField(max_length=255) + user_agent = models.CharField(max_length=255) + date_created = models.DateTimeField(auto_now_add=True) + + diff --git a/home/templates/home/contact.html b/home/templates/home/contact.html new file mode 100644 index 0000000..1aee529 --- /dev/null +++ b/home/templates/home/contact.html @@ -0,0 +1,28 @@ +{% extends "home/layout.html" %} + +{% block content %} +
+ {{ csrf_input }} +
+ {{ form.full_name.errors }} + {{ form.full_name.label_tag }} + {{ form.full_name }} +
+
+ {{ form.email.errors }} + {{ form.email.label_tag }} + {{ form.email }} +
+
+ {{ form.subject.errors }} + {{ form.subject.label_tag }} + {{ form.subject }} +
+
+ {{ form.message.errors }} + {{ form.message.label_tag }} + {{ form.message }} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/home/templates/home/contact_success.html b/home/templates/home/contact_success.html new file mode 100644 index 0000000..598ec49 --- /dev/null +++ b/home/templates/home/contact_success.html @@ -0,0 +1,5 @@ +{% extends "home/layout.html" %} + +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/home/templates/home/cv.html b/home/templates/home/cv.html new file mode 100644 index 0000000..8527c80 --- /dev/null +++ b/home/templates/home/cv.html @@ -0,0 +1,204 @@ +{% extends "home/layout.html" %} +{% load django_vite %} + +{% block css %} + {{ super }} + + +{% endblock %} + +{% block js %} + {% vite_asset "application/cv.js" %} +{% endblock %} + +{% block content %} +
+
+
+

Fabien ROUSSEAU

+

Développeur Python/Django

+

+ Passionné d'informatique. J'ai décidé d'en faire mon métier en apprenant par moi-même l'électronique, le réseau puis par la suite le développement + Aujourd'hui je souhaiterais poursuivre ce parcours au sein d'une entreprise dont je partage les valeurs qui m'accompagnera dans un processus de validation et certification de mon expérience et de mes compétences +

+
+
+

Coodonnées

+ +
+ +
+

Compétences

+

Language et framework

+
    +
  • Python
  • +
  • Django
  • +
  • Django Rest Framework
  • +
  • Django Channels
  • +
  • Jinja
  • +
  • Celery
  • +
  • VueJS
  • +
  • NuxtJS
  • +
  • Vuetify
  • +
  • OCPP
  • +
+

Système d'exploitation

+
    +
  • Linux
  • +
  • Windows
  • +
+

Outils

+
    +
  • Docker
  • +
  • Git
  • +
  • Kanban
  • +
+

Base de données

+
    +
  • PostgreSQL
  • +
  • MySQL
  • +
  • Redis
  • +
+
+ +
+

Points forts

+
    +
  • Passionné de développement
  • +
  • Polyvalent et adaptabilité
  • +
  • Travaille efficacement en équipe
  • +
  • Consciencieux
  • +
+
+
+
+
+
+
+

Avril 2017 à Maintenant

+
+
+

Développeur

+

Auto-entrepreneur - Nantes - Full remote

+
+
+
+

Services :

+
    +
  • Développement applicatif
  • +
  • Conseil en informatique
  • +
  • Hébergement et maintenance web
  • +
+

Missions :

+
    +
  • Site web de suivi d’actualité footballistique
  • +
  • Site de suivi des résultats et statistique footballistique
  • +
  • Site d’hébergement de CV pour sportifs amateurs
  • +
  • Site de gestion de formation dans le domaine des sports aquatiques
  • +
  • Conseil divers en informatique
  • +
  • Intervention en soutien pour maintenance d’infrastructure
  • +
  • intervention pour migration de sites
  • +
  • Site vitrine d’entreprise d’enfouissement électrique et gaz
  • +
  • Mise en place de scrapping pour entreprise
  • +
+

Compétences technique :

+
    +
  • Python
  • +
  • Django
  • +
  • Django REST FRAMEWORK
  • +
  • Django Channels
  • +
  • Celery
  • +
  • PostgreSQL
  • +
  • Redis
  • +
  • VueJS
  • +
  • NuxtJS
  • +
  • Vuetify
  • +
  • Docker
  • +
  • Docker compose
  • +
+
+
+
+
+
+

Octobre 2021 à Novembre 2023

+
+
+

Lead Développeur

+

PredicVision - Bordeaux - Full remote

+
+
+
+

Objectif :

+

+ Création d’une plateforme afin d’analyser et maintenir un parc de bornes de rechargement pour véhicules électriques. +

+

Compétences Techniques :

+
    +
  • Python
  • +
  • Django
  • +
  • Django REST FRAMEWORK
  • +
  • Django Channels
  • +
  • Celery
  • +
  • OCPP
  • +
  • PostgreSQL
  • +
  • Redis
  • +
  • VueJS
  • +
  • NuxtJS
  • +
  • Vuetify
  • +
  • Docker
  • +
  • Docker compose
  • +
+

Compétences supplémentaires :

+
    +
  • Gitlab
  • +
  • Kanban
  • +
  • conseil
  • +
  • réunion
  • +
  • veille technique
  • +
  • étude de faisabilité
  • +
  • présentation
  • +
+
+
+
+
+
+

Septembre 2020 à Octobre 2021

+
+
+

Développeur Django

+

FMC Production - Nantes - Hybride

+
+
+
+

Objectif :

+

+ Maintient d’une plateforme e-learning et création d’une plateforme de conférence live et VOD pour le monde médical +

+

Compétences Techniques :

+
    +
  • Python
  • +
  • Django
  • +
  • Django REST FRAMEWORK
  • +
  • Django Channels
  • +
  • Celery
  • +
  • PostgreSQL
  • +
  • Redis
  • +
  • Docker
  • +
  • Docker compose
  • +
+

Compétences supplémentaires :

+
    +
  • Gitlab
  • +
  • Daily
  • +
+
+
+
+
+{% endblock %} + diff --git a/home/templates/home/index.html b/home/templates/home/index.html new file mode 100644 index 0000000..7680688 --- /dev/null +++ b/home/templates/home/index.html @@ -0,0 +1,17 @@ +{% extends "home/layout.html" %} + +{% block content %} +

Présentation

+

+ Développeur passionné d'informatique depuis le plus jeune âge. + Cette page sera un condenser d'une vitrine de moi même, + des prestations que je peux offrir + ainsi qu'une multitude de partage de connaissance à travers mon blog +

+

En quête d'un projet sur la DATA

+

+ Ayant une expérience dans la data, + je suis en recherche d'un projet qui approfondira mes connaissances sur le sujet + avec pourquoi pas un cumul de connaissances sur le web +

+{% endblock %} diff --git a/home/templates/home/layout.html b/home/templates/home/layout.html new file mode 100644 index 0000000..63913c1 --- /dev/null +++ b/home/templates/home/layout.html @@ -0,0 +1 @@ +{% extends "base.html" %} \ No newline at end of file diff --git a/home/templates/home/service.html b/home/templates/home/service.html new file mode 100644 index 0000000..ae69d2a --- /dev/null +++ b/home/templates/home/service.html @@ -0,0 +1,19 @@ +{% extends "home/layout.html" %} + +{% block content %} +

+ Prestation de service ou de maintenance Python/Django/Flask/FastAPI : + facturation à la journée à raison de 450€ par jour en moyenne (ce prix peut varier si c'est un projet qui dur dans le temps) +

+
    +
  • Création de script
  • +
  • Création de site simple comme complex
  • +
  • Création de graphique (chart) sur des grands comme des petits volumes de donnée
  • +
  • Mise à jour de sécurité
  • +
  • Correction de bug
  • +
  • Modification de votre projet existant
  • +
  • Migration
  • +
  • Optimisation
  • +
+ +{% endblock %} \ No newline at end of file diff --git a/home/tests.py b/home/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/home/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/home/urls.py b/home/urls.py new file mode 100644 index 0000000..6a2e925 --- /dev/null +++ b/home/urls.py @@ -0,0 +1,14 @@ +from django.urls import path + +from . import views + + +app_name = 'home' +urlpatterns = [ + path('', views.IndexView.as_view(), name='index'), + path("cv/", views.CVView.as_view(), name='cv'), + path("service/", views.ServiceView.as_view(), name='service'), + path("contact/", views.ContactView.as_view(), name='contact'), + path("contact_valid/", views.ContactValidView.as_view(), name='contact_valid'), + path("hit/", views.hit, name="hit") +] diff --git a/home/views.py b/home/views.py new file mode 100644 index 0000000..683a14d --- /dev/null +++ b/home/views.py @@ -0,0 +1,50 @@ +from django.views.generic import TemplateView, FormView +from django.http import HttpResponse + +from ipware import get_client_ip +from urllib import parse + +from .forms import ContactForm +from .models import Hit + + +class IndexView(TemplateView): + template_name = 'home/index.html' + + +class CVView(TemplateView): + template_name = 'home/cv.html' + + +class ServiceView(TemplateView): + template_name = "home/service.html" + + +class ContactView(FormView): + template_name = 'home/contact.html' + form_class = ContactForm + + +class ContactValidView(TemplateView): + template_name = "home/contact_success.html" + + +def hit(request): + if request.method == "POST": + ip_address, _ = get_client_ip(request) + ip_address = '.'.join(ip_address.split('.')[:-1]+["0"]) + path_parser = parse.urlsplit(request.headers["referer"]) + path = path_parser.path + parameters = dict(parse.parse_qsl(path_parser.query)) + user_agent = request.headers["user-agent"] + session_id = request.session.session_key + + Hit.objects.create( + path=path_parser.path, + parameters=parameters, + session_id=session_id, + ip_address=ip_address, + user_agent=user_agent, + ) + return HttpResponse("") + diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..4931389 --- /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', 'app.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/pytest.ini b/pytest.ini new file mode 100644 index 0000000..302cf89 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = app.settings +python_files = test_* \ No newline at end of file diff --git a/requirements/common.txt b/requirements/common.txt new file mode 100644 index 0000000..27474cf --- /dev/null +++ b/requirements/common.txt @@ -0,0 +1,10 @@ +asgiref==3.8.* +Django==5.0.* +django-cors-headers==4.4.* +sqlparse==0.5.* +tzdata==2024.1 +django-vite==3.0.* +python-dotenv==1.0.* +django-ordered-model==3.7.* +django-tinymce==4.1.* +django-ipware==7.0.* \ No newline at end of file diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 0000000..017d1e4 --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,2 @@ +-r common.txt +pytest-django==4.8.* \ No newline at end of file diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000..c3899b0 --- /dev/null +++ b/requirements/prod.txt @@ -0,0 +1 @@ +-r common.txt \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..10191f2 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,45 @@ +{% load django_vite %} +{% load static %} + + + + + + + {% block base_title %}devpanel - {% block title %}Accueil{% endblock %}{% endblock %} + {% block base_css %} + + {% block css %} + {% endblock %} + {% endblock %} + + +
+ +
+ +
+ {% block content %} + {% endblock %} +
+ + + +{% block base_js %} + {% vite_asset "application/app.js" %} + + {% block js %} + {% endblock %} +{% endblock %} + + \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/blog/__init__.py b/tests/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/blog/test_models.py b/tests/blog/test_models.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/blog/test_views.py b/tests/blog/test_views.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/home/__init__.py b/tests/home/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/home/test_views.py b/tests/home/test_views.py new file mode 100644 index 0000000..2487f93 --- /dev/null +++ b/tests/home/test_views.py @@ -0,0 +1,51 @@ +from django.test import Client +from django.urls import reverse +import pytest +from pytest_django import asserts + + +def test_index(): + c = Client() + response = c.get(reverse('home:index')) + + assert response.status_code == 200 + asserts.assertTemplateUsed(response, 'home/index.html') + + +def test_cv(): + c = Client() + response = c.get(reverse('home:cv')) + + assert response.status_code == 200 + asserts.assertTemplateUsed(response, 'home/cv.html') + + +def test_service(): + c = Client() + response = c.get(reverse('home:service')) + + assert response.status_code == 200 + asserts.assertTemplateUsed(response, 'home/service.html') + + +def test_contact(): + c = Client() + response = c.get(reverse('home:contact')) + + assert response.status_code == 200 + asserts.assertTemplateUsed(response, 'home/contact.html') + + +def test_contact_submit_valid(): + c = Client() + data = {"full_name": "foo bar", "email": "foo@bar.com", "subject": "test", "message": "test"} + response = c.post(reverse('home:contact'), data=data) + asserts.assertRedirects(response, reverse('home:contact_valid')) + + +def test_contact_submit_invalid(): + c = Client() + data = {"bad": "form"} + response = c.post(reverse('home:contact'), data=data) + assert response.status_code == 200 + diff --git a/tests/user/__init__.py b/tests/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/admin.py b/user/admin.py new file mode 100644 index 0000000..dfd024b --- /dev/null +++ b/user/admin.py @@ -0,0 +1,27 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin + +from .models import User +from .forms import UserCreationForm, UserChangeForm + + +@admin.register(User) +class UserAdmin(BaseUserAdmin): + add_form = UserCreationForm + form = UserChangeForm + model = User + list_display = ["email", "is_staff", "is_superuser", "is_active"] + list_filter = ["email", "is_staff", "is_superuser", "is_active"] + fieldsets = [ + [None, {"fields": ["email", "password"]}], + ("permissions", {"fields": ["is_staff", "is_active", "is_superuser"]}) + ] + add_fieldsets = [ + [None, { + "classes": ["wide"], + "fields": ["email", "password1", "password2", "is_staff", "is_active", "is_superuser"] + }] + ] + search_fields = ["email"] + ordering = ["email"] + diff --git a/user/apps.py b/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/user/forms.py b/user/forms.py new file mode 100644 index 0000000..004b517 --- /dev/null +++ b/user/forms.py @@ -0,0 +1,11 @@ +from django.contrib.auth import forms as BaseAuthForms + +from .models import User + + +class UserCreationForm(BaseAuthForms.UserCreationForm): + pass + + +class UserChangeForm(BaseAuthForms.UserChangeForm): + pass diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 0000000..bdc2686 --- /dev/null +++ b/user/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.7 on 2024-07-19 08:31 + +import django.contrib.auth.validators +import django.utils.timezone +import user.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('email', models.EmailField(max_length=254, unique=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', user.models.UsernameUserManager()), + ], + ), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/models.py b/user/models.py new file mode 100644 index 0000000..65cd5f7 --- /dev/null +++ b/user/models.py @@ -0,0 +1,71 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser, BaseUserManager + + +# class EmailUserManager(BaseUserManager): +# use_in_migrations = True +# +# def _create_user(self, email, password, **extra_fields): +# if not email: +# raise ValueError("Un email doit être défini") +# +# email = self.normalize_email(email) +# user = self.model(email=email, **extra_fields) +# user.set_password(password) +# user.save(using=self.db) +# return user +# +# def create_user(self, username, email, password=None, **extra_fields): +# extra_fields.setdefault("is_staff", False) +# extra_fields.setdefault("is_superuser", False) +# return self._create_user(username, email, password, **extra_fields) +# +# def create_superuser(self, email, password, **extra_fields): +# extra_fields.setdefault("is_staff", True) +# extra_fields.setdefault("is_superuser", True) +# +# if extra_fields.get("is_staff") is not True: +# raise ValueError("Superuser doit être staff à True") +# if extra_fields.get("is_superuser") is not True: +# raise ValueError("SuperUser doit être is_superuser à True") +# +# return self._create_user(email, password, **extra_fields) + + +class UsernameUserManager(BaseUserManager): + use_in_migrations = True + + def _create_user(self, username, email, password, **extra_fields): + if not username: + raise ValueError("Un username doit être défini") + if not email: + raise ValueError("Un email doit être défini") + + email = self.normalize_email(email) + user = self.model(username=username, email=email, **extra_fields) + user.set_password(password) + user.save(using=self.db) + return user + + def create_user(self, username, email, password=None, **extra_fields): + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return self._create_user(username, email, password, **extra_fields) + + def create_superuser(self, username, email, password, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser doit être staff à True") + if extra_fields.get("is_superuser") is not True: + raise ValueError("SuperUser doit être is_superuser à True") + + return self._create_user(username, email, password, **extra_fields) + + +class User(AbstractUser): + email = models.EmailField(unique=True) + + REQUIRED_FIELDS = [] + objects = UsernameUserManager() diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/user/views.py b/user/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.