mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7dc2e8eb9 | |||
| 47e101c6fc | |||
| 9935da0496 | |||
| 05e4df2d7b | |||
| 06585163d0 | |||
| c40e3d20cb | |||
| f5757b47de | |||
| 029b02b3c8 | |||
| c5aeccf58f | |||
| 1927dedf22 | |||
| dd7f3e1fe9 | |||
| b1d2c374b6 | |||
| 2e0c4e557d | |||
| 3f761cfe7e | |||
| 6ad8837145 | |||
| ce8bb5c018 | |||
| 7a05a6040e | |||
| 587160811f | |||
| 4195fce415 | |||
| 4277298c47 | |||
| b08d505a5a |
+21
-19
@@ -1,25 +1,27 @@
|
|||||||
# Pi Ku E2E Environment Configuration Template
|
# DATABASE
|
||||||
|
DB_NAME=piku_test_db
|
||||||
|
DB_USER=test
|
||||||
|
DB_PASSWORD=password123
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5433
|
||||||
|
|
||||||
# Database (Postgres)
|
# SSL
|
||||||
E2E_DB_NAME=piku_e2e_db
|
SSL_ENABLED=false
|
||||||
E2E_DB_PORT=5433
|
|
||||||
E2E_DB_USER=piku_test
|
|
||||||
E2E_DB_PASS=piku_test
|
|
||||||
E2E_DB_DB=piku_e2e
|
|
||||||
|
|
||||||
# Backend (Django)
|
# DJANGO
|
||||||
E2E_BACKEND_PORT=8001
|
|
||||||
SECRET_KEY=e2e-secret-key-for-piku-testing
|
|
||||||
DEBUG=True
|
DEBUG=True
|
||||||
ALLOWED_HOSTS=*
|
SECRET_KEY=django-insecure-initial-key
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:5173
|
BACKEND_DOMAIN=127.0.0.1
|
||||||
FRONTEND_URL=http://localhost:5173
|
BACKEND_PORT=8001
|
||||||
EMAIL_HOST=localhost
|
|
||||||
EMAIL_PORT=1025
|
# EMAIL
|
||||||
|
EMAIL_HOST=127.0.0.1
|
||||||
|
EMAIL_PORT=1026
|
||||||
|
FROM_EMAIL="Test <test@pi-ku.app>"
|
||||||
EMAIL_HOST_USER=
|
EMAIL_HOST_USER=
|
||||||
EMAIL_HOST_PASSWORD=
|
EMAIL_HOST_PASSWORD=
|
||||||
FROM_EMAIL=testing@piku.local
|
EMAIL_API_PORT=8026
|
||||||
MAILPIT_API_URL=http://localhost:8025/api/v1
|
|
||||||
|
|
||||||
# Frontend (Vite/Playwright)
|
# FRONTEND
|
||||||
VITE_API_URL=http://localhost:8001
|
FRONTEND_PORT=5199
|
||||||
|
FRONTEND_DOMAIN=127.0.0.1
|
||||||
|
|||||||
+8
-9
@@ -5,21 +5,20 @@ DB_PASSWORD=password123
|
|||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# SSL
|
||||||
|
SSL_ENABLED=true
|
||||||
|
|
||||||
# DJANGO
|
# DJANGO
|
||||||
DEBUG=True
|
DEBUG=True
|
||||||
SECRET_KEY=django-secret-key
|
SECRET_KEY=django-secret-key
|
||||||
ALLOWED_HOSTS=localhost,127.0.0.1
|
BACKEND_DOMAIN=127.0.0.1
|
||||||
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
BACKEND_PORT=8000
|
||||||
|
|
||||||
# EMAIL
|
# EMAIL
|
||||||
EMAIL_HOST=localhost
|
EMAIL_HOST=127.0.0.1
|
||||||
EMAIL_PORT=1025
|
EMAIL_PORT=1025
|
||||||
FROM_EMAIL=Pi Ku <no-reply@piku.app>
|
FROM_EMAIL=Pi Ku <no-reply@test.com>
|
||||||
EMAIL_HOST_USER=root
|
|
||||||
EMAIL_HOST_PASSWORD=password123
|
|
||||||
|
|
||||||
# FRONTEND
|
# FRONTEND
|
||||||
VITE_API_URL=http://localhost:8000
|
|
||||||
FRONTEND_PORT=5173
|
FRONTEND_PORT=5173
|
||||||
FRONTEND_URL=http://localhost:5173
|
FRONTEND_DOMAIN=127.0.0.1
|
||||||
FRONTEND_DOMAIN=localhost
|
|
||||||
|
|||||||
+88
-47
@@ -7,34 +7,59 @@ on:
|
|||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
setup-environment:
|
||||||
|
name: Generate Certificates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Generate SSL Certificates
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y mkcert libnss3-tools
|
||||||
|
mkdir -p certs
|
||||||
|
mkcert -install
|
||||||
|
mkcert -cert-file certs/localhost.pem -key-file certs/localhost-key.pem localhost 127.0.0.1 ::1
|
||||||
|
|
||||||
|
- name: Cache certificates
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: certs
|
||||||
|
key: certs-${{ runner.os }}-${{ github.sha }}
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
name: Frontend CI
|
name: Frontend CI
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: setup-environment
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Create .env from example
|
|
||||||
run: cp ../.env.example ../.env
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
|
- name: Restore certificates
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
path: certs
|
||||||
|
key: certs-${{ runner.os }}-${{ github.sha }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
- name: Code Quality (Biome)
|
|
||||||
run: bun run check
|
- name: Code Quality
|
||||||
|
run: |
|
||||||
|
cp ../.env.example ../.env
|
||||||
|
bun run check
|
||||||
|
|
||||||
- name: Type Check & Build
|
- name: Type Check & Build
|
||||||
run: bun run build
|
run: bun run build
|
||||||
- name: Run Unit Tests
|
|
||||||
|
- name: Unit Tests
|
||||||
run: bun run test
|
run: bun run test
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
name: Backend CI
|
name: Backend CI
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
needs: setup-environment
|
||||||
run:
|
|
||||||
working-directory: ./backend
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
@@ -44,63 +69,79 @@ jobs:
|
|||||||
POSTGRES_PASSWORD: password123
|
POSTGRES_PASSWORD: password123
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 # wait till up before starting (integrating) django app
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./backend
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Create .env from example
|
|
||||||
run: cp ../.env.example ../.env
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@v5
|
uses: astral-sh/setup-uv@v5
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: backend/uv.lock
|
cache-dependency-glob: "backend/uv.lock"
|
||||||
- name: Install dependencies
|
|
||||||
run: uv sync
|
- name: Restore certificates
|
||||||
- name: Lint (Ruff)
|
uses: actions/cache/restore@v4
|
||||||
run: uv run ruff check
|
with:
|
||||||
- name: Run Tests
|
path: certs
|
||||||
run: uv run python manage.py test
|
key: certs-${{ runner.os }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Setup Environment
|
||||||
|
run: |
|
||||||
|
cp ../.env.example ../.env
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
- name: Lint & Test
|
||||||
|
run: |
|
||||||
|
uv run ruff check
|
||||||
|
uv run python manage.py test
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
needs: setup-environment
|
||||||
postgres:
|
|
||||||
image: postgres:16-alpine
|
|
||||||
env:
|
|
||||||
POSTGRES_DB: piku_e2e
|
|
||||||
POSTGRES_USER: piku_test
|
|
||||||
POSTGRES_PASSWORD: piku_test
|
|
||||||
ports:
|
|
||||||
- 5433:5432
|
|
||||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
|
||||||
mailpit:
|
|
||||||
image: axllent/mailpit:latest
|
|
||||||
ports:
|
|
||||||
- 8025:8025
|
|
||||||
- 1025:1025
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v5
|
- name: Restore Certificates
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
path: certs
|
||||||
|
key: certs-${{ runner.os }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Setup Tools
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
- name: Install Frontend dependencies
|
|
||||||
run: cd frontend && bun install
|
|
||||||
- name: Install Playwright Browsers
|
- name: Cache Playwright
|
||||||
run: cd frontend && bun x playwright install --with-deps
|
id: playwright-cache
|
||||||
- name: Create .env.e2e
|
uses: actions/cache@v4
|
||||||
run: cp .env.e2e.example .env.e2e
|
with:
|
||||||
- name: Run E2E Script
|
path: ~/.cache/ms-playwright
|
||||||
run: ./scripts/run-e2e.sh
|
key: ${{ runner.os }}-playwright-${{ hashFiles('frontend/bun.lock') }}
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
(cd frontend && bun install)
|
||||||
|
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" != "true" ]; then
|
||||||
|
(cd frontend && bun x playwright install --with-deps)
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run E2E
|
||||||
|
run: |
|
||||||
|
cp .env.e2e.example .env.e2e
|
||||||
|
chmod +x ./scripts/run-e2e.sh
|
||||||
|
./scripts/run-e2e.sh
|
||||||
env:
|
env:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
|
|
||||||
- name: Upload Playwright Report
|
- name: Upload Playwright Report
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: frontend/playwright-report/
|
path: frontend/playwright-report/
|
||||||
retention-days: 30
|
retention-days: 10
|
||||||
|
|||||||
@@ -9,3 +9,7 @@ dist/
|
|||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# Certificates
|
||||||
|
certs/*.pem
|
||||||
|
tmp/
|
||||||
|
|||||||
+14
-11
@@ -19,12 +19,17 @@ import environ
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
# Load environment variables
|
# Load dotenv files
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
# Allow overriding the .env file path (useful for E2E testing/CI)
|
env_file = os.path.join(BASE_DIR.parent, ".env")
|
||||||
env_file = os.environ.get("PIKU_ENV_FILE", os.path.join(BASE_DIR.parent, ".env"))
|
|
||||||
if os.path.exists(env_file):
|
if os.path.exists(env_file):
|
||||||
environ.Env.read_env(env_file)
|
environ.Env.read_env(env_file, overwrite=False)
|
||||||
|
|
||||||
|
|
||||||
|
SSL_ENABLED = env("SSL_ENABLED") == "true"
|
||||||
|
FRONTEND_URL = f"https://{env('FRONTEND_DOMAIN')}" if SSL_ENABLED else f"http://{env('FRONTEND_DOMAIN')}"
|
||||||
|
if env("FRONTEND_PORT"):
|
||||||
|
FRONTEND_URL += f":{env('FRONTEND_PORT')}"
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||||
@@ -35,7 +40,7 @@ SECRET_KEY = env("SECRET_KEY")
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env("DEBUG")
|
DEBUG = env("DEBUG")
|
||||||
|
|
||||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") or []
|
ALLOWED_HOSTS = [env("FRONTEND_DOMAIN")]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@@ -45,10 +50,12 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"django_extensions",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
"users",
|
"users",
|
||||||
"letters",
|
"letters",
|
||||||
|
"scripts",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -81,7 +88,7 @@ DATABASES = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = env.list("CORS_ALLOWED_ORIGINS")
|
CORS_ALLOWED_ORIGINS = [FRONTEND_URL]
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
AUTH_USER_MODEL = "users.User"
|
AUTH_USER_MODEL = "users.User"
|
||||||
@@ -106,7 +113,7 @@ NOTE: COOKIE_SAMESITE: Lax is used to allow cross-site redirection, like links
|
|||||||
AUTH_COOKIE = {
|
AUTH_COOKIE = {
|
||||||
"NAME": "refresh_token",
|
"NAME": "refresh_token",
|
||||||
"DOMAIN": None,
|
"DOMAIN": None,
|
||||||
"SECURE": not DEBUG,
|
"SECURE": SSL_ENABLED,
|
||||||
"HTTPONLY": True,
|
"HTTPONLY": True,
|
||||||
"SAMESITE": "Lax",
|
"SAMESITE": "Lax",
|
||||||
}
|
}
|
||||||
@@ -116,12 +123,8 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
|||||||
EMAIL_HOST = env("EMAIL_HOST")
|
EMAIL_HOST = env("EMAIL_HOST")
|
||||||
EMAIL_PORT = env("EMAIL_PORT")
|
EMAIL_PORT = env("EMAIL_PORT")
|
||||||
EMAIL_USE_TLS = not DEBUG
|
EMAIL_USE_TLS = not DEBUG
|
||||||
EMAIL_HOST_USER = env("EMAIL_HOST_USER")
|
|
||||||
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD")
|
|
||||||
FROM_EMAIL = env("FROM_EMAIL")
|
FROM_EMAIL = env("FROM_EMAIL")
|
||||||
|
|
||||||
FRONTEND_URL = env("FRONTEND_URL")
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "backend"
|
name = "piku_backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Django Rest Framework for handling requests for Pi Ku app"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"django>=6.0.4",
|
"django>=6.0.4",
|
||||||
"django-cors-headers>=4.9.0",
|
"django-cors-headers>=4.9.0",
|
||||||
"django-environ>=0.13.0",
|
"django-environ>=0.13.0",
|
||||||
|
"django-extensions>=4.1",
|
||||||
"djangorestframework>=3.17.1",
|
"djangorestframework>=3.17.1",
|
||||||
"djangorestframework-simplejwt>=5.5.1",
|
"djangorestframework-simplejwt>=5.5.1",
|
||||||
"psycopg2-binary>=2.9.11",
|
"psycopg2-binary>=2.9.11",
|
||||||
|
"pyopenssl>=26.0.0",
|
||||||
"ruff>=0.15.9",
|
"ruff>=0.15.9",
|
||||||
|
"werkzeug>=3.1.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -23,4 +26,4 @@ line-length = 120
|
|||||||
select = ["E", "F", "W", "UP", "I"]
|
select = ["E", "F", "W", "UP", "I"]
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"**/migrations/*" = ["E501"] # boilerplate - ignore
|
"**/migrations/*" = ["E501"]
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""
|
||||||
|
Check if SSL is enabled in the environment variables.
|
||||||
|
If SSL is enabled, use runserver_plus command.
|
||||||
|
If SSL is not enabled, use runserver command.
|
||||||
|
"""
|
||||||
|
ssl_enabled = os.getenv("SSL_ENABLED", "false").lower() == "true"
|
||||||
|
domain = os.getenv("BACKEND_DOMAIN", "127.0.0.1")
|
||||||
|
port = os.getenv("BACKEND_PORT", "8000")
|
||||||
|
addrport = f"{domain}:{port}"
|
||||||
|
|
||||||
|
if ssl_enabled:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Starting with SSL on {addrport}..."))
|
||||||
|
call_command(
|
||||||
|
"runserver_plus",
|
||||||
|
addrport,
|
||||||
|
cert_file=settings.BASE_DIR / "../certs/localhost.pem",
|
||||||
|
key_file=settings.BASE_DIR / "../certs/localhost-key.pem",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.WARNING(f"Starting without SSL on {addrport}..."))
|
||||||
|
call_command("runserver", addrport)
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from unittest.mock import _patch_dict
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -19,9 +21,10 @@ class AuthTests(APITestCase):
|
|||||||
self.refresh_url = reverse("token_refresh")
|
self.refresh_url = reverse("token_refresh")
|
||||||
self.logout_url = reverse("logout")
|
self.logout_url = reverse("logout")
|
||||||
|
|
||||||
|
@_patch_dict("config.settings.AUTH_COOKIE", {"SECURE": True})
|
||||||
def test_login_sets_secure_cookie(self):
|
def test_login_sets_secure_cookie(self):
|
||||||
"""
|
"""
|
||||||
Tests if the Login API can generate access token and set secure cookie for refresh token.
|
Tests if the Login API can generate access token and set secure cookie (when ssl is enabled) for refresh token.
|
||||||
"""
|
"""
|
||||||
data = {"email": self.user.email, "password": self.password}
|
data = {"email": self.user.email, "password": self.password}
|
||||||
cookie_name = "refresh_token"
|
cookie_name = "refresh_token"
|
||||||
@@ -32,9 +35,10 @@ class AuthTests(APITestCase):
|
|||||||
self.assertIn("access", response.data)
|
self.assertIn("access", response.data)
|
||||||
self.assertNotIn("refresh", response.data)
|
self.assertNotIn("refresh", response.data)
|
||||||
self.assertIn(cookie_name, response.cookies)
|
self.assertIn(cookie_name, response.cookies)
|
||||||
self.assertTrue(response.cookies[cookie_name].value)
|
self.assertIsNotNone(response.cookies[cookie_name].value)
|
||||||
self.assertTrue(response.cookies[cookie_name].httponly)
|
self.assertTrue(response.cookies[cookie_name].get("httponly"))
|
||||||
self.assertEqual(response.cookies[cookie_name]["samesite"], "Lax")
|
self.assertTrue(response.cookies[cookie_name].get("secure"))
|
||||||
|
self.assertEqual(response.cookies[cookie_name].get("samesite"), "Lax")
|
||||||
|
|
||||||
|
|
||||||
class ActivationTests(APITestCase):
|
class ActivationTests(APITestCase):
|
||||||
|
|||||||
Generated
+186
-19
@@ -12,28 +12,89 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backend"
|
name = "cffi"
|
||||||
version = "0.1.0"
|
version = "2.0.0"
|
||||||
source = { virtual = "." }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||||
{ name = "django-cors-headers" },
|
]
|
||||||
{ name = "django-environ" },
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||||
{ name = "djangorestframework" },
|
wheels = [
|
||||||
{ name = "djangorestframework-simplejwt" },
|
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||||
{ name = "psycopg2-binary" },
|
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||||
{ name = "ruff" },
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[[package]]
|
||||||
requires-dist = [
|
name = "cryptography"
|
||||||
{ name = "django", specifier = ">=6.0.4" },
|
version = "46.0.7"
|
||||||
{ name = "django-cors-headers", specifier = ">=4.9.0" },
|
source = { registry = "https://pypi.org/simple" }
|
||||||
{ name = "django-environ", specifier = ">=0.13.0" },
|
dependencies = [
|
||||||
{ name = "djangorestframework", specifier = ">=3.17.1" },
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "djangorestframework-simplejwt", specifier = ">=5.5.1" },
|
]
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
|
||||||
{ name = "ruff", specifier = ">=0.15.9" },
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -72,6 +133,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c4/00/3767393ece946084e1c6830a33ffb8e39d68642e27ad5ac7d4c8bd5de866/django_environ-0.13.0-py3-none-any.whl", hash = "sha256:37799d14cd78222c6fd8298e48bfe17965ff8e586091ad66a463e52e0e7b799e", size = 20682, upload-time = "2026-02-18T01:08:07.359Z" },
|
{ url = "https://files.pythonhosted.org/packages/c4/00/3767393ece946084e1c6830a33ffb8e39d68642e27ad5ac7d4c8bd5de866/django_environ-0.13.0-py3-none-any.whl", hash = "sha256:37799d14cd78222c6fd8298e48bfe17965ff8e586091ad66a463e52e0e7b799e", size = 20682, upload-time = "2026-02-18T01:08:07.359Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-extensions"
|
||||||
|
version = "4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6d/b3/ed0f54ed706ec0b54fd251cc0364a249c6cd6c6ec97f04dc34be5e929eac/django_extensions-4.1.tar.gz", hash = "sha256:7b70a4d28e9b840f44694e3f7feb54f55d495f8b3fa6c5c0e5e12bcb2aa3cdeb", size = 283078, upload-time = "2025-04-11T01:15:39.617Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "djangorestframework"
|
name = "djangorestframework"
|
||||||
version = "3.17.1"
|
version = "3.17.1"
|
||||||
@@ -98,6 +171,67 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/60/94/fdfb7b2f0b16cd3ed4d4171c55c1c07a2d1e3b106c5978c8ad0c15b4a48b/djangorestframework_simplejwt-5.5.1-py3-none-any.whl", hash = "sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469", size = 107674, upload-time = "2025-07-21T16:52:07.493Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/94/fdfb7b2f0b16cd3ed4d4171c55c1c07a2d1e3b106c5978c8ad0c15b4a48b/djangorestframework_simplejwt-5.5.1-py3-none-any.whl", hash = "sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469", size = 107674, upload-time = "2025-07-21T16:52:07.493Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piku-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
{ name = "django-cors-headers" },
|
||||||
|
{ name = "django-environ" },
|
||||||
|
{ name = "django-extensions" },
|
||||||
|
{ name = "djangorestframework" },
|
||||||
|
{ name = "djangorestframework-simplejwt" },
|
||||||
|
{ name = "psycopg2-binary" },
|
||||||
|
{ name = "pyopenssl" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "werkzeug" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "django", specifier = ">=6.0.4" },
|
||||||
|
{ name = "django-cors-headers", specifier = ">=4.9.0" },
|
||||||
|
{ name = "django-environ", specifier = ">=0.13.0" },
|
||||||
|
{ name = "django-extensions", specifier = ">=4.1" },
|
||||||
|
{ name = "djangorestframework", specifier = ">=3.17.1" },
|
||||||
|
{ name = "djangorestframework-simplejwt", specifier = ">=5.5.1" },
|
||||||
|
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
||||||
|
{ name = "pyopenssl", specifier = ">=26.0.0" },
|
||||||
|
{ name = "ruff", specifier = ">=0.15.9" },
|
||||||
|
{ name = "werkzeug", specifier = ">=3.1.8" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
version = "2.9.11"
|
version = "2.9.11"
|
||||||
@@ -117,6 +251,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" },
|
{ url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyjwt"
|
name = "pyjwt"
|
||||||
version = "2.12.1"
|
version = "2.12.1"
|
||||||
@@ -126,6 +269,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyopenssl"
|
||||||
|
version = "26.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8e/11/a62e1d33b373da2b2c2cd9eb508147871c80f12b1cacde3c5d314922afdd/pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc", size = 185534, upload-time = "2026-03-15T14:28:26.353Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/7d/d4f7d908fa8415571771b30669251d57c3cf313b36a856e6d7548ae01619/pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81", size = 57969, upload-time = "2026-03-15T14:28:24.864Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.9"
|
version = "0.15.9"
|
||||||
@@ -168,3 +323,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
|
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "werkzeug"
|
||||||
|
version = "3.1.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markupsafe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" },
|
||||||
|
]
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@
|
|||||||
"noUnusedVariables": "error"
|
"noUnusedVariables": "error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"includes": ["**", "!backend"]
|
"includes": ["**/src", "!backend"]
|
||||||
},
|
},
|
||||||
"assist": {
|
"assist": {
|
||||||
"actions": {
|
"actions": {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: ${DB_NAME}
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:5432"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit
|
||||||
|
container_name: piku_test_mail
|
||||||
|
ports:
|
||||||
|
- "${EMAIL_API_PORT}:8025"
|
||||||
|
- "${EMAIL_PORT}:1025"
|
||||||
|
restart: unless-stopped
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"@vitest/coverage-v8": "^4.1.4",
|
"@vitest/coverage-v8": "^4.1.4",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
@@ -257,6 +258,8 @@
|
|||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.3.0", "", { "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ=="],
|
||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||||
|
|
||||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="],
|
"@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="],
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export async function registerAndLogin(
|
|||||||
// 2. Activation via Mailpit
|
// 2. Activation via Mailpit
|
||||||
logger.info(`[Auth] Polling Mailpit for activation email...`);
|
logger.info(`[Auth] Polling Mailpit for activation email...`);
|
||||||
const activationLink = await MailpitHelper.getActivationLink(email);
|
const activationLink = await MailpitHelper.getActivationLink(email);
|
||||||
|
|
||||||
await page.goto(activationLink);
|
await page.goto(activationLink);
|
||||||
|
|
||||||
await expect(page.getByText(/account activated/i)).toBeVisible();
|
await expect(page.getByText(/account activated/i)).toBeVisible();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export interface MailpitMessage {
|
|||||||
To: { Address: string }[];
|
To: { Address: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAILPIT_API_URL = process.env.MAILPIT_API_URL;
|
const MAILPIT_API_URL = `http://${process.env.EMAIL_HOST}:${process.env.EMAIL_API_PORT}/api/v1`;
|
||||||
|
|
||||||
export const MailpitHelper = {
|
export const MailpitHelper = {
|
||||||
getActivationLink: async (
|
getActivationLink: async (
|
||||||
@@ -18,7 +18,6 @@ export const MailpitHelper = {
|
|||||||
const requestContext = await request.newContext();
|
const requestContext = await request.newContext();
|
||||||
|
|
||||||
while (Date.now() - startTime < timeout) {
|
while (Date.now() - startTime < timeout) {
|
||||||
// Search specifically for the recipient to reduce data transfer
|
|
||||||
const response = await requestContext.get(`${MAILPIT_API_URL}/search`, {
|
const response = await requestContext.get(`${MAILPIT_API_URL}/search`, {
|
||||||
params: { query: `to:${email}`, limit: 1 },
|
params: { query: `to:${email}`, limit: 1 },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"@vitest/coverage-v8": "^4.1.4",
|
"@vitest/coverage-v8": "^4.1.4",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
import process, { env } from "node:process";
|
||||||
import { defineConfig, devices } from "@playwright/test";
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
import { getBaseUrl } from "./utils/url-builder";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
*/
|
*/
|
||||||
dotenv.config({ path: path.resolve(process.cwd(), "../.env.e2e") });
|
dotenv.config({ path: path.resolve(process.cwd(), "../.env.e2e") });
|
||||||
|
const baseUrl = getBaseUrl(
|
||||||
|
env.SSL_ENABLED === "true",
|
||||||
|
env.FRONTEND_DOMAIN,
|
||||||
|
env.FRONTEND_PORT,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(baseUrl);
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
expect: {
|
expect: {
|
||||||
@@ -26,13 +34,17 @@ export default defineConfig({
|
|||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: process.env.FRONTEND_URL,
|
baseURL: baseUrl,
|
||||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
actionTimeout: 20000,
|
actionTimeout: 20000,
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: "on-first-retry",
|
trace: "on-first-retry",
|
||||||
/* Capture screenshot on failure */
|
/* Capture screenshot on failure */
|
||||||
screenshot: "only-on-failure",
|
screenshot: "only-on-failure",
|
||||||
|
/* Capture video on failure */
|
||||||
|
video: "retain-on-failure",
|
||||||
|
/* Ignore HTTPS errors */
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
@@ -49,8 +61,13 @@ export default defineConfig({
|
|||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
webServer: {
|
webServer: {
|
||||||
command: "bun run dev",
|
command: "bun run dev -- --mode e2e",
|
||||||
url: process.env.FRONTEND_URL,
|
url: getBaseUrl(
|
||||||
|
process.env.SSL_ENABLED === "true",
|
||||||
|
process.env.FRONTEND_DOMAIN,
|
||||||
|
process.env.FRONTEND_PORT,
|
||||||
|
),
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { server } from "../../test/mocks/server";
|
|||||||
import { useAuthStore } from "../store/useAuthStore";
|
import { useAuthStore } from "../store/useAuthStore";
|
||||||
import { api } from "./apiClient";
|
import { api } from "./apiClient";
|
||||||
|
|
||||||
const API_URL = "http://piku-server";
|
const VITE_API_URL = "http://piku-server";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
@@ -24,7 +24,7 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
vi.stubEnv("API_URL", API_URL);
|
vi.stubEnv("VITE_API_URL", VITE_API_URL);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -37,7 +37,7 @@ describe("request interceptor", () => {
|
|||||||
|
|
||||||
let capturedAuthHeader = "";
|
let capturedAuthHeader = "";
|
||||||
server.use(
|
server.use(
|
||||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||||
capturedAuthHeader = request.headers.get("Authorization") ?? "";
|
capturedAuthHeader = request.headers.get("Authorization") ?? "";
|
||||||
return HttpResponse.json(mockUser);
|
return HttpResponse.json(mockUser);
|
||||||
}),
|
}),
|
||||||
@@ -51,7 +51,7 @@ describe("request interceptor", () => {
|
|||||||
it("should not send Authorization header when the store has no token", async () => {
|
it("should not send Authorization header when the store has no token", async () => {
|
||||||
let capturedAuthHeader: string | null = "";
|
let capturedAuthHeader: string | null = "";
|
||||||
server.use(
|
server.use(
|
||||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||||
capturedAuthHeader = request.headers.get("Authorization");
|
capturedAuthHeader = request.headers.get("Authorization");
|
||||||
return HttpResponse.json({});
|
return HttpResponse.json({});
|
||||||
}),
|
}),
|
||||||
@@ -70,14 +70,14 @@ describe("response interceptor", () => {
|
|||||||
let _refreshApiCallCount = 0;
|
let _refreshApiCallCount = 0;
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||||
meApiCallCount++;
|
meApiCallCount++;
|
||||||
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
||||||
return new HttpResponse(null, { status: 401 });
|
return new HttpResponse(null, { status: 401 });
|
||||||
}
|
}
|
||||||
return HttpResponse.json(mockUser);
|
return HttpResponse.json(mockUser);
|
||||||
}),
|
}),
|
||||||
http.post(`${API_URL}/api/auth/refresh/`, () => {
|
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
|
||||||
_refreshApiCallCount++;
|
_refreshApiCallCount++;
|
||||||
return HttpResponse.json({ access: "refreshed-token" });
|
return HttpResponse.json({ access: "refreshed-token" });
|
||||||
}),
|
}),
|
||||||
@@ -94,13 +94,13 @@ describe("response interceptor", () => {
|
|||||||
useAuthStore.getState().setAuth("expired-token", mockUser);
|
useAuthStore.getState().setAuth("expired-token", mockUser);
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||||
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
||||||
return new HttpResponse(null, { status: 401 });
|
return new HttpResponse(null, { status: 401 });
|
||||||
}
|
}
|
||||||
return HttpResponse.json(mockUser);
|
return HttpResponse.json(mockUser);
|
||||||
}),
|
}),
|
||||||
http.post(`${API_URL}/api/auth/refresh/`, () =>
|
http.post(`${VITE_API_URL}/api/auth/refresh/`, () =>
|
||||||
HttpResponse.json({ access: "refreshed-token" }),
|
HttpResponse.json({ access: "refreshed-token" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -115,14 +115,14 @@ describe("response interceptor", () => {
|
|||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get(
|
http.get(
|
||||||
`${API_URL}/api/auth/me/`,
|
`${VITE_API_URL}/api/auth/me/`,
|
||||||
() =>
|
() =>
|
||||||
new HttpResponse(JSON.stringify({ detail: "Invalid token" }), {
|
new HttpResponse(JSON.stringify({ detail: "Invalid token" }), {
|
||||||
status: 401,
|
status: 401,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
http.post(
|
http.post(
|
||||||
`${API_URL}/api/auth/refresh/`,
|
`${VITE_API_URL}/api/auth/refresh/`,
|
||||||
() =>
|
() =>
|
||||||
new HttpResponse(JSON.stringify({ detail: "Refresh failed" }), {
|
new HttpResponse(JSON.stringify({ detail: "Refresh failed" }), {
|
||||||
status: 401,
|
status: 401,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { useAuth } from "./useAuth";
|
|||||||
vi.mock("../utils/crypto");
|
vi.mock("../utils/crypto");
|
||||||
vi.mock("../utils/keystore");
|
vi.mock("../utils/keystore");
|
||||||
|
|
||||||
const API_URL = "http://piku-server";
|
const VITE_API_URL = "http://piku-server";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -112,7 +112,7 @@ describe("logout", () => {
|
|||||||
it("should call the logout API endpoint", async () => {
|
it("should call the logout API endpoint", async () => {
|
||||||
let logoutCalled = false;
|
let logoutCalled = false;
|
||||||
server.use(
|
server.use(
|
||||||
http.post(`${API_URL}/api/auth/logout/`, () => {
|
http.post(`${VITE_API_URL}/api/auth/logout/`, () => {
|
||||||
logoutCalled = true;
|
logoutCalled = true;
|
||||||
return HttpResponse.json({});
|
return HttpResponse.json({});
|
||||||
}),
|
}),
|
||||||
@@ -139,7 +139,7 @@ describe("logout", () => {
|
|||||||
it("should clear the auth store even if the API call fails", async () => {
|
it("should clear the auth store even if the API call fails", async () => {
|
||||||
server.use(
|
server.use(
|
||||||
http.post(
|
http.post(
|
||||||
`${API_URL}/api/auth/logout/`,
|
`${VITE_API_URL}/api/auth/logout/`,
|
||||||
() => new HttpResponse(null, { status: 500 }),
|
() => new HttpResponse(null, { status: 500 }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -163,7 +163,7 @@ describe("initialize", () => {
|
|||||||
});
|
});
|
||||||
let refreshCalled = false;
|
let refreshCalled = false;
|
||||||
server.use(
|
server.use(
|
||||||
http.post(`${API_URL}/api/auth/refresh/`, () => {
|
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
|
||||||
refreshCalled = true;
|
refreshCalled = true;
|
||||||
return HttpResponse.json({ access: "new-token" });
|
return HttpResponse.json({ access: "new-token" });
|
||||||
}),
|
}),
|
||||||
@@ -194,7 +194,7 @@ describe("initialize", () => {
|
|||||||
it("should preserve the master key even if the refresh attempt fails", async () => {
|
it("should preserve the master key even if the refresh attempt fails", async () => {
|
||||||
server.use(
|
server.use(
|
||||||
http.post(
|
http.post(
|
||||||
`${API_URL}/api/auth/refresh/`,
|
`${VITE_API_URL}/api/auth/refresh/`,
|
||||||
() => new HttpResponse(null, { status: 401 }),
|
() => new HttpResponse(null, { status: 401 }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
const timeFormatter = new Intl.DateTimeFormat(undefined, {
|
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||||
timeStyle: "short",
|
timeStyle: "short",
|
||||||
});
|
});
|
||||||
|
|
||||||
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
|
const dateTimeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||||
dateStyle: "medium",
|
dateStyle: "medium",
|
||||||
timeStyle: "short",
|
timeStyle: "short",
|
||||||
});
|
});
|
||||||
|
|
||||||
const rtf = new Intl.RelativeTimeFormat(undefined, {
|
const rtf = new Intl.RelativeTimeFormat("en-US", {
|
||||||
numeric: "auto",
|
numeric: "auto",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const getBaseUrl = (
|
||||||
|
isSslEnabled: boolean,
|
||||||
|
domain: string | undefined,
|
||||||
|
port: string | undefined,
|
||||||
|
): string => {
|
||||||
|
const uriScheme = isSslEnabled ? "https" : "http";
|
||||||
|
const baseURL = `${uriScheme}://${domain}${port ? `:${port}` : ""}`;
|
||||||
|
return baseURL;
|
||||||
|
};
|
||||||
@@ -1,16 +1,41 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
|
import { getBaseUrl } from "./utils/url-builder";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, "../", "");
|
const env = loadEnv(mode, "../", "");
|
||||||
|
const isSslEnabled = env.SSL_ENABLED === "true";
|
||||||
|
let sslCerts: { key: Buffer; cert: Buffer } | undefined;
|
||||||
|
|
||||||
|
if (isSslEnabled) {
|
||||||
|
sslCerts = {
|
||||||
|
key: fs.readFileSync(
|
||||||
|
path.resolve(__dirname, "../certs/localhost-key.pem"),
|
||||||
|
),
|
||||||
|
cert: fs.readFileSync(path.resolve(__dirname, "../certs/localhost.pem")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseApiUrl = getBaseUrl(
|
||||||
|
isSslEnabled,
|
||||||
|
env.BACKEND_DOMAIN,
|
||||||
|
env.BACKEND_PORT,
|
||||||
|
);
|
||||||
|
console.log(baseApiUrl);
|
||||||
return {
|
return {
|
||||||
envDir: "../",
|
envDir: "../",
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
define: {
|
||||||
|
"import.meta.env.VITE_API_URL": JSON.stringify(baseApiUrl),
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: Number(env.FRONTEND_PORT),
|
port: Number(env.FRONTEND_PORT),
|
||||||
host: env.FRONTEND_DOMAIN,
|
host: env.FRONTEND_DOMAIN,
|
||||||
|
https: isSslEnabled ? sslCerts : undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
env: {
|
env: {
|
||||||
VITE_API_URL: "http://piku-server",
|
VITE_API_URL: "http://piku-server",
|
||||||
|
TZ: "Asia/Kolkata",
|
||||||
},
|
},
|
||||||
include: ["**/*.test.ts"],
|
include: ["**/*.test.ts"],
|
||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||||
|
* Running on https://127.0.0.1:8001
|
||||||
|
[33mPress CTRL+C to quit[0m
|
||||||
|
* Restarting with stat
|
||||||
|
* Debugger is active!
|
||||||
|
* Debugger PIN: 411-535-418
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:16] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:19] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:19] "GET /api/auth/activate/YmFlZTYyNTgtOTcxMi00ZjFmLWE1YTgtYzBiOGMwODdkY2Zi/d755fh-d81b8ac647be32c14f996ab09e783392/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "GET /api/auth/activate/YzJjM2NkOWUtZmIxYS00MGI2LWFiM2EtYTQwYmI5MDJjZGY2/d755fh-a2be30e77657af68697d0b7be375e5ab/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "GET /api/auth/activate/Yzc1MDFhMTctY2EzOC00YTY0LTkyNmYtNWYyZjgzNDUyZGQ1/d755fh-677d8f6662cce0e74bb9a776663617a9/ HTTP/1.1" 200 -
|
||||||
|
/var/home/atom/Documents/code/pi ku/backend/.venv/lib64/python3.14/site-packages/jwt/api_jwt.py:147: InsecureKeyLengthWarning: The HMAC key is 27 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
|
||||||
|
return self._jws.encode(
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:20] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
/var/home/atom/Documents/code/pi ku/backend/.venv/lib64/python3.14/site-packages/jwt/api_jwt.py:365: InsecureKeyLengthWarning: The HMAC key is 27 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
|
||||||
|
decoded = self.decode_complete(
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/auth/activate/YjI4MjczYWMtYWNlNC00OGRlLWJmZDItZmMyMTJiZjM2MDZl/d755fh-7728ee068008d7513a64dbe6282954f3/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "[35m[1mPUT /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/e8f47036-6e57-41f5-b057-8ea712589a73/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "[35m[1mPUT /api/letters/e8f47036-6e57-41f5-b057-8ea712589a73/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:24] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:24] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:24] "OPTIONS /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:24] "[35m[1mPUT /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:30] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:30] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:31] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:32] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:32] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:32] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:34] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:35] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:35] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:36] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:36] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:36] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:36] "GET /api/auth/activate/M2M0YzFiNTItMjE5Mi00Y2VmLWIwZmItMDlkNDg5NWE4NWU0/d755fw-9c2c43d45732c1cd5ccbef20d3dc4181/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:37] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:37] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:37] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:37] "GET /api/auth/activate/MjdiM2E1M2ItOTZlYS00Y2Y1LWFhMmQtZThjY2ZkNGQ5ZjQ1/d755fw-e60e3d0b66162c75962beea97a40b4c7/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:40] "OPTIONS /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:40] "[35m[1mPUT /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1[0m" 201 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:40] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:40] "GET /api/auth/activate/MDkwNTg5MDctNjAzOS00NDgwLTlkYTktNmUxOWE5ZTBjODJh/d755g0-546e5f4ee3ed111de64f011ac8d02836/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:41] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
Unauthorized: /api/auth/refresh/
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:41] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:41] "GET /api/auth/activate/MzAxODRjMWEtMTYxZC00ZTczLThlMWMtM2FmY2RmZDg2NThk/d755g1-2ae1dec8cbac57d223c0cd206a8741a8/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/letters/5dec1a15-1d19-47de-b5a5-21bc1845f90a/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:42] "[35m[1mPUT /api/letters/5dec1a15-1d19-47de-b5a5-21bc1845f90a/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:47] "OPTIONS /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:47] "[35m[1mPUT /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1[0m" 201 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:47] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:47] "GET /api/letters/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:49] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:49] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||||
|
Starting with SSL on 127.0.0.1:8001...
|
||||||
|
/usr/lib64/python3.14/multiprocessing/resource_tracker.py:396: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown: {'/mp-uf_ylwyc'}
|
||||||
|
warnings.warn(
|
||||||
+43
-76
@@ -1,98 +1,65 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Get absolute path of project root
|
# Use podman if available. Not everyone has it
|
||||||
PROJECT_ROOT=$(pwd)
|
CONTAINER_BIN=$(command -v podman || command -v docker)
|
||||||
|
if [ -z "$CONTAINER_BIN" ]; then
|
||||||
|
echo "Sorry, you need either podman or docker installed to run this script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Configuration
|
if [ "$CI" = "true" ]; then
|
||||||
ENV_FILE="$PROJECT_ROOT/.env.e2e"
|
CONTAINER_BIN=$(command -v docker)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using $CONTAINER_BIN for container operations..."
|
||||||
|
|
||||||
|
ENV_FILE="./.env.e2e"
|
||||||
|
|
||||||
if [ -f "$ENV_FILE" ]; then
|
if [ -f "$ENV_FILE" ]; then
|
||||||
echo "[INFO] Loading configuration from $ENV_FILE..."
|
echo "Loading settings..."
|
||||||
set -a
|
set -a
|
||||||
source "$ENV_FILE"
|
source "$ENV_FILE"
|
||||||
set +a
|
set +a
|
||||||
elif [ "$CI" != "true" ]; then
|
|
||||||
echo "[ERROR] $ENV_FILE not found! Please create it for local testing (use .env.e2e.example as template)."
|
|
||||||
exit 1
|
|
||||||
else
|
else
|
||||||
echo "[INFO] Running in CI mode (using direct environment variables)..."
|
echo "Error: Configuration file $ENV_FILE is missing!!"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Map E2E variables to Django expected names
|
# This cleans up containers. Very useful for local e2e to free system resources immediately.
|
||||||
# In CI, these should be set via GitHub Actions env variables
|
|
||||||
export DB_NAME=${E2E_DB_DB:-piku_e2e}
|
|
||||||
export DB_USER=${E2E_DB_USER:-piku_test}
|
|
||||||
export DB_PASSWORD=${E2E_DB_PASS:-piku_test}
|
|
||||||
export DB_HOST=${E2E_DB_HOST:-localhost}
|
|
||||||
export DB_PORT=${E2E_DB_PORT:-5433}
|
|
||||||
export E2E_BACKEND_PORT=${E2E_BACKEND_PORT:-8001}
|
|
||||||
|
|
||||||
echo "[START] Initializing E2E Test Environment..."
|
|
||||||
|
|
||||||
# 1. Cleanup / Start Services (Skip in CI)
|
|
||||||
if [ "$CI" != "true" ]; then
|
|
||||||
if podman ps -a --format "{{.Names}}" | grep -q "^$E2E_DB_NAME$"; then
|
|
||||||
echo "[CLEANUP] Removing existing container $E2E_DB_NAME..."
|
|
||||||
podman rm -f $E2E_DB_NAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[DB] Starting disposable Postgres on port $DB_PORT..."
|
|
||||||
podman run --name $E2E_DB_NAME \
|
|
||||||
-e POSTGRES_DB=$DB_NAME \
|
|
||||||
-e POSTGRES_USER=$DB_USER \
|
|
||||||
-e POSTGRES_PASSWORD=$DB_PASSWORD \
|
|
||||||
-p $DB_PORT:5432 \
|
|
||||||
-d docker.io/library/postgres:16-alpine > /dev/null
|
|
||||||
|
|
||||||
echo "[DB] Waiting for Postgres to be ready..."
|
|
||||||
until podman exec $E2E_DB_NAME pg_isready -U $DB_USER > /dev/null 2>&1; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo "[DB] Postgres is ready."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Trap to ensure cleanup
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
echo "[CLEANUP] Stopping services..."
|
echo "Cleaning up..."
|
||||||
if [ "$CI" != "true" ]; then
|
$CONTAINER_BIN rm -f "$DB_NAME" 2>/dev/null || true
|
||||||
podman rm -f $E2E_DB_NAME || true
|
[ -n "$BACKEND_PID" ] && kill "$BACKEND_PID" 2>/dev/null || true
|
||||||
fi
|
|
||||||
if [ ! -z "$BACKEND_PID" ]; then
|
|
||||||
kill "$BACKEND_PID" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
# 2. Prepare Backend
|
echo "Starting Database and Mail server..."
|
||||||
echo "[BACKEND] Running database migrations..."
|
COMPOSE_BIN="$(command -v docker-compose || true)"
|
||||||
export PIKU_ENV_FILE="$ENV_FILE"
|
|
||||||
(cd backend && uv run manage.py migrate --noinput)
|
|
||||||
|
|
||||||
echo "[BACKEND] Starting server on port $E2E_BACKEND_PORT..."
|
if echo "$CONTAINER_BIN" | grep -q "podman"; then
|
||||||
(cd backend && uv run manage.py runserver $E2E_BACKEND_PORT) > /tmp/piku_e2e_backend.log 2>&1 &
|
podman compose -f "./docker-compose.e2e.yml" up -d
|
||||||
|
elif [ -n "$COMPOSE_BIN" ]; then
|
||||||
|
"$COMPOSE_BIN" -f "./docker-compose.e2e.yml" up -d
|
||||||
|
else
|
||||||
|
docker compose -f "./docker-compose.e2e.yml" up -d
|
||||||
|
fi
|
||||||
|
|
||||||
|
# postgress will take some time, so we wait, and no race condition. Also, no point in logging this output
|
||||||
|
until $CONTAINER_BIN exec "$DB_NAME" pg_isready -U "${DB_USER:-test}" >/dev/null 2>&1; do
|
||||||
|
echo "Waiting for DB..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Starting Backend..."
|
||||||
|
mkdir -p ./tmp/logs
|
||||||
|
(cd backend && uv run manage.py migrate)
|
||||||
|
(cd backend && uv run manage.py serve) > ./tmp/logs/backend.log 2>&1 &
|
||||||
BACKEND_PID=$!
|
BACKEND_PID=$!
|
||||||
|
|
||||||
echo "[BACKEND] Waiting for server to respond..."
|
|
||||||
until curl -s http://localhost:$E2E_BACKEND_PORT > /dev/null; do
|
|
||||||
sleep 1
|
|
||||||
if ! kill -0 $BACKEND_PID 2>/dev/null; then
|
|
||||||
echo "[ERROR] Backend failed to start. Logs:"
|
|
||||||
cat /tmp/piku_e2e_backend.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "[BACKEND] Server is ready."
|
|
||||||
|
|
||||||
# 3. Run Playwright
|
|
||||||
export VITE_API_URL="http://localhost:$E2E_BACKEND_PORT"
|
|
||||||
|
|
||||||
if [ "$CI" = "true" ]; then
|
if [ "$CI" = "true" ]; then
|
||||||
echo "[TEST] Running Playwright Tests (CI)..."
|
cd frontend && bun run test:e2e "$@"
|
||||||
(cd frontend && bun run test:e2e --project=chromium "$@")
|
|
||||||
else
|
else
|
||||||
echo "[TEST] Running Playwright Tests in Distrobox..."
|
# Because playwright decided not to support Fedora :)
|
||||||
(cd frontend && distrobox-enter --name ubuntu-24.04 -- env VITE_API_URL=$VITE_API_URL bun run test:e2e --project=chromium "$@")
|
cd frontend && distrobox-enter --name ubuntu-24.04 -- bun run test:e2e "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[SUCCESS] E2E Tests Completed."
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
(podman compose up -d) &
|
(podman compose up -d) &
|
||||||
(cd backend && uv run manage.py runserver) &
|
(cd backend && uv run manage.py serve) &
|
||||||
(cd frontend && bun run dev)
|
(cd frontend && bun run dev)
|
||||||
|
|||||||
Reference in New Issue
Block a user