new supervisor step2
This commit is contained in:
@ -53,6 +53,7 @@ RUN pip install gunicorn
|
||||
|
||||
# xlsxwriterを追加
|
||||
RUN pip install -r requirements.txt \
|
||||
&& pip install django-cors-headers \
|
||||
&& pip install xlsxwriter gunicorn
|
||||
|
||||
COPY . /app
|
||||
|
||||
@ -53,10 +53,14 @@ INSTALLED_APPS = [
|
||||
'leaflet',
|
||||
'leaflet_admin_list',
|
||||
'rog.apps.RogConfig',
|
||||
'corsheaders', # added
|
||||
'django_filters'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware', # できるだけ上部に
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@ -68,6 +72,43 @@ MIDDLEWARE = [
|
||||
|
||||
ROOT_URLCONF = 'config.urls'
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = True # 開発環境のみ
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
CORS_ALLOWED_METHODS = [
|
||||
'GET',
|
||||
'POST',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'DELETE',
|
||||
'OPTIONS'
|
||||
]
|
||||
CORS_ALLOWED_HEADERS = [
|
||||
'accept',
|
||||
'accept-encoding',
|
||||
'authorization',
|
||||
'content-type',
|
||||
'dnt',
|
||||
'origin',
|
||||
'user-agent',
|
||||
'x-csrftoken',
|
||||
'x-requested-with',
|
||||
]
|
||||
|
||||
# 本番環境では以下のように制限する
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"https://rogaining.sumasen.net",
|
||||
"http://rogaining.sumasen.net",
|
||||
]
|
||||
|
||||
# CSRFの設定
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"http://rogaining.sumasen.net",
|
||||
"https://rogaining.sumasen.net",
|
||||
]
|
||||
|
||||
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
@ -231,11 +272,15 @@ LOGGING = {
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'loggers': {
|
||||
#'django': {
|
||||
# 'handlers': ['console'],
|
||||
# 'level': 'INFO',
|
||||
# 'propagate': False,
|
||||
#},
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.request': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'rog': {
|
||||
#'handlers': ['file','console'],
|
||||
'handlers': ['console'],
|
||||
|
||||
@ -18,6 +18,21 @@ from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
|
||||
# debug_urlsビューをrogアプリケーションのviewsからインポート
|
||||
from rog import views as rog_views
|
||||
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ['rogaining.sumasen.net', 'localhost', '127.0.0.1']
|
||||
|
||||
# CORSの設定
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://rogaining.sumasen.net",
|
||||
"http://localhost",
|
||||
"http://127.0.0.1",
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('auth/', include('knox.urls')),
|
||||
@ -27,3 +42,8 @@ urlpatterns = [
|
||||
admin.site.site_header = "ROGANING"
|
||||
admin.site.site_title = "Roganing Admin Portal"
|
||||
admin.site.index_title = "Welcome to Roganing Portal"
|
||||
|
||||
# 開発環境での静的ファイル配信
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
@ -30,40 +30,37 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
restart: "on-failure"
|
||||
# depends_on:
|
||||
# - postgres-db
|
||||
networks:
|
||||
- rog-api
|
||||
#entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""]
|
||||
#command: python3 manage.py runserver 0.0.0.0:8100
|
||||
|
||||
supervisor-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.supervisor
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./supervisor/html
|
||||
target: /usr/share/nginx/html
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./supervisor/nginx/default.conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: static_volume
|
||||
target: /app/static
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: nginx_logs
|
||||
target: /var/log/nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- api
|
||||
networks:
|
||||
- rog-api
|
||||
restart: always
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.supervisor
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./supervisor/html
|
||||
target: /usr/share/nginx/html/supervisor
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./supervisor/nginx/default.conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: static_volume
|
||||
target: /app/static
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: nginx_logs
|
||||
target: /var/log/nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- api
|
||||
networks:
|
||||
- rog-api
|
||||
restart: always
|
||||
|
||||
|
||||
|
||||
networks:
|
||||
|
||||
81
docker-compose.yaml.ssl
Normal file
81
docker-compose.yaml.ssl
Normal file
@ -0,0 +1,81 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
# postgres-db:
|
||||
# image: kartoza/postgis:12.0
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
# volumes:
|
||||
# - postgres_data:/var/lib/postgresql
|
||||
# - ./custom-postgresql.conf:/etc/postgresql/12/main/postgresql.conf
|
||||
# environment:
|
||||
# - POSTGRES_USER=${POSTGRES_USER}
|
||||
# - POSTGRES_PASS=${POSTGRES_PASS}
|
||||
# - POSTGRES_DBNAME=${POSTGRES_DBNAME}
|
||||
# - POSTGRES_MAX_CONNECTIONS=600
|
||||
|
||||
# restart: "on-failure"
|
||||
# networks:
|
||||
# - rog-api
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.gdal
|
||||
command: python3 manage.py runserver 0.0.0.0:8100
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- 8100:8100
|
||||
env_file:
|
||||
- .env
|
||||
restart: "on-failure"
|
||||
# depends_on:
|
||||
# - postgres-db
|
||||
networks:
|
||||
- rog-api
|
||||
#entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""]
|
||||
#command: python3 manage.py runserver 0.0.0.0:8100
|
||||
|
||||
supervisor-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.supervisor
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /etc/letsencrypt
|
||||
target: /etc/nginx/ssl
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./supervisor/html
|
||||
target: /usr/share/nginx/html
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: ./supervisor/nginx/default.conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: static_volume
|
||||
target: /app/static
|
||||
read_only: true
|
||||
- type: volume
|
||||
source: nginx_logs
|
||||
target: /var/log/nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- api
|
||||
networks:
|
||||
- rog-api
|
||||
restart: always
|
||||
|
||||
|
||||
networks:
|
||||
rog-api:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
geoserver-data:
|
||||
static_volume:
|
||||
nginx_logs:
|
||||
@ -65,3 +65,4 @@ django-extra-fields==3.0.2
|
||||
django-phonenumber-field==6.1.0
|
||||
django-rest-knox==4.2.0
|
||||
dj-database-url==2.0.0
|
||||
django-cors-headers==4.3.0
|
||||
|
||||
19
rog/urls.py
19
rog/urls.py
@ -1,7 +1,8 @@
|
||||
from sys import prefix
|
||||
from rest_framework import urlpatterns
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel
|
||||
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls
|
||||
|
||||
|
||||
from django.urls import path, include
|
||||
from knox import views as knox_views
|
||||
@ -51,7 +52,7 @@ router.register(r'newevent2', views.NewEvent2ViewSet)
|
||||
# GET /api/teams/<team_id>/members-with-user/: 特定のチームの全メンバーとそのユーザー情報を取得
|
||||
|
||||
|
||||
|
||||
app_name = 'rog' # 名前空間を追加
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
@ -113,11 +114,13 @@ urlpatterns += [
|
||||
|
||||
|
||||
# for Supervisor Web app
|
||||
path('api/events/', get_events, name='get_events'),
|
||||
path('api/zekken_numbers/<str:event_code>/', get_zekken_numbers, name='get_zekken_numbers'),
|
||||
path('api/team_info/<int:zekken_number>/', get_team_info, name='get_team_info'),
|
||||
path('api/checkins/<int:zekken_number>/', get_checkins, name='get_checkins'),
|
||||
path('api/update_checkins/', update_checkins, name='update_checkins'),
|
||||
path('api/export_excel/<int:zekken_number>/', export_excel, name='export_excel'),
|
||||
path('events/', views.get_events, name='get_events'),
|
||||
path('debug/urls/', views.debug_urls, name='debug_urls'),
|
||||
path('zekken_numbers/<str:event_code>/', views.get_zekken_numbers, name='get_zekken_numbers'),
|
||||
path('team_info/<int:zekken_number>/', views.get_team_info, name='get_team_info'),
|
||||
path('checkins/<int:zekken_number>/<str:event_code>/', views.get_checkins, name='get_checkins'),
|
||||
path('update_checkins/', views.update_checkins, name='update_checkins'),
|
||||
path('export_excel/<int:zekken_number>/<str:event_code>/', views.export_excel, name='export_excel'),
|
||||
# for Supervisor Web app
|
||||
path('test/', views.test_api, name='test_api'),
|
||||
]
|
||||
|
||||
151
rog/views.py
151
rog/views.py
@ -30,7 +30,7 @@ from .permissions import IsTeamOwner,IsTeamOwnerOrMember
|
||||
|
||||
from curses.ascii import NUL
|
||||
from django.core.serializers import serialize
|
||||
from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser,EntryMember
|
||||
from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser, EntryMember, GpsCheckin
|
||||
from rest_framework import viewsets
|
||||
from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer, PasswordResetRequestSerializer, PasswordResetConfirmSerializer
|
||||
from knox.models import AuthToken
|
||||
@ -52,7 +52,6 @@ from django.db.models import Q
|
||||
|
||||
from rest_framework import permissions
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.parsers import JSONParser, MultiPartParser
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@ -63,7 +62,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime,timedelta
|
||||
|
||||
from django.contrib.gis.measure import D
|
||||
from django.contrib.gis.geos import Point
|
||||
@ -88,6 +87,8 @@ from django.core.exceptions import ValidationError
|
||||
import xlsxwriter
|
||||
from io import BytesIO
|
||||
|
||||
from django.urls import get_resolver
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@api_view(['PATCH'])
|
||||
@ -2316,14 +2317,38 @@ class UserLastGoalTimeView(APIView):
|
||||
# ----- for Supervisor -----
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_events(request):
|
||||
events = NewEvent2.objects.filter(public=True)
|
||||
return Response([{
|
||||
'code': event.event_name,
|
||||
'name': event.event_name
|
||||
} for event in events])
|
||||
def debug_urls(request):
|
||||
"""デバッグ用:利用可能なURLパターンを表示"""
|
||||
resolver = get_resolver()
|
||||
urls = []
|
||||
for pattern in resolver.url_patterns:
|
||||
try:
|
||||
urls.append(str(pattern.pattern))
|
||||
except:
|
||||
urls.append(str(pattern))
|
||||
return JsonResponse({'urls': urls})
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_events(request):
|
||||
logger.debug(f"get_events was called. Path: {request.path}")
|
||||
try:
|
||||
events = NewEvent2.objects.filter(public=True)
|
||||
logger.debug(f"Found {events.count()} events") # デバッグ用ログ
|
||||
return Response([{
|
||||
'code': event.event_name,
|
||||
'name': event.event_name,
|
||||
'start_datetime': event.start_datetime,
|
||||
'end_datetime': event.end_datetime,
|
||||
} for event in events])
|
||||
logger.debug(f"Returning data: {data}") # デバッグ用ログ
|
||||
return JsonResponse(data, safe=False)
|
||||
except Exception as e:
|
||||
print(f"Error in get_events: {str(e)}") # デバッグ用
|
||||
return Response(
|
||||
{"error": "Failed to retrieve events"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def get_zekken_numbers(request, event_code):
|
||||
entries = Entry.objects.filter(
|
||||
event__event_name=event_code,
|
||||
@ -2333,31 +2358,101 @@ def get_zekken_numbers(request, event_code):
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_team_info(request, zekken_number):
|
||||
entry = Entry.objects.select_related('team').get(zekken_number=zekken_number)
|
||||
entry = Entry.objects.select_related('team','event').get(zekken_number=zekken_number)
|
||||
members = Member.objects.filter(team=entry.team)
|
||||
|
||||
# チームのゴール時間を取得
|
||||
goal_record = GoalImages.objects.filter(
|
||||
team_name=entry.team.team_name,
|
||||
event_code=entry.event.event_name
|
||||
).order_by('-goaltime').first()
|
||||
|
||||
return Response({
|
||||
'team_name': entry.team.team_name,
|
||||
'members': ', '.join([f"{m.lastname} {m.firstname}" for m in members]),
|
||||
'start_time': entry.start_time.strftime('%Y-%m-%d %H:%M:%S') if entry.start_time else None,
|
||||
'goal_time': entry.goal_time.strftime('%Y-%m-%d %H:%M:%S') if entry.goal_time else None,
|
||||
'late_points': entry.late_point or 0
|
||||
'event_code': entry.event.event_name,
|
||||
'start_datetime': entry.event.start_datetime,
|
||||
'end_datetime': goal_record.goaltime if goal_record else None
|
||||
})
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
logger.info(f"Attempting to create new member for team: {self.kwargs['team_id']}")
|
||||
logger.debug(f"Received data: {request.data}")
|
||||
|
||||
team = Team.objects.get(id=self.kwargs['team_id'])
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_checkins(request, zekken_number):
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=zekken_number
|
||||
).order_by('path_order')
|
||||
return Response([{
|
||||
'id': c.id,
|
||||
'path_order': c.path_order,
|
||||
'cp_number': c.cp_number,
|
||||
'create_at': c.create_at,
|
||||
'validate_location': c.validate_location,
|
||||
'points': c.points,
|
||||
'buy_flag': c.buy_flag
|
||||
} for c in checkins])
|
||||
def get_checkins(request, *args, **kwargs):
|
||||
#def get_checkins(request, zekken_number, event_code):
|
||||
try:
|
||||
# イベントコードをクエリパラメータから取得
|
||||
|
||||
zekken_number = kwargs['zekken_number']
|
||||
if not zekken_number:
|
||||
logger.debug(f"=== Zekken_number is required.")
|
||||
return Response({"error": "zekken_number is required"}, status=400)
|
||||
|
||||
event_code = kwargs['event_code']
|
||||
if not event_code:
|
||||
logger.debug(f"=== event_code is required.")
|
||||
return Response({"error": "event_code is required"}, status=400)
|
||||
|
||||
# チェックインデータの取得とCP情報の結合
|
||||
checkins = GpsCheckin.objects.filter(
|
||||
zekken_number=zekken_number,
|
||||
event_code=event_code
|
||||
).order_by('path_order')
|
||||
|
||||
# すべてのフィールドを確実に取得できるようにデバッグログを追加
|
||||
logger.debug(f"Found {checkins.count()} checkins for zekken_number {zekken_number} and event_code {event_code}")
|
||||
|
||||
data = []
|
||||
for c in checkins:
|
||||
location = Location.objects.filter(cp=c.cp_number,group=event_code).first()
|
||||
if location:
|
||||
formatted_time = None
|
||||
if c.create_at:
|
||||
try:
|
||||
# create_atが文字列の場合はdatetimeに変換
|
||||
if isinstance(c.create_at, str):
|
||||
c.create_at = datetime.strptime(c.create_at, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# JST(+9時間)に変換して時刻フォーマット
|
||||
jst_time = c.create_at + timedelta(hours=9)
|
||||
formatted_time = jst_time.strftime('%H:%M:%S')
|
||||
|
||||
except (ValueError, TypeError, AttributeError) as e:
|
||||
logger.error(f"Error formatting create_at for checkin {c.id}: {str(e)}")
|
||||
logger.error(f"Raw create_at value: {c.create_at}")
|
||||
formatted_time = None
|
||||
|
||||
data.append({
|
||||
'id': c.id,
|
||||
'path_order': c.path_order,
|
||||
'cp_number': c.cp_number,
|
||||
'sub_loc_id': location.sub_loc_id if location else f"#{c.cp_number}",
|
||||
'location_name': location.location_name if location else None,
|
||||
'create_at': formatted_time, #(c.create_at + timedelta(hours=9)).strftime('%H:%M:%S') if c.create_at else None,
|
||||
'validate_location': c.validate_location,
|
||||
'points': c.points or 0,
|
||||
'buy_flag': c.buy_flag,
|
||||
'photos': location.photos if location else None,
|
||||
'image_address': c.image_address,
|
||||
'receipt_address': c.image_receipt,
|
||||
'location_name': location.location_name if location else None,
|
||||
'checkin_point': location.checkin_point if location else None,
|
||||
'buy_point': location.buy_point
|
||||
})
|
||||
|
||||
logger.debug(f"data={data}")
|
||||
return Response(data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_checkins: {str(e)}", exc_info=True)
|
||||
return Response(
|
||||
{"error": f"Failed to retrieve checkins: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
@api_view(['POST'])
|
||||
def update_checkins(request):
|
||||
@ -2418,4 +2513,8 @@ def export_excel(request, zekken_number):
|
||||
|
||||
# ----- for Supervisor -----
|
||||
|
||||
@api_view(['GET'])
|
||||
def test_api(request):
|
||||
logger.debug("Test API endpoint called")
|
||||
return JsonResponse({"status": "API is working"})
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<h1 class="text-2xl font-bold mb-6">スーパーバイザーパネル</h1>
|
||||
|
||||
<!-- 選択フォーム -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">イベントコード</label>
|
||||
<select id="eventCode" class="w-full border border-gray-300 rounded-md px-3 py-2">
|
||||
@ -26,10 +26,6 @@
|
||||
<option value="">ゼッケン番号を選択してください</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- チーム情報サマリー -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">チーム名</div>
|
||||
<div id="teamName" class="font-semibold"></div>
|
||||
@ -38,6 +34,10 @@
|
||||
<div class="text-sm text-gray-500">メンバー</div>
|
||||
<div id="members" class="font-semibold"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- チーム情報サマリー -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">スタート時刻</div>
|
||||
<div id="startTime" class="font-semibold"></div>
|
||||
@ -46,15 +46,23 @@
|
||||
<div class="text-sm text-gray-500">ゴール時刻</div>
|
||||
<div id="goalTime" class="font-semibold"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">ゴール時計</div>
|
||||
<div id="goalTime" class="font-semibold"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">判定</div>
|
||||
<div id="validate" class="font-semibold text-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">チェックインポイント合計</div>
|
||||
<div class="text-sm text-gray-500">CP合計</div>
|
||||
<div id="totalPoints" class="font-semibold"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">買い物ポイント合計</div>
|
||||
<div class="text-sm text-gray-500">買物合計</div>
|
||||
<div id="buyPoints" class="font-semibold"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
@ -62,7 +70,7 @@
|
||||
<div id="latePoints" class="font-semibold text-red-600"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="text-sm text-gray-500">総合計ポイント</div>
|
||||
<div class="text-sm text-gray-500">総合計</div>
|
||||
<div id="finalPoints" class="font-semibold text-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -72,11 +80,14 @@
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">順序</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">CP番号</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">チェックイン時刻</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">検証</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ポイント</th>
|
||||
<th class="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase">走行順</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">規定写真</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">撮影写真</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">CP名称</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">通過時刻</th>
|
||||
<th class="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase">通過審査</th>
|
||||
<th class="px-2 py-3 text-left text-xs font-medium text-gray-500 uppercase">買物審査</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">獲得点数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="checkinList" class="bg-white divide-y divide-gray-200">
|
||||
@ -87,6 +98,9 @@
|
||||
|
||||
<!-- アクションボタン -->
|
||||
<div class="mt-6 flex justify-end space-x-4">
|
||||
<button onclick="showAddCPModal()" class="px-4 py-2 bg-blue-500 text-white rounded">
|
||||
新規CP追加
|
||||
</button>
|
||||
<button id="saveButton" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
||||
保存
|
||||
</button>
|
||||
@ -98,6 +112,10 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// APIのベースURLを環境に応じて設定
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Sortable初期化
|
||||
const checkinList = document.getElementById('checkinList');
|
||||
@ -108,16 +126,25 @@
|
||||
}
|
||||
});
|
||||
|
||||
// イベントコードのドロップダウン要素を取得
|
||||
const eventCodeSelect = document.getElementById('eventCode');
|
||||
const zekkenNumberSelect = document.getElementById('zekkenNumber');
|
||||
|
||||
// ゼッケン番号変更時の処理
|
||||
zekkenNumberSelect.addEventListener('change', function(e) {
|
||||
const eventCode = eventCodeSelect.value;
|
||||
if (!eventCode) {
|
||||
alert('イベントコードを選択してください');
|
||||
return;
|
||||
}
|
||||
loadTeamData(e.target.value, eventCode);
|
||||
});
|
||||
|
||||
// イベントコード変更時の処理
|
||||
document.getElementById('eventCode').addEventListener('change', function(e) {
|
||||
loadZekkenNumbers(e.target.value);
|
||||
});
|
||||
|
||||
// ゼッケン番号変更時の処理
|
||||
document.getElementById('zekkenNumber').addEventListener('change', function(e) {
|
||||
loadTeamData(e.target.value);
|
||||
});
|
||||
|
||||
// チェックボックス変更時の処理
|
||||
checkinList.addEventListener('change', function(e) {
|
||||
if (e.target.type === 'checkbox') {
|
||||
@ -131,28 +158,46 @@
|
||||
// Excel出力ボタンの処理
|
||||
document.getElementById('exportButton').addEventListener('click', exportExcel);
|
||||
|
||||
console.log('Page loaded, attempting to load events...');
|
||||
// 初期データ読み込み
|
||||
loadEventCodes();
|
||||
});
|
||||
|
||||
function loadEventCodes() {
|
||||
// APIからイベントコードを取得して選択肢を設定
|
||||
fetch('/api/events')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const select = document.getElementById('eventCode');
|
||||
data.forEach(event => {
|
||||
const option = document.createElement('option');
|
||||
option.value = event.code;
|
||||
option.textContent = event.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
async function loadEventCodes() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/events/`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType || !contentType.includes("application/json")) {
|
||||
throw new TypeError("Response is not JSON");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const select = document.getElementById('eventCode');
|
||||
// 既存のオプションをクリア
|
||||
select.innerHTML = '<option value="">イベントを選択してください</option>';
|
||||
|
||||
data.forEach(event => {
|
||||
const option = document.createElement('option');
|
||||
option.value = event.code;
|
||||
option.textContent = event.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading events:', error);
|
||||
// ユーザーにエラーを表示
|
||||
const select = document.getElementById('eventCode');
|
||||
select.innerHTML = '<option value="">エラー: イベントの読み込みに失敗しました</option>';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function loadZekkenNumbers(eventCode) {
|
||||
// APIからゼッケン番号を取得して選択肢を設定
|
||||
fetch(`/api/zekken-numbers/${eventCode}`)
|
||||
fetch(`${API_BASE_URL}/zekken_numbers/${eventCode}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const select = document.getElementById('zekkenNumber');
|
||||
@ -166,11 +211,129 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadTeamData(zekkenNumber) {
|
||||
|
||||
async function loadTeamData(zekkenNumber,event_code) {
|
||||
try {
|
||||
const [teamResponse, checkinsResponse] = await Promise.all([
|
||||
fetch(`${API_BASE_URL}/team_info/${zekkenNumber}/`),
|
||||
fetch(`${API_BASE_URL}/checkins/${zekkenNumber}/${event_code}/`),
|
||||
]);
|
||||
|
||||
// 各レスポンスのステータスを個別にチェック
|
||||
if (!teamResponse.ok)
|
||||
throw new Error(`Team info fetch failed with status ${teamResponse.status}`);
|
||||
if (!checkinsResponse.ok)
|
||||
throw new Error(`Checkins fetch failed with status ${checkinsResponse.status}`);
|
||||
|
||||
|
||||
const teamData = await teamResponse.json();
|
||||
const checkinsData = await checkinsResponse.json();
|
||||
|
||||
// イベントコードに対応するイベントを検索
|
||||
//const event = eventData.find(e => e.code === document.getElementById('eventCode').value);
|
||||
|
||||
document.getElementById('teamName').textContent = teamData.team_name || '-';
|
||||
document.getElementById('members').textContent = teamData.members || '-';
|
||||
document.getElementById('startTime').textContent =
|
||||
teamData.start_datetime ? new Date(teamData.start_datetime).toLocaleString() : '-';
|
||||
document.getElementById('goalTime').textContent =
|
||||
teamData.end_datetime ? new Date(teamData.end_datetime).toLocaleString() : '-';
|
||||
//'(未ゴール)';
|
||||
|
||||
// チェックインリストの更新
|
||||
const tbody = document.getElementById('checkinList');
|
||||
tbody.innerHTML = ''; // 既存のデータをクリア
|
||||
|
||||
let totalPoints = 0;
|
||||
let buyPoints = 0;
|
||||
|
||||
checkinsData.forEach((checkin, index) => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.dataset.id = checkin.id;
|
||||
tr.dataset.cpNumber = checkin.cp_number;
|
||||
|
||||
const bgColor = checkin.buy_point > 0 ? 'bg-blue-100' : 'bg-red-100';
|
||||
|
||||
tr.innerHTML = `
|
||||
<td class="px-6 py-4">${checkin.path_order}</td>
|
||||
<td class="px-6 py-4">
|
||||
${location.photos ?
|
||||
`<img src="${checkin.photos}" class="h-20 w-20 object-cover rounded">` : ''}
|
||||
<div class="text-sm">${checkin.photos}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
${checkin.image_address ?
|
||||
`<img src="${checkin.image_address}" class="h-20 w-20 object-cover rounded">` : ''}
|
||||
${checkin.receipt_address && checkin.buy_flag ?
|
||||
`<img src="${checkin.receipt_address}" class="h-20 w-20 object-cover rounded ml-2">` : ''}
|
||||
<div class="text-sm">${checkin.image_address}</div>
|
||||
<div class="text-sm">${checkin.receipt_address}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 ${bgColor}">
|
||||
<div class="font-bold">${checkin.sub_loc_id}</div>
|
||||
<div class="text-sm">${checkin.location_name}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">${checkin.create_at || '不明'}</td>
|
||||
<td class="px-6 py-4">
|
||||
<input type="checkbox"
|
||||
${checkin.validate_location ? 'checked' : ''}
|
||||
class="h-4 w-4 text-blue-600 rounded validate-checkbox"
|
||||
onchange="updatePoints(this)">
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
${checkin.buy_point > 0 ? `
|
||||
<input type="checkbox"
|
||||
${checkin.buy_flag ? 'checked' : ''}
|
||||
class="h-4 w-4 text-green-600 rounded buy-checkbox"
|
||||
onchange="updateBuyPoints(this)">
|
||||
` : ''}
|
||||
</td>
|
||||
<td class="px-6 py-4 point-value">${checkin.points}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
|
||||
if (checkin.points) {
|
||||
if (checkin.buy_flag) {
|
||||
buyPoints += checkin.points;
|
||||
} else {
|
||||
totalPoints += checkin.points;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 合計ポイントの更新
|
||||
document.getElementById('totalPoints').textContent = totalPoints;
|
||||
document.getElementById('buyPoints').textContent = buyPoints;
|
||||
document.getElementById('latePoints').textContent = teamData.late_points || 0;
|
||||
document.getElementById('finalPoints').textContent =
|
||||
totalPoints + buyPoints - (teamData.late_points || 0);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading team info:', error);
|
||||
|
||||
// エラーメッセージをユーザーに表示
|
||||
alert(`データの読み込みに失敗しました: ${error.message}`);
|
||||
|
||||
// UIをクリア
|
||||
document.getElementById('teamName').textContent = '-';
|
||||
document.getElementById('members').textContent = '-';
|
||||
document.getElementById('startTime').textContent = '-';
|
||||
document.getElementById('goalTime').textContent = '-';
|
||||
document.getElementById('checkinList').innerHTML = '';
|
||||
document.getElementById('totalPoints').textContent = '0';
|
||||
document.getElementById('buyPoints').textContent = '0';
|
||||
document.getElementById('latePoints').textContent = '0';
|
||||
document.getElementById('finalPoints').textContent = '0';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function loadTeamData_old(zekkenNumber) {
|
||||
// チーム情報とチェックインデータを取得
|
||||
Promise.all([
|
||||
fetch(`/api/team-info/${zekkenNumber}`),
|
||||
fetch(`/api/checkins/${zekkenNumber}`)
|
||||
fetch(`${API_BASE_URL}/team_info/${zekkenNumber}`),
|
||||
fetch(`${API_BASE_URL}/checkins/${zekkenNumber}`)
|
||||
]).then(responses => Promise.all(responses.map(r => r.json())))
|
||||
.then(([teamInfo, checkins]) => {
|
||||
updateTeamInfo(teamInfo);
|
||||
@ -194,20 +357,137 @@
|
||||
checkins.forEach((checkin, index) => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.dataset.id = checkin.id;
|
||||
tr.dataset.cpNumber = checkin.cp_number;
|
||||
|
||||
const bgColor = checkin.buy_point > 0 ? 'bg-blue-100' : 'bg-red-100';
|
||||
|
||||
tr.innerHTML = `
|
||||
<td class="px-6 py-4">${index + 1}</td>
|
||||
<td class="px-6 py-4">${checkin.cp_number}</td>
|
||||
<td class="px-6 py-4">${formatDateTime(checkin.create_at)}</td>
|
||||
<td class="px-6 py-4">
|
||||
<input type="checkbox" ${checkin.validate_location ? 'checked' : ''}
|
||||
class="h-4 w-4 text-blue-600 rounded validate-checkbox">
|
||||
<td class="px-6 py-4">${checkin.path_order}</td>
|
||||
<td class="px-6 py-4 ${bgColor}">
|
||||
<div class="font-bold">${checkin.sub_loc_id}</div>
|
||||
<div class="text-sm">${checkin.location_name}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">${checkin.points}</td>
|
||||
<td class="px-6 py-4">
|
||||
${checkin.image_address ?
|
||||
`<img src="${checkin.image_address}" class="h-20 w-20 object-cover rounded">` : ''}
|
||||
${checkin.receipt_address && checkin.buy_flag ?
|
||||
`<img src="${checkin.receipt_address}" class="h-20 w-20 object-cover rounded ml-2">` : ''}
|
||||
</td>
|
||||
<td class="px-6 py-4">${checkin.create_at || '不明'}</td>
|
||||
<td class="px-6 py-4">
|
||||
<input type="checkbox"
|
||||
${checkin.validate_location ? 'checked' : ''}
|
||||
class="h-4 w-4 text-blue-600 rounded validate-checkbox"
|
||||
onchange="updatePoints(this)">
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
${checkin.buy_point > 0 ? `
|
||||
<input type="checkbox"
|
||||
${checkin.buy_flag ? 'checked' : ''}
|
||||
class="h-4 w-4 text-green-600 rounded buy-checkbox"
|
||||
onchange="updateBuyPoints(this)">
|
||||
` : ''}
|
||||
</td>
|
||||
<td class="px-6 py-4 point-value">${checkin.points}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
calculateTotalPoints();
|
||||
}
|
||||
|
||||
// ポイント更新関数
|
||||
function updatePoints(checkbox) {
|
||||
const tr = checkbox.closest('tr');
|
||||
const pointCell = tr.querySelector('.point-value');
|
||||
const cpNumber = tr.dataset.cpNumber;
|
||||
|
||||
fetch(`${API_BASE_URL}/location/${cpNumber}/`)
|
||||
.then(response => response.json())
|
||||
.then(location => {
|
||||
const points = checkbox.checked ? location.checkin_point : 0;
|
||||
pointCell.textContent = points;
|
||||
calculateTotalPoints();
|
||||
});
|
||||
}
|
||||
|
||||
// 買い物ポイント更新関数
|
||||
function updateBuyPoints(checkbox) {
|
||||
const tr = checkbox.closest('tr');
|
||||
const pointCell = tr.querySelector('.point-value');
|
||||
const cpNumber = tr.dataset.cpNumber;
|
||||
|
||||
fetch(`${API_BASE_URL}/location/${cpNumber}/`)
|
||||
.then(response => response.json())
|
||||
.then(location => {
|
||||
const points = checkbox.checked ? location.buy_point : 0;
|
||||
pointCell.textContent = points;
|
||||
calculateTotalPoints();
|
||||
});
|
||||
}
|
||||
|
||||
// 新規CP追加用のモーダル
|
||||
function showAddCPModal() {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center';
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white p-6 rounded-lg w-96">
|
||||
<h3 class="text-lg font-bold mb-4">新規CPを追加</h3>
|
||||
<div class="space-y-4" id="cpInputs">
|
||||
<div class="flex gap-2">
|
||||
<input type="number" class="cp-input border rounded px-2 py-1 w-full" placeholder="CP番号">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4 space-x-2">
|
||||
<button onclick="addCPInput()" class="px-4 py-2 bg-blue-500 text-white rounded">
|
||||
追加
|
||||
</button>
|
||||
<button onclick="saveCPs()" class="px-4 py-2 bg-green-500 text-white rounded">
|
||||
保存
|
||||
</button>
|
||||
<button onclick="closeModal()" class="px-4 py-2 bg-gray-500 text-white rounded">
|
||||
キャンセル
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
function saveCPs() {
|
||||
const inputs = document.querySelectorAll('.cp-input');
|
||||
const eventCode = eventCodeSelect.value;
|
||||
const newCheckins = Array.from(inputs)
|
||||
.map(input => ({
|
||||
cp_number: input.value,
|
||||
create_at: null,
|
||||
validate_location: false,
|
||||
buy_flag: false,
|
||||
points: 0
|
||||
}));
|
||||
|
||||
// APIを呼び出して保存
|
||||
fetch(`${API_BASE_URL}/checkins/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
zekken_number: currentZekkenNumber,
|
||||
checkins: newCheckins
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
loadTeamData(currentZekkenNumber,eventCode);
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.querySelector('.fixed').remove();
|
||||
}
|
||||
|
||||
|
||||
function updatePathOrders() {
|
||||
const rows = Array.from(document.getElementById('checkinList').children);
|
||||
rows.forEach((row, index) => {
|
||||
@ -250,7 +530,7 @@
|
||||
validate_location: row.querySelector('.validate-checkbox').checked
|
||||
}));
|
||||
|
||||
fetch('/api/update-checkins', {
|
||||
fetch('${API_BASE_URL}/update_checkins', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -1,33 +1,38 @@
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name rogaining.sumasen.net;
|
||||
server_name rogaining.sumasen.net localhost;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log debug;
|
||||
access_log /var/log/nginx/api_access.log;
|
||||
error_log /var/log/nginx/api_error.log debug;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
# Django admin
|
||||
location ~ ^/(admin|api)/ {
|
||||
proxy_pass http://api:8100;
|
||||
proxy_http_version 1.1;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header X-CSRFToken $http_x_csrf_token;
|
||||
|
||||
# タイムアウト設定
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
proxy_read_timeout 300;
|
||||
send_timeout 300;
|
||||
}
|
||||
|
||||
# Supervisor webapp
|
||||
location /supervisor/ {
|
||||
alias /usr/share/nginx/html/supervisor/;
|
||||
try_files $uri $uri/ /supervisor/index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
@ -36,6 +41,10 @@ server {
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
location = / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
45
supervisor/nginx/default.conf.80
Normal file
45
supervisor/nginx/default.conf.80
Normal file
@ -0,0 +1,45 @@
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name rogaining.sumasen.net;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log debug;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8100;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
alias /app/static/;
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
@ -7,29 +7,38 @@ server {
|
||||
|
||||
# HTTPS server
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:80;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name rogaining.sumasen.net;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/rogaining.sumasen.net/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/rogaining.sumasen.net/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log debug;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
# Django admin
|
||||
location /admin/ {
|
||||
proxy_pass http://api:8100;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Supervisor webapp
|
||||
location /supervisor/ {
|
||||
alias /usr/share/nginx/html/supervisor/;
|
||||
try_files $uri $uri/ /supervisor/index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
|
||||
}
|
||||
|
||||
# API endpoints
|
||||
location /api/ {
|
||||
proxy_pass http://api:8100;
|
||||
proxy_http_version 1.1;
|
||||
@ -37,10 +46,15 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
}
|
||||
|
||||
|
||||
location /static/ {
|
||||
alias /app/static/;
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
Reference in New Issue
Block a user