diff --git a/backend/app/crud.py b/backend/app/crud.py index 1183de4f78ffc0c9d42fdd7fecd5d9aa2342e8c3..89eb38bc17c2ff80018b9ce3fcd70acfca70ffa6 100644 --- a/backend/app/crud.py +++ b/backend/app/crud.py @@ -2,6 +2,7 @@ import datetime from sqlalchemy.orm import Session import secrets +from utils import datetime_aware import models import schemas from hashing import Hasher @@ -66,6 +67,7 @@ def create_contact(db: Session, user, contact): db.refresh(user) return user + def create_user_survey_weekly(db: Session, user_id: int, survey: schemas.SurveyCreate): db_user_survey_weekly = models.UserSurveyWeekly(user_id=user_id, **survey.dict()) db.add(db_user_survey_weekly) @@ -73,6 +75,7 @@ def create_user_survey_weekly(db: Session, user_id: int, survey: schemas.SurveyC db.refresh(db_user_survey_weekly) return db_user_survey_weekly + def get_contact_sessions(db: Session, user_id: int, contact_id: int): return ( db.query(models.Session) @@ -83,7 +86,9 @@ def get_contact_sessions(db: Session, user_id: int, contact_id: int): def create_session(db: Session, user: schemas.User): + print("before") db_session = models.Session(is_active=True, users=[user]) + print(db_session.created_at) db.add(db_session) db.commit() db.refresh(db_session) @@ -93,8 +98,8 @@ def create_session(db: Session, user: schemas.User): def create_session_with_users( db: Session, users: list[schemas.User], - start_time: datetime.datetime | None = None, - end_time: datetime.datetime | None = None, + start_time: datetime.datetime | None = datetime_aware(), + end_time: datetime.datetime | None = datetime_aware() + datetime.timedelta(hours=12), ): db_session = models.Session( is_active=True, users=users, start_time=start_time, end_time=end_time diff --git a/backend/app/models.py b/backend/app/models.py index 7a03108ed07f1a84d885ca0454c471d0f9d4b03c..04fd61c3cfe762fef5c73b0ebb030da8410f76a4 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -13,6 +13,7 @@ from enum import Enum from database import Base import datetime +from utils import datetime_aware class UserType(Enum): @@ -43,7 +44,7 @@ class User(Base): ui_language = Column(String, default="fr") home_language = Column(String, default="en") target_language = Column(String, default="fr") - birthdate = Column(Integer, default=None) + birthdate = Column(DateTime, default=None) gender = Column(String, default=None) calcom_link = Column(String, default="") study_id = Column(Integer, ForeignKey("studies.id"), default=None) @@ -74,7 +75,7 @@ class UserSurveyWeekly(Base): __tablename__ = "users_survey_weekly" id = Column(Integer, primary_key=True, index=True) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) user_id = Column(Integer, ForeignKey("users.id")) q1 = Column(Float) q2 = Column(Float) @@ -86,11 +87,12 @@ class Session(Base): __tablename__ = "sessions" id = Column(Integer, primary_key=True, index=True) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) is_active = Column(Boolean, default=True) - start_time = Column(DateTime, default=datetime.datetime.now) + start_time = Column(DateTime, default=datetime_aware) end_time = Column( - DateTime, default=lambda: datetime.datetime.now() + datetime.timedelta(hours=12) + DateTime, + default=lambda: datetime_aware() + datetime.timedelta(hours=12), ) language = Column(String, default="fr") @@ -103,7 +105,7 @@ class SessionSatisfy(Base): id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id")) session_id = Column(Integer, ForeignKey("sessions.id")) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) usefullness = Column(Integer) easiness = Column(Integer) remarks = Column(String) @@ -124,7 +126,7 @@ class Message(Base): content = Column(String) user_id = Column(Integer, ForeignKey("users.id")) session_id = Column(Integer, ForeignKey("sessions.id")) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) feedbacks = relationship("MessageFeedback", backref="message") @@ -146,7 +148,7 @@ class MessageFeedback(Base): start = Column(Integer) end = Column(Integer) content = Column(String, default="") - date = Column(DateTime, default=datetime.datetime.now) + date = Column(DateTime, default=datetime_aware) class TestTyping(Base): @@ -154,7 +156,7 @@ class TestTyping(Base): id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), index=True) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) entries = relationship("TestTypingEntry", backref="typing") @@ -227,7 +229,7 @@ class SurveyResponse(Base): id = Column(Integer, primary_key=True, index=True) uuid = Column(String) sid = Column(String) - created_at = Column(DateTime, default=datetime.datetime.now) + created_at = Column(DateTime, default=datetime_aware) survey_id = Column(Integer, ForeignKey("survey_surveys.id")) group_id = Column(Integer, ForeignKey("survey_groups.id")) question_id = Column(Integer, ForeignKey("survey_questions.id")) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 5e75ab7248fdffa0515990cec1a4418883aa759b..fdb16f69464173d8d3dab51d11e22cc2bd52e992 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -1,7 +1,6 @@ -from pydantic import BaseModel +from pydantic import BaseModel, NaiveDatetime from models import UserType -import datetime class User(BaseModel): @@ -14,11 +13,11 @@ class User(BaseModel): ui_language: str | None home_language: str | None target_language: str | None - birthdate: datetime.datetime | None + birthdate: NaiveDatetime | None gender: str | None = None calcom_link: str | None study_id: int | None = None - last_survey: datetime.datetime | None = None + last_survey: NaiveDatetime | None = None class Config: from_attributes = True @@ -34,11 +33,11 @@ class UserCreate(BaseModel): ui_language: str | None = None home_language: str | None = None target_language: str | None = None - birthdate: datetime.datetime | None = None + birthdate: NaiveDatetime | None = None gender: str | None = None calcom_link: str | None = None study_id: int | None = None - last_survey: datetime.datetime | None = None + last_survey: NaiveDatetime | None = None class UserUpdate(BaseModel): @@ -51,11 +50,11 @@ class UserUpdate(BaseModel): ui_language: str | None = None home_language: str | None = None target_language: str | None = None - birthdate: datetime.datetime | None = None + birthdate: NaiveDatetime | None = None gender: str | None = None calcom_link: str | None = None study_id: int | None = None - last_survey: datetime.datetime | None = None + last_survey: NaiveDatetime | None = None class Config: from_attributes = True @@ -77,11 +76,11 @@ class UserSurveyWeeklyCreate(BaseModel): class Session(BaseModel): id: int - created_at: datetime.datetime + created_at: NaiveDatetime is_active: bool users: list[User] - start_time: datetime.datetime - end_time: datetime.datetime + start_time: NaiveDatetime + end_time: NaiveDatetime language: str class Config: @@ -108,7 +107,7 @@ class MessageFeedback(BaseModel): start: int end: int content: str - date: datetime.datetime + date: NaiveDatetime class Config: from_attributes = True @@ -136,7 +135,7 @@ class Message(BaseModel): content: str user_id: int session_id: int - created_at: datetime.datetime + created_at: NaiveDatetime feedbacks: list[MessageFeedback] class Config: @@ -190,7 +189,7 @@ class TestVocabularyCreate(BaseModel): class CalComWebhook(BaseModel): triggerEvent: str - createdAt: datetime.datetime + createdAt: NaiveDatetime payload: dict @@ -269,7 +268,7 @@ class SurveyResponse(BaseModel): id: int uuid: str sid: str - created_at: datetime.datetime + created_at: NaiveDatetime survey_id: int group_id: int question_id: int @@ -282,14 +281,14 @@ class Study(BaseModel): id: int title: str description: str - start_date: datetime.datetime - end_date: datetime.datetime + start_date: NaiveDatetime + end_date: NaiveDatetime chat_duration: int class StudyCreate(BaseModel): title: str description: str - start_date: datetime.datetime - end_date: datetime.datetime + start_date: NaiveDatetime + end_date: NaiveDatetime chat_duration: int = 30 * 60 diff --git a/backend/app/utils.py b/backend/app/utils.py index 799857646ea7f95d53aebff888a3dc7409a0a2ef..95b76652da50e7f938614a3228ece2dcc50947d2 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -1,6 +1,11 @@ -import schemas +import datetime -def check_user_level(user: schemas.User, required_level: schemas.UserType): + +def check_user_level(user, required_level): if user.type > required_level.value: return False - return True \ No newline at end of file + return True + + +def datetime_aware(): + return datetime.datetime.now().astimezone(datetime.timezone.utc) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 908ce7dc6c9d65029530a61fb97d9b5d5d2c0d07..eea21621e7b76784da24491fdbfa0b8b414470a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.1", "dependencies": { + "dayjs": "^1.11.13", "emoji-picker-element": "^1.21.3", "linkify-html": "^4.1.3", "linkifyjs": "^4.1.3", @@ -2210,6 +2211,12 @@ "url": "https://opencollective.com/daisyui" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -4211,9 +4218,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "license": "ISC" }, "node_modules/picomatch": { @@ -4251,9 +4258,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -4271,8 +4278,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4940,9 +4947,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5704,15 +5711,15 @@ "license": "MIT" }, "node_modules/vite": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", - "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -5731,6 +5738,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -5748,6 +5756,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/frontend/package.json b/frontend/package.json index f04376bc49ad1b78bde48d4511dd04cf53496a5c..d51f1c91ecf4c9b2fdca7d704cf123b5295d19a0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,7 @@ }, "type": "module", "dependencies": { + "dayjs": "^1.11.13", "emoji-picker-element": "^1.21.3", "linkify-html": "^4.1.3", "linkifyjs": "^4.1.3", diff --git a/frontend/src/lib/components/users/weeklySurvey.svelte b/frontend/src/lib/components/users/weeklySurvey.svelte index a3f0fcbfc15f9c01a9fa6971e87265ddb6221e14..9cb9d26729c0a02c2f36bd52c46572725087192f 100644 --- a/frontend/src/lib/components/users/weeklySurvey.svelte +++ b/frontend/src/lib/components/users/weeklySurvey.svelte @@ -3,6 +3,7 @@ import config from '$lib/config'; import { t } from '$lib/services/i18n'; import { user } from '$lib/types/user'; + import { formatToUTCDate } from '$lib/utils/date'; import { toastAlert, toastSuccess, toastWarning } from '$lib/utils/toasts'; let open = @@ -27,7 +28,7 @@ toastAlert($t('session.modal.weekly.errors.submit')); } - await $user.patch({ last_survey: new Date() }); + await $user.patch({ last_survey: formatToUTCDate(new Date()) }); open = false; diff --git a/frontend/src/lib/types/user.ts b/frontend/src/lib/types/user.ts index ec8e2de3a81d9234a98bfe570c97931fb087af51..c3a53d8d51951a44bc26bacf92c5952132ca30e2 100644 --- a/frontend/src/lib/types/user.ts +++ b/frontend/src/lib/types/user.ts @@ -258,7 +258,7 @@ export default class User { json.gender, json.calcom_link, json.study_id, - json.last_survey === null ? null : new Date(json.last_survey) + json.last_survey === null ? null : parseToLocalDate(json.last_survey) ); users.update((us) => { diff --git a/frontend/src/lib/utils/date.ts b/frontend/src/lib/utils/date.ts index 665f18c919f03397c6ebb3139079cd82eb8578e5..c8de2aa14b74b6fdfdabaa5bba3e601591e5edeb 100644 --- a/frontend/src/lib/utils/date.ts +++ b/frontend/src/lib/utils/date.ts @@ -100,27 +100,9 @@ export function displayDuration(start: Date, end: Date): string | null { } export function parseToLocalDate(dateStr: string): Date { - const isDST = isInDST(new Date(dateStr + 'Z')); - - const offset = isDST ? '+02:00' : '+01:00'; - - return new Date(dateStr + offset); + return new Date(dateStr + 'Z'); } -function isInDST(date: Date): boolean { - const year = date.getUTCFullYear(); - - // Calculate the last Sunday in March - const startOfDST = new Date(Date.UTC(year, 2, 31)); - const startDay = startOfDST.getUTCDay(); - const lastSundayMarch = 31 - startDay; - const startOfDSTDate = new Date(Date.UTC(year, 2, lastSundayMarch, 1)); - - // Calculate the last Sunday in October - const endOfDST = new Date(Date.UTC(year, 9, 31)); - const endDay = endOfDST.getUTCDay(); - const lastSundayOctober = 31 - endDay; - const endOfDSTDate = new Date(Date.UTC(year, 9, lastSundayOctober, 1)); - - return date >= startOfDSTDate && date < endOfDSTDate; +export function formatToUTCDate(date: Date): string { + return date.toISOString().split('Z')[0]; }