From 24ce580bd2e1ff7d7362d73aec45de410d016a83 Mon Sep 17 00:00:00 2001 From: andrei Date: Sat, 31 May 2025 18:25:02 +0500 Subject: [PATCH] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=D0=B5=D1=82=D0=BA=D0=B8=20=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API/app/contollers/rss_router.py | 17 ++ API/app/domain/entities/activity_item.py | 6 + API/app/infrastructure/rss_service.py | 39 ++++ API/app/main.py | 2 + WEB/package-lock.json | 31 ++- WEB/package.json | 3 +- WEB/src/pages/HomePage.vue | 283 +++++++++++++---------- 7 files changed, 258 insertions(+), 123 deletions(-) create mode 100644 API/app/contollers/rss_router.py create mode 100644 API/app/domain/entities/activity_item.py create mode 100644 API/app/infrastructure/rss_service.py diff --git a/API/app/contollers/rss_router.py b/API/app/contollers/rss_router.py new file mode 100644 index 0000000..95ef45e --- /dev/null +++ b/API/app/contollers/rss_router.py @@ -0,0 +1,17 @@ +from fastapi import APIRouter + +from app.domain.entities.activity_item import ActivityItem +from app.infrastructure.rss_service import RSSService + +router = APIRouter() + + +@router.get( + "/{username}", + response_model=list[ActivityItem], + summary="Get the user's RSS activity", + description="Returns an array with the date and number of activities in the last 30 days" +) +def get_user_rss(username: str): + rss_service = RSSService() + return rss_service.get_rss_by_username(username) diff --git a/API/app/domain/entities/activity_item.py b/API/app/domain/entities/activity_item.py new file mode 100644 index 0000000..72cf910 --- /dev/null +++ b/API/app/domain/entities/activity_item.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class ActivityItem(BaseModel): + date: str + count: int diff --git a/API/app/infrastructure/rss_service.py b/API/app/infrastructure/rss_service.py new file mode 100644 index 0000000..724d745 --- /dev/null +++ b/API/app/infrastructure/rss_service.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter, HTTPException +import requests +import xml.etree.ElementTree as ET +from datetime import datetime, timedelta + + +class RSSService: + @staticmethod + def get_rss_by_username(username: str): + url = f"https://git.numerum.team/{username}.rss" + response = requests.get(url) + + if response.status_code != 200: + raise HTTPException(status_code=400, detail="Не удалось загрузить RSS") + + root = ET.fromstring(response.text) + items = root.findall(".//item") + + activity_map = {} + + for item in items: + pub_date_elem = item.find("pubDate") + if pub_date_elem is not None: + pub_date_str = pub_date_elem.text.strip() + pub_date = datetime.strptime(pub_date_str, "%a, %d %b %Y %H:%M:%S %z") + date_str = pub_date.strftime("%Y-%m-%d") + activity_map[date_str] = activity_map.get(date_str, 0) + 1 + + today = datetime.now(pub_date.tzinfo) + activity_days = 365 + result = [] + + for i in range(activity_days - 1, -1, -1): + day = today - timedelta(days=i) + date_str = day.strftime("%Y-%m-%d") + count = activity_map.get(date_str, 0) + result.append({"date": date_str, "count": count}) + + return result diff --git a/API/app/main.py b/API/app/main.py index 38a2a14..4235d4d 100644 --- a/API/app/main.py +++ b/API/app/main.py @@ -8,6 +8,7 @@ from app.contollers.project_files_router import router as project_files_router from app.contollers.project_members_router import router as project_members_router from app.contollers.projects_router import router as projects_router from app.contollers.register_router import router as register_router +from app.contollers.rss_router import router as rss_router from app.contollers.teams_router import router as team_router from app.contollers.users_router import router as users_router from app.settings import settings @@ -31,6 +32,7 @@ def start_app(): api_app.include_router(project_members_router, prefix=f'{settings.PREFIX}/project_members', tags=['project_members']) api_app.include_router(projects_router, prefix=f'{settings.PREFIX}/projects', tags=['projects']) api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register']) + api_app.include_router(rss_router, prefix=f'{settings.PREFIX}/rss', tags=['rss_router']) api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams']) api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users']) diff --git a/WEB/package-lock.json b/WEB/package-lock.json index f08e674..9dc3887 100644 --- a/WEB/package-lock.json +++ b/WEB/package-lock.json @@ -12,7 +12,8 @@ "axios": "^1.9.0", "quasar": "^2.18.1", "vue": "^3.5.13", - "vue-router": "^4.5.1" + "vue-router": "^4.5.1", + "xml2js": "^0.6.2" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.2", @@ -1849,6 +1850,12 @@ "node": ">=14.0.0" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2038,6 +2045,28 @@ "peerDependencies": { "vue": "^3.2.0" } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } } } } diff --git a/WEB/package.json b/WEB/package.json index b7530aa..40f81b9 100644 --- a/WEB/package.json +++ b/WEB/package.json @@ -13,7 +13,8 @@ "axios": "^1.9.0", "quasar": "^2.18.1", "vue": "^3.5.13", - "vue-router": "^4.5.1" + "vue-router": "^4.5.1", + "xml2js": "^0.6.2" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.2", diff --git a/WEB/src/pages/HomePage.vue b/WEB/src/pages/HomePage.vue index 0251249..c5cdbc3 100644 --- a/WEB/src/pages/HomePage.vue +++ b/WEB/src/pages/HomePage.vue @@ -60,43 +60,60 @@
- +
- + -
Активность команды и история коммитов
+
Активность команды за последний год
-
-
-
- {{ item.date.slice(5) }} -
-
- -
-
+ +
+
+ {{ monthLabel }}
-
-
- {{ commit.date }}: {{ commit.message }} +
+ +
+
+ {{ day }} +
+ + +
+
+
+
+
+
+ + +
+ Меньше + Больше
@@ -116,9 +133,12 @@ @@ -218,25 +320,19 @@ function getActivityColor(count) { .team-name-card { border-radius: 20px; background: #ede9fe; - box-shadow: - 0 8px 24px rgba(124, 58, 237, 0.18), - 0 2px 8px rgba(124, 58, 237, 0.12); + box-shadow: 0 8px 24px rgba(124, 58, 237, 0.18), 0 2px 8px rgba(124, 58, 237, 0.12); max-width: 900px; } .violet-card { border-radius: 22px; background: #ede9fe; - box-shadow: - 0 8px 24px rgba(124, 58, 237, 0.18), - 0 2px 8px rgba(124, 58, 237, 0.12); + box-shadow: 0 8px 24px rgba(124, 58, 237, 0.18), 0 2px 8px rgba(124, 58, 237, 0.12); transition: box-shadow 0.3s ease, transform 0.3s ease; } .violet-card:hover { - box-shadow: - 0 14px 40px rgba(124, 58, 237, 0.30), - 0 6px 16px rgba(124, 58, 237, 0.20); + box-shadow: 0 14px 40px rgba(124, 58, 237, 0.30), 0 6px 16px rgba(124, 58, 237, 0.20); transform: translateY(-6px); cursor: pointer; } @@ -249,81 +345,26 @@ function getActivityColor(count) { justify-content: center; } -.horizontal-scroll { - display: flex; - overflow-x: auto; - padding-bottom: 6px; - -webkit-overflow-scrolling: touch; -} -.horizontal-scroll::-webkit-scrollbar { - height: 6px; -} -.horizontal-scroll::-webkit-scrollbar-thumb { - background: rgba(124, 58, 237, 0.4); - border-radius: 3px; -} - .activity-card { max-width: 920px; border-radius: 20px; padding: 16px; } -.activity-container { - /* align-items: center; */ -} - -.dates-column { - display: flex; - flex-direction: column; - justify-content: flex-start; - color: #5e35b1; - font-weight: 600; - user-select: none; - font-size: 11px; -} - -.activity-date { - margin-bottom: 4px; - text-align: right; - white-space: nowrap; -} - .activity-grid { display: flex; - flex-wrap: nowrap; - overflow-x: auto; - user-select: none; - scrollbar-width: thin; - scrollbar-color: rgba(124, 58, 237, 0.4) transparent; + flex-direction: row; } -.activity-grid::-webkit-scrollbar { - height: 10px; -} -.activity-grid::-webkit-scrollbar-thumb { - background: rgba(124, 58, 237, 0.4); - border-radius: 5px; + +.activity-week { + display: flex; + flex-direction: column; } .activity-square { - width: 16px; - height: 16px; border-radius: 4px; - margin-right: 2px; box-shadow: 0 0 3px rgba(124, 58, 237, 0.3); cursor: default; transition: background-color 0.3s ease; } - -.commits-list { - background: #f5f3ff; - border-radius: 12px; - padding: 8px 12px; - font-family: 'Courier New', Courier, monospace; - color: #4b0082; - box-shadow: inset 0 0 6px #c7b3f7; -} -.commit-item strong { - color: #7c3aed; -} - + \ No newline at end of file