| | 1 | = BuildInstructions = |
| | 2 | |
| | 3 | [[PageOutline(2-3, Содржина, inline)]] |
| | 4 | |
| | 5 | Оваа страница ја опишува развојната околина, начинот на градење и начинот на тестирање на проектот '''Shifter Web App''' (гранка `main`). |
| | 6 | |
| | 7 | ---- |
| | 8 | |
| | 9 | == Опис на развојната околина == |
| | 10 | |
| | 11 | === Архитектура на проектот === |
| | 12 | |
| | 13 | Shifter е веб-апликација составена од два независни дела кои се пуштаат одделно: |
| | 14 | |
| | 15 | || '''Компонента''' || '''Технологија''' || '''Директориум''' || '''Порта (локално)''' || |
| | 16 | || Backend (REST API) || Java 17, Spring Boot 3.5.3, Spring Security (JWT + OAuth2), Spring Data JPA / Hibernate, Maven || `backend/` || `8080` || |
| | 17 | || Frontend (SPA) || React 19, TypeScript 5.8, Vite 6, Tailwind CSS 4, react-i18next || `frontend/` || `5173` || |
| | 18 | || База на податоци || PostgreSQL || – || `5432` || |
| | 19 | |
| | 20 | === Задолжителен софтвер === |
| | 21 | |
| | 22 | || '''Софтвер''' || '''Верзија''' || '''Зошто е потребен''' || '''Проверка''' || |
| | 23 | || Git || било која || Преземање на изворниот код || {{{git --version}}} || |
| | 24 | || JDK (Java Development Kit) || '''17''' (Temurin / OpenJDK / Oracle) || Компајлирање и извршување на backend-от; `pom.xml` е фиксиран на `java.version=17` || {{{java -version}}} || |
| | 25 | || Maven || '''не се инсталира одделно''' || Проектот има Maven Wrapper (`mvnw` / `mvnw.cmd`) кој сам ја презема Maven 3.9.9 || {{{./mvnw -v}}} || |
| | 26 | || PostgreSQL || 14 или понова (драјвер 42.7.7) || База на податоци на апликацијата || {{{psql --version}}} || |
| | 27 | || Node.js || 20.19+ или 22.12+ (LTS) || Градење и пуштање на frontend-от (Vite 6 / React 19) || {{{node -v}}} || |
| | 28 | || npm || 10 или понова (доаѓа со Node.js) || Инсталација на frontend зависности || {{{npm -v}}} || |
| | 29 | || Веб-прелистувач || Chrome, Firefox, Edge или Safari (актуелна верзија) || Тестирање на апликацијата || – || |
| | 30 | |
| | 31 | === Надворешни сервиси и клучеви === |
| | 32 | |
| | 33 | Backend-от се поврзува на неколку надворешни сервиси. Клучевите се внесуваат преку променливи на околината, види `Конфигурација на backend`. |
| | 34 | |
| | 35 | || '''Сервис''' || '''За што служи''' || '''Потребен за подигање на апликацијата?''' || |
| | 36 | || PostgreSQL || Главна база на податоци || '''Да''' – без неа апликацијата не се подига || |
| | 37 | || ZeptoMail (Zoho) || Емаил за верификација, потврди за состаноци, контакт-форма || '''Да''' – клучот мора да постои; мора да е и '''валиден''' за регистрација преку емаил || |
| | 38 | || Google Cloud – OAuth 2.0 Client ID || Најава со Google („Sign in with Google“) || '''Да''' – вредностите мора да постојат || |
| | 39 | || Google Cloud – Service Account + Calendar ID || Читање слободни термини од календарот на експертот || '''Да''' – види `Google Service Account`, инаку подигањето паѓа со грешка || |
| | 40 | || Amazon S3 || Прикачување и чување фајлови (слики, материјали) || '''Да''' – вредностите мора да постојат || |
| | 41 | || Zoom (Server-to-Server OAuth App) || Автоматско креирање Zoom состанок при закажување консултација || '''Да''' – вредностите мора да постојат || |
| | 42 | |
| | 43 | '''ВАЖНО:''' повеќето променливи во `application.properties` немаат стандардна вредност (пр. `${AWS_S3_REGION}`, `${JWT_CONFIG_SECRET}`, `${ZOOM_CLIENT_ID}`). Ако некоја од нив недостасува, Spring нема да успее да ги создаде бавовите и апликацијата '''воопшто нема да се стартува'''. За локален развој е сосема во ред да се стават '''лажни (dummy) вредности''' за сервисите што нема да ги тестирате – важно е клучевите да '''постојат'''. |
| | 44 | |
| | 45 | ---- |
| | 46 | |
| | 47 | == Инструкции за градење == |
| | 48 | |
| | 49 | === Преземање на изворниот код === |
| | 50 | |
| | 51 | {{{#!sh |
| | 52 | git clone <URL-на-репозиториумот> Shifter-Web-App |
| | 53 | cd Shifter-Web-App |
| | 54 | git checkout main |
| | 55 | }}} |
| | 56 | |
| | 57 | Структура на проектот: |
| | 58 | |
| | 59 | {{{ |
| | 60 | Shifter-Web-App/ |
| | 61 | ├── backend/ # Spring Boot апликација (Maven) |
| | 62 | ├── frontend/ # React + Vite апликација (npm) |
| | 63 | └── modeling/ # модели и дијаграми |
| | 64 | }}} |
| | 65 | |
| | 66 | === Подготовка на базата на податоци === |
| | 67 | |
| | 68 | Инсталирајте и стартувајте PostgreSQL, па креирајте корисник и празна база: |
| | 69 | |
| | 70 | {{{#!sql |
| | 71 | CREATE USER shifter WITH PASSWORD 'shifter'; |
| | 72 | CREATE DATABASE shifter OWNER shifter; |
| | 73 | GRANT ALL PRIVILEGES ON DATABASE shifter TO shifter; |
| | 74 | }}} |
| | 75 | |
| | 76 | '''Не е потребно рачно да се извршуваат SQL скрипти.''' На гранката `main` вредноста е `spring.jpa.hibernate.ddl-auto=update`, што значи дека Hibernate автоматски ги креира и ажурира сите табели при првото стартување на backend-от. Во продукцискиот профил `prod` вредноста е `validate`, т.е. шемата мора веќе да постои. |
| | 77 | |
| | 78 | === Конфигурација на backend === |
| | 79 | |
| | 80 | Backend-от при стартување чита `.env` датотека (библиотека `dotenv-java`). Датотеката '''не е''' во Git (наведена е во `.gitignore`), па мора рачно да се создаде на патека `backend/.env`: |
| | 81 | |
| | 82 | {{{ |
| | 83 | # ===== Основно ===== |
| | 84 | SPRING_PROFILES_ACTIVE=default |
| | 85 | PORT=8080 |
| | 86 | BACKEND_URL=http://localhost:8080 |
| | 87 | FRONTEND_URL=http://localhost:5173 |
| | 88 | ALLOWED_ORIGINS=http://localhost:5173 |
| | 89 | |
| | 90 | # ===== База на податоци ===== |
| | 91 | SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/shifter |
| | 92 | SPRING_DATASOURCE_USERNAME=shifter |
| | 93 | SPRING_DATASOURCE_PASSWORD=shifter |
| | 94 | DDL_AUTO=update |
| | 95 | SHOW_SQL=true |
| | 96 | |
| | 97 | # ===== JWT (мин. 32 знаци, HS256) ===== |
| | 98 | JWT_CONFIG_SECRET=promeni-go-ovoj-taen-kluc-min-32-znaci!! |
| | 99 | JWT_EXPIRATION=86400000 |
| | 100 | JWT_REFRESH_EXPIRATION=604800000 |
| | 101 | COOKIE_SECURE=false |
| | 102 | COOKIE_SAME_SITE=Lax |
| | 103 | |
| | 104 | # ===== Amazon S3 ===== |
| | 105 | AWS_S3_REGION=eu-central-1 |
| | 106 | AWS_S3_BUCKET_NAME=shifter-bucket |
| | 107 | AWS_S3_ACCESS_KEY=xxxxxxxxxxxx |
| | 108 | AWS_S3_SECRET_KEY=xxxxxxxxxxxx |
| | 109 | |
| | 110 | # ===== Емаил (ZeptoMail) ===== |
| | 111 | ZEPTOMAIL_API_KEY=xxxxxxxxxxxx |
| | 112 | |
| | 113 | # ===== Google OAuth2 ===== |
| | 114 | GOOGLE_CLIENT_ID=xxxxxxxxxxxx.apps.googleusercontent.com |
| | 115 | GOOGLE_CLIENT_SECRET=xxxxxxxxxxxx |
| | 116 | |
| | 117 | # ===== Google Calendar ===== |
| | 118 | GOOGLE_EXPERT_CALENDAR_ID=expert@shift-er.com |
| | 119 | # алтернатива на service-account.json (Base64 од JSON фајлот): |
| | 120 | # GOOGLE_CALENDAR_SERVICE_ACCOUNT_JSON_BASE64=xxxxxxxxxxxx |
| | 121 | |
| | 122 | # ===== Zoom (Server-to-Server OAuth) ===== |
| | 123 | ZOOM_ACCOUNT_ID=xxxxxxxxxxxx |
| | 124 | ZOOM_CLIENT_ID=xxxxxxxxxxxx |
| | 125 | ZOOM_CLIENT_SECRET=xxxxxxxxxxxx |
| | 126 | |
| | 127 | # ===== Логирање ===== |
| | 128 | LOG_LEVEL=INFO |
| | 129 | APP_LOG_LEVEL=DEBUG |
| | 130 | }}} |
| | 131 | |
| | 132 | === Google Service Account === |
| | 133 | |
| | 134 | Класата `GoogleCalendarService` се иницијализира '''при подигање на апликацијата'''. Ако не најде credentials, целата апликација паѓа со `GoogleCalendarException: Service account JSON not found in classpath or environment`. |
| | 135 | |
| | 136 | Затоа мора да обезбедите '''еден''' од следниве два начина: |
| | 137 | |
| | 138 | * Поставете ја JSON-датотеката на Service Account-от на патека `backend/src/main/resources/service-account.json` (датотеката е во `.gitignore` и не се комитира). |
| | 139 | * '''или''' поставете ја променливата `GOOGLE_CALENDAR_SERVICE_ACCOUNT_JSON_BASE64` со Base64-кодирана содржина на истата датотека: |
| | 140 | |
| | 141 | {{{#!sh |
| | 142 | base64 -i service-account.json | tr -d '\n' |
| | 143 | }}} |
| | 144 | |
| | 145 | Service Account-от треба да има пристап (Google Calendar API scope `https://www.googleapis.com/auth/calendar`) до календарот наведен во `GOOGLE_EXPERT_CALENDAR_ID`. |
| | 146 | |
| | 147 | === Google OAuth2 redirect URI === |
| | 148 | |
| | 149 | Во Google Cloud Console, во OAuth 2.0 Client ID, додајте го овој '''Authorized redirect URI''': |
| | 150 | |
| | 151 | {{{ |
| | 152 | http://localhost:8080/login/oauth2/code/google |
| | 153 | }}} |
| | 154 | |
| | 155 | === Компајлирање и стартување на backend === |
| | 156 | |
| | 157 | Сите команди се извршуваат од директориумот `backend/`. |
| | 158 | |
| | 159 | {{{#!sh |
| | 160 | cd backend |
| | 161 | |
| | 162 | # Компајлирање и градење (Linux / macOS) |
| | 163 | ./mvnw clean package -DskipTests |
| | 164 | |
| | 165 | # Компајлирање и градење (Windows) |
| | 166 | mvnw.cmd clean package -DskipTests |
| | 167 | }}} |
| | 168 | |
| | 169 | Резултат: извршна JAR-датотека `backend/target/shifter.jar`. |
| | 170 | |
| | 171 | {{{#!sh |
| | 172 | # Стартување во развоен режим |
| | 173 | ./mvnw spring-boot:run |
| | 174 | |
| | 175 | # или стартување на изградената JAR-датотека |
| | 176 | java -jar target/shifter.jar |
| | 177 | }}} |
| | 178 | |
| | 179 | Проверка дека backend-от работи: |
| | 180 | |
| | 181 | {{{#!sh |
| | 182 | curl http://localhost:8080/actuator/health |
| | 183 | # очекуван одговор: {"status":"UP"} |
| | 184 | }}} |
| | 185 | |
| | 186 | === Конфигурација и градење на frontend === |
| | 187 | |
| | 188 | Сите команди се извршуваат од директориумот `frontend/`. |
| | 189 | |
| | 190 | Прво креирајте `frontend/.env` – frontend-от бара '''една''' променлива: |
| | 191 | |
| | 192 | {{{ |
| | 193 | VITE_BACKEND_URL=http://localhost:8080 |
| | 194 | }}} |
| | 195 | |
| | 196 | Потоа: |
| | 197 | |
| | 198 | {{{#!sh |
| | 199 | cd frontend |
| | 200 | npm install # инсталација на зависностите |
| | 201 | npm run dev # развоен режим → http://localhost:5173 |
| | 202 | |
| | 203 | npm run build # TypeScript проверка + Vite build → frontend/dist/ |
| | 204 | npm run preview # локален преглед на изградената верзија |
| | 205 | npm run lint # ESLint проверка |
| | 206 | }}} |
| | 207 | |
| | 208 | Содржината од `frontend/dist/` се качува на статички хостинг; проектот содржи `vercel.json` со SPA rewrite правило за Vercel. |
| | 209 | |
| | 210 | === Цел редослед на пуштање === |
| | 211 | |
| | 212 | {{{#!sh |
| | 213 | # 1. Стартувај PostgreSQL и создај база `shifter` |
| | 214 | |
| | 215 | # 2. Backend |
| | 216 | cd backend |
| | 217 | # создај .env и service-account.json |
| | 218 | ./mvnw spring-boot:run |
| | 219 | # → http://localhost:8080 |
| | 220 | |
| | 221 | # 3. Frontend (во нов терминал) |
| | 222 | cd frontend |
| | 223 | # создај .env со VITE_BACKEND_URL |
| | 224 | npm install |
| | 225 | npm run dev |
| | 226 | # → http://localhost:5173 |
| | 227 | }}} |
| | 228 | |
| | 229 | === Продукциско градење и пуштање === |
| | 230 | |
| | 231 | * Backend: поставете `SPRING_PROFILES_ACTIVE=prod`. Профилот `prod` автоматски вклучува `spring.jpa.hibernate.ddl-auto=validate` (шемата мора да постои однапред), `cookie.secure=true` и `cookie.same-site=Strict` (задолжителен HTTPS), намалено логирање и сокриени stack trace-ови. |
| | 232 | * Поставете ги `BACKEND_URL`, `FRONTEND_URL` и `ALLOWED_ORIGINS` на вистинските домени; `ALLOWED_ORIGINS` прифаќа повеќе вредности одделени со запирка. |
| | 233 | * Додајте го продукцискиот redirect URI во Google Cloud Console: `https://<backend-domen>/login/oauth2/code/google`. |
| | 234 | * Frontend: `npm run build` и качете го `frontend/dist/`; поставете `VITE_BACKEND_URL` на продукцискиот backend домен. |
| | 235 | |
| | 236 | === Чести проблеми при градење === |
| | 237 | |
| | 238 | || '''Проблем''' || '''Причина''' || '''Решение''' || |
| | 239 | || `Could not resolve placeholder 'AWS_S3_REGION'` (или слична променлива) || Недостасува променлива во `.env` || Додајте ја во `backend/.env`; за нетестирани сервиси ставете dummy вредност || |
| | 240 | || `GoogleCalendarException: Service account JSON not found` || Недостасува `service-account.json` || Види `Google Service Account` || |
| | 241 | || `Connection refused` кон PostgreSQL || Базата не работи или е погрешен URL || Проверете дали PostgreSQL е стартуван и дали `SPRING_DATASOURCE_URL` е точен || |
| | 242 | || CORS грешка во конзолата на прелистувачот || `ALLOWED_ORIGINS` не ја содржи адресата на frontend-от || Додајте `http://localhost:5173` во `ALLOWED_ORIGINS` || |
| | 243 | || Мрежни грешки во frontend-от / празни податоци || Недостасува `frontend/.env` || Создајте `.env` со `VITE_BACKEND_URL=http://localhost:8080` и рестартирајте `npm run dev` || |
| | 244 | || `WeakKeyException` при најава || `JWT_CONFIG_SECRET` е пократок од 32 знаци || Поставете подолг таен клуч || |
| | 245 | || Грешки „cannot find symbol“ за getter/setter методи во IDE || Исклучено annotation processing (Lombok/MapStruct) || Вклучете `Enable annotation processing` во IDE-то || |
| | 246 | || Vite не се подига || Стара верзија на Node.js || Инсталирајте Node.js 20.19+ или 22.12+ || |
| | 247 | |
| | 248 | ---- |
| | 249 | |
| | 250 | == Инструкции за тестирање == |
| | 251 | |
| | 252 | Shifter е '''веб-апликација''', па се тестира преку веб-прелистувач. |
| | 253 | |
| | 254 | === Каде се пушта апликацијата === |
| | 255 | |
| | 256 | || '''Што''' || '''Адреса''' || |
| | 257 | || Frontend (корисничкиот дел) || http://localhost:5173 || |
| | 258 | || Автоматско пренасочување со јазичен префикс || http://localhost:5173/en или http://localhost:5173/mk || |
| | 259 | || Backend REST API || http://localhost:8080/api || |
| | 260 | || Health check || http://localhost:8080/actuator/health || |
| | 261 | || Најава со Google || http://localhost:8080/oauth2/authorization/google || |
| | 262 | |
| | 263 | === Кориснички имиња и лозинки === |
| | 264 | |
| | 265 | Апликацијата '''нема однапред креирани (seed) корисници'''. При првото стартување базата е празна и '''сметката се креира преку формата за регистрација'''. |
| | 266 | |
| | 267 | || '''Тип на пристап''' || '''Корисничко име''' || '''Лозинка''' || |
| | 268 | || Апликација (регистриран корисник) || вашата емаил адреса, пр. `test@example.com` || лозинка што ја бирате при регистрација, пр. `Test123!` || |
| | 269 | || Апликација (Google најава) || вашата Google сметка || управувана од Google (нема лозинка во апликацијата) || |
| | 270 | || PostgreSQL база || `shifter` (или `postgres`) || онаа што сте ја поставиле, пр. `shifter` || |
| | 271 | |
| | 272 | '''Правила за лозинката при регистрација''' – се проверуваат во формата: |
| | 273 | |
| | 274 | * најмалку една '''голема буква''' (A–Z) |
| | 275 | * најмалку една '''цифра''' (0–9) |
| | 276 | * најмалку еден '''специјален знак''' (`!@#$%^&*(),.?":{}|<>`) |
| | 277 | * двете полиња за лозинка мора да се совпаѓаат |
| | 278 | |
| | 279 | Пример за валидна тест-сметка: `test@example.com` / `Test123!` |
| | 280 | |
| | 281 | '''Совет за тестирање без валиден емаил-клуч:''' регистрацијата преку емаил испраќа порака за верификација преку ZeptoMail и, ако клучот не е валиден, целата регистрација паѓа бидејќи трансакцијата се враќа назад. Ако немате валиден `ZEPTOMAIL_API_KEY`, тестирајте со '''Најава преку Google''' – тој тек не испраќа емаил и корисникот автоматски се означува како верификуван. |
| | 282 | |
| | 283 | === Мини-водич низ главните елементи === |
| | 284 | |
| | 285 | Долниот водич ги поминува главните екрани по редослед на нормално користење. |
| | 286 | |
| | 287 | '''Чекор 1: Почетна страница''' |
| | 288 | |
| | 289 | * Отворете http://localhost:5173 – автоматски ќе бидете пренасочени на `/en` (или `/mk`). |
| | 290 | * '''Navbar''' (горе): линкови до `Курсеви`, `Менторство`, `Консалтинг`, `Академии`, `За Нас`, `Контакт`, `Бесплатна Сесија` и копче `Најави се / Регистрирај се`. |
| | 291 | * '''Јазичен прекинувач''' (долу десно, знаменце): менува меѓу '''EN''' и '''MK'''; јазикот се одразува во URL-то (`/en/...` ↔ `/mk/...`). |
| | 292 | * '''Footer''': контакт информации и линкови до социјални мрежи. |
| | 293 | |
| | 294 | '''Чекор 2: Регистрација''' |
| | 295 | |
| | 296 | * Кликнете `Најави се / Регистрирај се` → `Регистрирај се` (`/en/register`). |
| | 297 | * Внесете емаил, лозинка и потврда на лозинката – види `Кориснички имиња и лозинки`. |
| | 298 | * Алтернативно кликнете '''„Continue with Google“''' за најава преку Google сметка. |
| | 299 | * По успешна регистрација, апликацијата автоматски ве носи на страницата за персонализација (`/welcome`). |
| | 300 | |
| | 301 | '''Чекор 3: Верификација и персонализација (`/welcome`)''' |
| | 302 | |
| | 303 | * Страницата го чита `token` параметарот од URL-то и ја верифицира вашата емаил адреса. |
| | 304 | * Пополнете ги полињата: '''Целосно име''', '''Работно место''' и '''Големина на компанијата''' (Фриленсер, Микро, Мала, Средна, Среден пазар, Голема компанија, Друго). |
| | 305 | * Кликнете `Започни да користиш Shifter`. Профилот сега е комплетен и сите заштитени страници стануваат достапни. |
| | 306 | * Ако верификацијата не успее, постои копче `Испрати нов емаил` за нов линк за верификација. |
| | 307 | |
| | 308 | '''Чекор 4: Најава (`/login`)''' |
| | 309 | |
| | 310 | * Внесете емаил и лозинка, или користете `Continue with Google`. |
| | 311 | * По најава access token се чува во меморија, а refresh token во `HttpOnly` колаче; сесијата се обновува автоматски при истек (401 → `/api/auth/refresh`). |
| | 312 | |
| | 313 | '''Чекор 5: Профил (`/profile`)''' |
| | 314 | |
| | 315 | * '''Мој Профил''' – измена на име, работна позиција и големина на компанијата; се зачувува со `Зачувај Промени`. |
| | 316 | * '''Интереси''' и '''Стекнати Вештини''' – кликнете на секцијата за да отворите модал со теми и изберете барем една. |
| | 317 | * '''Одјави се''' – ја затвора сесијата. |
| | 318 | |
| | 319 | '''Чекор 6: Бесплатна сесија (`/free-consultation`)''' |
| | 320 | |
| | 321 | Ова е најкомплексниот тек и ги користи Google Calendar, Zoom и емаил интеграциите: |
| | 322 | |
| | 323 | * Апликацијата ги повлекува '''слободните термини''' на експертот од Google Calendar – работни денови, 08:00–16:30, во вашата временска зона. |
| | 324 | * Изберете '''датум''' и '''час'''. |
| | 325 | * Пополнете ги полињата: основни информации, за компанијата, предизвици и дополнителни информации. |
| | 326 | * Кликнете на копчето за закажување. Во позадина се креира настан во Google Calendar, се отвора Zoom состанок и се испраќаат емаил потврди до вас и до експертот. |
| | 327 | * Секој корисник има право на '''една''' бесплатна консултација – при обид за втора се појавува порака дека веќе е искористена. |
| | 328 | |
| | 329 | '''Чекор 7: Контакт (`/contact`)''' |
| | 330 | |
| | 331 | * Полињата '''Име''' и '''Е-пошта''' се автоматски пополнети од вашиот профил. |
| | 332 | * Внесете '''Причина за контакт''' и '''Порака''', па кликнете `Испрати`. |
| | 333 | * Пораката се испраќа до експертот преку ZeptoMail; се појавува потврда на екранот. |
| | 334 | |
| | 335 | '''Чекор 8: Информативни страници''' |
| | 336 | |
| | 337 | * '''За Нас''' (`/about`) – мисија, вредности и претставување на тимот. Јавно достапна. |
| | 338 | * '''Менторство''' (`/mentoring`), '''Консалтинг''' (`/consulting`), '''Академии''' (`/academies`) – опис на услугите. '''Достапни само за најавени корисници.''' |
| | 339 | * '''Курсеви''' (`/courses`) – моментално прикажува страница „Дигитални Курсеви – Наскоро“; модулот за курсеви и учење е подготвен во backend-от, но е привремено исклучен во frontend-от. |
| | 340 | |
| | 341 | === Автоматски тестови === |
| | 342 | |
| | 343 | {{{#!sh |
| | 344 | cd backend |
| | 345 | ./mvnw test |
| | 346 | }}} |