feat: ship MVP itinerary optimization, weather, AI key prefs, and MCP tools

This commit is contained in:
2026-03-08 13:49:32 +00:00
parent 9eb0325c7a
commit 8c0637c518
25 changed files with 1888 additions and 511 deletions

View File

@@ -28,88 +28,88 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Core Security & Debug
# ---------------------------------------------------------------------------
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = getenv('SECRET_KEY')
SECRET_KEY = getenv("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = getenv('DEBUG', 'true').lower() == 'true'
DEBUG = getenv("DEBUG", "true").lower() == "true"
# ALLOWED_HOSTS = [
# 'localhost',
# '127.0.0.1',
# 'server'
# ]
ALLOWED_HOSTS = ['*'] # In production, restrict to known hosts.
ALLOWED_HOSTS = ["*"] # In production, restrict to known hosts.
# ---------------------------------------------------------------------------
# Installed Apps
# ---------------------------------------------------------------------------
INSTALLED_APPS = (
"allauth_ui",
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'rest_framework',
'rest_framework.authtoken',
'allauth',
'allauth.account',
'allauth.mfa',
'allauth.headless',
'allauth.socialaccount',
'allauth.socialaccount.providers.github',
'allauth.socialaccount.providers.openid_connect',
'invitations',
'drf_yasg',
'djmoney',
'corsheaders',
'adventures',
'worldtravel',
'users',
'integrations',
'django.contrib.gis',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.sites",
"rest_framework",
"rest_framework.authtoken",
"allauth",
"allauth.account",
"allauth.mfa",
"allauth.headless",
"allauth.socialaccount",
"allauth.socialaccount.providers.github",
"allauth.socialaccount.providers.openid_connect",
"invitations",
"drf_yasg",
"djmoney",
"corsheaders",
"adventures",
"worldtravel",
"users",
"integrations",
"mcp_server",
"django.contrib.gis",
# 'achievements', # Not done yet, will be added later in a future update
'widget_tweaks',
'slippers',
"widget_tweaks",
"slippers",
)
# ---------------------------------------------------------------------------
# Middleware
# ---------------------------------------------------------------------------
MIDDLEWARE = (
'whitenoise.middleware.WhiteNoiseMiddleware',
'adventures.middleware.XSessionTokenMiddleware',
'adventures.middleware.DisableCSRFForSessionTokenMiddleware',
'adventures.middleware.DisableCSRFForMobileLoginSignup',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'adventures.middleware.OverrideHostMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
"whitenoise.middleware.WhiteNoiseMiddleware",
"adventures.middleware.XSessionTokenMiddleware",
"adventures.middleware.DisableCSRFForSessionTokenMiddleware",
"adventures.middleware.DisableCSRFForMobileLoginSignup",
"corsheaders.middleware.CorsMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"adventures.middleware.OverrideHostMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
)
# ---------------------------------------------------------------------------
# Caching
# ---------------------------------------------------------------------------
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 60 * 60 * 24, # Optional: 1 day cache
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
"TIMEOUT": 60 * 60 * 24, # Optional: 1 day cache
}
}
# For backwards compatibility for Django 1.8
MIDDLEWARE_CLASSES = MIDDLEWARE
ROOT_URLCONF = 'main.urls'
ROOT_URLCONF = "main.urls"
# WSGI_APPLICATION = 'demo.wsgi.application'
@@ -118,6 +118,7 @@ ROOT_URLCONF = 'main.urls'
# ---------------------------------------------------------------------------
# Using legacy PG environment variables for compatibility with existing setups
def env(*keys, default=None):
"""Return the first non-empty environment variable from a list of keys."""
for key in keys:
@@ -126,16 +127,17 @@ def env(*keys, default=None):
return value
return default
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': env('PGDATABASE', 'POSTGRES_DB'),
'USER': env('PGUSER', 'POSTGRES_USER'),
'PASSWORD': env('PGPASSWORD', 'POSTGRES_PASSWORD'),
'HOST': env('PGHOST', default='localhost'),
'PORT': int(env('PGPORT', default='5432')),
'OPTIONS': {
'sslmode': 'prefer', # Prefer SSL, but allow non-SSL connections
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": env("PGDATABASE", "POSTGRES_DB"),
"USER": env("PGUSER", "POSTGRES_USER"),
"PASSWORD": env("PGPASSWORD", "POSTGRES_PASSWORD"),
"HOST": env("PGHOST", default="localhost"),
"PORT": int(env("PGPORT", default="5432")),
"OPTIONS": {
"sslmode": "prefer", # Prefer SSL, but allow non-SSL connections
},
}
}
@@ -146,8 +148,8 @@ DATABASES = {
# ---------------------------------------------------------------------------
# Internationalization
# ---------------------------------------------------------------------------
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
@@ -156,20 +158,20 @@ USE_TZ = True
# Frontend URL & Cookies
# ---------------------------------------------------------------------------
# Derive frontend URL from environment and configure cookie behavior.
unParsedFrontenedUrl = getenv('FRONTEND_URL', 'http://localhost:3000')
FRONTEND_URL = unParsedFrontenedUrl.translate(str.maketrans('', '', '\'"'))
unParsedFrontenedUrl = getenv("FRONTEND_URL", "http://localhost:3000")
FRONTEND_URL = unParsedFrontenedUrl.translate(str.maketrans("", "", "'\""))
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_NAME = 'sessionid'
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_NAME = "sessionid"
# Secure cookies if frontend is served over HTTPS
SESSION_COOKIE_SECURE = FRONTEND_URL.startswith('https')
CSRF_COOKIE_SECURE = FRONTEND_URL.startswith('https')
SESSION_COOKIE_SECURE = FRONTEND_URL.startswith("https")
CSRF_COOKIE_SECURE = FRONTEND_URL.startswith("https")
# Dynamically determine cookie domain to support subdomains while avoiding IPs
hostname = urlparse(FRONTEND_URL).hostname
is_ip_address = hostname.replace('.', '').isdigit()
is_single_label = '.' not in hostname # single-label hostnames (e.g., "localhost")
is_ip_address = hostname.replace(".", "").isdigit()
is_single_label = "." not in hostname # single-label hostnames (e.g., "localhost")
if is_ip_address or is_single_label:
SESSION_COOKIE_DOMAIN = None
@@ -181,15 +183,15 @@ else:
# ---------------------------------------------------------------------------
# Static & Media Files
# ---------------------------------------------------------------------------
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = '/static/'
STATIC_URL = "/static/"
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media' # Must match NGINX root for media serving
STATICFILES_DIRS = [BASE_DIR / 'static']
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media" # Must match NGINX root for media serving
STATICFILES_DIRS = [BASE_DIR / "static"]
STORAGES = {
"staticfiles": {
@@ -197,7 +199,7 @@ STORAGES = {
},
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
}
},
}
SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
@@ -207,15 +209,17 @@ SILENCED_SYSTEM_CHECKS = ["slippers.E001"]
# ---------------------------------------------------------------------------
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
os.path.join(BASE_DIR, "templates"),
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
@@ -226,24 +230,29 @@ ALLAUTH_UI_THEME = "dim"
# ---------------------------------------------------------------------------
# Authentication & Accounts
# ---------------------------------------------------------------------------
DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'false').lower() == 'true'
DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.')
DISABLE_REGISTRATION = getenv("DISABLE_REGISTRATION", "false").lower() == "true"
DISABLE_REGISTRATION_MESSAGE = getenv(
"DISABLE_REGISTRATION_MESSAGE",
"Registration is disabled. Please contact the administrator if you need an account.",
)
SOCIALACCOUNT_ALLOW_SIGNUP = getenv('SOCIALACCOUNT_ALLOW_SIGNUP', 'false').lower() == 'true'
SOCIALACCOUNT_ALLOW_SIGNUP = (
getenv("SOCIALACCOUNT_ALLOW_SIGNUP", "false").lower() == "true"
)
AUTH_USER_MODEL = 'users.CustomUser'
ACCOUNT_ADAPTER = 'users.adapters.CustomAccountAdapter'
AUTH_USER_MODEL = "users.CustomUser"
ACCOUNT_ADAPTER = "users.adapters.CustomAccountAdapter"
INVITATIONS_ADAPTER = ACCOUNT_ADAPTER
INVITATIONS_ACCEPT_INVITE_AFTER_SIGNUP = True
INVITATIONS_EMAIL_SUBJECT_PREFIX = 'Voyage: '
SOCIALACCOUNT_ADAPTER = 'users.adapters.CustomSocialAccountAdapter'
ACCOUNT_SIGNUP_FORM_CLASS = 'users.form_overrides.CustomSignupForm'
INVITATIONS_EMAIL_SUBJECT_PREFIX = "Voyage: "
SOCIALACCOUNT_ADAPTER = "users.adapters.CustomSocialAccountAdapter"
ACCOUNT_SIGNUP_FORM_CLASS = "users.form_overrides.CustomSignupForm"
SESSION_SAVE_EVERY_REQUEST = True
LOGIN_REDIRECT_URL = FRONTEND_URL # Redirect to frontend after login
SOCIALACCOUNT_LOGIN_ON_GET = True
INVITATIONS_INVITE_FORM = 'users.form_overrides.UseAdminInviteForm'
INVITATIONS_INVITE_FORM = "users.form_overrides.UseAdminInviteForm"
INVITATIONS_SIGNUP_REDIRECT_URL = f"{FRONTEND_URL}/signup"
HEADLESS_FRONTEND_URLS = {
@@ -256,34 +265,38 @@ HEADLESS_FRONTEND_URLS = {
}
AUTHENTICATION_BACKENDS = [
'users.backends.NoPasswordAuthBackend',
"users.backends.NoPasswordAuthBackend",
# 'allauth.account.auth_backends.AuthenticationBackend',
# 'django.contrib.auth.backends.ModelBackend',
]
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_EMAIL_VERIFICATION = getenv('ACCOUNT_EMAIL_VERIFICATION', 'none') # 'none', 'optional', 'mandatory'
ACCOUNT_EMAIL_VERIFICATION = getenv(
"ACCOUNT_EMAIL_VERIFICATION", "none"
) # 'none', 'optional', 'mandatory'
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True # Auto-link by email
SOCIALACCOUNT_AUTO_SIGNUP = True # Allow auto-signup post adapter checks
FORCE_SOCIALACCOUNT_LOGIN = getenv('FORCE_SOCIALACCOUNT_LOGIN', 'false').lower() == 'true' # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured.
FORCE_SOCIALACCOUNT_LOGIN = (
getenv("FORCE_SOCIALACCOUNT_LOGIN", "false").lower() == "true"
) # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured.
if getenv('EMAIL_BACKEND', 'console') == 'console':
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
if getenv("EMAIL_BACKEND", "console") == "console":
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = getenv('EMAIL_HOST')
EMAIL_USE_TLS = getenv('EMAIL_USE_TLS', 'true').lower() == 'true'
EMAIL_PORT = getenv('EMAIL_PORT', 587)
EMAIL_USE_SSL = getenv('EMAIL_USE_SSL', 'false').lower() == 'true'
EMAIL_HOST_USER = getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = getenv('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = getenv('DEFAULT_FROM_EMAIL')
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = getenv("EMAIL_HOST")
EMAIL_USE_TLS = getenv("EMAIL_USE_TLS", "true").lower() == "true"
EMAIL_PORT = getenv("EMAIL_PORT", 587)
EMAIL_USE_SSL = getenv("EMAIL_USE_SSL", "false").lower() == "true"
EMAIL_HOST_USER = getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = getenv("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = getenv("DEFAULT_FROM_EMAIL")
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.resend.com'
@@ -299,63 +312,71 @@ else:
# Django REST Framework
# ---------------------------------------------------------------------------
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.UserRateThrottle',
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema",
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.UserRateThrottle",
],
'DEFAULT_THROTTLE_RATES': {
'user': '1000/day',
'image_proxy': '60/minute',
"DEFAULT_THROTTLE_RATES": {
"user": "1000/day",
"image_proxy": "60/minute",
},
}
if DEBUG:
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = (
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
)
else:
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = (
'rest_framework.renderers.JSONRenderer',
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = (
"rest_framework.renderers.JSONRenderer",
)
# ---------------------------------------------------------------------------
# CORS & CSRF
# ---------------------------------------------------------------------------
CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
CORS_ALLOWED_ORIGINS = [
origin.strip()
for origin in getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split(",")
if origin.strip()
]
CSRF_TRUSTED_ORIGINS = [
origin.strip()
for origin in getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split(",")
if origin.strip()
]
CORS_ALLOW_CREDENTIALS = True
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
'file': {
'class': 'logging.FileHandler',
'filename': 'scheduler.log',
"file": {
"class": "logging.FileHandler",
"filename": "scheduler.log",
},
},
'root': {
'handlers': ['console', 'file'],
'level': 'INFO',
"root": {
"handlers": ["console", "file"],
"level": "INFO",
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': False,
"loggers": {
"django": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
},
}
@@ -363,18 +384,28 @@ LOGGING = {
# ---------------------------------------------------------------------------
# Public URLs & Third-Party Integrations
# ---------------------------------------------------------------------------
PUBLIC_URL = getenv('PUBLIC_URL', 'http://localhost:8000')
PUBLIC_URL = getenv("PUBLIC_URL", "http://localhost:8000")
# VOYAGE_CDN_URL = getenv('VOYAGE_CDN_URL', 'https://cdn.voyage.app')
# Major release version of Voyage, not including the patch version date.
VOYAGE_RELEASE_VERSION = 'v0.12.0'
VOYAGE_RELEASE_VERSION = "v0.12.0"
# https://github.com/dr5hn/countries-states-cities-database/tags
COUNTRY_REGION_JSON_VERSION = 'v3.0'
COUNTRY_REGION_JSON_VERSION = "v3.0"
# External service keys (do not hardcode secrets)
GOOGLE_MAPS_API_KEY = getenv('GOOGLE_MAPS_API_KEY', '')
STRAVA_CLIENT_ID = getenv('STRAVA_CLIENT_ID', '')
STRAVA_CLIENT_SECRET = getenv('STRAVA_CLIENT_SECRET', '')
OSRM_BASE_URL = getenv('OSRM_BASE_URL', 'https://router.project-osrm.org')
GOOGLE_MAPS_API_KEY = getenv("GOOGLE_MAPS_API_KEY", "")
STRAVA_CLIENT_ID = getenv("STRAVA_CLIENT_ID", "")
STRAVA_CLIENT_SECRET = getenv("STRAVA_CLIENT_SECRET", "")
OSRM_BASE_URL = getenv("OSRM_BASE_URL", "https://router.project-osrm.org")
FIELD_ENCRYPTION_KEY = getenv("FIELD_ENCRYPTION_KEY", "")
DJANGO_MCP_ENDPOINT = getenv("DJANGO_MCP_ENDPOINT", "api/mcp")
DJANGO_MCP_AUTHENTICATION_CLASSES = [
"rest_framework.authentication.TokenAuthentication",
]
DJANGO_MCP_ENDPOINT_PERMISSION_CLASSES = [
"rest_framework.permissions.IsAuthenticated",
]

View File

@@ -1,52 +1,100 @@
from django.urls import include, re_path, path
from django.contrib import admin
from django.views.generic import RedirectView, TemplateView
from users.views import IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView, UpdateUserMetadataView, EnabledSocialProvidersView, DisablePasswordAuthenticationView
from users.views import (
IsRegistrationDisabled,
PublicUserListView,
PublicUserDetailView,
UserMetadataView,
UpdateUserMetadataView,
EnabledSocialProvidersView,
DisablePasswordAuthenticationView,
)
from .views import get_csrf_token, get_public_url, serve_protected_media
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from mcp_server.views import MCPServerStreamableHttpView
from django.conf import settings
from django.utils.module_loading import import_string
schema_view = get_schema_view(
openapi.Info(
title='API Docs',
default_version='v1',
title="API Docs",
default_version="v1",
)
)
urlpatterns = [
path('api/', include('adventures.urls')),
path('api/', include('worldtravel.urls')),
path("api/", include("adventures.urls")),
path("api/", include("worldtravel.urls")),
path(
getattr(settings, "DJANGO_MCP_ENDPOINT", "api/mcp"),
MCPServerStreamableHttpView.as_view(
permission_classes=[
import_string(cls)
for cls in getattr(
settings,
"DJANGO_MCP_ENDPOINT_PERMISSION_CLASSES",
["rest_framework.permissions.IsAuthenticated"],
)
],
authentication_classes=[
import_string(cls)
for cls in getattr(
settings,
"DJANGO_MCP_AUTHENTICATION_CLASSES",
["rest_framework.authentication.TokenAuthentication"],
)
],
),
name="mcp_server_streamable_http_endpoint",
),
path("auth/", include("allauth.headless.urls")),
# Serve protected media files
re_path(r'^media/(?P<path>.*)$', serve_protected_media, name='serve-protected-media'),
path('auth/is-registration-disabled/', IsRegistrationDisabled.as_view(), name='is_registration_disabled'),
path('auth/users/', PublicUserListView.as_view(), name='public-user-list'),
path('auth/user/<str:username>/', PublicUserDetailView.as_view(), name='public-user-detail'),
path('auth/update-user/', UpdateUserMetadataView.as_view(), name='update-user-metadata'),
path('auth/user-metadata/', UserMetadataView.as_view(), name='user-metadata'),
path('auth/social-providers/', EnabledSocialProvidersView.as_view(), name='enabled-social-providers'),
path('auth/disable-password/', DisablePasswordAuthenticationView.as_view(), name='disable-password-authentication'),
path('csrf/', get_csrf_token, name='get_csrf_token'),
path('public-url/', get_public_url, name='get_public_url'),
path("invitations/", include('invitations.urls', namespace='invitations')),
path('', TemplateView.as_view(template_name='home.html')),
re_path(r'^admin/', admin.site.urls),
re_path(r'^accounts/profile/$', RedirectView.as_view(url='/',
permanent=True), name='profile-redirect'),
re_path(r'^docs/$', schema_view.with_ui('swagger',
cache_timeout=0), name='api_docs'),
re_path(
r"^media/(?P<path>.*)$", serve_protected_media, name="serve-protected-media"
),
path(
"auth/is-registration-disabled/",
IsRegistrationDisabled.as_view(),
name="is_registration_disabled",
),
path("auth/users/", PublicUserListView.as_view(), name="public-user-list"),
path(
"auth/user/<str:username>/",
PublicUserDetailView.as_view(),
name="public-user-detail",
),
path(
"auth/update-user/",
UpdateUserMetadataView.as_view(),
name="update-user-metadata",
),
path("auth/user-metadata/", UserMetadataView.as_view(), name="user-metadata"),
path(
"auth/social-providers/",
EnabledSocialProvidersView.as_view(),
name="enabled-social-providers",
),
path(
"auth/disable-password/",
DisablePasswordAuthenticationView.as_view(),
name="disable-password-authentication",
),
path("csrf/", get_csrf_token, name="get_csrf_token"),
path("public-url/", get_public_url, name="get_public_url"),
path("invitations/", include("invitations.urls", namespace="invitations")),
path("", TemplateView.as_view(template_name="home.html")),
re_path(r"^admin/", admin.site.urls),
re_path(
r"^accounts/profile/$",
RedirectView.as_view(url="/", permanent=True),
name="profile-redirect",
),
re_path(
r"^docs/$", schema_view.with_ui("swagger", cache_timeout=0), name="api_docs"
),
# path('auth/account-confirm-email/', VerifyEmailView.as_view(), name='account_email_verification_sent'),
path("accounts/", include("allauth.urls")),
path("api/integrations/", include("integrations.urls")),
# Include the API endpoints:
]
# Include the API endpoints:
]