diff --git a/Makefile b/Makefile index 004ce6a4..41ff13c9 100644 --- a/Makefile +++ b/Makefile @@ -65,12 +65,18 @@ docker-lint-python: docker-run docker-logs: docker-compose -f docker-compose-dev.yml logs --follow +docker-migrate-db: + docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FLASK) db migrate --directory $(DOCKER_MIGRATIONS) + docker-rebuild: docker-compose -f docker-compose-dev.yml build --no-cache docker-restart: docker-compose -f docker-compose-dev.yml restart fittrackee +docker-revision: + docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FLASK) db revision --directory $(DOCKER_MIGRATIONS) --message $(MIGRATION_MESSAGE) + docker-run-all: docker-run docker-run-workers docker-run: @@ -107,6 +113,9 @@ docker-test-python: docker-run docker-up: docker-compose -f docker-compose-dev.yml up fittrackee +docker-upgrade-db: + docker-compose -f docker-compose-dev.yml exec fittrackee $(DOCKER_FTCLI) db upgrade + downgrade-db: $(FLASK) db downgrade --directory $(MIGRATIONS) diff --git a/Makefile.config b/Makefile.config index 59deb91b..84c85fb2 100644 --- a/Makefile.config +++ b/Makefile.config @@ -27,6 +27,12 @@ BANDIT = $(VENV)/bin/bandit PYBABEL = $(VENV)/bin/pybabel FTCLI = $(VENV)/bin/ftcli +# Docker env +export DOCKER_APP_DIR = /usr/src/app +export DOCKER_MIGRATIONS = $(DOCKER_APP_DIR)/fittrackee/migrations +export DOCKER_FLASK = /usr/local/bin/flask +export DOCKER_FTCLI = /usr/local/bin/ftcli + # Node env NODE_MODULES = $(PWD)/fittrackee_client/node_modules NPM ?= yarn diff --git a/fittrackee/migrations/versions/26_bf13b8f5589d_add_date_format_user_pref.py b/fittrackee/migrations/versions/26_bf13b8f5589d_add_date_format_user_pref.py new file mode 100644 index 00000000..4c0d3507 --- /dev/null +++ b/fittrackee/migrations/versions/26_bf13b8f5589d_add_date_format_user_pref.py @@ -0,0 +1,28 @@ +"""Add date_format for date display to user preferences in DB + +Revision ID: bf13b8f5589d +Revises: 84d840ce853b +Create Date: 2022-10-25 18:53:59.378423 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bf13b8f5589d' +down_revision = '84d840ce853b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('date_format', sa.String(length=50), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('users', 'date_format') + # ### end Alembic commands ### diff --git a/fittrackee/users/auth.py b/fittrackee/users/auth.py index 34aff6db..4a0efea1 100644 --- a/fittrackee/users/auth.py +++ b/fittrackee/users/auth.py @@ -173,6 +173,7 @@ def register_user() -> Union[Tuple[Dict, int], HttpResponse]: if not user: new_user = User(username=username, email=email, password=password) new_user.timezone = 'Europe/Paris' + new_user.date_format = 'dd/MM/yyyy' new_user.confirmation_token = secrets.token_urlsafe(30) new_user.language = language db.session.add(new_user) @@ -780,6 +781,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]: "bio": null, "birth_date": null, "created_at": "Sun, 14 Jul 2019 14:09:58 GMT", + "date_format": "dd/MM/yyyy", "display_ascent": true, "email": "sam@example.com", "first_name": null, @@ -854,6 +856,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]: } : Union[Dict, HttpResponse]: # get post data post_data = request.get_json() user_mandatory_data = { + 'date_format', 'display_ascent', 'imperial_units', 'language', @@ -883,6 +887,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]: if not post_data or not post_data.keys() >= user_mandatory_data: return InvalidPayloadErrorResponse() + date_format = post_data.get('date_format') display_ascent = post_data.get('display_ascent') imperial_units = post_data.get('imperial_units') language = get_language(post_data.get('language')) @@ -890,6 +895,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]: weekm = post_data.get('weekm') try: + auth_user.date_format = date_format auth_user.display_ascent = display_ascent auth_user.imperial_units = imperial_units auth_user.language = language diff --git a/fittrackee/users/models.py b/fittrackee/users/models.py index 08c9421d..b5585a6e 100644 --- a/fittrackee/users/models.py +++ b/fittrackee/users/models.py @@ -33,6 +33,7 @@ class User(BaseModel): bio = db.Column(db.String(200), nullable=True) picture = db.Column(db.String(255), nullable=True) timezone = db.Column(db.String(50), nullable=True) + date_format = db.Column(db.String(50), nullable=True) # does the week start Monday? weekm = db.Column(db.Boolean, default=False, nullable=False) workouts = db.relationship( @@ -190,6 +191,7 @@ class User(BaseModel): serialized_user = { **serialized_user, **{ + 'date_format': self.date_format, 'display_ascent': self.display_ascent, 'imperial_units': self.imperial_units, 'language': self.language, diff --git a/fittrackee_client/src/components/Administration/AdminUsers.vue b/fittrackee_client/src/components/Administration/AdminUsers.vue index 912b9a2d..5f319475 100644 --- a/fittrackee_client/src/components/Administration/AdminUsers.vue +++ b/fittrackee_client/src/components/Administration/AdminUsers.vue @@ -64,7 +64,7 @@ {{ format( getDateWithTZ(user.created_at, authUser.timezone), - 'dd/MM/yyyy HH:mm' + `${authUser.date_format} HH:mm` ) }} diff --git a/fittrackee_client/src/components/Dashboard/UserRecords/index.vue b/fittrackee_client/src/components/Dashboard/UserRecords/index.vue index c75736c9..e707f043 100644 --- a/fittrackee_client/src/components/Dashboard/UserRecords/index.vue +++ b/fittrackee_client/src/components/Dashboard/UserRecords/index.vue @@ -43,7 +43,8 @@ translateSports(props.sports, t), props.user.timezone, props.user.imperial_units, - props.user.display_ascent + props.user.display_ascent, + props.user.date_format ) ) diff --git a/fittrackee_client/src/components/User/ProfileDisplay/UserInfos.vue b/fittrackee_client/src/components/User/ProfileDisplay/UserInfos.vue index 39b7ab73..a3e0cf47 100644 --- a/fittrackee_client/src/components/User/ProfileDisplay/UserInfos.vue +++ b/fittrackee_client/src/components/User/ProfileDisplay/UserInfos.vue @@ -147,12 +147,12 @@ ) const registrationDate = computed(() => props.user.created_at - ? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm') + ? format(new Date(props.user.created_at), `${props.user.date_format} HH:mm`) : '' ) const birthDate = computed(() => props.user.birth_date - ? format(new Date(props.user.birth_date), 'dd/MM/yyyy') + ? format(new Date(props.user.birth_date), props.user.date_format) : '' ) const isSuccess = computed( diff --git a/fittrackee_client/src/components/User/ProfileDisplay/UserPreferences.vue b/fittrackee_client/src/components/User/ProfileDisplay/UserPreferences.vue index 01f5ea9d..cba7d7b3 100644 --- a/fittrackee_client/src/components/User/ProfileDisplay/UserPreferences.vue +++ b/fittrackee_client/src/components/User/ProfileDisplay/UserPreferences.vue @@ -5,6 +5,8 @@
{{ language }}
{{ $t('user.PROFILE.TIMEZONE') }}:
{{ timezone }}
+
{{ $t('user.PROFILE.DATE_FORMAT') }}:
+
{{ date_format }}
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}:
{{ $t(`user.PROFILE.${fistDayOfWeek}`) }}
{{ $t('user.PROFILE.UNITS.LABEL') }}:
@@ -47,6 +49,9 @@ const timezone = computed(() => props.user.timezone ? props.user.timezone : 'Europe/Paris' ) + const date_format = computed(() => + props.user.date_format ? props.user.date_format : 'dd/MM/yyyy' + ) const display_ascent = computed(() => props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN' ) diff --git a/fittrackee_client/src/components/User/ProfileEdition/UserInfosEdition.vue b/fittrackee_client/src/components/User/ProfileEdition/UserInfosEdition.vue index b1a4f189..15c349aa 100644 --- a/fittrackee_client/src/components/User/ProfileEdition/UserInfosEdition.vue +++ b/fittrackee_client/src/components/User/ProfileEdition/UserInfosEdition.vue @@ -84,7 +84,7 @@ }) const registrationDate = computed(() => props.user.created_at - ? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm') + ? format(new Date(props.user.created_at), `${props.user.date_format} HH:mm`) : '' ) const loading = computed( diff --git a/fittrackee_client/src/components/User/ProfileEdition/UserPreferencesEdition.vue b/fittrackee_client/src/components/User/ProfileEdition/UserPreferencesEdition.vue index 6b4fcc8b..56519786 100644 --- a/fittrackee_client/src/components/User/ProfileEdition/UserPreferencesEdition.vue +++ b/fittrackee_client/src/components/User/ProfileEdition/UserPreferencesEdition.vue @@ -23,6 +23,20 @@ @updateTimezone="updateTZ" /> +
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }} @@ -120,6 +134,7 @@ imperial_units: false, language: '', timezone: 'Europe/Paris', + date_format: 'dd/MM/yyyy', weekm: false, }) const weekStart = [ @@ -170,6 +185,7 @@ userForm.imperial_units = user.imperial_units ? user.imperial_units : false userForm.language = user.language ? user.language : 'en' userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris' + userForm.date_format = user.date_format ? user.date_format : 'dd/MM/yyyy' userForm.weekm = user.weekm ? user.weekm : false } function updateProfile() { diff --git a/fittrackee_client/src/components/User/UserApps/UserApp.vue b/fittrackee_client/src/components/User/UserApps/UserApp.vue index 055245ae..86ffc17f 100644 --- a/fittrackee_client/src/components/User/UserApps/UserApp.vue +++ b/fittrackee_client/src/components/User/UserApps/UserApp.vue @@ -52,7 +52,7 @@ {{ format( getDateWithTZ(client.issued_at, authUser.timezone), - 'dd/MM/yyyy HH:mm' + `${authUser.date_format} HH:mm` ) }} diff --git a/fittrackee_client/src/components/User/UserApps/UserAppsList.vue b/fittrackee_client/src/components/User/UserApps/UserAppsList.vue index 1a3e6815..afac9ff2 100644 --- a/fittrackee_client/src/components/User/UserApps/UserAppsList.vue +++ b/fittrackee_client/src/components/User/UserApps/UserAppsList.vue @@ -11,7 +11,7 @@ {{ format( getDateWithTZ(client.issued_at, authUser.timezone), - 'dd/MM/yyyy HH:mm' + `${authUser.date_format} HH:mm` ) }} diff --git a/fittrackee_client/src/components/Workout/WorkoutCard.vue b/fittrackee_client/src/components/Workout/WorkoutCard.vue index dc3582f7..0efd8913 100644 --- a/fittrackee_client/src/components/Workout/WorkoutCard.vue +++ b/fittrackee_client/src/components/Workout/WorkoutCard.vue @@ -31,7 +31,7 @@ :title=" format( getDateWithTZ(workout.workout_date, user.timezone), - 'dd/MM/yyyy HH:mm' + `${user.date_format} HH:mm` ) " > diff --git a/fittrackee_client/src/components/Workout/WorkoutDetail/index.vue b/fittrackee_client/src/components/Workout/WorkoutDetail/index.vue index da12396b..da89f52f 100644 --- a/fittrackee_client/src/components/Workout/WorkoutDetail/index.vue +++ b/fittrackee_client/src/components/Workout/WorkoutDetail/index.vue @@ -131,7 +131,8 @@ getDateWithTZ( props.workoutData.workout.workout_date, props.authUser.timezone - ) + ), + props.authUser.date_format ) return { ascent: segment ? segment.ascent : workout.ascent, diff --git a/fittrackee_client/src/components/Workouts/WorkoutsList.vue b/fittrackee_client/src/components/Workouts/WorkoutsList.vue index 1f0f7763..071b052c 100644 --- a/fittrackee_client/src/components/Workouts/WorkoutsList.vue +++ b/fittrackee_client/src/components/Workouts/WorkoutsList.vue @@ -86,7 +86,7 @@ {{ format( getDateWithTZ(workout.workout_date, user.timezone), - 'dd/MM/yyyy HH:mm' + `${user.date_format} HH:mm` ) }} diff --git a/fittrackee_client/src/locales/de/user.json b/fittrackee_client/src/locales/de/user.json index 608e5ac0..6d3beaa6 100644 --- a/fittrackee_client/src/locales/de/user.json +++ b/fittrackee_client/src/locales/de/user.json @@ -52,6 +52,7 @@ "BACK_TO_PROFILE": "Zurück zum Profil", "BIO": "Biographie", "BIRTH_DATE": "Geburtsdatum", + "DATE_FORMAT": "Datumsanzeigeformat", "EDIT": "Profil bearbeiten", "EDIT_PREFERENCES": "Einstellungen ändern", "EDIT_SPORTS_PREFERENCES": "Einstellungen für Sportarten ändern", diff --git a/fittrackee_client/src/locales/en/user.json b/fittrackee_client/src/locales/en/user.json index 01c0e31d..66987cb8 100644 --- a/fittrackee_client/src/locales/en/user.json +++ b/fittrackee_client/src/locales/en/user.json @@ -52,6 +52,7 @@ "BACK_TO_PROFILE": "Back to profile", "BIO": "Bio", "BIRTH_DATE": "Birth date", + "DATE_FORMAT": "Date display format", "EDIT": "Edit profile", "EDIT_PREFERENCES": "Edit preferences", "EDIT_SPORTS_PREFERENCES": "Edit sports preferences", diff --git a/fittrackee_client/src/locales/fr/user.json b/fittrackee_client/src/locales/fr/user.json index 5bcd2de3..6841763c 100644 --- a/fittrackee_client/src/locales/fr/user.json +++ b/fittrackee_client/src/locales/fr/user.json @@ -52,6 +52,7 @@ "BACK_TO_PROFILE": "Revenir au profil", "BIO": "Bio", "BIRTH_DATE": "Date de naissance", + "DATE_FORMAT": "Format d'affichage de la date", "EDIT": "Modifier le profil", "EDIT_PREFERENCES": "Modifier les préférences", "EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports", diff --git a/fittrackee_client/src/types/user.ts b/fittrackee_client/src/types/user.ts index a2412237..f73464ab 100644 --- a/fittrackee_client/src/types/user.ts +++ b/fittrackee_client/src/types/user.ts @@ -29,6 +29,7 @@ export interface IAuthUserProfile extends IUserProfile { imperial_units: boolean language: string | null timezone: string + date_format: string weekm: boolean } @@ -64,6 +65,7 @@ export interface IUserPreferencesPayload { imperial_units: boolean language: string timezone: string + date_format: string weekm: boolean } diff --git a/fittrackee_client/src/utils/records.ts b/fittrackee_client/src/utils/records.ts index b5d0662a..4081fa35 100644 --- a/fittrackee_client/src/utils/records.ts +++ b/fittrackee_client/src/utils/records.ts @@ -7,7 +7,8 @@ import { convertDistance, units } from '@/utils/units' export const formatRecord = ( record: IRecord, tz: string, - useImperialUnits: boolean + useImperialUnits: boolean, + date_format: string ): Record => { const distanceUnitFrom: TUnit = 'km' const distanceUnitTo: TUnit = useImperialUnits @@ -53,7 +54,7 @@ export const formatRecord = ( ) } return { - workout_date: formatWorkoutDate(getDateWithTZ(record.workout_date, tz)) + workout_date: formatWorkoutDate(getDateWithTZ(record.workout_date, tz), date_format) .workout_date, workout_id: record.workout_id, id: record.id, @@ -73,7 +74,8 @@ export const getRecordsBySports = ( translatedSports: ITranslatedSport[], tz: string, useImperialUnits: boolean, - display_ascent: boolean + display_ascent: boolean, + date_format: string ): IRecordsBySports => records .filter((r) => (display_ascent ? true : r.record_type !== 'HA')) @@ -88,7 +90,7 @@ export const getRecordsBySports = ( } } sportList[sport.translatedLabel].records.push( - formatRecord(record, tz, useImperialUnits) + formatRecord(record, tz, useImperialUnits, date_format) ) } return sportList diff --git a/fittrackee_client/tests/unit/utils/records.spec.ts b/fittrackee_client/tests/unit/utils/records.spec.ts index 21c0f450..f363a214 100644 --- a/fittrackee_client/tests/unit/utils/records.spec.ts +++ b/fittrackee_client/tests/unit/utils/records.spec.ts @@ -19,6 +19,7 @@ describe('formatRecord', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'dd/MM/yyyy' }, expected: { id: 9, @@ -41,6 +42,7 @@ describe('formatRecord', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/MM/dd' }, expected: { id: 10, @@ -63,6 +65,7 @@ describe('formatRecord', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/MM/dd' }, expected: { id: 11, @@ -85,12 +88,13 @@ describe('formatRecord', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'dd/MM/yyyy' }, expected: { id: 12, record_type: 'MS', value: '18 km/h', - workout_date: '2019/07/08', + workout_date: '08/07/2019', workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, }, @@ -107,12 +111,13 @@ describe('formatRecord', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'MMM. do, yyyy' }, expected: { id: 13, record_type: 'HA', value: '100 m', - workout_date: '2019/07/07', + workout_date: 'Jul. 7th, 2019', workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, }, @@ -123,7 +128,8 @@ describe('formatRecord', () => { formatRecord( testParams.inputParams.record, testParams.inputParams.timezone, - false + false, + testParams.inputParams.date_format ), testParams.expected ) @@ -146,6 +152,7 @@ describe('formatRecord after conversion', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { id: 9, @@ -168,6 +175,7 @@ describe('formatRecord after conversion', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { id: 10, @@ -190,6 +198,7 @@ describe('formatRecord after conversion', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { id: 11, @@ -212,6 +221,7 @@ describe('formatRecord after conversion', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { id: 12, @@ -234,6 +244,7 @@ describe('formatRecord after conversion', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, timezone: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { id: 13, @@ -250,7 +261,8 @@ describe('formatRecord after conversion', () => { formatRecord( testParams.inputParams.record, testParams.inputParams.timezone, - true + true, + testParams.inputParams.date_format ), testParams.expected ) @@ -272,7 +284,8 @@ describe('formatRecord (invalid record type)', () => { workout_id: 'hvYBqYBRa7wwXpaStWR4V2', }, 'Europe/Paris', - false + false, + 'yyyy/dd/MM' ) ).to.throw( 'Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "M"' @@ -287,6 +300,7 @@ describe('getRecordsBySports', () => { input: { records: [], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: {}, }, @@ -305,6 +319,7 @@ describe('getRecordsBySports', () => { }, ], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { 'Cycling (Sport)': { @@ -355,6 +370,7 @@ describe('getRecordsBySports', () => { }, ], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { 'Cycling (Sport)': { @@ -401,7 +417,8 @@ describe('getRecordsBySports', () => { translatedSports, testParams.input.tz, false, - true + true, + testParams.input.date_format ), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -418,6 +435,7 @@ describe('getRecordsBySports after conversion', () => { input: { records: [], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: {}, }, @@ -436,6 +454,7 @@ describe('getRecordsBySports after conversion', () => { }, ], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { 'Cycling (Sport)': { @@ -486,6 +505,7 @@ describe('getRecordsBySports after conversion', () => { }, ], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { 'Cycling (Sport)': { @@ -532,7 +552,8 @@ describe('getRecordsBySports after conversion', () => { translatedSports, testParams.input.tz, true, - true + true, + testParams.input.date_format ), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -549,6 +570,7 @@ describe('getRecordsBySports with HA record', () => { input: { records: [], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: {}, }, @@ -576,6 +598,7 @@ describe('getRecordsBySports with HA record', () => { }, ], tz: 'Europe/Paris', + date_format: 'yyyy/dd/MM' }, expected: { 'Cycling (Sport)': { @@ -602,7 +625,8 @@ describe('getRecordsBySports with HA record', () => { translatedSports, testParams.input.tz, false, - false + false, + testParams.input.date_format ), // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore