= Други Развојни Активности = == Анализа на перформанси == === Извештај за годишна финансиска активност на корисници === Анализата на перформанси се врши врз основа на моменталната состојба во базата, која има податоци кои служат за тестирање. Базата содржи: ~1000 корисници, ~2000 приходи (incomes), ~1000 finance профили. SQL: {{{ CREATE OR REPLACE FUNCTION test_finance_report() RETURNS void AS $$ DECLARE start_time timestamptz; end_time timestamptz; duration int; BEGIN start_time := clock_timestamp(); PERFORM * FROM ( SELECT fb.user_id, COALESCE(stats.income_count, 0), COALESCE(stats.annual_total_income, 0), COALESCE(stats.active_months, 0) FROM finance_users fb LEFT JOIN ( -- Combined logic into one subquery for efficiency SELECT user_id, COUNT(*) AS income_count, SUM(amount) AS annual_total_income, COUNT(DISTINCT EXTRACT(MONTH FROM date)) AS active_months FROM incomes WHERE EXTRACT(YEAR FROM date) = 2026 GROUP BY user_id ) stats ON stats.user_id = fb.user_id ) t; end_time := clock_timestamp(); duration := round(1000 * (extract(epoch FROM end_time) - extract(epoch FROM start_time))); RAISE NOTICE 'Query executed in: % ms', duration; END; }}} Индекси: 1. {{{ idx_incomes_user_date CREATE INDEX idx_incomes_user_date ON incomes(user_id, date); }}} Подобрување: Index Scan наместо Seq Scan + подобро филтрирање по година 2. {{{ idx_incomes_year CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date))); }}} Подобрување: Избегнува full table scan за годишен филтер 3. {{{ idx_incomes_covering CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount); }}} Подобрување: Direct lookup 4. {{{ CREATE UNIQUE INDEX users_pkey ON users(user_id); }}} Подобрување: Fast join 5. {{{ CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount); }}} Подобрување: Index-only scan (без пристап до табелата) ==SQL за анализа на брзината {{{ CREATE OR REPLACE FUNCTION test_finance_report() RETURNS void AS $$ DECLARE start_time timestamptz; end_time timestamptz; duration int; BEGIN start_time := clock_timestamp(); PERFORM * FROM ( SELECT fb.user_id, COALESCE(stats.income_count, 0), COALESCE(stats.annual_total_income, 0), COALESCE(stats.active_months, 0) FROM finance_users fb LEFT JOIN ( -- Combined logic into one subquery for efficiency SELECT user_id, COUNT(*) AS income_count, SUM(amount) AS annual_total_income, COUNT(DISTINCT EXTRACT(MONTH FROM date)) AS active_months FROM incomes WHERE EXTRACT(YEAR FROM date) = 2026 GROUP BY user_id ) stats ON stats.user_id = fb.user_id ) t; end_time := clock_timestamp(); duration := round(1000 * (extract(epoch FROM end_time) - extract(epoch FROM start_time))); RAISE NOTICE 'Query executed in: % ms', duration; END; $$ LANGUAGE plpgsql; -- 4. PERFORMANCE RUNS -- run 1: no indexes SELECT test_finance_report(); -- run 2: add idx_incomes_user_date CREATE INDEX idx_incomes_user_date ON incomes(user_id, date); ANALYZE incomes; SELECT test_finance_report(); -- run 3: add idx_incomes_year (Expression Index) CREATE INDEX idx_incomes_year ON incomes ((EXTRACT(YEAR FROM date))); ANALYZE incomes; SELECT test_finance_report(); -- run 4: add covering index CREATE INDEX idx_incomes_covering ON incomes(user_id, date, amount); ANALYZE incomes; SELECT test_finance_report(); -- Cleanup DROP FUNCTION test_finance_report(); }}} Сумарно: Без индекси: ~41ms Со индекси: ~16ms Забрзување: ~2.6x == Безбедност и заштита == === JWT Token Authorization (Spring Security) === JWT (JSON Web Token) e stateless начин на автентикација - server НЕ чува информации за активни сесии во база, туку сите потребни податоци се во самиот token кој корисникот го чува локално/cookie. JWT содржи енкодирана json структура на информации (user_id, email, role, expiry...). Java код во Spring Boot: {{{ @Configuration @Getter @Setter @ConfigurationProperties(prefix = "auth") public class AuthProperties { private String secret; private int accessTokenMaxAge; private int refreshTokenMaxAge; } @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(Customizer.withDefaults()) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .httpBasic(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated()); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }}} === Хеширање на пасворди (BCrypt) === Пасвордите на корисниците се чуваат во база во хеширана форма преку BCrypt, а не како plain text. Ова овозможува сигурно чување на пасвордите. {{{ public class LegacyPasswordEncoder implements PasswordEncoder { private final BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder(); @Override public String encode(CharSequence rawPassword) { return bcrypt.encode(rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { if (encodedPassword == null || encodedPassword.isEmpty()) { return false; } if (encodedPassword.startsWith("$2a$") || encodedPassword.startsWith("$2b$")) { return bcrypt.matches(rawPassword, encodedPassword); } return rawPassword.toString().equals(encodedPassword); } } }}} === SQL Injection Prevention (Spring JPA/JPQL) Целиот backend користи Spring Data JPA која автоматски генерира параметризирани пропити што спречуваат SQL injection. ==== Безбедно: Derived Query Methods ==== {{{ // UserRepository.java public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByUsername(String username); } // SELECT * FROM users WHERE email = ? // параметарот се праќа одвоено од SQL командата }}} ==== Безбедно: @Query со @Param ==== {{{ // 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 параметри ==== {{{ // TrainingSessionRepository.java @java.util.List findByTrainingUser_UserIdAndDateIn( Long userId, Collection dates ); === CORS Configuration === CORS е безбеден механизам за заштита од requests од различни домени. Со тоа се заштитуваме од можни злонамерни requests. Java код во Spring Boot: {{{ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of( "http://localhost:5173", "http://127.0.0.1:5173")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }}}