Index: backend/docs/SQL_Injection_Prevention.txt
===================================================================
--- backend/docs/SQL_Injection_Prevention.txt	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
+++ backend/docs/SQL_Injection_Prevention.txt	(revision 42da64dc0d28f9380336816f6fb82c7572d0021d)
@@ -0,0 +1,350 @@
+= SQL Injection Prevention Strategy =
+
+==概述 ==
+
+SQL Injection е критична безбедносна ранливост каде нападачот внесува злонамерен SQL код преку корисничкиот влез за манипулирање со базата на податоци. Овој документ опишува како е имплементирана заштитата во Trekr backend за спречување на овој вид напади.
+
+== 1. Безбедни пракси имплементирани во проектот ==
+
+=== 1.1 Параметризирани пропити преку JPA ===
+
+Целиот backend користи Spring Data JPA која автоматски генерира параметризирани пропити што спречуваат SQL injection.
+
+==== Безбедно: Derived Query Methods ====
+
+Кога користиме method names на Spring Data, те се автоматски преведуваат во параметризирани SQL пропити:
+
+{{{
+// UserRepository.java
+public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByEmail(String email);
+    Optional<User> findByUsername(String username);
+}
+
+// Сэчно користи параметриран пропит во подложина (EQUIVALENT TO):
+// SELECT * FROM users WHERE email = ?
+// параметарот se праќа одвоено од SQL командата
+}}}
+
+==== Безбедно: @Query со @Param ====
+
+За комплексни пропити, користториве @Query со именувани параметри:
+
+{{{
+// TaskRepository.java
+@Modifying
+@Query("update Task t set t.finished = false where t.disciplineUser.userId = :userId")
+int resetFinishedForUser(@Param("userId") Long userId);
+
+// Параметарот :userId се врзува безбедно преку JDBC, не преку string concatenation
+}}}
+
+==== Безбедно: Collection параметри ====
+
+За множество вредности (IN клаузули), користи параметризиран пропит:
+
+{{{
+// TrainingSessionRepository.java
+@java.util.List<TrainingSession> findByTrainingUser_UserIdAndDateIn(
+    Long userId,
+    Collection<LocalDate> dates
+);
+
+// Сэчно: SELECT * FROM training_sessions WHERE training_user_id = ? AND date IN (?, ?, ?)
+// Сите параметри се врзени безбедно, не преку строг конкатенација
+}}}
+
+=== 1.2 Строга типизација на параметри ===
+
+Кога параметарите се типизирани (Long, BigDecimal, LocalDate), т.е. не strings, SQL injection е веќе невозможна:
+
+{{{
+// FinanceService.java - пример од createIncome
+BigDecimal amount = request.getAmount();  // BigDecimal, не String
+if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+    throw new RuntimeException("Amount must be greater than 0");
+}
+
+Income income = new Income();
+income.setAmount(amount);  // Num values can't inject SQL
+incomeRepository.save(income);
+
+// Генерирана SQL: INSERT INTO incomes (amount) VALUES (?)
+// Параметарот amount се врзува како број, не како строк
+}}}
+
+=== 1.3 Основна валидација на влезот ===
+
+Така поради внесување и валидација на нивото на апликација пред db операции:
+
+{{{
+// CustomTrackingService.java
+public CustomTrackingCategoryDto createCustomTrackingCategory(
+        Long userId,
+        CreateCustomTrackingCategoryRequest request) {
+
+    String name = request.getName();
+
+    // Валидација: не null/empty
+    if (name == null || name.isBlank()) {
+        throw new RuntimeException("Name is required");
+    }
+
+    String trimmed = name.trim();
+
+    // Валидација: должина
+    if (trimmed.length() > 100) {
+        throw new RuntimeException("Name is too long");
+    }
+
+    // На крај, trimmed value се зачувува преку ORM
+    // Никогаш не се користи raw SQL string concatenation
+    CustomTrackingCategory category = new CustomTrackingCategory();
+    category.setName(trimmed);
+    customTrackingCategoryRepository.save(category);
+}
+}}}
+
+=== 1.4 Authorization checks за ownership ===
+
+Дополнително заштита: провера дека конкретен корисник има право да приступи на специфичната информација:
+
+{{{
+// CustomTrackingService.java
+@Transactional
+public void deleteCustomCategoryTask(Long userId, Long customTrackingId, Long taskId) {
+    Task task = taskRepository.findById(taskId)
+            .orElseThrow(() -> new RuntimeException("Task not found"));
+
+    // Провера: дали задачата припаѓа на корисникот
+    if (task.getCustomTrackingCategory() == null
+            || !customTrackingId.equals(task.getCustomTrackingCategory().getCustomTrackingId())
+            || task.getCustomTrackingCategory().getUser() == null
+            || !userId.equals(task.getCustomTrackingCategory().getUser().getUserId())) {
+        throw new RuntimeException("Task not found");  // Скриена информација
+    }
+
+    int deleted = taskRepository.deleteByTaskIdAndCustomTrackingCategory_CustomTrackingIdAndCustomTrackingCategory_User_UserId(
+            taskId, customTrackingId, userId);
+    if (deleted == 0) {
+        throw new RuntimeException("Task not found");
+    }
+}
+}}}
+
+== 2. НЕБЕЗБЕДНИ ПРАКСИ која НИКОГАШ не смеаат да бидат користени ==
+
+=== 2.1 String Concatenation со SQL ===
+
+❌ НЕБЕЗБЕДНО - НЕ КВАЧИ НИКОГАШ:
+
+{{{
+// ОПАСНО: SQL injection уязвимост
+String email = userInput;  // од форма или параметар
+String query = "SELECT * FROM users WHERE email = '" + email + "'";
+// Ако userInput = "admin' OR '1'='1", резултира:
+// SELECT * FROM users WHERE email = 'admin' OR '1'='1'
+// Сие врати сите корисници!
+}}}
+
+=== 2.2 Директни SQL пропити со методи како createNativeQuery без параметри ===
+
+❌ НЕБЕЗБЕДНО:
+
+{{{
+// ОПАСНО
+String userProvidedFilter = request.getFilter();
+TypedQuery<User> query = entityManager.createNativeQuery(
+    "SELECT * FROM users WHERE name = '" + userProvidedFilter + "'",
+    User.class
+);
+// Позволува SQL injection
+}}}
+
+✅ БЕЗБЕДНО:
+
+{{{
+// ПРАВИЛНО
+TypedQuery<User> query = entityManager.createNativeQuery(
+    "SELECT * FROM users WHERE name = ?1",
+    User.class
+);
+query.setParameter(1, userProvidedFilter);
+// Параметарот se врзува безбедно
+}}}
+
+=== 2.3 Динамички конструирање на JPQL пропити преку string concatenation ===
+
+❌ НЕБЕЗБЕДНО:
+
+{{{
+// ОПАСНО
+String sortBy = request.getSortBy();  // "name' OR 'x'='x"
+String jpql = "SELECT u FROM User u ORDER BY u." + sortBy;
+TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
+// Позволува SQL injection преку ORDER BY clause
+}}}
+
+✅ БЕЗБЕДНО:
+
+{{{
+// ПРАВИЛНО: користи Specification или CriteriaBuilder за динамички пропити
+CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+CriteriaQuery<User> cq = cb.createQuery(User.class);
+Root<User> root = cq.from(User.class);
+
+// Динамички sort параметар е безбедно обработен
+Order order = Sort.Direction.ASC.equals(direction)
+    ? cb.asc(root.get(sortField))
+    : cb.desc(root.get(sortField));
+
+cq.orderBy(order);
+TypedQuery<User> query = entityManager.createQuery(cq);
+}}}
+
+== 3. Best Practices за имплементација ==
+
+=== 3.1 Правило: Никогаш не користи user input директно во SQL ===
+
+* Сите user inputs мора да бидат параметри
+* Користи Spring Data JPA методи deriva или @Query со @Param
+* За комплексни пропити, користи CriteriaBuilder или Specification
+
+=== 3.2 Правило: Типизирај параметри секогаш ===
+
+❌ ЛОШО:
+{{{
+Long userId = Long.parseLong(request.getUserId());  // String -> Long конверзија
+String query = "SELECT * FROM users WHERE id = " + userId;  // Неопходно SQL injection ризик
+}}}
+
+✅ ДОБРО:
+{{{
+Long userId = Long.parseLong(request.getUserId());  // String -> Long конверзија
+userRepository.findById(userId);  // Параметар е Long, не String
+// Типизирани параметри не можат да содржат SQL код
+}}}
+
+=== 3.3 Правило: Валидирай влез пред користење ===
+
+* Должина на strings
+* Формат на emails (regex или validator)
+* Диапаз��н на числа
+* Null/empty проверки
+
+{{{
+// FinanceService.java - пример
+public void startOrUpdateTracking(Long userId, FinanceStartRequest request) {
+    BigDecimal spending = normalizePercent(request.getSpendingBudget());
+    // ... остали валидации ...
+
+    // Валидирај сума = 100
+    validateSumTo100(spending, saving, investing, donation, credit);
+
+    // Сека валидација е успешна пред зачување во DB
+    financeUserRepository.save(financeUser);
+}
+}}}
+
+=== 3.4 Правило: Користи ОРМ библиотеки (JPA/Hibernate) ===
+
+* ОРМ автоматски параметризира пропити
+* Избегнувај raw JDBC комнди без параметри
+* За специјални случаи, користи JPA Criteria API
+
+=== 3.5 Правило: Логирај и мониторирај пропокусни пропити ===
+
+{{{
+// application.properties
+spring.jpa.show-sql=true
+logging.level.org.hibernate.SQL=DEBUG
+logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
+
+// TRACE анализира пропити и видија каков параметри се врзуваат
+}}}
+
+== 4. Минимални правила за нови допринесувачи ==
+
+При собачување нов feature, следи:
+
+1. ✅ Користи Spring Data JPA методи (findBy*, save*, etc.)
+2. ✅ За custom пропити, користи @Query("#jpql") со @Param параметри
+3. ✅ Никогаш не користи string concatenation со SQL
+4. ✅ Валидирај user inputs пред DB операции
+5. ✅ Провери ownership/authorization пред delete/update операции
+6. ✅ Типизирај сите параметри (Long, BigDecimal, LocalDate, не String)
+
+== 5. Пример: Правилна имплементација новог endpoint-а ==
+
+Сценарио: Нов endpoint за ажурирање на трошок по категорија.
+
+{{{
+// FinanceController.java
+@PutMapping("/allocations/{categoryId}")
+public ResponseEntity<AllocationDto> updateAllocation(
+        @PathVariable Long categoryId,
+        @Valid @RequestBody UpdateAllocationRequest request) {
+
+    AllocationDto result = financeService.updateAllocation(userId, categoryId, request);
+    return ResponseEntity.ok(result);
+}
+
+// FinanceService.java
+@Transactional
+public AllocationDto updateAllocation(Long userId, Long categoryId, UpdateAllocationRequest request) {
+
+    // 1. Валидирај input
+    BigDecimal amount = request.getAmount();
+    if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+        throw new RuntimeException("Amount must be greater than 0");
+    }
+
+    // 2. Преземи објект од DB (JPA - безбедно)
+    Allocation allocation = allocationRepository.findById(categoryId)
+            .orElseThrow(() -> new RuntimeException("Allocation not found"));
+
+    // 3. Провери ownership
+    if (!userId.equals(allocation.getUser().getUserId())) {
+        throw new RuntimeException("Allocation not found");
+    }
+
+    // 4. Ажурирај и зачувај (JPA - безбедно)
+    allocation.setAmount(amount);
+    Allocation saved = allocationRepository.save(allocation);
+
+    // 5. Врати DTO
+    return new AllocationDto(saved.getId(), saved.getAmount());
+}
+}}}
+
+== 6. Заднила и прилична функција ==
+
+Оваа заштита е имплементирана во:
+
+* UserRepository.java - findByEmail/Username
+* TaskRepository.java - findByDisciplineUser_UserId, resetFinishedForUser (@Query)
+* TrainingSessionRepository.java - findByTrainingUser_UserIdAndDateIn
+* CustomTrackingService.java - ownership checking + validation
+* FinanceService.java - input validation + JPA save
+* DisciplineService.java - ownership checks + validation
+
+== 7. Потребни алатки за QA ==
+
+При тестирање на нови endpoints, следи:
+
+1. SQL logging: вклучи DEBUG на Hibernate SQL во тест environment
+2. Penetration testing: алати како OWASP ZAP или SQLmap (за белите хакери)
+3. Code review: проверка дека нема string concatenation со SQL
+4. Static analysis: SonarQube или Checkmarx може да детектери потенцијални SQL injection ризици
+
+== Закончување ==
+
+Trekr backend е заштитен од SQL Injection напади преку:
+* Spring Data JPA параметризирани пропити
+* Типизирани параметри
+* Приоритетна валидација
+* Ownership & authorization checks
+* Никогаш нема string concatenation со SQL
+
+За сите нови features, следи ги best practices навултени во овој документ.
+
