= BuildInstructions = [[PageOutline(2-3, Содржина, inline)]] Оваа страница ја опишува развојната околина, начинот на градење и начинот на тестирање на проектот '''Shifter Web App''' (гранка `main`). ---- == Опис на развојната околина == === Архитектура на проектот === Shifter е веб-апликација составена од два независни дела кои се пуштаат одделно: || '''Компонента''' || '''Технологија''' || '''Директориум''' || '''Порта (локално)''' || || Backend (REST API) || Java 17, Spring Boot 3.5.3, Spring Security (JWT + OAuth2), Spring Data JPA / Hibernate, Maven || `backend/` || `8080` || || Frontend (SPA) || React 19, TypeScript 5.8, Vite 6, Tailwind CSS 4, react-i18next || `frontend/` || `5173` || || База на податоци || PostgreSQL || – || `5432` || === Задолжителен софтвер === || '''Софтвер''' || '''Верзија''' || '''Зошто е потребен''' || '''Проверка''' || || Git || било која || Преземање на изворниот код || {{{git --version}}} || || JDK (Java Development Kit) || '''17''' (Temurin / OpenJDK / Oracle) || Компајлирање и извршување на backend-от; `pom.xml` е фиксиран на `java.version=17` || {{{java -version}}} || || Maven || '''не се инсталира одделно''' || Проектот има Maven Wrapper (`mvnw` / `mvnw.cmd`) кој сам ја презема Maven 3.9.9 || {{{./mvnw -v}}} || || PostgreSQL || 14 или понова (драјвер 42.7.7) || База на податоци на апликацијата || {{{psql --version}}} || || Node.js || 20.19+ или 22.12+ (LTS) || Градење и пуштање на frontend-от (Vite 6 / React 19) || {{{node -v}}} || || npm || 10 или понова (доаѓа со Node.js) || Инсталација на frontend зависности || {{{npm -v}}} || || Веб-прелистувач || Chrome, Firefox, Edge или Safari (актуелна верзија) || Тестирање на апликацијата || – || === Надворешни сервиси и клучеви === Backend-от се поврзува на неколку надворешни сервиси. Клучевите се внесуваат преку променливи на околината, види `Конфигурација на backend`. || '''Сервис''' || '''За што служи''' || '''Потребен за подигање на апликацијата?''' || || PostgreSQL || Главна база на податоци || '''Да''' – без неа апликацијата не се подига || || ZeptoMail (Zoho) || Емаил за верификација, потврди за состаноци, контакт-форма || '''Да''' – клучот мора да постои; мора да е и '''валиден''' за регистрација преку емаил || || Google Cloud – OAuth 2.0 Client ID || Најава со Google („Sign in with Google“) || '''Да''' – вредностите мора да постојат || || Google Cloud – Service Account + Calendar ID || Читање слободни термини од календарот на експертот || '''Да''' – види `Google Service Account`, инаку подигањето паѓа со грешка || || Amazon S3 || Прикачување и чување фајлови (слики, материјали) || '''Да''' – вредностите мора да постојат || || Zoom (Server-to-Server OAuth App) || Автоматско креирање Zoom состанок при закажување консултација || '''Да''' – вредностите мора да постојат || '''ВАЖНО:''' повеќето променливи во `application.properties` немаат стандардна вредност (пр. `${AWS_S3_REGION}`, `${JWT_CONFIG_SECRET}`, `${ZOOM_CLIENT_ID}`). Ако некоја од нив недостасува, Spring нема да успее да ги создаде бавовите и апликацијата '''воопшто нема да се стартува'''. За локален развој е сосема во ред да се стават '''лажни (dummy) вредности''' за сервисите што нема да ги тестирате – важно е клучевите да '''постојат'''. ---- == Инструкции за градење == === Преземање на изворниот код === {{{#!sh git clone Shifter-Web-App cd Shifter-Web-App git checkout main }}} Структура на проектот: {{{ Shifter-Web-App/ ├── backend/ # Spring Boot апликација (Maven) ├── frontend/ # React + Vite апликација (npm) └── modeling/ # модели и дијаграми }}} === Подготовка на базата на податоци === Инсталирајте и стартувајте PostgreSQL, па креирајте корисник и празна база: {{{#!sql CREATE USER shifter WITH PASSWORD 'shifter'; CREATE DATABASE shifter OWNER shifter; GRANT ALL PRIVILEGES ON DATABASE shifter TO shifter; }}} '''Не е потребно рачно да се извршуваат SQL скрипти.''' На гранката `main` вредноста е `spring.jpa.hibernate.ddl-auto=update`, што значи дека Hibernate автоматски ги креира и ажурира сите табели при првото стартување на backend-от. Во продукцискиот профил `prod` вредноста е `validate`, т.е. шемата мора веќе да постои. === Конфигурација на backend === Backend-от при стартување чита `.env` датотека (библиотека `dotenv-java`). Датотеката '''не е''' во Git (наведена е во `.gitignore`), па мора рачно да се создаде на патека `backend/.env`: {{{ # ===== Основно ===== SPRING_PROFILES_ACTIVE=default PORT=8080 BACKEND_URL=http://localhost:8080 FRONTEND_URL=http://localhost:5173 ALLOWED_ORIGINS=http://localhost:5173 # ===== База на податоци ===== SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/shifter SPRING_DATASOURCE_USERNAME=shifter SPRING_DATASOURCE_PASSWORD=shifter DDL_AUTO=update SHOW_SQL=true # ===== JWT (мин. 32 знаци, HS256) ===== JWT_CONFIG_SECRET=promeni-go-ovoj-taen-kluc-min-32-znaci!! JWT_EXPIRATION=86400000 JWT_REFRESH_EXPIRATION=604800000 COOKIE_SECURE=false COOKIE_SAME_SITE=Lax # ===== Amazon S3 ===== AWS_S3_REGION=eu-central-1 AWS_S3_BUCKET_NAME=shifter-bucket AWS_S3_ACCESS_KEY=xxxxxxxxxxxx AWS_S3_SECRET_KEY=xxxxxxxxxxxx # ===== Емаил (ZeptoMail) ===== ZEPTOMAIL_API_KEY=xxxxxxxxxxxx # ===== Google OAuth2 ===== GOOGLE_CLIENT_ID=xxxxxxxxxxxx.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=xxxxxxxxxxxx # ===== Google Calendar ===== GOOGLE_EXPERT_CALENDAR_ID=expert@shift-er.com # алтернатива на service-account.json (Base64 од JSON фајлот): # GOOGLE_CALENDAR_SERVICE_ACCOUNT_JSON_BASE64=xxxxxxxxxxxx # ===== Zoom (Server-to-Server OAuth) ===== ZOOM_ACCOUNT_ID=xxxxxxxxxxxx ZOOM_CLIENT_ID=xxxxxxxxxxxx ZOOM_CLIENT_SECRET=xxxxxxxxxxxx # ===== Логирање ===== LOG_LEVEL=INFO APP_LOG_LEVEL=DEBUG }}} === Google Service Account === Класата `GoogleCalendarService` се иницијализира '''при подигање на апликацијата'''. Ако не најде credentials, целата апликација паѓа со `GoogleCalendarException: Service account JSON not found in classpath or environment`. Затоа мора да обезбедите '''еден''' од следниве два начина: * Поставете ја JSON-датотеката на Service Account-от на патека `backend/src/main/resources/service-account.json` (датотеката е во `.gitignore` и не се комитира). * '''или''' поставете ја променливата `GOOGLE_CALENDAR_SERVICE_ACCOUNT_JSON_BASE64` со Base64-кодирана содржина на истата датотека: {{{#!sh base64 -i service-account.json | tr -d '\n' }}} Service Account-от треба да има пристап (Google Calendar API scope `https://www.googleapis.com/auth/calendar`) до календарот наведен во `GOOGLE_EXPERT_CALENDAR_ID`. === Google OAuth2 redirect URI === Во Google Cloud Console, во OAuth 2.0 Client ID, додајте го овој '''Authorized redirect URI''': {{{ http://localhost:8080/login/oauth2/code/google }}} === Компајлирање и стартување на backend === Сите команди се извршуваат од директориумот `backend/`. {{{#!sh cd backend # Компајлирање и градење (Linux / macOS) ./mvnw clean package -DskipTests # Компајлирање и градење (Windows) mvnw.cmd clean package -DskipTests }}} Резултат: извршна JAR-датотека `backend/target/shifter.jar`. {{{#!sh # Стартување во развоен режим ./mvnw spring-boot:run # или стартување на изградената JAR-датотека java -jar target/shifter.jar }}} Проверка дека backend-от работи: {{{#!sh curl http://localhost:8080/actuator/health # очекуван одговор: {"status":"UP"} }}} === Конфигурација и градење на frontend === Сите команди се извршуваат од директориумот `frontend/`. Прво креирајте `frontend/.env` – frontend-от бара '''една''' променлива: {{{ VITE_BACKEND_URL=http://localhost:8080 }}} Потоа: {{{#!sh cd frontend npm install # инсталација на зависностите npm run dev # развоен режим → http://localhost:5173 npm run build # TypeScript проверка + Vite build → frontend/dist/ npm run preview # локален преглед на изградената верзија npm run lint # ESLint проверка }}} Содржината од `frontend/dist/` се качува на статички хостинг; проектот содржи `vercel.json` со SPA rewrite правило за Vercel. === Цел редослед на пуштање === {{{#!sh # 1. Стартувај PostgreSQL и создај база `shifter` # 2. Backend cd backend # создај .env и service-account.json ./mvnw spring-boot:run # → http://localhost:8080 # 3. Frontend (во нов терминал) cd frontend # создај .env со VITE_BACKEND_URL npm install npm run dev # → http://localhost:5173 }}} === Продукциско градење и пуштање === * Backend: поставете `SPRING_PROFILES_ACTIVE=prod`. Профилот `prod` автоматски вклучува `spring.jpa.hibernate.ddl-auto=validate` (шемата мора да постои однапред), `cookie.secure=true` и `cookie.same-site=Strict` (задолжителен HTTPS), намалено логирање и сокриени stack trace-ови. * Поставете ги `BACKEND_URL`, `FRONTEND_URL` и `ALLOWED_ORIGINS` на вистинските домени; `ALLOWED_ORIGINS` прифаќа повеќе вредности одделени со запирка. * Додајте го продукцискиот redirect URI во Google Cloud Console: `https:///login/oauth2/code/google`. * Frontend: `npm run build` и качете го `frontend/dist/`; поставете `VITE_BACKEND_URL` на продукцискиот backend домен. === Чести проблеми при градење === || '''Проблем''' || '''Причина''' || '''Решение''' || || `Could not resolve placeholder 'AWS_S3_REGION'` (или слична променлива) || Недостасува променлива во `.env` || Додајте ја во `backend/.env`; за нетестирани сервиси ставете dummy вредност || || `GoogleCalendarException: Service account JSON not found` || Недостасува `service-account.json` || Види `Google Service Account` || || `Connection refused` кон PostgreSQL || Базата не работи или е погрешен URL || Проверете дали PostgreSQL е стартуван и дали `SPRING_DATASOURCE_URL` е точен || || CORS грешка во конзолата на прелистувачот || `ALLOWED_ORIGINS` не ја содржи адресата на frontend-от || Додајте `http://localhost:5173` во `ALLOWED_ORIGINS` || || Мрежни грешки во frontend-от / празни податоци || Недостасува `frontend/.env` || Создајте `.env` со `VITE_BACKEND_URL=http://localhost:8080` и рестартирајте `npm run dev` || || `WeakKeyException` при најава || `JWT_CONFIG_SECRET` е пократок од 32 знаци || Поставете подолг таен клуч || || Грешки „cannot find symbol“ за getter/setter методи во IDE || Исклучено annotation processing (Lombok/MapStruct) || Вклучете `Enable annotation processing` во IDE-то || || Vite не се подига || Стара верзија на Node.js || Инсталирајте Node.js 20.19+ или 22.12+ || ---- == Инструкции за тестирање == Shifter е '''веб-апликација''', па се тестира преку веб-прелистувач. === Каде се пушта апликацијата === || '''Што''' || '''Адреса''' || || Frontend (корисничкиот дел) || http://localhost:5173 || || Автоматско пренасочување со јазичен префикс || http://localhost:5173/en или http://localhost:5173/mk || || Backend REST API || http://localhost:8080/api || || Health check || http://localhost:8080/actuator/health || || Најава со Google || http://localhost:8080/oauth2/authorization/google || === Кориснички имиња и лозинки === Апликацијата '''нема однапред креирани (seed) корисници'''. При првото стартување базата е празна и '''сметката се креира преку формата за регистрација'''. || '''Тип на пристап''' || '''Корисничко име''' || '''Лозинка''' || || Апликација (регистриран корисник) || вашата емаил адреса, пр. `test@example.com` || лозинка што ја бирате при регистрација, пр. `Test123!` || || Апликација (Google најава) || вашата Google сметка || управувана од Google (нема лозинка во апликацијата) || || PostgreSQL база || `shifter` (или `postgres`) || онаа што сте ја поставиле, пр. `shifter` || '''Правила за лозинката при регистрација''' – се проверуваат во формата: * најмалку една '''голема буква''' (A–Z) * најмалку една '''цифра''' (0–9) * најмалку еден '''специјален знак''' (`!@#$%^&*(),.?":{}|<>`) * двете полиња за лозинка мора да се совпаѓаат Пример за валидна тест-сметка: `test@example.com` / `Test123!` '''Совет за тестирање без валиден емаил-клуч:''' регистрацијата преку емаил испраќа порака за верификација преку ZeptoMail и, ако клучот не е валиден, целата регистрација паѓа бидејќи трансакцијата се враќа назад. Ако немате валиден `ZEPTOMAIL_API_KEY`, тестирајте со '''Најава преку Google''' – тој тек не испраќа емаил и корисникот автоматски се означува како верификуван. === Мини-водич низ главните елементи === Долниот водич ги поминува главните екрани по редослед на нормално користење. '''Чекор 1: Почетна страница''' * Отворете http://localhost:5173 – автоматски ќе бидете пренасочени на `/en` (или `/mk`). * '''Navbar''' (горе): линкови до `Курсеви`, `Менторство`, `Консалтинг`, `Академии`, `За Нас`, `Контакт`, `Бесплатна Сесија` и копче `Најави се / Регистрирај се`. * '''Јазичен прекинувач''' (долу десно, знаменце): менува меѓу '''EN''' и '''MK'''; јазикот се одразува во URL-то (`/en/...` ↔ `/mk/...`). * '''Footer''': контакт информации и линкови до социјални мрежи. '''Чекор 2: Регистрација''' * Кликнете `Најави се / Регистрирај се` → `Регистрирај се` (`/en/register`). * Внесете емаил, лозинка и потврда на лозинката – види `Кориснички имиња и лозинки`. * Алтернативно кликнете '''„Continue with Google“''' за најава преку Google сметка. * По успешна регистрација, апликацијата автоматски ве носи на страницата за персонализација (`/welcome`). '''Чекор 3: Верификација и персонализација (`/welcome`)''' * Страницата го чита `token` параметарот од URL-то и ја верифицира вашата емаил адреса. * Пополнете ги полињата: '''Целосно име''', '''Работно место''' и '''Големина на компанијата''' (Фриленсер, Микро, Мала, Средна, Среден пазар, Голема компанија, Друго). * Кликнете `Започни да користиш Shifter`. Профилот сега е комплетен и сите заштитени страници стануваат достапни. * Ако верификацијата не успее, постои копче `Испрати нов емаил` за нов линк за верификација. '''Чекор 4: Најава (`/login`)''' * Внесете емаил и лозинка, или користете `Continue with Google`. * По најава access token се чува во меморија, а refresh token во `HttpOnly` колаче; сесијата се обновува автоматски при истек (401 → `/api/auth/refresh`). '''Чекор 5: Профил (`/profile`)''' * '''Мој Профил''' – измена на име, работна позиција и големина на компанијата; се зачувува со `Зачувај Промени`. * '''Интереси''' и '''Стекнати Вештини''' – кликнете на секцијата за да отворите модал со теми и изберете барем една. * '''Одјави се''' – ја затвора сесијата. '''Чекор 6: Бесплатна сесија (`/free-consultation`)''' Ова е најкомплексниот тек и ги користи Google Calendar, Zoom и емаил интеграциите: * Апликацијата ги повлекува '''слободните термини''' на експертот од Google Calendar – работни денови, 08:00–16:30, во вашата временска зона. * Изберете '''датум''' и '''час'''. * Пополнете ги полињата: основни информации, за компанијата, предизвици и дополнителни информации. * Кликнете на копчето за закажување. Во позадина се креира настан во Google Calendar, се отвора Zoom состанок и се испраќаат емаил потврди до вас и до експертот. * Секој корисник има право на '''една''' бесплатна консултација – при обид за втора се појавува порака дека веќе е искористена. '''Чекор 7: Контакт (`/contact`)''' * Полињата '''Име''' и '''Е-пошта''' се автоматски пополнети од вашиот профил. * Внесете '''Причина за контакт''' и '''Порака''', па кликнете `Испрати`. * Пораката се испраќа до експертот преку ZeptoMail; се појавува потврда на екранот. '''Чекор 8: Информативни страници''' * '''За Нас''' (`/about`) – мисија, вредности и претставување на тимот. Јавно достапна. * '''Менторство''' (`/mentoring`), '''Консалтинг''' (`/consulting`), '''Академии''' (`/academies`) – опис на услугите. '''Достапни само за најавени корисници.''' * '''Курсеви''' (`/courses`) – моментално прикажува страница „Дигитални Курсеви – Наскоро“; модулот за курсеви и учење е подготвен во backend-от, но е привремено исклучен во frontend-от. === Автоматски тестови === {{{#!sh cd backend ./mvnw test }}}