From a01d0023f6c9fef81922b483733eed2de606a170 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Tue, 25 Nov 2025 04:08:16 +0000 Subject: [PATCH 01/16] added google api python client package --- server/poetry.lock | 103 ++++++++++++++++++++++++++++++++++++++++++ server/pyproject.toml | 1 + 2 files changed, 104 insertions(+) diff --git a/server/poetry.lock b/server/poetry.lock index fba0844..7992506 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -242,6 +242,109 @@ files = [ [package.dependencies] python-dateutil = ">=2.7" +[[package]] +name = "google-api-core" +version = "2.28.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c"}, + {file = "google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.0" +googleapis-common-protos = ">=1.56.2,<2.0.0" +proto-plus = [ + {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, + {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, +] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" +requests = ">=2.18.0,<3.0.0" + +[package.extras] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0)", "grpcio (>=1.75.1,<2.0.0)", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0)", "grpcio-status (>=1.75.1,<2.0.0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] + +[[package]] +name = "google-api-python-client" +version = "2.187.0" +description = "Google API Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_api_python_client-2.187.0-py3-none-any.whl", hash = "sha256:d8d0f6d85d7d1d10bdab32e642312ed572bdc98919f72f831b44b9a9cebba32f"}, + {file = "google_api_python_client-2.187.0.tar.gz", hash = "sha256:e98e8e8f49e1b5048c2f8276473d6485febc76c9c47892a8b4d1afa2c9ec8278"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.0.0" +uritemplate = ">=3.0.1,<5" + +[[package]] +name = "google-auth" +version = "2.43.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_auth-2.43.0-py2.py3-none-any.whl", hash = "sha256:af628ba6fa493f75c7e9dbe9373d148ca9f4399b5ea29976519e0a3848eddd16"}, + {file = "google_auth-2.43.0.tar.gz", hash = "sha256:88228eee5fc21b62a1b5fe773ca15e67778cb07dc8363adcb4a8827b52d81483"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<7.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] +enterprise-cert = ["cryptography", "pyopenssl"] +pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0)"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +urllib3 = ["packaging", "urllib3"] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.1" +description = "Google Authentication Library: httplib2 transport" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_auth_httplib2-0.2.1-py3-none-any.whl", hash = "sha256:1be94c611db91c01f9703e7f62b0a59bbd5587a95571c7b6fade510d648bc08b"}, + {file = "google_auth_httplib2-0.2.1.tar.gz", hash = "sha256:5ef03be3927423c87fb69607b42df23a444e434ddb2555b73b3679793187b7de"}, +] + +[package.dependencies] +google-auth = ">=1.32.0,<3.0.0" +httplib2 = ">=0.19.0,<1.0.0" + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, + {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] + [[package]] name = "gunicorn" version = "23.0.0" diff --git a/server/pyproject.toml b/server/pyproject.toml index ba983a5..aaa6720 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -21,6 +21,7 @@ drf-spectacular = "^0.29.0" boto3 = "^1.34.0" django-storages = "^1.14.0" pillow = "^12.0.0" +google-api-python-client = "^2.187.0" [tool.poetry.group.dev.dependencies] From 108e818aa5d1e2757b9aac705040d92a8abc5f7b Mon Sep 17 00:00:00 2001 From: Devarsh Date: Wed, 26 Nov 2025 04:24:14 +0000 Subject: [PATCH 02/16] added google api key template and CRUD for events --- server/.env.example | 4 +++ .../api/booking/google-calendar/__init__.py | 0 server/api/booking/google-calendar/client.py | 16 +++++++++++ server/api/booking/google-calendar/events.py | 28 +++++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 server/api/booking/google-calendar/__init__.py create mode 100644 server/api/booking/google-calendar/client.py create mode 100644 server/api/booking/google-calendar/events.py diff --git a/server/.env.example b/server/.env.example index a589c4e..bd821f3 100644 --- a/server/.env.example +++ b/server/.env.example @@ -19,4 +19,8 @@ AWS_SECRET_ACCESS_KEY=xxxx AWS_STORAGE_BUCKET_NAME=bucket_name AWS_REGION_NAME=ap-southeast-2 +# Google Calendar API Credentials +GOOGLE_CREDENTIALS_FILE= +GOOGLE_CALENDAR_ID= + FRONTEND_URL="http://localhost:3000" \ No newline at end of file diff --git a/server/api/booking/google-calendar/__init__.py b/server/api/booking/google-calendar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/booking/google-calendar/client.py b/server/api/booking/google-calendar/client.py new file mode 100644 index 0000000..75f9534 --- /dev/null +++ b/server/api/booking/google-calendar/client.py @@ -0,0 +1,16 @@ +import os +from google.oauth2 import service_account +from googleapiclient.discovery import build + + +def get_calendar_service(): + """Return a Google Calendar API service using a service account.""" + cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") + if not cred_path or not os.path.exists(cred_path): + raise FileNotFoundError("Set GOOGLE_CREDENTIALS_FILE in .env") + + creds = service_account.Credentials.from_service_account_file( + cred_path, + scopes=["https://www.googleapis.com/auth/calendar"] + ) + return build("calendar", "v3", credentials=creds) diff --git a/server/api/booking/google-calendar/events.py b/server/api/booking/google-calendar/events.py new file mode 100644 index 0000000..be40488 --- /dev/null +++ b/server/api/booking/google-calendar/events.py @@ -0,0 +1,28 @@ +import os +from .client import get_calendar_service + +CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID") + + +def create_event(event_data: dict): + """Create a new event.""" + service = get_calendar_service() + return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() + + +def get_event(event_id: str): + """Retrieve a specific event.""" + service = get_calendar_service() + return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() + + +def update_event(event_id: str, updated_data: dict): + """Update an existing event.""" + service = get_calendar_service() + return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() + + +def delete_event(event_id: str): + """Delete an event.""" + service = get_calendar_service() + return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() From e60a2406a1e8b51f619faf0ed7981a7e77866f32 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Wed, 26 Nov 2025 06:58:40 +0000 Subject: [PATCH 03/16] Revert "added google api key template and CRUD for events" This reverts commit a13dd93bb7227543a4882fafb1c7070e131572bd. --- .../api/booking/google-calendar/__init__.py | 0 server/api/booking/google-calendar/client.py | 16 ----------- server/api/booking/google-calendar/events.py | 28 ------------------- 3 files changed, 44 deletions(-) delete mode 100644 server/api/booking/google-calendar/__init__.py delete mode 100644 server/api/booking/google-calendar/client.py delete mode 100644 server/api/booking/google-calendar/events.py diff --git a/server/api/booking/google-calendar/__init__.py b/server/api/booking/google-calendar/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/api/booking/google-calendar/client.py b/server/api/booking/google-calendar/client.py deleted file mode 100644 index 75f9534..0000000 --- a/server/api/booking/google-calendar/client.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -from google.oauth2 import service_account -from googleapiclient.discovery import build - - -def get_calendar_service(): - """Return a Google Calendar API service using a service account.""" - cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") - if not cred_path or not os.path.exists(cred_path): - raise FileNotFoundError("Set GOOGLE_CREDENTIALS_FILE in .env") - - creds = service_account.Credentials.from_service_account_file( - cred_path, - scopes=["https://www.googleapis.com/auth/calendar"] - ) - return build("calendar", "v3", credentials=creds) diff --git a/server/api/booking/google-calendar/events.py b/server/api/booking/google-calendar/events.py deleted file mode 100644 index be40488..0000000 --- a/server/api/booking/google-calendar/events.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -from .client import get_calendar_service - -CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID") - - -def create_event(event_data: dict): - """Create a new event.""" - service = get_calendar_service() - return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() - - -def get_event(event_id: str): - """Retrieve a specific event.""" - service = get_calendar_service() - return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() - - -def update_event(event_id: str, updated_data: dict): - """Update an existing event.""" - service = get_calendar_service() - return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() - - -def delete_event(event_id: str): - """Delete an event.""" - service = get_calendar_service() - return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() From cdf4d14559563bf1a99bfe6bd6fb52e724b70742 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Fri, 28 Nov 2025 23:50:33 +0000 Subject: [PATCH 04/16] Redownload google api python client package and add google api .env template --- server/.env.example | 4 ++++ server/poetry.lock | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server/.env.example b/server/.env.example index bd821f3..e85bd0f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -23,4 +23,8 @@ AWS_REGION_NAME=ap-southeast-2 GOOGLE_CREDENTIALS_FILE= GOOGLE_CALENDAR_ID= +# Google API +GOOGLE_CREDENTIALS_FILE= +GOOGLE_CALENDAR_ID= + FRONTEND_URL="http://localhost:3000" \ No newline at end of file diff --git a/server/poetry.lock b/server/poetry.lock index 7992506..b4eefaa 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -162,14 +162,14 @@ s3 = ["boto3 (>=1.4.4)"] sftp = ["paramiko (>=1.15)"] [[package]] -name = "djangorestframework" -version = "3.16.1" -description = "Web APIs for Django, made easy." +name = "django-storages" +version = "1.14.6" +description = "Support for many storage backends in Django" optional = false python-versions = ">=3.9" files = [ - {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, - {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, + {file = "django_storages-1.14.6-py3-none-any.whl", hash = "sha256:11b7b6200e1cb5ffcd9962bd3673a39c7d6a6109e8096f0e03d46fab3d3aabd9"}, + {file = "django_storages-1.14.6.tar.gz", hash = "sha256:7a25ce8f4214f69ac9c7ce87e2603887f7ae99326c316bc8d2d75375e09341c9"}, ] [package.dependencies] @@ -393,7 +393,7 @@ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, From ad1906794ac2366f89f38dfb11fc98716d81da6e Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 29 Nov 2025 02:59:46 +0000 Subject: [PATCH 05/16] added crud for events and test file --- server/api/booking/__init__.py | 0 .../api/booking/google_calendar/__init__.py | 0 server/api/booking/google_calendar/client.py | 29 ++++++++++++ server/api/booking/google_calendar/events.py | 28 +++++++++++ .../google_calendar/test_google_calendar.py | 47 +++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 server/api/booking/__init__.py create mode 100644 server/api/booking/google_calendar/__init__.py create mode 100644 server/api/booking/google_calendar/client.py create mode 100644 server/api/booking/google_calendar/events.py create mode 100644 server/api/booking/google_calendar/test_google_calendar.py diff --git a/server/api/booking/__init__.py b/server/api/booking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/booking/google_calendar/__init__.py b/server/api/booking/google_calendar/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/booking/google_calendar/client.py b/server/api/booking/google_calendar/client.py new file mode 100644 index 0000000..8f13a1f --- /dev/null +++ b/server/api/booking/google_calendar/client.py @@ -0,0 +1,29 @@ +import os +from google.oauth2 import service_account +from googleapiclient.discovery import build + + +def get_calendar_service(): + """ + Create and return a Google Calendar API service client using a service account. + + Returns: + googleapiclient.discovery.Resource: Authenticated Calendar API service. + + Raises: + FileNotFoundError: If GOOGLE_CREDENTIALS_FILE is not set or the file does not exist. + """ + cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") + + if not cred_path or not os.path.exists(cred_path): + raise FileNotFoundError( + "GOOGLE_CREDENTIALS_FILE is missing or the file path is invalid. " + "Set it in your .env file." + ) + + creds = service_account.Credentials.from_service_account_file( + cred_path, + scopes=["https://www.googleapis.com/auth/calendar"] + ) + + return build("calendar", "v3", credentials=creds) diff --git a/server/api/booking/google_calendar/events.py b/server/api/booking/google_calendar/events.py new file mode 100644 index 0000000..be40488 --- /dev/null +++ b/server/api/booking/google_calendar/events.py @@ -0,0 +1,28 @@ +import os +from .client import get_calendar_service + +CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID") + + +def create_event(event_data: dict): + """Create a new event.""" + service = get_calendar_service() + return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() + + +def get_event(event_id: str): + """Retrieve a specific event.""" + service = get_calendar_service() + return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() + + +def update_event(event_id: str, updated_data: dict): + """Update an existing event.""" + service = get_calendar_service() + return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() + + +def delete_event(event_id: str): + """Delete an event.""" + service = get_calendar_service() + return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() diff --git a/server/api/booking/google_calendar/test_google_calendar.py b/server/api/booking/google_calendar/test_google_calendar.py new file mode 100644 index 0000000..b4d6f1f --- /dev/null +++ b/server/api/booking/google_calendar/test_google_calendar.py @@ -0,0 +1,47 @@ +# api/booking/google_calendar/test_google_calendar_mock.py + +from unittest.mock import patch, MagicMock +from api.booking.google_calendar import events + + +@patch("api.booking.google_calendar.events.get_calendar_service") +def test_create_event(mock_service): + # Create a fake service client + mock_client = MagicMock() + mock_service.return_value = mock_client + + # Call the create_event function + events.create_event({"summary": "Test Event"}) + + # Assert that insert was called + mock_client.events.return_value.insert.assert_called_once() + + +@patch("api.booking.google_calendar.events.get_calendar_service") +def test_get_event(mock_service): + mock_client = MagicMock() + mock_service.return_value = mock_client + + events.get_event("123") + + mock_client.events.return_value.get.assert_called_once() + + +@patch("api.booking.google_calendar.events.get_calendar_service") +def test_update_event(mock_service): + mock_client = MagicMock() + mock_service.return_value = mock_client + + events.update_event("123", {"summary": "Updated"}) + + mock_client.events.return_value.update.assert_called_once() + + +@patch("api.booking.google_calendar.events.get_calendar_service") +def test_delete_event(mock_service): + mock_client = MagicMock() + mock_service.return_value = mock_client + + events.delete_event("123") + + mock_client.events.return_value.delete.assert_called_once() From 3f66aaf22a42864f2d033fc3134b0bc02c74870f Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 29 Nov 2025 03:16:34 +0000 Subject: [PATCH 06/16] poetry update --- server/.env.example | 4 ++++ server/poetry.lock | 8 ++++---- server/pyproject.toml | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server/.env.example b/server/.env.example index e85bd0f..7f5b42f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -26,5 +26,9 @@ GOOGLE_CALENDAR_ID= # Google API GOOGLE_CREDENTIALS_FILE= GOOGLE_CALENDAR_ID= +AWS_ACCESS_KEY_ID=xxxx +AWS_SECRET_ACCESS_KEY=xxxx +AWS_STORAGE_BUCKET_NAME=bucket_name +AWS_REGION_NAME=ap-southeast-2 FRONTEND_URL="http://localhost:3000" \ No newline at end of file diff --git a/server/poetry.lock b/server/poetry.lock index b4eefaa..69893a8 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1079,17 +1079,17 @@ files = [ [[package]] name = "sqlparse" -version = "0.5.3" +version = "0.5.4" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, - {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, + {file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"}, + {file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"}, ] [package.extras] -dev = ["build", "hatch"] +dev = ["build"] doc = ["sphinx"] [[package]] diff --git a/server/pyproject.toml b/server/pyproject.toml index aaa6720..d077229 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -22,6 +22,10 @@ boto3 = "^1.34.0" django-storages = "^1.14.0" pillow = "^12.0.0" google-api-python-client = "^2.187.0" +drf-spectacular = "^0.29.0" +boto3 = "^1.34.0" +django-storages = "^1.14.0" +pillow = "^12.0.0" [tool.poetry.group.dev.dependencies] From 7e72f9d3e85ad83c9ade94bc1d0d5b4d2cf5df4f Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 29 Nov 2025 03:30:50 +0000 Subject: [PATCH 07/16] poetry.lock new file --- server/.env.example | 4 - server/poetry.lock | 310 ++++++++++++++++++++++++++++++++++++++++-- server/pyproject.toml | 5 +- 3 files changed, 297 insertions(+), 22 deletions(-) diff --git a/server/.env.example b/server/.env.example index 7f5b42f..328dd4a 100644 --- a/server/.env.example +++ b/server/.env.example @@ -19,10 +19,6 @@ AWS_SECRET_ACCESS_KEY=xxxx AWS_STORAGE_BUCKET_NAME=bucket_name AWS_REGION_NAME=ap-southeast-2 -# Google Calendar API Credentials -GOOGLE_CREDENTIALS_FILE= -GOOGLE_CALENDAR_ID= - # Google API GOOGLE_CREDENTIALS_FILE= GOOGLE_CALENDAR_ID= diff --git a/server/poetry.lock b/server/poetry.lock index 69893a8..46b9554 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -42,17 +42,17 @@ files = [ [[package]] name = "boto3" -version = "1.41.4" +version = "1.41.5" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" files = [ - {file = "boto3-1.41.4-py3-none-any.whl", hash = "sha256:77d84b7ce890a9b0c6a8993f8de106d8cf8138f332a4685e6de453965e60cb24"}, - {file = "boto3-1.41.4.tar.gz", hash = "sha256:2c6b8d13df6beb9255d0c8cb60a7a2164f5270580ea5b921a65658a0c28e3223"}, + {file = "boto3-1.41.5-py3-none-any.whl", hash = "sha256:bb278111bfb4c33dca8342bda49c9db7685e43debbfa00cc2a5eb854dd54b745"}, + {file = "boto3-1.41.5.tar.gz", hash = "sha256:bc7806bee681dfdff2fe2b74967b107a56274f1e66ebe4d20dc8eee1ea408d17"}, ] [package.dependencies] -botocore = ">=1.41.4,<1.42.0" +botocore = ">=1.41.5,<1.42.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.15.0,<0.16.0" @@ -61,13 +61,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.41.4" +version = "1.41.5" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" files = [ - {file = "botocore-1.41.4-py3-none-any.whl", hash = "sha256:7143ef845f1d1400dbbf05d999f8c5e8cfaecd6bd84cbfbe5fa0a40e3a9f6353"}, - {file = "botocore-1.41.4.tar.gz", hash = "sha256:45c78f07b53a64cbe55e5d60297958f151bd4a2c6acb944a8bb65874bc2fd953"}, + {file = "botocore-1.41.5-py3-none-any.whl", hash = "sha256:3fef7fcda30c82c27202d232cfdbd6782cb27f20f8e7e21b20606483e66ee73a"}, + {file = "botocore-1.41.5.tar.gz", hash = "sha256:0367622b811597d183bfcaab4a350f0d3ede712031ce792ef183cabdee80d3bf"}, ] [package.dependencies] @@ -78,6 +78,150 @@ urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version > [package.extras] crt = ["awscrt (==0.29.0)"] +[[package]] +name = "cachetools" +version = "6.2.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.9" +files = [ + {file = "cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace"}, + {file = "cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6"}, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +files = [ + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -162,14 +306,14 @@ s3 = ["boto3 (>=1.4.4)"] sftp = ["paramiko (>=1.15)"] [[package]] -name = "django-storages" -version = "1.14.6" -description = "Support for many storage backends in Django" +name = "djangorestframework" +version = "3.16.1" +description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.9" files = [ - {file = "django_storages-1.14.6-py3-none-any.whl", hash = "sha256:11b7b6200e1cb5ffcd9962bd3673a39c7d6a6109e8096f0e03d46fab3d3aabd9"}, - {file = "django_storages-1.14.6.tar.gz", hash = "sha256:7a25ce8f4214f69ac9c7ce87e2603887f7ae99326c316bc8d2d75375e09341c9"}, + {file = "djangorestframework-3.16.1-py3-none-any.whl", hash = "sha256:33a59f47fb9c85ede792cbf88bde71893bcda0667bc573f784649521f1102cec"}, + {file = "djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7"}, ] [package.dependencies] @@ -366,6 +510,34 @@ setproctitle = ["setproctitle"] testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] +[[package]] +name = "httplib2" +version = "0.31.0" +description = "A comprehensive HTTP client library." +optional = false +python-versions = ">=3.6" +files = [ + {file = "httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24"}, + {file = "httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c"}, +] + +[package.dependencies] +pyparsing = ">=3.0.4,<4" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "inflection" version = "0.5.1" @@ -393,7 +565,7 @@ name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" optional = false -python-versions = ">=3.10" +python-versions = ">=3.7" files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -632,6 +804,42 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "proto-plus" +version = "1.26.1" +description = "Beautiful, Pythonic protocol buffers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, + {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<7.0.0" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "6.33.1" +description = "" +optional = false +python-versions = ">=3.9" +files = [ + {file = "protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b"}, + {file = "protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed"}, + {file = "protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1"}, + {file = "protobuf-6.33.1-cp39-cp39-win32.whl", hash = "sha256:023af8449482fa884d88b4563d85e83accab54138ae098924a985bcbb734a213"}, + {file = "protobuf-6.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:df051de4fd7e5e4371334e234c62ba43763f15ab605579e04c7008c05735cd82"}, + {file = "protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa"}, + {file = "protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b"}, +] + [[package]] name = "psycopg" version = "3.2.13" @@ -742,6 +950,31 @@ files = [ [package.dependencies] typing-extensions = ">=4.6" +[[package]] +name = "pyasn1" +version = "0.6.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, +] + +[package.dependencies] +pyasn1 = ">=0.6.1,<0.7.0" + [[package]] name = "pycodestyle" version = "2.11.1" @@ -778,6 +1011,20 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyparsing" +version = "3.2.5" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, + {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "pytest" version = "8.4.2" @@ -925,6 +1172,27 @@ attrs = ">=22.2.0" rpds-py = ">=0.7.0" typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "rpds-py" version = "0.29.0" @@ -1049,6 +1317,20 @@ files = [ {file = "rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359"}, ] +[[package]] +name = "rsa" +version = "4.9.1" +description = "Pure-Python RSA implementation" +optional = false +python-versions = "<4,>=3.6" +files = [ + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "s3transfer" version = "0.15.0" @@ -1235,4 +1517,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "35575e0b8193efb0b95f0c5bdfcf1146cd60d90e91d3ab7f8c1adbe9a705053c" +content-hash = "3eeceb35dc80d8bb08e391e89fb7ab77c2f6855ab289daef6cd17ef45212003e" diff --git a/server/pyproject.toml b/server/pyproject.toml index d077229..9f17f2d 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -22,10 +22,7 @@ boto3 = "^1.34.0" django-storages = "^1.14.0" pillow = "^12.0.0" google-api-python-client = "^2.187.0" -drf-spectacular = "^0.29.0" -boto3 = "^1.34.0" -django-storages = "^1.14.0" -pillow = "^12.0.0" + [tool.poetry.group.dev.dependencies] From 143a396d1cadb0e4573948f8883f6eb04c9bae6b Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 29 Nov 2025 07:43:43 +0000 Subject: [PATCH 08/16] Updated the google calendar test file plus client.py. Works on personal calendar --- .gitignore | 3 + server/api/booking/google_calendar/client.py | 36 ++++---- .../google_calendar/test_google_calendar.py | 83 ++++++++----------- 3 files changed, 60 insertions(+), 62 deletions(-) diff --git a/.gitignore b/.gitignore index 0308569..4ff59a1 100644 --- a/.gitignore +++ b/.gitignore @@ -295,3 +295,6 @@ dist pyrightconfig.json opt/ + +# sample google service account +server/api/booking/google_calendar/google_calendar_service.json \ No newline at end of file diff --git a/server/api/booking/google_calendar/client.py b/server/api/booking/google_calendar/client.py index 8f13a1f..b092274 100644 --- a/server/api/booking/google_calendar/client.py +++ b/server/api/booking/google_calendar/client.py @@ -1,29 +1,35 @@ import os +from pathlib import Path from google.oauth2 import service_account from googleapiclient.discovery import build +from dotenv import load_dotenv +# ----------------------------- +# Resolve BASE_DIR and load .env +# ----------------------------- +# adjust to server root +BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent +load_dotenv(os.path.join(BASE_DIR, ".env")) -def get_calendar_service(): - """ - Create and return a Google Calendar API service client using a service account. +# Use environment variable for Google credentials +cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") - Returns: - googleapiclient.discovery.Resource: Authenticated Calendar API service. +# ----------------------------- +# Validate credentials file +# ----------------------------- +if not cred_path or not os.path.exists(cred_path): + raise FileNotFoundError( + f"GOOGLE_CREDENTIALS_FILE is missing or invalid.\nChecked: {os.path.abspath(cred_path)}" + ) - Raises: - FileNotFoundError: If GOOGLE_CREDENTIALS_FILE is not set or the file does not exist. - """ - cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") +# ----------------------------- +# Create Google Calendar service +# ----------------------------- - if not cred_path or not os.path.exists(cred_path): - raise FileNotFoundError( - "GOOGLE_CREDENTIALS_FILE is missing or the file path is invalid. " - "Set it in your .env file." - ) +def get_calendar_service(): creds = service_account.Credentials.from_service_account_file( cred_path, scopes=["https://www.googleapis.com/auth/calendar"] ) - return build("calendar", "v3", credentials=creds) diff --git a/server/api/booking/google_calendar/test_google_calendar.py b/server/api/booking/google_calendar/test_google_calendar.py index b4d6f1f..90dc8c3 100644 --- a/server/api/booking/google_calendar/test_google_calendar.py +++ b/server/api/booking/google_calendar/test_google_calendar.py @@ -1,47 +1,36 @@ -# api/booking/google_calendar/test_google_calendar_mock.py - -from unittest.mock import patch, MagicMock -from api.booking.google_calendar import events - - -@patch("api.booking.google_calendar.events.get_calendar_service") -def test_create_event(mock_service): - # Create a fake service client - mock_client = MagicMock() - mock_service.return_value = mock_client - - # Call the create_event function - events.create_event({"summary": "Test Event"}) - - # Assert that insert was called - mock_client.events.return_value.insert.assert_called_once() - - -@patch("api.booking.google_calendar.events.get_calendar_service") -def test_get_event(mock_service): - mock_client = MagicMock() - mock_service.return_value = mock_client - - events.get_event("123") - - mock_client.events.return_value.get.assert_called_once() - - -@patch("api.booking.google_calendar.events.get_calendar_service") -def test_update_event(mock_service): - mock_client = MagicMock() - mock_service.return_value = mock_client - - events.update_event("123", {"summary": "Updated"}) - - mock_client.events.return_value.update.assert_called_once() - - -@patch("api.booking.google_calendar.events.get_calendar_service") -def test_delete_event(mock_service): - mock_client = MagicMock() - mock_service.return_value = mock_client - - events.delete_event("123") - - mock_client.events.return_value.delete.assert_called_once() +from api.booking.google_calendar.events import ( + create_event, + get_event, + update_event, + delete_event, +) + + +def test_google_calendar_crud(): + + event_data = { + "summary": "Test Event", + "description": "CRUD test event", + "start": { + "dateTime": "2025-11-29T15:00:00", + "timeZone": "Australia/Perth", + }, + "end": { + "dateTime": "2025-11-29T16:00:00", + "timeZone": "Australia/Perth", + }, + } + + created = create_event(event_data) + event_id = created["id"] + + fetched = get_event(event_id) + assert fetched["summary"] == "Test Event" + + updated_data = dict(event_data) + updated_data["summary"] = "Updated Test Event" + + updated = update_event(event_id, updated_data) + assert updated["summary"] == "Updated Test Event" + + delete_event(event_id) From 05f0bea474d6d91bd9707cb635974389ac5f8785 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Tue, 2 Dec 2025 02:22:13 +0000 Subject: [PATCH 09/16] fix back code check issue and updated cleint.py --- .gitignore | 2 +- server/.env.example | 4 ++-- server/api/booking/google_calendar/client.py | 16 +++++++--------- .../template_service_account.json | 8 ++++++++ .../google_calendar/test_google_calendar.py | 4 ++-- 5 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 server/api/booking/google_calendar/template_service_account.json diff --git a/.gitignore b/.gitignore index 4ff59a1..eccd28e 100644 --- a/.gitignore +++ b/.gitignore @@ -296,5 +296,5 @@ pyrightconfig.json opt/ -# sample google service account +# google api server/api/booking/google_calendar/google_calendar_service.json \ No newline at end of file diff --git a/server/.env.example b/server/.env.example index 328dd4a..ef505d7 100644 --- a/server/.env.example +++ b/server/.env.example @@ -20,8 +20,8 @@ AWS_STORAGE_BUCKET_NAME=bucket_name AWS_REGION_NAME=ap-southeast-2 # Google API -GOOGLE_CREDENTIALS_FILE= -GOOGLE_CALENDAR_ID= +GOOGLE_CREDENTIALS_FILE=server/api/booking/google_calendar/template_service_account.json +GOOGLE_CALENDAR_ID=xxxx AWS_ACCESS_KEY_ID=xxxx AWS_SECRET_ACCESS_KEY=xxxx AWS_STORAGE_BUCKET_NAME=bucket_name diff --git a/server/api/booking/google_calendar/client.py b/server/api/booking/google_calendar/client.py index b092274..27179d9 100644 --- a/server/api/booking/google_calendar/client.py +++ b/server/api/booking/google_calendar/client.py @@ -11,20 +11,18 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent load_dotenv(os.path.join(BASE_DIR, ".env")) -# Use environment variable for Google credentials cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") +calendar_id = os.getenv("GOOGLE_CALENDAR_ID") -# ----------------------------- -# Validate credentials file -# ----------------------------- -if not cred_path or not os.path.exists(cred_path): +if not cred_path: raise FileNotFoundError( - f"GOOGLE_CREDENTIALS_FILE is missing or invalid.\nChecked: {os.path.abspath(cred_path)}" + "GOOGLE_CREDENTIALS_FILE is missing or invalid. " + f"Checked: {os.path.join(BASE_DIR, '.env')}" ) -# ----------------------------- -# Create Google Calendar service -# ----------------------------- +if not calendar_id: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your .env file.") def get_calendar_service(): diff --git a/server/api/booking/google_calendar/template_service_account.json b/server/api/booking/google_calendar/template_service_account.json new file mode 100644 index 0000000..7501614 --- /dev/null +++ b/server/api/booking/google_calendar/template_service_account.json @@ -0,0 +1,8 @@ +{ + "type": "service_account", + "project_id": "xxx", + "private_key_id": "xxx", + "private_key": "-----BEGIN PRIVATE KEY-----\nxxx\n-----END PRIVATE KEY-----\n", + "client_email": "xxx@xxx.iam.gserviceaccount.com", + "client_id": "xxx" +} diff --git a/server/api/booking/google_calendar/test_google_calendar.py b/server/api/booking/google_calendar/test_google_calendar.py index 90dc8c3..5e08583 100644 --- a/server/api/booking/google_calendar/test_google_calendar.py +++ b/server/api/booking/google_calendar/test_google_calendar.py @@ -12,11 +12,11 @@ def test_google_calendar_crud(): "summary": "Test Event", "description": "CRUD test event", "start": { - "dateTime": "2025-11-29T15:00:00", + "dateTime": "2025-12-2T15:00:00", "timeZone": "Australia/Perth", }, "end": { - "dateTime": "2025-11-29T16:00:00", + "dateTime": "2025-12-2T16:00:00", "timeZone": "Australia/Perth", }, } From 9a5edae6b5165e8826b2aa56f7660d1d1f4c5a78 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Wed, 3 Dec 2025 09:34:24 +0000 Subject: [PATCH 10/16] remove duplicate aws variables, added docs to events and removed dependency on env vars need to be set in test file --- server/.env.example | 5 +- server/api/booking/google_calendar/events.py | 54 +++++++++++++++++-- .../google_calendar/test_google_calendar.py | 8 +++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/server/.env.example b/server/.env.example index ef505d7..7e85673 100644 --- a/server/.env.example +++ b/server/.env.example @@ -22,9 +22,6 @@ AWS_REGION_NAME=ap-southeast-2 # Google API GOOGLE_CREDENTIALS_FILE=server/api/booking/google_calendar/template_service_account.json GOOGLE_CALENDAR_ID=xxxx -AWS_ACCESS_KEY_ID=xxxx -AWS_SECRET_ACCESS_KEY=xxxx -AWS_STORAGE_BUCKET_NAME=bucket_name -AWS_REGION_NAME=ap-southeast-2 + FRONTEND_URL="http://localhost:3000" \ No newline at end of file diff --git a/server/api/booking/google_calendar/events.py b/server/api/booking/google_calendar/events.py index be40488..23df239 100644 --- a/server/api/booking/google_calendar/events.py +++ b/server/api/booking/google_calendar/events.py @@ -5,24 +5,70 @@ def create_event(event_data: dict): - """Create a new event.""" + """ + Create a new Google Calendar event. + + **Expected event_data keys (Google Calendar API format):** + - summary (str): Title of the event. + - description (str, optional): Details about the event. + - location (str, optional): Physical or virtual location. + - start (dict): Start date/time. + Example: + { + "dateTime": "2025-02-01T10:00:00+08:00", + "timeZone": "Australia/Perth" + } + - end (dict): End date/time. + Example: + { + "dateTime": "2025-02-01T11:00:00+08:00", + "timeZone": "Australia/Perth" + } + Returns: + dict: The created event object from Google Calendar. + """ service = get_calendar_service() return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() def get_event(event_id: str): - """Retrieve a specific event.""" + """ + Retrieve a specific event. + + Args: + event_id (str): Google Calendar event ID. + + Returns: + dict: Event details. + """ service = get_calendar_service() return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() def update_event(event_id: str, updated_data: dict): - """Update an existing event.""" + """ + Update an existing event. + + Args: + event_id (str): Google Calendar event ID. + updated_data (dict): Same structure as event_data. + + Returns: + dict: Updated event object. + """ service = get_calendar_service() return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() def delete_event(event_id: str): - """Delete an event.""" + """ + Delete an event. + + Args: + event_id (str): Google Calendar event ID. + + Returns: + dict: Response from Google Calendar API (usually empty). + """ service = get_calendar_service() return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() diff --git a/server/api/booking/google_calendar/test_google_calendar.py b/server/api/booking/google_calendar/test_google_calendar.py index 5e08583..f659fc9 100644 --- a/server/api/booking/google_calendar/test_google_calendar.py +++ b/server/api/booking/google_calendar/test_google_calendar.py @@ -1,9 +1,17 @@ +import pytest +import os from api.booking.google_calendar.events import ( create_event, get_event, update_event, delete_event, ) +# skip if no Google Calendar env variables are set +pytestmark = pytest.mark.skipif( + not os.getenv("GOOGLE_CREDENTIALS_FILE") or not os.getenv( + "GOOGLE_CALENDAR_ID"), + reason="Google Calendar integration env vars missing. Set GOOGLE_CREDENTIALS_FILE and GOOGLE_CALENDAR_ID to run this test." +) def test_google_calendar_crud(): From 9be90f6db20bd84dfbf402092a194f6d35e49cc1 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Wed, 3 Dec 2025 09:44:35 +0000 Subject: [PATCH 11/16] fix google creds file error --- server/api/booking/google_calendar/client.py | 18 +++++++----------- server/api/booking/google_calendar/events.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/server/api/booking/google_calendar/client.py b/server/api/booking/google_calendar/client.py index 27179d9..618e28d 100644 --- a/server/api/booking/google_calendar/client.py +++ b/server/api/booking/google_calendar/client.py @@ -4,9 +4,8 @@ from googleapiclient.discovery import build from dotenv import load_dotenv -# ----------------------------- + # Resolve BASE_DIR and load .env -# ----------------------------- # adjust to server root BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent load_dotenv(os.path.join(BASE_DIR, ".env")) @@ -14,18 +13,15 @@ cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") calendar_id = os.getenv("GOOGLE_CALENDAR_ID") -if not cred_path: - raise FileNotFoundError( - "GOOGLE_CREDENTIALS_FILE is missing or invalid. " - f"Checked: {os.path.join(BASE_DIR, '.env')}" - ) -if not calendar_id: - raise ValueError( - "GOOGLE_CALENDAR_ID is missing. Set it in your .env file.") +def get_calendar_service(): + if not cred_path: + raise FileNotFoundError( + "GOOGLE_CREDENTIALS_FILE is missing or invalid. " + f"Checked: {os.path.join(BASE_DIR, '.env')}" + ) -def get_calendar_service(): creds = service_account.Credentials.from_service_account_file( cred_path, scopes=["https://www.googleapis.com/auth/calendar"] diff --git a/server/api/booking/google_calendar/events.py b/server/api/booking/google_calendar/events.py index 23df239..691c5a1 100644 --- a/server/api/booking/google_calendar/events.py +++ b/server/api/booking/google_calendar/events.py @@ -27,6 +27,10 @@ def create_event(event_data: dict): Returns: dict: The created event object from Google Calendar. """ + if not CALENDAR_ID: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") + service = get_calendar_service() return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() @@ -41,6 +45,9 @@ def get_event(event_id: str): Returns: dict: Event details. """ + if not CALENDAR_ID: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() @@ -56,6 +63,9 @@ def update_event(event_id: str, updated_data: dict): Returns: dict: Updated event object. """ + if not CALENDAR_ID: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() @@ -70,5 +80,8 @@ def delete_event(event_id: str): Returns: dict: Response from Google Calendar API (usually empty). """ + if not CALENDAR_ID: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() From 9a279506f7c91963cac045a8290312915a562d32 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Wed, 3 Dec 2025 12:59:33 +0000 Subject: [PATCH 12/16] implement copilot changes --- server/api/booking/google_calendar/client.py | 6 +-- server/api/booking/google_calendar/events.py | 37 ++++++++++--------- .../google_calendar/test_google_calendar.py | 5 ++- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/server/api/booking/google_calendar/client.py b/server/api/booking/google_calendar/client.py index 618e28d..cb45667 100644 --- a/server/api/booking/google_calendar/client.py +++ b/server/api/booking/google_calendar/client.py @@ -10,14 +10,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent load_dotenv(os.path.join(BASE_DIR, ".env")) -cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") -calendar_id = os.getenv("GOOGLE_CALENDAR_ID") - def get_calendar_service(): + cred_path = os.getenv("GOOGLE_CREDENTIALS_FILE") if not cred_path: - raise FileNotFoundError( + raise ValueError( "GOOGLE_CREDENTIALS_FILE is missing or invalid. " f"Checked: {os.path.join(BASE_DIR, '.env')}" ) diff --git a/server/api/booking/google_calendar/events.py b/server/api/booking/google_calendar/events.py index 691c5a1..7030214 100644 --- a/server/api/booking/google_calendar/events.py +++ b/server/api/booking/google_calendar/events.py @@ -1,10 +1,20 @@ import os from .client import get_calendar_service -CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID") +def requires_calendar_id(func): + def wrapper(*args, **kwargs): + CALENDAR_ID = os.getenv("GOOGLE_CALENDAR_ID") + if not CALENDAR_ID: + raise ValueError( + "GOOGLE_CALENDAR_ID is missing. Set it in your environment." + ) + return func(CALENDAR_ID, *args, **kwargs) + return wrapper -def create_event(event_data: dict): + +@requires_calendar_id +def create_event(CALENDAR_ID, event_data: dict): """ Create a new Google Calendar event. @@ -24,18 +34,16 @@ def create_event(event_data: dict): "dateTime": "2025-02-01T11:00:00+08:00", "timeZone": "Australia/Perth" } + - 'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=10']. Returns: dict: The created event object from Google Calendar. """ - if not CALENDAR_ID: - raise ValueError( - "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") - service = get_calendar_service() return service.events().insert(calendarId=CALENDAR_ID, body=event_data).execute() -def get_event(event_id: str): +@requires_calendar_id +def get_event(CALENDAR_ID, event_id: str): """ Retrieve a specific event. @@ -45,14 +53,12 @@ def get_event(event_id: str): Returns: dict: Event details. """ - if not CALENDAR_ID: - raise ValueError( - "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().get(calendarId=CALENDAR_ID, eventId=event_id).execute() -def update_event(event_id: str, updated_data: dict): +@requires_calendar_id +def update_event(CALENDAR_ID, event_id: str, updated_data: dict): """ Update an existing event. @@ -63,14 +69,12 @@ def update_event(event_id: str, updated_data: dict): Returns: dict: Updated event object. """ - if not CALENDAR_ID: - raise ValueError( - "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().update(calendarId=CALENDAR_ID, eventId=event_id, body=updated_data).execute() -def delete_event(event_id: str): +@requires_calendar_id +def delete_event(CALENDAR_ID, event_id: str): """ Delete an event. @@ -80,8 +84,5 @@ def delete_event(event_id: str): Returns: dict: Response from Google Calendar API (usually empty). """ - if not CALENDAR_ID: - raise ValueError( - "GOOGLE_CALENDAR_ID is missing. Set it in your environment.") service = get_calendar_service() return service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute() diff --git a/server/api/booking/google_calendar/test_google_calendar.py b/server/api/booking/google_calendar/test_google_calendar.py index f659fc9..4d9c661 100644 --- a/server/api/booking/google_calendar/test_google_calendar.py +++ b/server/api/booking/google_calendar/test_google_calendar.py @@ -20,13 +20,14 @@ def test_google_calendar_crud(): "summary": "Test Event", "description": "CRUD test event", "start": { - "dateTime": "2025-12-2T15:00:00", + "dateTime": "2025-12-02T15:00:00", "timeZone": "Australia/Perth", }, "end": { - "dateTime": "2025-12-2T16:00:00", + "dateTime": "2025-12-02T16:00:00", "timeZone": "Australia/Perth", }, + 'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=10'], } created = create_event(event_data) From 950a181e8319281ad23785ce9f1ad80f4cee12e1 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 6 Dec 2025 01:25:50 +0000 Subject: [PATCH 13/16] update poetry.lock --- server/poetry.lock | 29 +++++++++++++++++++++++------ server/pyproject.toml | 2 -- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index f54071f..aa68ac2 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -54,7 +54,7 @@ files = [ [package.dependencies] botocore = ">=1.41.5,<1.42.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.16.0,<0.17.0" +s3transfer = ">=0.15.0,<0.16.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] @@ -76,7 +76,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.29.1)"] +crt = ["awscrt (==0.29.0)"] [[package]] name = "cachetools" @@ -1031,6 +1031,23 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pyparsing" version = "3.2.5" @@ -1353,13 +1370,13 @@ pyasn1 = ">=0.1.3" [[package]] name = "s3transfer" -version = "0.16.0" +version = "0.15.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" files = [ - {file = "s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe"}, - {file = "s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920"}, + {file = "s3transfer-0.15.0-py3-none-any.whl", hash = "sha256:6f8bf5caa31a0865c4081186689db1b2534cef721d104eb26101de4b9d6a5852"}, + {file = "s3transfer-0.15.0.tar.gz", hash = "sha256:d36fac8d0e3603eff9b5bfa4282c7ce6feb0301a633566153cbd0b93d11d8379"}, ] [package.dependencies] @@ -1537,4 +1554,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "3eeceb35dc80d8bb08e391e89fb7ab77c2f6855ab289daef6cd17ef45212003e" +content-hash = "7528995697b6f2225b5a4bf2bfae9b3fd376774eeccaad6a8eb2302228e2cbd7" diff --git a/server/pyproject.toml b/server/pyproject.toml index a1bd6ce..a533b9d 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -22,8 +22,6 @@ boto3 = "^1.34.0" django-storages = "^1.14.0" pillow = "^12.0.0" google-api-python-client = "^2.187.0" - - djangorestframework-simplejwt = "^5.5" [tool.poetry.group.dev.dependencies] From c10105b6f62a94f6af2ff2efa57e659cb35ae898 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 6 Dec 2025 01:32:23 +0000 Subject: [PATCH 14/16] change env variable names --- server/.env.example | 3 +-- ...r_service.example.jsongoogle_calendar_service.example.json} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename server/api/booking/google_calendar/{template_service_account.json => google_calendar_service.example.jsongoogle_calendar_service.example.json} (100%) diff --git a/server/.env.example b/server/.env.example index 7e85673..54e6e43 100644 --- a/server/.env.example +++ b/server/.env.example @@ -19,8 +19,7 @@ AWS_SECRET_ACCESS_KEY=xxxx AWS_STORAGE_BUCKET_NAME=bucket_name AWS_REGION_NAME=ap-southeast-2 -# Google API -GOOGLE_CREDENTIALS_FILE=server/api/booking/google_calendar/template_service_account.json +GOOGLE_CREDENTIALS_FILE=api/booking/google_calendar/google_calendar_service.example.json GOOGLE_CALENDAR_ID=xxxx diff --git a/server/api/booking/google_calendar/template_service_account.json b/server/api/booking/google_calendar/google_calendar_service.example.jsongoogle_calendar_service.example.json similarity index 100% rename from server/api/booking/google_calendar/template_service_account.json rename to server/api/booking/google_calendar/google_calendar_service.example.jsongoogle_calendar_service.example.json From 4e0a17acac237e43d7c0299be13601144bb2cac9 Mon Sep 17 00:00:00 2001 From: Devarsh Date: Sat, 6 Dec 2025 01:33:45 +0000 Subject: [PATCH 15/16] fix .json file name --- ..._service.example.json => google_calendar_service.example.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename server/api/booking/google_calendar/{google_calendar_service.example.jsongoogle_calendar_service.example.json => google_calendar_service.example.json} (100%) diff --git a/server/api/booking/google_calendar/google_calendar_service.example.jsongoogle_calendar_service.example.json b/server/api/booking/google_calendar/google_calendar_service.example.json similarity index 100% rename from server/api/booking/google_calendar/google_calendar_service.example.jsongoogle_calendar_service.example.json rename to server/api/booking/google_calendar/google_calendar_service.example.json From ab847af94b5e68404d81704fb8f38c880e864ed7 Mon Sep 17 00:00:00 2001 From: ErikaKK <491649804@qq.com> Date: Sun, 7 Dec 2025 11:44:35 +0000 Subject: [PATCH 16/16] edit configuration --- server/.env.example | 8 ++++---- server/README.md | 7 ++++++- .../google_calendar/google_calendar_service.example.json | 7 ++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/server/.env.example b/server/.env.example index 54e6e43..fecedcf 100644 --- a/server/.env.example +++ b/server/.env.example @@ -14,13 +14,13 @@ DJANGO_SUPERUSER_PASSWORD=Password123 DJANGO_SUPERUSER_EMAIL=admin@test.com DJANGO_SUPERUSER_USERNAME=admin -AWS_ACCESS_KEY_ID=xxxx -AWS_SECRET_ACCESS_KEY=xxxx +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= AWS_STORAGE_BUCKET_NAME=bucket_name AWS_REGION_NAME=ap-southeast-2 -GOOGLE_CREDENTIALS_FILE=api/booking/google_calendar/google_calendar_service.example.json -GOOGLE_CALENDAR_ID=xxxx +GOOGLE_CREDENTIALS_FILE=api/booking/google_calendar/google_calendar_service.json +GOOGLE_CALENDAR_ID= FRONTEND_URL="http://localhost:3000" \ No newline at end of file diff --git a/server/README.md b/server/README.md index d288e92..aec62be 100644 --- a/server/README.md +++ b/server/README.md @@ -5,8 +5,13 @@ A template Django server. 1. Install dependencies: `poetry install` 2. Run: `python manage.py runserver` or `.\dev.sh` -Note this file needs to be here otherwise poetry won't recognise this as a valid project. +Note this README file needs to be here otherwise poetry won't recognise this as a valid project. # Python version - 3.12 + +# Google calendar integration + +- Place your Google service account JSON key file in `server/api/booking/google_calendar/google_calendar_service.json` +- An example JSON key file is `server/api/booking/google_calendar/google_calendar_service.example.json` diff --git a/server/api/booking/google_calendar/google_calendar_service.example.json b/server/api/booking/google_calendar/google_calendar_service.example.json index 7501614..b83e550 100644 --- a/server/api/booking/google_calendar/google_calendar_service.example.json +++ b/server/api/booking/google_calendar/google_calendar_service.example.json @@ -4,5 +4,10 @@ "private_key_id": "xxx", "private_key": "-----BEGIN PRIVATE KEY-----\nxxx\n-----END PRIVATE KEY-----\n", "client_email": "xxx@xxx.iam.gserviceaccount.com", - "client_id": "xxx" + "client_id": "xxx", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "xxx", + "universe_domain": "googleapis.com" }