├── .flake8 ├── .github └── FUNDING.yml ├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── bin ├── configure ├── run └── setup ├── django_backend ├── .gitignore ├── authentication │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── extras │ │ ├── __init__.py │ │ ├── constants.py │ │ └── tokenizer.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── schemas │ │ ├── __init__.py │ │ ├── auth_schema.py │ │ ├── mail_schema.py │ │ └── user_schema.py │ ├── tests.py │ ├── urls.py │ └── views │ │ ├── __init__.py │ │ ├── auth_views.py │ │ ├── confirm_email_views.py │ │ ├── forgot_password_views.py │ │ └── users_views.py ├── django_backend │ ├── __init__.py │ ├── asgi.py │ ├── kit.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── djapy_ext │ ├── errorhandler.py │ └── exception.py ├── generics │ └── schemas.py ├── home │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_sitedata_email_sitedata_scheme.py │ │ └── __init__.py │ ├── models.py │ ├── schema.py │ ├── startup.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py ├── msgs.py ├── profile │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── defaults.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── schemas.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── requirements.txt ├── todo │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_remove_todoitem_completed_todoitem_completed_at_and_more.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── utils │ └── c_logging.py ├── pyproject.toml └── svelte_frontend ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── README.md ├── eslint.config.js ├── messages ├── de-ch.json ├── en.json └── fr.json ├── package-lock.json ├── package.json ├── project.inlang ├── .gitignore ├── project_id └── settings.json ├── src ├── app.css ├── app.d.ts ├── app.html ├── demo.spec.ts ├── fonts │ ├── assets │ │ └── work-sans │ │ │ ├── WorkSans-Black.ttf │ │ │ ├── WorkSans-BlackItalic.ttf │ │ │ ├── WorkSans-Bold.ttf │ │ │ ├── WorkSans-BoldItalic.ttf │ │ │ ├── WorkSans-ExtraBold.ttf │ │ │ ├── WorkSans-ExtraBoldItalic.ttf │ │ │ ├── WorkSans-ExtraLight.ttf │ │ │ ├── WorkSans-ExtraLightItalic.ttf │ │ │ ├── WorkSans-Italic.ttf │ │ │ ├── WorkSans-Light.ttf │ │ │ ├── WorkSans-LightItalic.ttf │ │ │ ├── WorkSans-Medium.ttf │ │ │ ├── WorkSans-MediumItalic.ttf │ │ │ ├── WorkSans-Regular.ttf │ │ │ ├── WorkSans-SemiBold.ttf │ │ │ ├── WorkSans-SemiBoldItalic.ttf │ │ │ ├── WorkSans-Thin.ttf │ │ │ └── WorkSans-ThinItalic.ttf │ └── styles │ │ └── work-sans.css ├── hooks.server.ts ├── hooks.ts ├── icons │ ├── BiCircleFill.svelte │ ├── BxsCheckCircle.svelte │ ├── BxsError.svelte │ ├── BxsErrorCircle.svelte │ ├── BxsInfoCircle.svelte │ ├── BxsXSquare.svelte │ ├── FluentArrowImport16Filled.svelte │ ├── FluentArrowImport16Regular.svelte │ ├── FluentChevronRight16Filled.svelte │ ├── FluentCompassNorthwest16Filled.svelte │ ├── FluentDismiss20Filled.svelte │ ├── FluentKeyCommand16Filled.svelte │ ├── FluentLineHorizontal324Filled.svelte │ ├── FluentLinkMultiple20Filled.svelte │ ├── FluentReadingListAdd16Filled.svelte │ ├── FluentSearch16Filled.svelte │ ├── FluentSettings16Filled.svelte │ ├── FluentTableMoveLeft16Regular.svelte │ └── TablerAdFilled.svelte ├── items │ ├── Error.svelte │ ├── Flash.svelte │ ├── Form.svelte │ ├── Input.svelte │ ├── PutFlash.svelte │ ├── SearchInput.svelte │ ├── SidebarItem.svelte │ ├── TodoItem.svelte │ ├── ViewDate.svelte │ └── menu │ │ ├── NavGroup.svelte │ │ ├── NavItem.svelte │ │ ├── Sidebar.svelte │ │ └── menu-types.ts ├── lib │ ├── defaults │ │ ├── messages.ts │ │ ├── sitedata.ts │ │ └── status.ts │ ├── helpers │ │ └── variants │ │ │ ├── flash-variants.ts │ │ │ └── todo-variants.ts │ ├── i18n.ts │ ├── index.ts │ ├── interfaces │ │ ├── auth.ts │ │ ├── index.ts │ │ ├── repl-modes.ts │ │ ├── site-data.ts │ │ └── todos.ts │ ├── server │ │ ├── cache.ts │ │ ├── flash.ts │ │ ├── index.ts │ │ ├── initializer.ts │ │ ├── logging.ts │ │ ├── repl.ts │ │ ├── request.ts │ │ └── response.ts │ └── stores │ │ └── notifier.svelte.ts └── routes │ ├── (auth) │ ├── +layout.svelte │ ├── login │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── logout │ │ └── +page.server.ts │ └── register │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── (dashboard) │ ├── +layout.svelte │ ├── +layout.ts │ ├── +page.svelte │ ├── settings │ │ ├── +page.server.ts │ │ └── +page.svelte │ └── todo │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ └── page.svelte.test.ts │ ├── +layout.server.ts │ ├── +layout.svelte │ └── demo │ ├── +page.svelte │ └── paraglide │ └── +page.svelte ├── static └── favicon.png ├── svelte.config.js ├── tsconfig.json ├── vite.config.ts └── vitest-setup-client.ts /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | exclude = 4 | .git, 5 | __pycache__, 6 | build, 7 | dist, 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: bishwasbh 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: bishwasbh 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *env* 3 | db.sqlite3 4 | __pycache__ 5 | .fleet -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Pre-commit configuration with auto-fixing formatters and linting checks 2 | repos: 3 | # Basic file cleanups (will auto-fix) 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.5.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | 12 | # Python formatters (will auto-fix) 13 | - repo: https://github.com/psf/black 14 | rev: 23.12.1 15 | hooks: 16 | - id: black 17 | files: ^django_backend/ 18 | 19 | - repo: https://github.com/pycqa/isort 20 | rev: 5.13.2 21 | hooks: 22 | - id: isort 23 | files: ^django_backend/ 24 | args: ['--profile=black'] 25 | 26 | # Frontend formatters (will auto-fix) 27 | - repo: local 28 | hooks: 29 | - id: prettier 30 | name: prettier 31 | entry: bash -c 'cd svelte_frontend && npm run format' 32 | language: system 33 | files: ^svelte_frontend/.*\.(js|ts|svelte|json|css|scss|md)$ 34 | pass_filenames: false 35 | 36 | # Python linting (will block commit on error) 37 | - repo: https://github.com/PyCQA/flake8 38 | rev: 7.1.2 39 | hooks: 40 | - id: flake8 41 | files: ^django_backend/ 42 | types: [python] 43 | additional_dependencies: [flake8-bugbear] 44 | 45 | # Frontend checks (will block commit on error) 46 | - repo: local 47 | hooks: 48 | - id: svelte-check 49 | name: svelte-check 50 | entry: bash -c 'cd svelte_frontend && npm run check' 51 | language: system 52 | files: ^svelte_frontend/.*\.(js|ts|svelte)$ 53 | pass_filenames: false 54 | 55 | - id: svelte-lint 56 | name: svelte-lint 57 | entry: bash -c 'cd svelte_frontend && npm run lint' 58 | language: system 59 | files: ^svelte_frontend/.*\.(js|ts|svelte)$ 60 | pass_filenames: false 61 | 62 | # Commit message formatting 63 | - repo: https://github.com/commitizen-tools/commitizen 64 | rev: v1.17.0 65 | hooks: 66 | - id: commitizen 67 | stages: [commit-msg] 68 | 69 | # SvelteKit frontend checks (these will block commits if they fail) 70 | - repo: local 71 | hooks: 72 | - id: svelte-check 73 | name: svelte-check 74 | entry: bash -c 'cd svelte_frontend && npm run check' 75 | language: system 76 | files: ^svelte_frontend/.*\.(js|ts|svelte)$ 77 | pass_filenames: false 78 | stages: [pre-commit] 79 | 80 | - id: svelte-lint 81 | name: svelte-lint 82 | entry: bash -c 'cd svelte_frontend && npm run lint' 83 | language: system 84 | files: ^svelte_frontend/.*\.(js|ts|svelte)$ 85 | stages: [pre-commit] 86 | 87 | # Basic file checks 88 | - repo: https://github.com/pre-commit/pre-commit-hooks 89 | rev: v4.5.0 90 | hooks: 91 | - id: trailing-whitespace 92 | stages: [pre-commit] 93 | - id: end-of-file-fixer 94 | stages: [pre-commit] 95 | - id: check-yaml 96 | stages: [pre-commit] 97 | - id: check-added-large-files 98 | stages: [pre-commit] 99 | -------------------------------------------------------------------------------- /bin/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Set base directories 5 | ROOT_DIR="$(dirname "$(dirname "${BASH_SOURCE[0]}")")" 6 | BACKEND_DIR="${ROOT_DIR}/django_backend" 7 | CONFIG_DIR="${BACKEND_DIR}/config" 8 | FRONTEND_DIR="${ROOT_DIR}/svelte_frontend" 9 | 10 | # Print function with colors 11 | print_status() { 12 | local level=$1 13 | shift 14 | local message=$@ 15 | local timestamp=$(date '+%Y-%m-%d %H:%M:%S') 16 | case $level in 17 | INFO) echo -e "\033[0;32m[$timestamp] [$level] $message\033[0m" ;; 18 | WARN) echo -e "\033[0;33m[$timestamp] [$level] $message\033[0m" ;; 19 | ERROR) echo -e "\033[0;31m[$timestamp] [$level] $message\033[0m" ;; 20 | *) echo "[$timestamp] [$level] $message" ;; 21 | esac 22 | } 23 | 24 | # Error handling 25 | set -e 26 | trap 'print_status ERROR "An error occurred on line $LINENO"' ERR 27 | 28 | # Check python and node versions 29 | check_dependencies() { 30 | print_status INFO "Checking dependencies..." 31 | 32 | if ! command -v python3 &> /dev/null; then 33 | print_status ERROR "Python3 is not installed" 34 | exit 1 35 | fi 36 | 37 | if ! command -v node &> /dev/null; then 38 | print_status ERROR "Node.js is not installed" 39 | exit 1 40 | fi 41 | 42 | if ! command -v pip &> /dev/null; then 43 | print_status ERROR "pip is not installed" 44 | exit 1 45 | fi 46 | 47 | python3 --version 48 | node --version 49 | pip --version 50 | } 51 | 52 | # Create config directories 53 | create_directories() { 54 | print_status INFO "Creating config directories" 55 | mkdir -p "$CONFIG_DIR" 56 | mkdir -p "$FRONTEND_DIR" 57 | } 58 | 59 | # Function to create config files with backup functionality 60 | create_config() { 61 | local file=$1 62 | local content=$2 63 | local filename=$(basename "$file") 64 | 65 | if [ -f "$file" ]; then 66 | print_status WARN "Found existing $filename" 67 | read -p "Do you want to overwrite it? (y/N): " should_overwrite 68 | 69 | if [[ ! $should_overwrite =~ ^[Yy]$ ]]; then 70 | print_status INFO "Keeping existing $filename" 71 | return 72 | fi 73 | 74 | # Create backup before overwriting 75 | local backup_file="${file}.backup.$(date +%Y%m%d_%H%M%S)" 76 | mv "$file" "$backup_file" 77 | print_status INFO "Created backup: $backup_file" 78 | fi 79 | 80 | print_status INFO "Creating: $filename" 81 | echo "$content" > "$file" && print_status INFO "Created: $filename" 82 | } 83 | 84 | # Setup pre-commit configuration 85 | setup_precommit() { 86 | print_status INFO "Checking pre-commit installation..." 87 | 88 | # Install pre-commit if not installed 89 | if ! command -v pre-commit &> /dev/null; then 90 | print_status INFO "Installing pre-commit..." 91 | pip install pre-commit 92 | else 93 | print_status INFO "pre-commit is already installed" 94 | fi 95 | 96 | # Install pre-commit hooks regardless of config file status 97 | print_status INFO "Installing pre-commit hooks..." 98 | pre-commit install --hook-type pre-commit --hook-type commit-msg 99 | } 100 | 101 | # Setup frontend dependencies 102 | setup_frontend() { 103 | print_status INFO "Setting up frontend dependencies..." 104 | cd "$FRONTEND_DIR" 105 | echo "$FRONTEND_DIR" 106 | 107 | if [ -f "package.json" ]; then 108 | print_status INFO "Installing frontend dependencies..." 109 | # Check for existing node_modules 110 | if [ -d "node_modules" ]; then 111 | print_status WARN "Found existing node_modules" 112 | read -p "Do you want to clean install? (y/N): " should_clean 113 | 114 | if [[ $should_clean =~ ^[Yy]$ ]]; then 115 | print_status INFO "Removing node_modules..." 116 | rm -rf node_modules 117 | npm install 118 | else 119 | print_status INFO "Updating existing dependencies..." 120 | npm install 121 | fi 122 | else 123 | npm install 124 | fi 125 | else 126 | print_status ERROR "package.json not found in frontend directory" 127 | exit 1 128 | fi 129 | 130 | if [ -d "node_modules/.bin" ]; then 131 | print_status INFO "Setting permissions for node_modules/.bin..." 132 | chmod +x node_modules/.bin/* 133 | fi 134 | 135 | cd "$ROOT_DIR" 136 | } 137 | 138 | # Setup backend dependencies 139 | setup_backend() { 140 | print_status INFO "Setting up backend dependencies..." 141 | cd "$CONFIG_DIR/.." 142 | 143 | if [ -f "requirements.txt" ]; then 144 | print_status INFO "Installing backend dependencies..." 145 | pip install -r requirements.txt 146 | else 147 | print_status WARN "requirements.txt not found in backend directory" 148 | fi 149 | 150 | cd "$ROOT_DIR" 151 | } 152 | # Create virtual environment 153 | create_venv() { 154 | print_status INFO "Checking Python virtual environment..." 155 | local venv_dir="${BACKEND_DIR}/venv" 156 | 157 | if [ -d "$venv_dir" ]; then 158 | print_status WARN "Found existing virtual environment" 159 | read -p "Do you want to recreate it? (y/N): " should_recreate 160 | 161 | if [[ $should_recreate =~ ^[Yy]$ ]]; then 162 | print_status INFO "Removing existing virtual environment..." 163 | rm -rf "$venv_dir" 164 | python3 -m venv "$venv_dir" 165 | print_status INFO "Created new virtual environment" 166 | else 167 | print_status INFO "Using existing virtual environment" 168 | fi 169 | else 170 | print_status INFO "Creating new virtual environment..." 171 | python3 -m venv "$venv_dir" 172 | fi 173 | 174 | # Activate virtual environment 175 | source "$venv_dir/bin/activate" || source "$venv_dir/Scripts/activate" 176 | print_status INFO "Virtual environment activated" 177 | } 178 | 179 | # Main execution 180 | main() { 181 | print_status INFO "Starting configuration setup..." 182 | 183 | # Check dependencies 184 | check_dependencies 185 | 186 | # Create virtual environment 187 | create_venv 188 | 189 | # Create directories 190 | create_directories 191 | 192 | # Create config files 193 | print_status INFO "Creating configuration files..." 194 | 195 | # db.py 196 | create_config "$CONFIG_DIR/db.py" "from pathlib import Path 197 | BASE_DIR = Path(__file__).resolve().parent.parent 198 | DATABASES = { 199 | 'default': { 200 | 'ENGINE': 'django.db.backends.sqlite3', 201 | 'NAME': BASE_DIR / 'db.sqlite3', 202 | } 203 | }" 204 | 205 | # env.py 206 | create_config "$CONFIG_DIR/env.py" "CSRF_TRUSTED_ORIGINS = [ 207 | 'http://localhost:5173', 208 | ] 209 | DEBUG = True 210 | PRODUCTION = False 211 | ALLOWED_HOSTS = [] 212 | STRIPE_SECRET_KEY = 'sk_test_*' 213 | STRIPE_WEBHOOK_SECRET_KEY = 'whsec*' 214 | SECRET_KEY = 'django-insecure-pt50ualer8otrcli1@#@nsfqe*_f4mbtp+rug@rkyr^bia_fz!' 215 | LOGGING = {} 216 | SITE_ID = 1" 217 | 218 | # mail.py 219 | create_config "$CONFIG_DIR/mail.py" "EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 220 | EMAIL_HOST = \"localhost\" 221 | EMAIL_PORT = \"1725\" 222 | EMAIL_HOST_USER = \"\" 223 | EMAIL_HOST_PASSWORD = \"\" 224 | EMAIL_USE_TLS = False 225 | EMAIL_USE_SSL = False 226 | DEFAULT_FROM_EMAIL = \"info@developsite.com\"" 227 | 228 | # frontend .env 229 | create_config "$FRONTEND_DIR/.env" "API_URL=http://localhost:8000" 230 | 231 | # Setup pre-commit 232 | setup_precommit 233 | 234 | # Setup dependencies 235 | setup_frontend 236 | setup_backend 237 | 238 | # Setup run command 239 | print_status INFO "Setting up run command..." 240 | source "$ROOT_DIR/bin/setup" 241 | 242 | # Summary 243 | print_status INFO "Configuration setup completed" 244 | echo -e "\nCreated files:" 245 | find "$CONFIG_DIR" "$FRONTEND_DIR/.env" -type f -exec ls -l {} \; 246 | 247 | print_status INFO "Next steps:" 248 | echo "1. Review the created configuration files" 249 | echo "2. Update the environment variables in django_backend/config/env.py" 250 | echo "3. Update the frontend .env file if needed" 251 | echo "4. Start the development servers:" 252 | echo " - Backend: python manage.py runserver" 253 | echo " - Frontend: npm run dev" 254 | echo " - Or simply use: run" 255 | echo " - To format code: format" 256 | } 257 | 258 | # Run main function 259 | main 260 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Set base directories (one level up from bin directory) 5 | ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." > /dev/null 2>&1 && pwd )" 6 | BACKEND_DIR="${ROOT_DIR}/django_backend" 7 | FRONTEND_DIR="${ROOT_DIR}/svelte_frontend" 8 | 9 | # Print function with colors 10 | print_status() { 11 | local level=$1 12 | shift 13 | local message=$@ 14 | local timestamp=$(date '+%Y-%m-%d %H:%M:%S') 15 | case $level in 16 | INFO) echo -e "\033[0;32m[$timestamp] [$level] $message\033[0m" ;; 17 | WARN) echo -e "\033[0;33m[$timestamp] [$level] $message\033[0m" ;; 18 | ERROR) echo -e "\033[0;31m[$timestamp] [$level] $message\033[0m" ;; 19 | *) echo "[$timestamp] [$level] $message" ;; 20 | esac 21 | } 22 | 23 | # Error handling 24 | trap 'print_status ERROR "An error occurred on line $LINENO"' ERR 25 | 26 | # Function to check if virtual environment is activated 27 | check_venv() { 28 | local venv_dir="${BACKEND_DIR}/venv" 29 | if [ -z "${VIRTUAL_ENV:-}" ]; then 30 | if [ -d "$venv_dir" ]; then 31 | print_status INFO "Activating virtual environment..." 32 | source "${venv_dir}/bin/activate" || source "${venv_dir}/Scripts/activate" 33 | else 34 | print_status ERROR "Virtual environment not found. Please run configure first." 35 | exit 1 36 | fi 37 | fi 38 | } 39 | 40 | # Function to check if all necessary services are installed 41 | check_services() { 42 | print_status INFO "Checking services..." 43 | 44 | if ! command -v python3 &> /dev/null; then 45 | print_status ERROR "Python3 is not installed" 46 | exit 1 47 | fi 48 | 49 | if ! command -v node &> /dev/null; then 50 | print_status ERROR "Node.js is not installed" 51 | exit 1 52 | fi 53 | 54 | if [ ! -d "${FRONTEND_DIR}/node_modules" ]; then 55 | print_status ERROR "Frontend dependencies not installed. Please run configure first." 56 | exit 1 57 | fi 58 | } 59 | 60 | # Function to check and install aiosmtpd if not installed 61 | check_aiosmtpd() { 62 | if ! python3 -c "import aiosmtpd" &> /dev/null; then 63 | print_status INFO "Installing aiosmtpd..." 64 | pip install aiosmtpd 65 | fi 66 | } 67 | 68 | # Function to run Django server 69 | run_django() { 70 | print_status INFO "Starting Django server..." 71 | cd "$BACKEND_DIR" 72 | 73 | # Check for pending migrations 74 | print_status INFO "Checking for pending migrations..." 75 | # Store migrations check output and look for unapplied migrations (ones without [X]) 76 | migrations_output=$(python manage.py showmigrations) 77 | unapplied_count=$(echo "$migrations_output" | grep -c "\[ \]" || true) # Count entries with [ ] (not [X]) 78 | 79 | if [ "$unapplied_count" -gt 0 ]; then 80 | print_status WARN "Found $unapplied_count pending migrations" 81 | echo "$migrations_output" # Show detailed migration status 82 | 83 | read -p "Do you want to run migrations? (y/N): " should_migrate 84 | if [[ $should_migrate =~ ^[Yy]$ ]]; then 85 | print_status INFO "Running migrations..." 86 | python manage.py migrate 87 | fi 88 | else 89 | print_status INFO "All migrations are up to date" 90 | fi 91 | 92 | python manage.py runserver & 93 | DJANGO_PID=$! 94 | cd "$ROOT_DIR" 95 | } 96 | 97 | # Function to run Svelte dev server 98 | run_svelte() { 99 | print_status INFO "Starting Svelte dev server..." 100 | cd "$FRONTEND_DIR" 101 | npm run dev & 102 | SVELTE_PID=$! 103 | cd "$ROOT_DIR" 104 | } 105 | 106 | # Function to run aiosmtpd server 107 | run_aiosmtpd() { 108 | print_status INFO "Starting aiosmtpd server..." 109 | aiosmtpd -n -l localhost:1725 --debug & 110 | AIOSMTPD_PID=$! 111 | } 112 | 113 | # Function to handle script termination 114 | cleanup() { 115 | print_status INFO "Shutting down servers..." 116 | if [ -n "${DJANGO_PID:-}" ]; then 117 | kill $DJANGO_PID 2>/dev/null || true 118 | fi 119 | if [ -n "${SVELTE_PID:-}" ]; then 120 | kill $SVELTE_PID 2>/dev/null || true 121 | fi 122 | if [ -n "${AIOSMTPD_PID:-}" ]; then 123 | kill $AIOSMTPD_PID 2>/dev/null || true 124 | fi 125 | print_status INFO "Servers stopped" 126 | exit 0 127 | } 128 | 129 | # Main execution 130 | main() { 131 | print_status INFO "Starting development servers..." 132 | 133 | # Check services 134 | check_services 135 | 136 | # Check and activate virtual environment 137 | check_venv 138 | 139 | # Check and install aiosmtpd if not installed 140 | check_aiosmtpd 141 | 142 | # Set up cleanup on script termination 143 | trap cleanup SIGINT SIGTERM 144 | 145 | # Start servers 146 | run_django 147 | run_svelte 148 | run_aiosmtpd 149 | 150 | print_status INFO "All servers started!" 151 | print_status INFO "Django server running at http://localhost:8000" 152 | print_status INFO "Svelte dev server running at http://localhost:5173" 153 | print_status INFO "aiosmtpd server running at localhost:1725" 154 | print_status INFO "Press Ctrl+C to stop all servers" 155 | 156 | # Wait for signal 157 | wait 158 | } 159 | 160 | # Run main function 161 | main 162 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/" && pwd)" 4 | 5 | run() { 6 | "$PROJECT_ROOT/bin/run" "$@" 7 | } 8 | format() { 9 | pre-commit run --all-files 10 | } 11 | configure() { 12 | "$PROJECT_ROOT/bin/configure" "$@" 13 | } 14 | 15 | echo "🛠️ The following commands are now available:" 16 | echo " 🚀 run - Start all development servers (Django, Svelte, SMTP)" 17 | echo " ✨ format - Run pre-commit hooks to format and lint all files" 18 | echo 19 | echo "💡 Try them out:" 20 | echo " $ run # 🏃 Start development servers" 21 | echo " $ format # 🎨 Format code" 22 | -------------------------------------------------------------------------------- /django_backend/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .idea/ 3 | __pycache__/ 4 | media/ 5 | db.sqlite3 6 | config/* 7 | !config/*.sample 8 | virtualenv/ 9 | static/ 10 | -------------------------------------------------------------------------------- /django_backend/authentication/__init__.py: -------------------------------------------------------------------------------- 1 | """Authentication module.""" 2 | -------------------------------------------------------------------------------- /django_backend/authentication/admin.py: -------------------------------------------------------------------------------- 1 | """Authentication Admin Configuration.""" 2 | 3 | from profile.models import Profile 4 | 5 | from django.contrib import admin 6 | from django.contrib.auth.admin import UserAdmin 7 | from django.contrib.auth.models import User 8 | from django.contrib.sessions.models import Session 9 | 10 | from .models import Status 11 | 12 | admin.site.site_header = "Webmatrices Admin" 13 | admin.site.site_title = "Admin" 14 | admin.site.unregister(User) 15 | 16 | 17 | @admin.register(Session) 18 | class SessionAdmin(admin.ModelAdmin): 19 | """Session Admin Configuration.""" 20 | 21 | def _session_data(self, obj): 22 | return obj.get_decoded() 23 | 24 | list_display = ["session_key", "_session_data", "expire_date"] 25 | 26 | 27 | class StatusInline(admin.StackedInline): 28 | """Status Inline Configuration.""" 29 | 30 | model = Status 31 | can_delete = False 32 | verbose_name_plural = "Status" 33 | fk_name = "user" 34 | 35 | 36 | class ProfileInline(admin.StackedInline): 37 | """Profile Inline Configuration.""" 38 | 39 | model = Profile 40 | can_delete = False 41 | verbose_name_plural = "Profile" 42 | fk_name = "user" 43 | 44 | 45 | @admin.register(User) 46 | class CustomUserAdmin(UserAdmin): 47 | """Custom User Admin Configuration.""" 48 | 49 | inlines = (ProfileInline, StatusInline) 50 | list_display = ( 51 | "username", 52 | "email", 53 | "get_is_confirmed", 54 | "is_superuser", 55 | "date_joined", 56 | ) 57 | list_filter = ("status__is_confirmed", "is_superuser", "date_joined") 58 | list_select_related = ("profile", "status") 59 | ordering = ("-id",) 60 | 61 | @admin.display(description="Is Confirmed", ordering="status__is_confirmed") 62 | def get_is_confirmed(self, instance): 63 | """Return is_confirmed status.""" 64 | return instance.status.is_confirmed 65 | -------------------------------------------------------------------------------- /django_backend/authentication/apps.py: -------------------------------------------------------------------------------- 1 | """Authentication app configuration.""" 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class AuthenticationConfig(AppConfig): 7 | default_auto_field = "django.db.models.BigAutoField" 8 | name = "authentication" 9 | -------------------------------------------------------------------------------- /django_backend/authentication/extras/__init__.py: -------------------------------------------------------------------------------- 1 | """Authentication extras.""" 2 | -------------------------------------------------------------------------------- /django_backend/authentication/extras/constants.py: -------------------------------------------------------------------------------- 1 | class ConfirmationTypes: 2 | EMAIL = "email_confirmation" 3 | PASSWORD_RESET = "password_reset" 4 | 5 | 6 | class MessageTypes: 7 | SUCCESS = "success" 8 | ERROR = "error" 9 | WARNING = "warning" 10 | INFO = "info" 11 | 12 | 13 | TOKEN_EXPIRES_MINUTES = 1 14 | RESEND_EMAIL_MINUTES = 1 15 | -------------------------------------------------------------------------------- /django_backend/authentication/extras/tokenizer.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Type, Union 2 | 3 | import msgs 4 | from django.contrib.auth.models import User 5 | 6 | if TYPE_CHECKING: 7 | from authentication.models import ( 8 | AuthToken, 9 | ) 10 | 11 | from authentication.schemas.mail_schema import EmailTimeMessageOut 12 | 13 | 14 | class Tokenizer: 15 | def __init__( 16 | self, 17 | token_object: "Type[AuthToken]", 18 | user: User, 19 | ): 20 | self.token_object = token_object 21 | self.user = user 22 | self.message: EmailTimeMessageOut | None = None 23 | self.token = None 24 | 25 | def get_or_create_token(self) -> "Union[AuthToken, None]": 26 | try: 27 | token = self.token_object.objects.get(user=self.user) 28 | resend_allowed, time_left = token.is_resend_allowed() 29 | if not resend_allowed: 30 | time_left_in_minutes = msgs.TIME_LEFT_MESSAGE.format( 31 | mins=time_left.seconds // 60, secs=time_left.seconds % 60 32 | ) 33 | self.message = { 34 | "message": msgs.WAIT_MESSAGE.format( 35 | time_left=time_left_in_minutes 36 | ), 37 | "message_type": "error", 38 | "alias": "wait_before_resend", 39 | "time_left": time_left.seconds, 40 | } 41 | return None 42 | token.delete() 43 | except self.token_object.DoesNotExist: 44 | pass 45 | token = self.token_object(user=self.user) 46 | token.set_token() 47 | self.token = token 48 | -------------------------------------------------------------------------------- /django_backend/authentication/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.6 on 2024-05-24 03:13 2 | 3 | import django.db.models.deletion 4 | import django.utils.timezone 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="ConfirmMailToken", 19 | fields=[ 20 | ( 21 | "id", 22 | models.BigAutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ("token_key", models.CharField(max_length=100, unique=True)), 30 | ( 31 | "token_key_expires", 32 | models.DateTimeField(blank=True, null=True), 33 | ), 34 | ( 35 | "last_sent_time", 36 | models.DateTimeField( 37 | blank=True, default=django.utils.timezone.now, null=True 38 | ), 39 | ), 40 | ( 41 | "user", 42 | models.OneToOneField( 43 | null=True, 44 | on_delete=django.db.models.deletion.CASCADE, 45 | related_name="confirm_mail_token", 46 | to=settings.AUTH_USER_MODEL, 47 | ), 48 | ), 49 | ], 50 | options={ 51 | "verbose_name": "Confirm Mail Token", 52 | "verbose_name_plural": "Confirm Mail Tokens", 53 | }, 54 | ), 55 | migrations.CreateModel( 56 | name="ForgotPasswordToken", 57 | fields=[ 58 | ( 59 | "id", 60 | models.BigAutoField( 61 | auto_created=True, 62 | primary_key=True, 63 | serialize=False, 64 | verbose_name="ID", 65 | ), 66 | ), 67 | ("token_key", models.CharField(max_length=100, unique=True)), 68 | ( 69 | "token_key_expires", 70 | models.DateTimeField(blank=True, null=True), 71 | ), 72 | ( 73 | "last_sent_time", 74 | models.DateTimeField( 75 | blank=True, default=django.utils.timezone.now, null=True 76 | ), 77 | ), 78 | ( 79 | "user", 80 | models.OneToOneField( 81 | on_delete=django.db.models.deletion.CASCADE, 82 | related_name="forgot_password_token", 83 | to=settings.AUTH_USER_MODEL, 84 | ), 85 | ), 86 | ], 87 | options={ 88 | "abstract": False, 89 | }, 90 | ), 91 | migrations.CreateModel( 92 | name="ForgotUsernameToken", 93 | fields=[ 94 | ( 95 | "id", 96 | models.BigAutoField( 97 | auto_created=True, 98 | primary_key=True, 99 | serialize=False, 100 | verbose_name="ID", 101 | ), 102 | ), 103 | ("token_key", models.CharField(max_length=100, unique=True)), 104 | ( 105 | "token_key_expires", 106 | models.DateTimeField(blank=True, null=True), 107 | ), 108 | ( 109 | "last_sent_time", 110 | models.DateTimeField( 111 | blank=True, default=django.utils.timezone.now, null=True 112 | ), 113 | ), 114 | ( 115 | "user", 116 | models.OneToOneField( 117 | on_delete=django.db.models.deletion.CASCADE, 118 | related_name="forgot_email_token", 119 | to=settings.AUTH_USER_MODEL, 120 | ), 121 | ), 122 | ], 123 | options={ 124 | "abstract": False, 125 | }, 126 | ), 127 | migrations.CreateModel( 128 | name="Status", 129 | fields=[ 130 | ( 131 | "id", 132 | models.BigAutoField( 133 | auto_created=True, 134 | primary_key=True, 135 | serialize=False, 136 | verbose_name="ID", 137 | ), 138 | ), 139 | ("is_confirmed", models.BooleanField(default=False)), 140 | ( 141 | "token", 142 | models.ForeignKey( 143 | blank=True, 144 | null=True, 145 | on_delete=django.db.models.deletion.SET_NULL, 146 | to="authentication.confirmmailtoken", 147 | ), 148 | ), 149 | ( 150 | "user", 151 | models.OneToOneField( 152 | on_delete=django.db.models.deletion.CASCADE, 153 | to=settings.AUTH_USER_MODEL, 154 | ), 155 | ), 156 | ], 157 | options={ 158 | "verbose_name": "Status", 159 | "verbose_name_plural": "Status", 160 | }, 161 | ), 162 | ] 163 | -------------------------------------------------------------------------------- /django_backend/authentication/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bishwas-py/django-svelte-template/b5b7e1783750d602f75ef5c11e20f73db78684f7/django_backend/authentication/migrations/__init__.py -------------------------------------------------------------------------------- /django_backend/authentication/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | from profile.models import Profile 4 | 5 | import msgs 6 | from authentication.extras import constants 7 | from authentication.extras.tokenizer import Tokenizer 8 | from django.contrib.auth.models import User 9 | from django.contrib.sites.models import Site 10 | from django.db import models 11 | from django.db.models.signals import post_save 12 | from django.utils import timezone 13 | from django.utils.crypto import get_random_string 14 | 15 | 16 | class AuthToken(models.Model): 17 | token_key = models.CharField(max_length=100, unique=True) 18 | token_key_expires = models.DateTimeField(blank=True, null=True) 19 | last_sent_time = models.DateTimeField( 20 | blank=True, null=True, default=timezone.now 21 | ) 22 | 23 | class Meta: 24 | abstract = True 25 | 26 | def set_token(self, expires_in: int = constants.TOKEN_EXPIRES_MINUTES): 27 | self.token_key = get_random_string(length=9) 28 | self.token_key_expires = timezone.now() + datetime.timedelta( 29 | minutes=expires_in 30 | ) 31 | self.save() 32 | 33 | def is_resend_allowed(self): 34 | if self.last_sent_time and timezone.now() < self.token_key_expires: 35 | time_difference_from_last_sent_time = ( 36 | timezone.now() - self.last_sent_time 37 | ) 38 | time_gap = datetime.timedelta( 39 | minutes=constants.RESEND_EMAIL_MINUTES 40 | ) 41 | if time_difference_from_last_sent_time < time_gap: 42 | time_left = time_gap - time_difference_from_last_sent_time 43 | return False, time_left 44 | return True, 0 45 | 46 | def is_token_expired(self): 47 | return timezone.now() > self.token_key_expires 48 | 49 | def is_token_valid(self, token_to_compare: str): 50 | return self.token_key == token_to_compare 51 | 52 | 53 | class ForgotPasswordToken(AuthToken): 54 | user = models.OneToOneField( 55 | User, on_delete=models.CASCADE, related_name="forgot_password_token" 56 | ) 57 | 58 | 59 | class ForgotUsernameToken(AuthToken): 60 | user = models.OneToOneField( 61 | User, on_delete=models.CASCADE, related_name="forgot_email_token" 62 | ) 63 | 64 | def __str__(self): 65 | return f"{self.user.username} - {self.token_key}" 66 | 67 | 68 | class ConfirmMailToken(AuthToken): 69 | """ 70 | Tokens are used for email confirmation, password reset, etc. 71 | """ 72 | 73 | user = models.OneToOneField( 74 | User, 75 | on_delete=models.CASCADE, 76 | null=True, 77 | related_name="confirm_mail_token", 78 | ) 79 | 80 | class Meta: 81 | verbose_name = "Confirm Mail Token" 82 | verbose_name_plural = "Confirm Mail Tokens" 83 | 84 | def __str__(self): 85 | return f"Mail confirmation Token: {self.token_key}" 86 | 87 | @classmethod 88 | def send_token(cls, request, user): 89 | confirm_email_token = Tokenizer(ConfirmMailToken, user) 90 | confirm_email_token.get_or_create_token() 91 | 92 | user.status.set_token_and_save(confirm_email_token.token) 93 | 94 | site = Site.objects.get_current(request) 95 | user.email_user( 96 | subject="Confirm your email", 97 | message=msgs.CONFIRM_EMAIL_MESSAGE.format( 98 | origin=site.sitedata.url, 99 | token=confirm_email_token.token.token_key, 100 | ), 101 | ) 102 | 103 | 104 | class Status(models.Model): 105 | user = models.OneToOneField(User, on_delete=models.CASCADE) 106 | token = models.ForeignKey( 107 | ConfirmMailToken, on_delete=models.SET_NULL, blank=True, null=True 108 | ) 109 | is_confirmed = models.BooleanField(default=False) 110 | 111 | def set_confirmed(self): 112 | self.token = None 113 | self.is_confirmed = True 114 | self.save() 115 | return True, "Successfully confirmed email." 116 | 117 | def set_token_and_save(self, token): 118 | self.token = token 119 | self.save() 120 | 121 | def __str__(self): 122 | return f"{self.user.username} - {self.is_confirmed}" 123 | 124 | class Meta: 125 | verbose_name = "Status" 126 | verbose_name_plural = "Status" 127 | 128 | 129 | def create_profile_with_status(sender, instance, created, **kwargs): 130 | if created: 131 | logging.info(f"Creating profile for {instance.username}") 132 | user_profile = Profile.objects.create(user=instance) 133 | user_status = Status.objects.create(user=instance) 134 | user_profile.save() 135 | user_status.save() 136 | 137 | 138 | post_save.connect(create_profile_with_status, sender=User) 139 | -------------------------------------------------------------------------------- /django_backend/authentication/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from django.contrib.auth import get_user_model 4 | from django.contrib.auth.password_validation import validate_password 5 | from django.core.exceptions import ValidationError 6 | from django.core.validators import EmailValidator 7 | from djapy import Schema 8 | from djapy.schema import Form 9 | from pydantic import EmailStr, Field, constr, field_validator 10 | 11 | MESSAGE_SEPERATOR = "|--|" 12 | 13 | email_validator = EmailValidator() 14 | 15 | User = get_user_model() 16 | 17 | 18 | class UserRegisterSchema(Form): 19 | username: constr(min_length=3, max_length=150) 20 | email: EmailStr 21 | password: constr(min_length=8) 22 | 23 | @field_validator("username") 24 | def username_must_be_valid(cls, username: str): 25 | if not username.isalnum(): 26 | raise ValueError("Username must be alphanumeric.") 27 | if username.isdigit(): 28 | raise ValueError("Username must not be only digit.") 29 | if User.objects.filter(username=username).exists(): 30 | raise ValueError("User with this username already exists.") 31 | 32 | return username 33 | 34 | @field_validator("email") 35 | def email_must_be_valid(cls, email: str): 36 | try: 37 | email_validator(email) 38 | except ValidationError: 39 | raise ValueError(email_validator.message) 40 | if User.objects.filter(email=email).exists(): 41 | raise ValueError("User with this email might already exists.") 42 | return email 43 | 44 | @field_validator("password") 45 | def password_must_be_valid(cls, password: str): 46 | try: 47 | validate_password(password) 48 | except ValidationError as e: 49 | raise ValueError(MESSAGE_SEPERATOR.join(e.messages)) 50 | 51 | 52 | class AuthOutSchema(Schema): 53 | is_authenticated: bool 54 | user_id: int = Field(..., alias="id") 55 | username: str 56 | redirect: Optional[str] = None 57 | -------------------------------------------------------------------------------- /django_backend/authentication/schemas/auth_schema.py: -------------------------------------------------------------------------------- 1 | from djapy.schema import Form 2 | from djapy_ext.exception import MessageValueError 3 | from pydantic import model_validator 4 | 5 | 6 | class LoginSchema(Form): 7 | username: str | None = None 8 | email: str | None = None 9 | password: str 10 | 11 | @model_validator(mode="after") 12 | def username_or_email_must_be_valid(cls, values): 13 | username = values.username 14 | email = values.email 15 | if username or email: 16 | return values 17 | raise MessageValueError( 18 | message="Username or email must be provided.", 19 | message_type="error", 20 | alias="username_or_email_not_provided", 21 | inline={ 22 | "username": "This field is required.", 23 | "email": "This field is required.", 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /django_backend/authentication/schemas/mail_schema.py: -------------------------------------------------------------------------------- 1 | from djapy import Schema 2 | from generics.schemas import MessageOut 3 | from pydantic import EmailStr 4 | 5 | 6 | class EmailTimeMessageOut(MessageOut): 7 | time_left: int = None 8 | 9 | 10 | class UsernameOutSchema(Schema): 11 | username: str 12 | 13 | 14 | class GetEmailSchema(Schema): 15 | email: EmailStr 16 | -------------------------------------------------------------------------------- /django_backend/authentication/schemas/user_schema.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from djapy import Schema 4 | from djapy.schema import Outsource 5 | from djapy.schema.schema import ImageUrl 6 | from pydantic import computed_field 7 | from typing import Optional 8 | from datetime import date 9 | 10 | class StatusSchema(Schema): 11 | is_confirmed: bool 12 | 13 | 14 | class ProfileSchema(Schema): 15 | bio: Optional[str] = None 16 | location: Optional[str] = None 17 | birth_date: Optional[date] = None 18 | image: ImageUrl 19 | 20 | 21 | class GenericUserSchema(Schema): 22 | id: int 23 | username: str 24 | email: str 25 | first_name: str 26 | last_name: str 27 | is_staff: bool 28 | is_superuser: bool 29 | is_active: bool 30 | status: StatusSchema 31 | profile: ProfileSchema 32 | 33 | 34 | class UserSchema(GenericUserSchema, Outsource): 35 | date_joined: datetime 36 | 37 | @computed_field 38 | def user_permissions(self) -> set[str]: 39 | return self._obj.get_user_permissions() 40 | -------------------------------------------------------------------------------- /django_backend/authentication/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase # noqa: F401 2 | -------------------------------------------------------------------------------- /django_backend/authentication/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | auth_views, 5 | confirm_email_views, 6 | forgot_password_views, 7 | users_views, 8 | ) 9 | 10 | urlpatterns = [ 11 | path("login/", auth_views.login_user, name="login"), 12 | path("logout/", auth_views.logout_user, name="logout"), 13 | path("register/", auth_views.register_user, name="register"), 14 | path( 15 | "email-confirm/", 16 | confirm_email_views.send_confirmation_email, 17 | name="send_confirmation_email", 18 | ), 19 | path( 20 | "email-confirm//", 21 | confirm_email_views.confirm_email, 22 | name="confirm_email", 23 | ), 24 | path( 25 | "forgot-password/", 26 | forgot_password_views.request_password_token, 27 | name="request_password_token", 28 | ), 29 | path( 30 | "forgot-password/reset-password/", 31 | forgot_password_views.reset_password, 32 | name="reset_password", 33 | ), 34 | path( 35 | "user//", 36 | users_views.get_user_by_username, 37 | name="update_user", 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /django_backend/authentication/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bishwas-py/django-svelte-template/b5b7e1783750d602f75ef5c11e20f73db78684f7/django_backend/authentication/views/__init__.py -------------------------------------------------------------------------------- /django_backend/authentication/views/auth_views.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from authentication.models import ConfirmMailToken 4 | from authentication.schemas import UserRegisterSchema 5 | from authentication.schemas.auth_schema import LoginSchema 6 | from authentication.schemas.mail_schema import EmailTimeMessageOut 7 | from django.contrib.auth import get_user_model, login 8 | from django.db import IntegrityError 9 | from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie 10 | from djapy import SessionAuth, djapify 11 | from generics.schemas import ActionMessageOut, Inline, MessageOut 12 | 13 | User = get_user_model() 14 | 15 | 16 | @djapify(method="POST") 17 | @csrf_exempt 18 | def register_user( 19 | request, user_payload: UserRegisterSchema 20 | ) -> ActionMessageOut | Tuple[400, Inline] | Tuple[429, EmailTimeMessageOut]: 21 | """ 22 | Register User 23 | If user is registered successfully, user will be logged in, 24 | and email will be sent to user's email with confirmation link. 25 | Also, login token will be returned in response headers. 26 | ``` 27 | Set-Cookie: sessionid=; 28 | expires=