From ad3a763326fae4c4c06d7e7dee740fafc80aa780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B0=D1=82=D1=83=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Sat, 1 Nov 2025 18:35:23 +0300 Subject: [PATCH 1/4] update deps --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2b048cb..d4d609c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@eslint/eslintrc": "^3.3.0", "@eslint/js": "^9.22.0", "@mdi/font": "^7.4.47", - "@profcomff/api-uilib": "^2025.10.15", + "@profcomff/api-uilib": "^2025.11.1-test", "@types/node": "^22.13.10", "@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/parser": "^8.26.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e1370c..8282f77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,8 +31,8 @@ importers: specifier: ^7.4.47 version: 7.4.47 '@profcomff/api-uilib': - specifier: ^2025.10.15 - version: 2025.10.15 + specifier: ^2025.11.1-test + version: 2025.11.1-test '@types/node': specifier: ^22.13.10 version: 22.13.10 @@ -649,8 +649,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@profcomff/api-uilib@2025.10.15': - resolution: {integrity: sha512-M4/PlFfMR85bBNXyY3ejTaHlvcLc0Y5Oq4PQUoQm+oYW/zEOLJrAb2UjGOw9ZUUEcQTRpW1rs/2LCqeMpGGZVg==} + '@profcomff/api-uilib@2025.11.1-test': + resolution: {integrity: sha512-vNQOQABilVf06vJFivzBQB8SAfjA96yqzRFgJXBwUlTdgjCBn9A5EwMbExtQl2jpSFDaXK/QM04iKGULRufrxw==} '@redocly/ajv@8.11.2': resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} @@ -2872,7 +2872,7 @@ snapshots: '@pkgr/core@0.1.1': {} - '@profcomff/api-uilib@2025.10.15': + '@profcomff/api-uilib@2025.11.1-test': dependencies: openapi-fetch: 0.10.6 From 1982b1fdce0151de68c5987f0496a236e152b2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B0=D1=82=D1=83=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Sat, 1 Nov 2025 18:46:54 +0300 Subject: [PATCH 2/4] auth and doc --- .env.development | 2 + README.md | 133 +++++++++++++++------------------ src/App.vue | 3 +- src/store/profileStore.ts | 151 ++++++++++++++++++++++++-------------- 4 files changed, 157 insertions(+), 132 deletions(-) create mode 100644 .env.development diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..68bb9f3 --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +VITE_AUTH_API_BASE_URL=https://api.test.profcomff.com +VITE_TVOI_FF_TOKEN=null \ No newline at end of file diff --git a/README.md b/README.md index 46df55a..343642a 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,94 @@ -# Мини-приложение рейтинга преподавателей +# Rating UI -- интерфейс сервиса оценки преподавателей "Дубинушка" -[](https://easycode.profcomff.com/templates/docker-node/workspace?mode=manual¶m.Repository+URL=https://github.com/profcomff/rating-ui.git¶m.Working+directory=rating-ui) +Фронтенд для приложения по оценке преподавателей на Vue.js с TypeScript и Vite. -В этом репозитории представлен пример простейшего фронтенд-сервиса с Vue.js. +Большая часть команд и инструкций написаны для Linux и MacOS, но должны работать и на Windows. - -*Большая часть команд и инструкций написаны для операционных систем Linux и MacOS.* +--- ## Зависимости -- Node.js v18 – среда исполнения на языке JavaScript/TypeScript -- NPM – консольный менеджер пакетов для установки библиотек (идет в поставке с Node.js) -- Vue.js – фраемворк для разработки фронтенда -- Vite – консольный менеджер для удобства работы с Vue.js -## Frontend разработка на Vue.js +- **Node.js** >= v18 – среда выполнения JavaScript/TypeScript +- **PNPM** – менеджер пакетов (можно использовать NPM, но мы рекомендуем PNPM) +- **Vue.js** – фреймворк для фронтенда +- **Vite** – инструмент сборки и локального сервера +- **Docker** – для сборки и деплоя -Фронтенд (англ. front end, frontend) — презентационная часть web приложения, её пользовательский -интерфейс и связанные с ним компоненты. +--- -В данном примере используется популярный фраемворк [Vue.js](https://vuejs.org/). Разработка ведется -на языке TypeScript. +## Разработка +1. Склонируй проект: + 1. `cd /path/to/folder` + 2. `git clone https://github.com/profcomff/rental-ui.git project-folder-name` -## Разработка +2. Установи зависимости: `pnpm install` -Для удобства разработки в VS Code создан [workspace](../frontend.code-workspace) с преднастроенными -командами и рекомендованными расширениями для работы. +3. Запусти локальный dev-сервер: `pnpm dev` -Перед началом работы нужно установить зависимости командой -``` -pnpm install -``` +Приложение будет доступно на http://localhost:5173. -Для локального запуска необходимо выполнить команду -``` -pnpm dev -``` +### Форматирование +1. Открыть файл с расширением `.vue`. +2. Открыть палитру команд (Help > Show All Commands или `Ctrl+Shift+P`) +3. Ввести и выбрать `Format document with`. +4. Выбрать `Configure Default Formatter`. +5. Выбрать `Prettier`. -### Важные замечания по коду -- Приложение предполагает, что вы запускаете его из Твой ФФ. Чтобы имитировать запуск из Твой ФФ: +Теперь можно форматировать файлы с помощью `Shift+Alt+F`. Еще можно настроить автоформатирование при сохранении файла (`File > Preferences > Settings`, Format on save). - 1. Зарегистрируйтесь в тестовой среде «Твой ФФ!» по адресу https://app.test.profcomff.com/auth. Подтвердите аккаунт и войдите в пользователя (при необходимости). +### Локальная авторизация - 2. Перейдите в панель администрирования https://app.test.profcomff.com/admin. +Для разработки с определенными скоупами можно проделать следующие шаги: - 3. Нажмите кнопку «скопировать параметры приложения». +1. Переходим в [тестовое приложение](https://app.test.profcomff.com/admin) и копируем токен. +2. Вставляем токен в .env.development +3. В `/src/store/profileStore.ts` в функции `setupDevAdminSession` проверяем, есть ли все нужные нам скоупы. +4. В `src/App.vue` в хуке `onMounted`: - 4. Подставьте полученную строку после адреса вашего приложения в браузере + ```typescript + if (import.meta.env.MODE === 'development') await profileStore.setupDevAdminSession(null); + ``` - Код, который обрабатывает данные пользователя из URL находится здесь: https://github.com/profcomff/app-template/blob/1070d4370d37529702d7499baeaf145ba4cd9e62/frontend/src/store/profileStore.ts#L15-L28 +> ⚠️ Важно: токен в .env.development не коммитим — он только для локального девелопмента. +Метод создает сессию через API с заданными скоупами. Для создания сессий с другим набором скоупов можно писать аналогичные методы. -- `./src/api/user/AuthApi.ts` и `./src/api/user/UserdataApi.ts` +### Интеграция с "Твой ФФ" - в этих файлах хранится код взаимодействия с [Auth API](https://api.profcomff.com/?urls.primaryName=auth) - и [Userdata API](https://api.profcomff.com/?urls.primaryName=userdata), позволяющие получить - информацию о текущем пользователе. +Когда приложение подключено к Твой ФФ, оно ожидает увидеть токен, скоупы и всю дополнительную информацию в параметрах URL. Для этого в `src/store/profileStore.ts` есть метод `fromUrl` -- его стоит вызывать всегда кроме режима разработки. -- По умолчанию используется тестовая среда для общения с API Твой ФФ! Данное поведение меняется в файле `.env`: https://github.com/profcomff/app-template/blob/main/frontend/.env +### Пулл реквесты +Перед пулл-реквестом: -## Инструкции -### Получение кода на свой компьютер -Для работы с данным примером необходимо забрать его к себе на ПК. Для этого нужно: -1. *(Опционально)* Если вы хотите далее опубликовать код на GitHub полезно сначала скопировать - репозиторий к себе кнопкой Fork на GitHub. Кнопка доступна в правом верхнем углу - [страницы репозитория](https://github.com/profcomff/app-template). -2. Создайте папку в удобном вам расположении. - - *Удобно создать папку на рабочем столе с названием вашего приложения.* -3. Откройте приложение "Командная строка" или "Терминал", в зависимости от операционной системы. -4. Прейдите в папку проекта командой `cd /путь/к/папке`. - - Если вы пользователь windows и создали на рабочем столе папку `my_app`, то команда будет - выглядеть так: `cd %userprofile%/Desktop/my_app` - - Если вы пользователь linux или MacOS и создали на рабочем столе папку `my_app`, то команда - будет выглядеть сделующим образом: `cd ~/Desktop/my_app` -5. Склонируйте код репозитория к себе на ПК командой - `git clone https://github.com/profcomff/app-template.git .` (точка в конце означает скачивание - кода в текущую папку). Если вы выполнили пункт 1 используйте в команде ссылку из зеленой кнопки - "Code" в правом верхнем углу вашего репозитория. -6. Откройте код в удобной среде разработки. Рекомендуем использовать VSCode, который можно открыть - из терминала командой `code .` +1. Проверь стили: `pnpm check` +2. _Опционально_: подробная версия `pnpm check:hard` +3. Заполни шаблон пулл-реквеста: что, как и зачем сделано. +4. Запроси ревью у команды. -### Сборка и запуск приложения для публикации -Сборка исходного кода в один пакет производится с помощью Docker. В этом случае создается -независимый от операционной системы пакет, который можно без проблем разместить на любом сервере. +## Сборка и деплой -Выполните команду `make` для сборки приложения. После окончания выполнения этой команды будет создан -новый Docker образ с названием `my_app`, который можно запустить командой `make run` +### Сборка и запуск через Docker: +```bash +make # создаёт Docker образ +make run # запускает приложение +``` -### Автосборка -Коммит в main запускает автоматическую сборку проекта средствами GitHub Actions. Настройки автосборки находятся в -файле [.github/workflows/build_and_publish.yml](.github/workflows/build_and_publish.yml). +Dockerfile и docker-compose уже настроены для простого деплоя. В .env указываем базовый URL API: -### Тесты на Pull Request -При создании запроса на слияние, автоматически создаются проверки кода юниттестами и на стили. -Следующие тесты будут запущены: -- Проверки стилей `eslint`, `prettier`, `stylelint` на код в папке `frontend` +```env +VITE_API_BASE_URL=https://api.example.com +``` -Настройки автотестов находятся в файле[.github/workflows/checks.yml](.github/workflows/checks.yml). +### CI/CD -### Публикация готового приложения -На данном этапе вам необходимо разместить приложение на каком-либо хостинге, поддерживающем протокол https. +GitHub Actions автоматически проверяет код на PR: eslint, prettier, stylelint -Для этого подойдет любой VPS сервер, который вы можете найти в интернете. Мы рекомендуем использовать VPS сервера на операционной системе linux, для удобства размещения приложений и компонентов использовать Docker. +Автосборка: коммит в main создаёт Docker-образ через workflow .github/workflows/build_and_publish.yml -Чтобы получить SSL сертификаы для поддержки https можно использовать letsencrypt.com, предоставляющий SSL сертификаты бесплатно. Для удобства работы можно использовать веб сервер с встроенной поддержкой этих сертификатов, например Traefik или Caddy. +### Ссылки -Шаблон приложения имеет уже готовые [Dockerfile](cicd/Dockerfile) для сборки вашего приложения, файл [docker-compose.production.yml](cicd/docker-compose.production.yml) с настройками развертывания приложения с Caddy Server и включенным https, а также базой данных PostgreSQL. +API документация -- [Swagger](https://api.test.profcomff.com/?urls.primaryName=rental) \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 0cf026f..7862383 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,9 +8,10 @@ import { ToastType } from './models'; const profileStore = useProfileStore(); const toastStore = useToastStore(); -onMounted(() => { +onMounted(async () => { profileStore.clearLocalStorage(); profileStore.fromUrl(); + if (import.meta.env.MODE === 'development') await profileStore.setupDevAdminSession(null); if (!profileStore.isLoggedIn) { toastStore.push({ title: 'Не получится оставить отзыв', diff --git a/src/store/profileStore.ts b/src/store/profileStore.ts index 0903c46..d545208 100644 --- a/src/store/profileStore.ts +++ b/src/store/profileStore.ts @@ -1,56 +1,95 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; -import { LocalStorage, LocalStorageItem } from '../models/LocalStorage'; -import { setupAuth } from '@profcomff/api-uilib'; - -export const useProfileStore = defineStore('profile', () => { - const token = ref(undefined); - const groups = ref(null); - const sessionScopes = ref(null); - const isLoggedIn = ref(false); - - const full_name = ref(null); - - const fromUrl = () => { - const url = new URL(document.location.toString()); - - const urlToken = url.searchParams.get('token'); - const urlScopes = url.searchParams.get('scopes')?.split(','); - - if (urlToken === null) { - token.value = undefined; - isLoggedIn.value = false; - sessionScopes.value = []; - } else { - token.value = urlToken; - isLoggedIn.value = true; - sessionScopes.value = urlScopes ?? []; - } - - setupAuth(token.value); - }; - - const isAdmin = () => { - return ( - (sessionScopes.value?.includes('rating.comment.review') && - sessionScopes.value?.includes('rating.comment.review')) ?? - false - ); - }; - - const clearLocalStorage = () => { - LocalStorage.delete(LocalStorageItem.Token, LocalStorageItem.TokenScopes, LocalStorageItem.UserId); - }; - - return { - token, - groups, - isLoggedIn, - - full_name, - - fromUrl, - isAdmin, - clearLocalStorage, - }; -}); +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { LocalStorage, LocalStorageItem } from '../models/LocalStorage'; +import { setupAuth } from '@profcomff/api-uilib'; +import apiClient from '@/api'; + +export const useProfileStore = defineStore('profile', () => { + const token = ref(undefined); + const groups = ref(null); + const sessionScopes = ref(null); + const isLoggedIn = ref(false); + + const full_name = ref(null); + + const fromUrl = () => { + const url = new URL(document.location.toString()); + + const urlToken = url.searchParams.get('token'); + const urlScopes = url.searchParams.get('scopes')?.split(','); + + if (urlToken === null) { + token.value = undefined; + isLoggedIn.value = false; + sessionScopes.value = []; + } else { + token.value = urlToken; + isLoggedIn.value = true; + sessionScopes.value = urlScopes ?? []; + } + + setupAuth(token.value); + }; + + const TVOI_FF_TEST_TOKEN = import.meta.env.VITE_TVOI_FF_TOKEN; + + async function setupDevAdminSession(tvff_token: string | null) { + setupAuth(tvff_token ?? TVOI_FF_TEST_TOKEN); + + const serviceScopes = [ + 'comment.create', + 'comment.delete', + 'comment.import', + 'comment.review', + 'lecturer.comment.delete', + 'lecturer.comment.review', + ]; + const serviceName = 'rating'; + const scopes = serviceScopes.map(value => `${serviceName}.${value}`); + + const { data, error } = await apiClient.POST('/auth/session', { + body: { + scopes, + expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + }, + }); + + if (error) { + console.log('Ошибка при попытке авторизоваться'); + isLoggedIn.value = false; + return; + } + token.value = data.token || ''; + sessionScopes.value = data.session_scopes ?? []; + + setupAuth(data.token || ''); + isLoggedIn.value = true; + console.log(token); + } + + async function setupDevUserSession(tvff_token: string | null) { + setupAuth(tvff_token ?? TVOI_FF_TEST_TOKEN); + } + + const isAdmin = () => { + return sessionScopes.value?.includes('rating.comment.review') ?? false; + }; + + const clearLocalStorage = () => { + LocalStorage.delete(LocalStorageItem.Token, LocalStorageItem.TokenScopes, LocalStorageItem.UserId); + }; + + return { + token, + groups, + isLoggedIn, + + full_name, + + fromUrl, + setupDevAdminSession, + setupDevUserSession, + isAdmin, + clearLocalStorage, + }; +}); From 400211bc64c20799068d83c525e5b005ba7a07c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B0=D1=82=D1=83=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Sat, 1 Nov 2025 18:48:38 +0300 Subject: [PATCH 3/4] commands update --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d4d609c..a194f24 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,13 @@ "docker-build": "vue-tsc && vite build --base=/ui", "preview": "vite preview", "lint": "eslint src/", + "lint:hard": "pnpm lint && pnpm lint:deadcode && pnpm lint:circular", "lint:deadcode": "knip --exclude binaries,dependencies,unlisted", "lint:circular": "dpdm --exit-code circular:1 --no-tree --no-warning --progress false --transform ./src/main.ts", "format": "eslint src/ --fix && prettier src/ --write", "stylelint": "stylelint 'src/**/*.{vue,css}' --fix", - "check": "vue-tsc && pnpm run format && pnpm run lint && pnpm run stylelint", + "check": "vue-tsc && pnpm lint && pnpm format && pnpm stylelint", + "check:hard": "vue-tsc && pnpm lint:hard && pnpm format && pnpm stylelint", "test": "echo ok" }, "dependencies": { @@ -56,4 +58,4 @@ "vue-tsc": "^2.2.8", "vuetify": "^3.7.16" } -} +} \ No newline at end of file From 11f3f00ddae2d721e6c33cb99d1844ad17b88dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=91=D0=B0=D1=82=D1=83=D0=B5?= =?UTF-8?q?=D0=B2?= Date: Sat, 1 Nov 2025 18:52:56 +0300 Subject: [PATCH 4/4] linitng, formatting, api change support --- src/components/TheLecturerSearchTable.vue | 8 +-- src/components/TheSearchBar.vue | 1 - src/pages/AdminPage.vue | 76 +++++++++++------------ src/pages/LecturerPage.vue | 8 +-- src/utils/marks.ts | 2 +- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/components/TheLecturerSearchTable.vue b/src/components/TheLecturerSearchTable.vue index 726234c..8918334 100644 --- a/src/components/TheLecturerSearchTable.vue +++ b/src/components/TheLecturerSearchTable.vue @@ -43,9 +43,9 @@ {{ item.raw.comments?.length || '—' }} -