feat(config): add Google OAuth client configuration and extend external URL routes
Add GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REDIRECT_URI env vars and configure external access endpoints for OAuth callback and logout.
This commit is contained in:
2 files changed
+262
-67
No files matched your search
+38
-17
@@ -3,7 +3,6 @@ generated with djinit
|
||||
Common settings shared between development and production environment
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
@@ -24,11 +23,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
THIRD_PARTY_APPS = [
|
||||
"rest_framework",
|
||||
"rest_framework_simplejwt",
|
||||
"rest_framework_simplejwt.token_blacklist",
|
||||
"corsheaders",
|
||||
"drf_yasg",
|
||||
"drf_spectacular",
|
||||
"django_tailwind_cli",
|
||||
"django_htmx",
|
||||
"import_export",
|
||||
"django_filters",
|
||||
]
|
||||
|
||||
USER_DEFINED_APPS = [
|
||||
@@ -71,13 +71,14 @@ ROOT_URLCONF = "config.urls"
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"DIRS": [BASE_DIR / "templates"],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"src.config.context_processors.site_stats",
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -86,6 +87,8 @@ TEMPLATES = [
|
||||
WSGI_APPLICATION = "config.wsgi.application"
|
||||
|
||||
|
||||
AUTH_USER_MODEL = "accounts.User"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
@@ -104,12 +107,14 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
},
|
||||
]
|
||||
|
||||
LOGIN_URL = "/login/"
|
||||
LOGIN_REDIRECT_URL = "/dashboard/"
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
TIME_ZONE = "UTC"
|
||||
TIME_ZONE = "Asia/Kathmandu"
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
@@ -133,29 +138,45 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# REST Framework Settings
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
],
|
||||
"DEFAULT_PARSER_CLASSES": [
|
||||
"rest_framework.parsers.JSONParser",
|
||||
],
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema",
|
||||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
||||
"PAGE_SIZE": 20,
|
||||
"PAGE_SIZE": 50,
|
||||
"DEFAULT_FILTER_BACKENDS": (
|
||||
"django_filters.rest_framework.DjangoFilterBackend",
|
||||
"rest_framework.filters.SearchFilter",
|
||||
"rest_framework.filters.OrderingFilter",
|
||||
),
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
}
|
||||
|
||||
# JWT Settings
|
||||
# SimpleJWT
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=3),
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(hours=1),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
|
||||
"ROTATE_REFRESH_TOKENS": True,
|
||||
"BLACKLIST_AFTER_ROTATION": True,
|
||||
"AUTH_HEADER_TYPES": ("Bearer",),
|
||||
}
|
||||
|
||||
|
||||
# Google OAuth
|
||||
GOOGLE_CLIENT_ID = env("GOOGLE_CLIENT_ID", default="")
|
||||
GOOGLE_CLIENT_SECRET = env("GOOGLE_CLIENT_SECRET", default="")
|
||||
GOOGLE_REDIRECT_URI = env(
|
||||
"GOOGLE_REDIRECT_URI", default="http://localhost:8000/api/auth/google/callback/"
|
||||
)
|
||||
|
||||
# Spectacular
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": "Project Management API",
|
||||
"DESCRIPTION": "Django port of Project Management",
|
||||
"VERSION": "1.0.0",
|
||||
}
|
||||
|
||||
|
||||
# CORS Settings
|
||||
CORS_ALLOW_METHODS = [
|
||||
"DELETE",
|
||||
|
||||
+224
-50
@@ -1,58 +1,232 @@
|
||||
"""generated with djinit"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path
|
||||
from django.conf import settings
|
||||
from django.views.static import serve
|
||||
from django.urls import include
|
||||
from drf_yasg.views import get_schema_view
|
||||
from drf_yasg import openapi
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
TokenBlacklistView,
|
||||
)
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import include, path
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from src.config import board_views, list_views, page_views, views
|
||||
|
||||
api_patterns = [
|
||||
path("auth/", include("src.accounts.urls")),
|
||||
path("projects/", include("src.projects.urls")),
|
||||
path("tickets/", include("src.tickets.urls")),
|
||||
path("epics/", include("src.epics.urls")),
|
||||
path("notes/", include("src.notes.urls")),
|
||||
path("notifications/", include("src.notifications.urls")),
|
||||
path("external/", include("src.external.urls")),
|
||||
]
|
||||
|
||||
login_req = login_required
|
||||
dashboard_patterns = [
|
||||
path("", login_req(views.dashboard), name="dashboard"),
|
||||
path("stats-cards/", login_req(views.stats_cards), name="stats-cards"),
|
||||
path(
|
||||
"charts/tickets-per-project/",
|
||||
login_req(views.tickets_per_project_chart),
|
||||
name="chart-tickets-per-project",
|
||||
),
|
||||
path(
|
||||
"charts/user-statistics-data/",
|
||||
login_req(views.user_statistics_data),
|
||||
name="chart-user-statistics-data",
|
||||
),
|
||||
path(
|
||||
"charts/monthly-trend/",
|
||||
login_req(views.monthly_trend_chart),
|
||||
name="chart-monthly-trend",
|
||||
),
|
||||
path("recent-activity/", login_req(views.recent_activity), name="recent-activity"),
|
||||
path(
|
||||
"project-timeline-widget/",
|
||||
login_req(views.project_timeline_widget),
|
||||
name="project-timeline-widget",
|
||||
),
|
||||
path(
|
||||
"notifications/unread-count/",
|
||||
login_req(list_views.notifications_unread_count),
|
||||
name="notif-unread-count",
|
||||
),
|
||||
path(
|
||||
"notifications/dropdown/",
|
||||
login_req(page_views.notifications_dropdown),
|
||||
name="notifications-dropdown",
|
||||
),
|
||||
path(
|
||||
"notifications/",
|
||||
login_req(page_views.notifications_list),
|
||||
name="notifications-list",
|
||||
),
|
||||
path(
|
||||
"notifications/mark-all-read/",
|
||||
login_req(page_views.notification_mark_all_read),
|
||||
name="notifications-mark-all-read",
|
||||
),
|
||||
path(
|
||||
"notifications/<int:notification_id>/mark-read/",
|
||||
login_req(page_views.notification_mark_read),
|
||||
name="notification-mark-read",
|
||||
),
|
||||
path("projects/", login_req(list_views.projects_list), name="projects-list"),
|
||||
path(
|
||||
"project-timeline/",
|
||||
login_req(page_views.project_timeline),
|
||||
name="project-timeline",
|
||||
),
|
||||
path("project-board/", login_req(board_views.project_board), name="project-board"),
|
||||
path(
|
||||
"project-board/<int:project_id>/",
|
||||
login_req(board_views.project_board),
|
||||
name="project-board-project",
|
||||
),
|
||||
path(
|
||||
"board/<int:project_id>/data/",
|
||||
login_req(board_views.board_data),
|
||||
name="board-data",
|
||||
),
|
||||
path(
|
||||
"tickets/bulk/status/",
|
||||
login_req(page_views.tickets_bulk_status),
|
||||
name="tickets-bulk-status",
|
||||
),
|
||||
path(
|
||||
"tickets/bulk/priority/",
|
||||
login_req(page_views.tickets_bulk_priority),
|
||||
name="tickets-bulk-priority",
|
||||
),
|
||||
path(
|
||||
"tickets/bulk/assign/",
|
||||
login_req(page_views.tickets_bulk_assign),
|
||||
name="tickets-bulk-assign",
|
||||
),
|
||||
path(
|
||||
"tickets/bulk/epic/",
|
||||
login_req(page_views.tickets_bulk_epic),
|
||||
name="tickets-bulk-epic",
|
||||
),
|
||||
path(
|
||||
"tickets/<int:ticket_id>/",
|
||||
login_req(page_views.ticket_detail),
|
||||
name="ticket-detail",
|
||||
),
|
||||
path(
|
||||
"tickets/<int:ticket_id>/description/",
|
||||
login_req(page_views.ticket_update_description),
|
||||
name="ticket-update-description",
|
||||
),
|
||||
path(
|
||||
"tickets/<int:ticket_id>/field/",
|
||||
login_req(page_views.ticket_update_field),
|
||||
name="ticket-update-field",
|
||||
),
|
||||
path(
|
||||
"tickets/<int:ticket_id>/comments/add/",
|
||||
login_req(page_views.ticket_comment_add),
|
||||
name="ticket-comment-add",
|
||||
),
|
||||
path(
|
||||
"tickets/export/", login_req(page_views.tickets_export), name="tickets-export"
|
||||
),
|
||||
path(
|
||||
"tickets/import/", login_req(page_views.tickets_import), name="tickets-import"
|
||||
),
|
||||
path("tickets/", login_req(list_views.tickets_list), name="tickets-list"),
|
||||
path(
|
||||
"ticket-timeline/",
|
||||
login_req(page_views.ticket_timeline),
|
||||
name="ticket-timeline",
|
||||
),
|
||||
path(
|
||||
"ticket-timeline/<int:project_id>/",
|
||||
login_req(page_views.ticket_timeline),
|
||||
name="ticket-timeline-project",
|
||||
),
|
||||
path("epics/", login_req(page_views.epics_overview), name="epics-overview"),
|
||||
path(
|
||||
"epics/<int:project_id>/",
|
||||
login_req(page_views.epics_overview),
|
||||
name="epics-overview-project",
|
||||
),
|
||||
path("leaderboard/", login_req(page_views.leaderboard), name="leaderboard"),
|
||||
path(
|
||||
"contributions/", login_req(page_views.user_contributions), name="contributions"
|
||||
),
|
||||
path(
|
||||
"ticket-priorities/",
|
||||
login_req(page_views.ticket_priorities),
|
||||
name="ticket-priorities",
|
||||
),
|
||||
path(
|
||||
"ticket-priorities/add/",
|
||||
login_req(page_views.ticket_priority_add),
|
||||
name="ticket-priority-add",
|
||||
),
|
||||
path(
|
||||
"ticket-priorities/<int:priority_id>/edit/",
|
||||
login_req(page_views.ticket_priority_edit),
|
||||
name="ticket-priority-edit",
|
||||
),
|
||||
path(
|
||||
"ticket-priorities/<int:priority_id>/delete/",
|
||||
login_req(page_views.ticket_priority_delete),
|
||||
name="ticket-priority-delete",
|
||||
),
|
||||
path("users/", login_req(page_views.users_list), name="users-list"),
|
||||
path("settings/ui/", login_req(page_views.ui_settings), name="ui-settings"),
|
||||
path(
|
||||
"external/",
|
||||
login_req(page_views.external_access_list),
|
||||
name="external-access-list",
|
||||
),
|
||||
path(
|
||||
"external/create/<int:project_id>/",
|
||||
login_req(page_views.external_access_create),
|
||||
name="external-access-create",
|
||||
),
|
||||
path(
|
||||
"external/<int:access_id>/regenerate/",
|
||||
login_req(page_views.external_access_regenerate),
|
||||
name="external-access-regenerate",
|
||||
),
|
||||
path(
|
||||
"external/<int:access_id>/toggle/",
|
||||
login_req(page_views.external_access_toggle),
|
||||
name="external-access-toggle",
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
# JWT tokens
|
||||
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"),
|
||||
# App URLs
|
||||
path("", include("src.epics.urls")),
|
||||
path("", include("src.notes.urls")),
|
||||
path("", include("src.projects.urls")),
|
||||
path("", include("src.accounts.urls")),
|
||||
path("", include("src.external.urls")),
|
||||
path("", include("src.notifications.urls")),
|
||||
path("", include("src.tickets.urls")),
|
||||
path("api/", include(api_patterns)),
|
||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path(
|
||||
"api/docs/",
|
||||
SpectacularSwaggerView.as_view(url_name="schema"),
|
||||
name="swagger-ui",
|
||||
),
|
||||
path("dashboard/", include(dashboard_patterns)),
|
||||
path(
|
||||
"login/",
|
||||
auth_views.LoginView.as_view(
|
||||
template_name="auth/login.html",
|
||||
redirect_authenticated_user=True,
|
||||
),
|
||||
name="login",
|
||||
),
|
||||
path("logout/", auth_views.LogoutView.as_view(next_page="login"), name="logout"),
|
||||
path(
|
||||
"external/<str:token>/",
|
||||
page_views.external_access_view,
|
||||
name="external-dashboard",
|
||||
),
|
||||
path(
|
||||
"external/<str:token>/logout/",
|
||||
page_views.external_access_logout,
|
||||
name="external-logout",
|
||||
),
|
||||
]
|
||||
|
||||
# Don't show schema in production
|
||||
if settings.DEBUG:
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="config API",
|
||||
default_version="v1",
|
||||
),
|
||||
public=True,
|
||||
)
|
||||
urlpatterns += [
|
||||
path("docs/", schema_view.with_ui("swagger", cache_timeout=0)),
|
||||
path("schema/", schema_view.with_ui("redoc", cache_timeout=0)),
|
||||
]
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# django-browser-reload for tailwind live reload (dev only)
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
path("__reload__/", include("django_browser_reload.urls")),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
re_path(
|
||||
r"^media/(?P<path>.*)$",
|
||||
serve,
|
||||
{"document_root": settings.MEDIA_ROOT},
|
||||
),
|
||||
]
|
||||
Reference in new issue
Block a user