From 7e4012bf09c53089a0fbc88f0852df1399cf4f90 Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 23:24:48 +0300 Subject: [PATCH 1/8] add handler on `user not found` exception --- services/backend/api/v1/auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/backend/api/v1/auth.py b/services/backend/api/v1/auth.py index 1d36ce9..4284e49 100644 --- a/services/backend/api/v1/auth.py +++ b/services/backend/api/v1/auth.py @@ -19,7 +19,10 @@ class BearerAuth(HttpBearer): except Exception: raise AuthenticationError - user = User.objects.get(id=data["id"]) + try: + user = User.objects.get(id=data["id"]) + except User.DoesNotExist: + raise AuthenticationError return user @staticmethod From 03210c70e470a465d95f39cf79e0a4a9cd276e4c Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 23:26:12 +0300 Subject: [PATCH 2/8] (scope): [body] [footer(s)] --- docs/docusaurus.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 6665170..758ca67 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -60,7 +60,7 @@ const config: Config = { items: [ { label: 'Начало', - to: '/docs/intro', + to: '/docs/docs/intro', }, ], }, From c021ae19e7e4f1d9b4344f104a4d716d6c6cd32c Mon Sep 17 00:00:00 2001 From: moolcoov Date: Sat, 1 Mar 2025 23:26:43 +0300 Subject: [PATCH 3/8] fix: signup screen --- services/frontend/src/components/ui/icons/datarush.tsx | 9 ++++++++- services/frontend/src/pages/Login/index.tsx | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/services/frontend/src/components/ui/icons/datarush.tsx b/services/frontend/src/components/ui/icons/datarush.tsx index ecc1627..0cebbe9 100644 --- a/services/frontend/src/components/ui/icons/datarush.tsx +++ b/services/frontend/src/components/ui/icons/datarush.tsx @@ -1,10 +1,17 @@ -const DataRush = ({ size = 52 }: { size?: number }) => { +const DataRush = ({ + size = 52, + className, +}: { + size?: number; + className?: string; +}) => { return ( { }, []); return ( -
- +
+

Добро пожаловать! From 62e44aba4cb495162cb3a22bc2c0f200adc46242 Mon Sep 17 00:00:00 2001 From: moolcoov Date: Sat, 1 Mar 2025 23:30:44 +0300 Subject: [PATCH 4/8] fix: spacing --- services/frontend/src/widgets/navbar-layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/frontend/src/widgets/navbar-layout.tsx b/services/frontend/src/widgets/navbar-layout.tsx index a8cd738..24c1bfe 100644 --- a/services/frontend/src/widgets/navbar-layout.tsx +++ b/services/frontend/src/widgets/navbar-layout.tsx @@ -6,7 +6,7 @@ const NavbarLayout = () => { <>
-
+
From fc78972b845b1182b90bb56f03e1df53eedb8fc6 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 1 Mar 2025 23:34:42 +0300 Subject: [PATCH 5/8] (scope): [body] [footer(s)] --- docs/docusaurus.config.ts | 128 ++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 758ca67..03ed198 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,77 +1,81 @@ -import {themes as prismThemes} from 'prism-react-renderer'; -import type {Config} from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; +import { themes as prismThemes } from "prism-react-renderer"; +import type { Config } from "@docusaurus/types"; +import type * as Preset from "@docusaurus/preset-classic"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const config: Config = { - title: 'DataRush', - tagline: 'Изучите основы анализа данных здесь!', - favicon: 'https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg', + title: "DataRush", + tagline: "Изучите основы анализа данных здесь!", + favicon: "https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg", - url: 'https://prod-team-15-2pc0i3lc.final.prodcontest.ru', - baseUrl: '/docs/', + url: "https://prod-team-15-2pc0i3lc.final.prodcontest.ru", + baseUrl: "/docs/", - organizationName: 'megazord', - projectName: 'megazord', + organizationName: "megazord", + projectName: "megazord", - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", - i18n: { - defaultLocale: 'ru', - locales: ['ru'], - }, - - presets: [ - [ - 'classic', - { - docs: {}, - theme: { - customCss: './src/css/custom.css', - }, - } satisfies Preset.Options, - ], - ], - - themeConfig: { - image: 'https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg', - navbar: { - title: 'DataRush', - logo: { - alt: 'DataRush', - src: 'https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg', - }, - items: [ - { - type: 'docSidebar', - sidebarId: 'defaultSidebar', - position: 'left', - label: 'Документация', - }, - ], + i18n: { + defaultLocale: "ru", + locales: ["ru"], }, - footer: { - style: 'dark', - links: [ - { - title: 'Документация', - items: [ + + presets: [ + [ + "classic", { - label: 'Начало', - to: '/docs/docs/intro', + docs: { + routeBasePath: "/", + }, + theme: { + customCss: "./src/css/custom.css", + }, + } satisfies Preset.Options, + ], + ], + + themeConfig: { + image: "https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg", + navbar: { + title: "DataRush", + logo: { + alt: "DataRush", + src: "https://prod-team-15-2pc0i3lc.final.prodcontest.ru/dr.svg", }, - ], + items: [ + { + type: "docSidebar", + sidebarId: "defaultSidebar", + position: "left", + label: "Документация", + }, + ], }, - ], - copyright: `Создано для Megazord ♥`, - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - }, - } satisfies Preset.ThemeConfig, + footer: { + style: "dark", + links: [ + { + title: "Документация", + items: [ + { + label: "Начало", + to: "/intro", + }, + ], + }, + ], + copyright: `Создано для Megazord ♥`, + }, + prism: { + theme: prismThemes.github, + darkTheme: prismThemes.dracula, + }, + } satisfies Preset.ThemeConfig, + + staticDirectories: ["static"], }; export default config; From e96f14eb829dcd7ea08b26c3b66ba60ccc6b4ac6 Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 23:37:03 +0300 Subject: [PATCH 6/8] fix intro.md text --- docs/docs/intro.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 5fabaac..0e5fae9 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -2,4 +2,6 @@ sidebar_position: 1 --- -# Начала! \ No newline at end of file +# Начало! + +Выбирай интересующий раздел слева и просвещайся! From b8158e4bbab9b886a6255e114fdea03379d1d38c Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 23:47:10 +0300 Subject: [PATCH 7/8] add test on getting competitions --- services/backend/apps/competition/tests.py | 158 +++++++++++++++++++++ services/backend/pyproject.toml | 1 + 2 files changed, 159 insertions(+) diff --git a/services/backend/apps/competition/tests.py b/services/backend/apps/competition/tests.py index f9508a2..196675f 100644 --- a/services/backend/apps/competition/tests.py +++ b/services/backend/apps/competition/tests.py @@ -1,5 +1,8 @@ import uuid +from datetime import timedelta, datetime, tzinfo +from dateutil.parser import isoparse +import pytz from django.contrib.auth.hashers import make_password from django.test import TestCase @@ -106,3 +109,158 @@ class CompetitionEndpointTests(TestCase): HTTP_AUTHORIZATION=header ) self.assertEqual(response.status_code, expected_status) + + +class CompetitionsEndpointTests(TestCase): + def setUp(self): + self.user = User.objects.create( + email="user@example.com", + password=make_password("password123"), + username="t1wk4" + ) + + resp = self.client.post( + "/api/v1/sign-in", + data={"email": self.user.email, "password": "password123"}, + content_type="application/json", + ).json() + token = resp["token"] + + # Create test competitions + now = datetime.now(tz=pytz.utc) + self.competitions = [] + for i in range(1, 6): + competition = Competition.objects.create( + title=f"Competition {i}", + description=f"Description {i}", + type=Competition.CompetitionType.SOLO, + participation_type=( + Competition.CompetitionParticipationType.EDU if i % 2 == 0 + else Competition.CompetitionParticipationType.COMPETITIVE + ), + start_date=(now + timedelta(days=i)).isoformat(), + end_date=(now + timedelta(days=i + 7)).isoformat(), + ) + if i <= 2: + competition.participants.add(self.user) + self.competitions.append(competition) + + self.valid_headers = { + "HTTP_AUTHORIZATION": f"Bearer {token}" + } + + def get_url(self, params=None): + base_url = "/api/v1/competitions" + return f"{base_url}?{params}" if params else base_url + + def test_get_participating_competitions(self): + """Test filtering competitions where user is participating""" + response = self.client.get( + self.get_url("is_participating=true"), + **self.valid_headers + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(len(data), 2) + self.assertEqual( + {item["id"] for item in data}, + {str(self.competitions[0].id), str(self.competitions[1].id)} + ) + + def test_competition_type_values(self): + """Test competition type choices are respected""" + response = self.client.get( + self.get_url("is_participating=true"), + **self.valid_headers + ) + + for item in response.json(): + self.assertEqual(item["type"], "solo") + + def test_participation_type_values(self): + """Test participation type alternates between edu/competitive""" + response = self.client.get( + self.get_url("is_participating=false"), + **self.valid_headers + ) + + types = [item["participation_type"] for item in response.json()] + self.assertCountEqual( + types, + ["competitive", "edu", "competitive"] + ) + + def test_datetime_formatting(self): + """Test start/end date ISO formatting""" + response = self.client.get( + self.get_url("is_participating=true"), + **self.valid_headers + ) + + for item in response.json(): + if item["start_date"]: + try: + isoparse(item["start_date"]) + except ValueError: + self.fail("Invalid start_date format") + if item["end_date"]: + try: + isoparse(item["end_date"]) + except ValueError: + self.fail("Invalid end_date format") + + def test_competition_metadata(self): + """Test competition metadata fields""" + response = self.client.get( + self.get_url("is_participating=true"), + **self.valid_headers + ) + + item = response.json()[0] + self.assertEqual(item["title"], "Competition 1") + self.assertEqual(item["description"], "Description 1") + self.assertEqual(item["type"], "solo") + self.assertEqual(item["participation_type"], "competitive") + + def test_verbose_name_consistency(self): + """Test model verbose names don't affect API schema""" + response = self.client.get( + self.get_url("is_participating=true"), + **self.valid_headers + ) + + item = response.json()[0] + self.assertNotIn("название", item) # Russian verbose name + self.assertIn("title", item) # Actual API field name + + def test_null_dates_handling(self): + """Test competitions with null dates""" + competition = Competition.objects.create( + title="No Dates Competition", + description="Test competition", + type=Competition.CompetitionType.SOLO, + participation_type=Competition.CompetitionParticipationType.EDU + ) + + response = self.client.get( + self.get_url("is_participating=false"), + **self.valid_headers + ) + + test_item = next( + item for item in response.json() + if item["id"] == str(competition.id) + ) + self.assertIsNone(test_item["start_date"]) + self.assertIsNone(test_item["end_date"]) + + def test_participation_status_filtering(self): + """Test filtering by participation_type""" + response = self.client.get( + self.get_url("is_participating=false"), + **self.valid_headers + ) + + data = response.json() + self.assertEqual(len(data), 3) diff --git a/services/backend/pyproject.toml b/services/backend/pyproject.toml index def2053..056017b 100644 --- a/services/backend/pyproject.toml +++ b/services/backend/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "psycopg2-binary>=2.9.10", "pydantic>=2.10.5", "pyjwt>=2.10.1", + "python-dateutil>=2.9.0.post0", "python-gettext>=5.0", "python-json-logger>=3.2.1", "pytz>=2024.2", From 868254449c9c06fb46db179cdd6e8e73fb6fde7e Mon Sep 17 00:00:00 2001 From: Timur Date: Sat, 1 Mar 2025 23:55:18 +0300 Subject: [PATCH 8/8] return verbose name to completion task --- services/backend/apps/task/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py index dbee8da..72147c3 100644 --- a/services/backend/apps/task/models.py +++ b/services/backend/apps/task/models.py @@ -60,6 +60,7 @@ class CompetitionTask(BaseModel): return self.title class Meta: + verbose_name = "задание" verbose_name_plural = "задания"