Index: pom.xml
===================================================================
--- pom.xml	(revision 73c3109dc7dc91e556d10e6a40fd45cd3ac103b0)
+++ pom.xml	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -91,4 +91,9 @@
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
     </dependencies>
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/DevNotifyController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,42 @@
+package com.zinemasterapp.zinemasterapp.controller;
+
+import com.zinemasterapp.zinemasterapp.dto.Notificationdto;
+import com.zinemasterapp.zinemasterapp.dto.UserDTO;
+import com.zinemasterapp.zinemasterapp.service.RequestNotificationService;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Instant;
+import java.util.UUID;
+
+//samo za DEMO(proba dali rabotat notifikaciite ako e nekoj online) -> vo krajniot sistem nema da ima vakvo kopce
+@RestController
+@RequestMapping("/api/dev")//pocetok na url
+public class DevNotifyController {
+
+    private final RequestNotificationService notifier;//servis za prakjanje notifikacii
+
+    public DevNotifyController(RequestNotificationService notifier) {
+        this.notifier = notifier;
+    }
+
+    @PostMapping("/ping-admins")
+    @PreAuthorize("hasRole('ProductAdministrator')")//samo ako e administrator na produkti
+    public ResponseEntity<Void> pingAdmins(@AuthenticationPrincipal org.springframework.security.core.userdetails.User me) {//samo vrakja http status
+        var payload = new Notificationdto(
+                "TEST-" + UUID.randomUUID().toString().substring(0,8).toUpperCase(),
+                me.getUsername(),
+                Instant.now(),
+                1,
+                "Test notification from " + me.getUsername()
+        );
+        notifier.notifyAdmins(payload);
+        return ResponseEntity.noContent().build();
+    }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java	(revision 73c3109dc7dc91e556d10e6a40fd45cd3ac103b0)
+++ src/main/java/com/zinemasterapp/zinemasterapp/controller/ProductRequestController.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -10,7 +10,13 @@
 import com.zinemasterapp.zinemasterapp.repository.ProductRequestRepository;
 import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import com.zinemasterapp.zinemasterapp.service.RequestNotificationService;
+import jakarta.transaction.Transactional;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.Instant;
 import java.time.LocalDate;
 import java.util.ArrayList;
@@ -29,43 +35,68 @@
     private final UserRepository userRepo;
     private final ProductRepository productRepo;
-
-    public ProductRequestController(ProductRequestRepository requestRepo, ProductRequestItemRepository itemRepo,UserRepository userRepo,ProductRepository productRepo) {
+    private final RequestNotificationService requestNotificationService;
+
+    public ProductRequestController(ProductRequestRepository requestRepo, ProductRequestItemRepository itemRepo,UserRepository userRepo,ProductRepository productRepo,RequestNotificationService requestNotificationService) {
         this.requestRepo = requestRepo;
         this.itemRepo = itemRepo;
         this.userRepo = userRepo;
         this.productRepo = productRepo;
-    }
-
-    @PostMapping//sto pravime ako imame POST "ovoj URL"
-    public ResponseEntity<String> createRequest(@RequestBody CreatedRequest dto) {
-        String requestId;
-        do {
-            requestId = "R" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
-        } while (requestRepo.existsById(requestId));//za sek slucaj da nema isti ID
-
-        ProductRequest request = new ProductRequest();//kreiranata naracka
-        request.setId(requestId);
-        request.setUserId(dto.getUserId());
-        request.setRequestDate(LocalDate.now());
-        request.setStatus("pending");//default e
-
-        requestRepo.save(request);//go zacuvuvame vo baza
-
-        for (ProductRequestItemDTO itemDTO : dto.getItems()) {
-            Product product = productRepo.findById(itemDTO.getProductId())//dali postoi sekoj produkt
-                    .orElseThrow(() -> new RuntimeException("Продуктот не е најден"));
-
-            ProductRequestItem item = new ProductRequestItem();//ova mi e za taa tabela vo baza kade sto ima ID na narackata i ID na produktot
-            item.setRequest(request);
-            item.setProduct(product);
-            item.setQuantityRequested(itemDTO.getQuantityRequested());
-            itemRepo.save(item);
-
-            product.setReserved(product.getReserved() + itemDTO.getQuantityRequested());//se zgolemuva reserved
-            productRepo.save(product);
-        }
-
-        return ResponseEntity.ok(requestId);
-    }
+        this.requestNotificationService = requestNotificationService;
+    }
+
+
+@PostMapping
+@Transactional
+public ResponseEntity<String> createRequest(@RequestBody CreatedRequest dto) {
+    String requestId;
+    do {
+        requestId = "R" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
+    } while (requestRepo.existsById(requestId));
+
+    ProductRequest request = new ProductRequest();
+    request.setId(requestId);
+    request.setUserId(dto.getUserId());
+    request.setRequestDate(LocalDate.now());
+    request.setStatus("pending");
+
+    requestRepo.save(request);
+
+    for (ProductRequestItemDTO itemDTO : dto.getItems()) {
+        Product product = productRepo.findById(itemDTO.getProductId())
+                .orElseThrow(() -> new RuntimeException("Продуктот не е најден"));
+
+        ProductRequestItem item = new ProductRequestItem();
+        item.setRequest(request);
+        item.setProduct(product);
+        item.setQuantityRequested(itemDTO.getQuantityRequested());
+        itemRepo.save(item);
+
+        product.setReserved(product.getReserved() + itemDTO.getQuantityRequested());
+        productRepo.save(product);
+    }
+
+    String createdBy =
+            SecurityContextHolder.getContext().getAuthentication() != null
+                    ? SecurityContextHolder.getContext().getAuthentication().getName()
+                    : dto.getUserId();
+
+    Notificationdto payload = new Notificationdto(
+            requestId,
+            createdBy,
+            Instant.now(),
+            dto.getItems() != null ? dto.getItems().size() : 0,
+            "New request #" + requestId + " (" +
+                    (dto.getItems() != null ? dto.getItems().size() : 0) + " items)"
+    );
+
+
+    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+        @Override public void afterCommit() {
+            requestNotificationService.notifyAdmins(payload);//ako nesto ne uspee so zacuvuvanje so narakcata da ne dobijat notifikacija
+        }
+    });
+
+    return ResponseEntity.ok(requestId);
+}
 
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/dto/Notificationdto.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,59 @@
+package com.zinemasterapp.zinemasterapp.dto;
+
+import java.time.Instant;
+
+public class Notificationdto {
+    String requestId;
+    String createdBy;
+    Instant createdAt;
+    int itemCount;
+    String summary;
+
+    public Notificationdto(String requestId, String createdBy, Instant createdAt, int itemCount, String summary) {
+        this.requestId = requestId;
+        this.createdBy = createdBy;
+        this.createdAt = createdAt;
+        this.itemCount = itemCount;
+        this.summary = summary;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public Instant getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Instant createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public int getItemCount() {
+        return itemCount;
+    }
+
+    public void setItemCount(int itemCount) {
+        this.itemCount = itemCount;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+}
Index: src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java	(revision 73c3109dc7dc91e556d10e6a40fd45cd3ac103b0)
+++ src/main/java/com/zinemasterapp/zinemasterapp/repository/UserRepository.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -4,4 +4,5 @@
 import org.springframework.data.jpa.repository.JpaRepository;
 
+import java.util.List;
 import java.util.Optional;
 
@@ -9,4 +10,5 @@
     Optional<User> findByUsername(String username);//SELECT * FROM users WHERE username = ?, in the background
     Optional<User> findByEmail(String email);
+    List<User> findByUserTypeAndAccess(String user_type, int access);
 }
 
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java	(revision 73c3109dc7dc91e556d10e6a40fd45cd3ac103b0)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/SecurityConfig.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -76,5 +76,7 @@
                                 "/api/categories/**",
                                 "/api/uploads/**",
-                                "/uploads/**"//ova e za da mozat da se zemat slikite
+                                "/stomp/**",
+                                "/uploads/**",
+                                "/api/dev/**"//ova e za da mozat da se zemat slikite
                         ).permitAll()
                         .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/WebSocketConfig.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,26 @@
+package com.zinemasterapp.zinemasterapp.security;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+    @Override
+    public void registerStompEndpoints(StompEndpointRegistry registry) {
+        registry.addEndpoint("/stomp")//patekata
+                .setAllowedOriginPatterns("*")
+                .withSockJS();//ako kaj klientot nemoze so websocket togas e ao ajax
+    }
+
+    @Override
+    public void configureMessageBroker(MessageBrokerRegistry config) {
+        config.enableSimpleBroker("/topic", "/queue");//vo /topic site klienti dobivaat edno ensto sto se pratilo a so /queue samo eden klient dobiva(private e)
+        config.setApplicationDestinationPrefixes("/app");//na pr /app/hello samo ke go prati na url so /hello
+        config.setUserDestinationPrefix("/user");//za specificni korisnici
+    }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/security/jwt/JwtChannelInterceptor.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,53 @@
+package com.zinemasterapp.zinemasterapp.security.jwt;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.simp.config.ChannelRegistration;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
+import org.springframework.messaging.support.ChannelInterceptor;
+import org.springframework.messaging.support.MessageHeaderAccessor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+@Component
+public class JwtChannelInterceptor implements ChannelInterceptor {//za da moze da gi vidime websocket porakite(online/offline)
+    private final JwtService jwtService;
+    private final UserDetailsService userDetailsService;
+
+    public JwtChannelInterceptor(JwtService jwtService, UserDetailsService uds) {
+        this.jwtService = jwtService; this.userDetailsService = uds;
+    }
+
+    @Override
+    public Message<?> preSend(Message<?> message, MessageChannel channel) {//stom dojde poraka od klientot
+        StompHeaderAccessor acc = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);//da go citame zaglavjeto
+        if (acc != null && StompCommand.CONNECT.equals(acc.getCommand())) {//ako e connect(online e)
+            String auth = acc.getFirstNativeHeader("Authorization"); //tokenot
+            if (auth != null && auth.startsWith("Bearer ")) {
+                String token = auth.substring(7);//trgame bearer
+                String username = jwtService.extractUsername(token);
+                UserDetails user = userDetailsService.loadUserByUsername(username);
+                UsernamePasswordAuthenticationToken authn = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+                acc.setUser(authn);//da moze da znaeme koja sesija za koj korisnik e
+            }
+        }
+        return message;
+    }
+}
+
+@Configuration
+class WebSocketAuthConfig implements WebSocketMessageBrokerConfigurer {
+    private final JwtChannelInterceptor interceptor;//ova e toa pogore kodot^
+    WebSocketAuthConfig(JwtChannelInterceptor i) { this.interceptor = i; }
+
+    @Override
+    public void configureClientInboundChannel(ChannelRegistration reg) {//pred kontrollerite mora da se proveri dali e konektirano
+        reg.interceptors(interceptor);
+    }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/PresenceService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,33 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import org.springframework.context.event.EventListener;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.messaging.SessionConnectEvent;
+import org.springframework.web.socket.messaging.SessionDisconnectEvent;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class PresenceService {
+    private final Set<String> onlineAdmins = ConcurrentHashMap.newKeySet();
+
+    @EventListener
+    public void onConnect(SessionConnectEvent e) {
+        var user = (Authentication) e.getUser();
+        if (user != null && user.getAuthorities().stream()
+                .anyMatch(a -> a.getAuthority().equals("ROLE_ProductAdministrator"))) {
+            onlineAdmins.add(user.getName());
+        }
+    }
+
+    @EventListener
+    public void onDisconnect(SessionDisconnectEvent e) {
+        var user = (Authentication) e.getUser();
+        if (user != null) onlineAdmins.remove(user.getName());
+    }
+
+    public Set<String> getOnlineAdmins() { return Set.copyOf(onlineAdmins); }
+}
+
Index: src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java
===================================================================
--- src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
+++ src/main/java/com/zinemasterapp/zinemasterapp/service/RequestNotificationService.java	(revision 644d6fbea5324eadd4c531f6c37f855fb121be1f)
@@ -0,0 +1,73 @@
+package com.zinemasterapp.zinemasterapp.service;
+
+import com.zinemasterapp.zinemasterapp.dto.Notificationdto;
+import com.zinemasterapp.zinemasterapp.model.User;
+import com.zinemasterapp.zinemasterapp.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class RequestNotificationService {
+    private final PresenceService presence;
+    private final SimpMessagingTemplate messaging;
+    private final UserRepository userRepository;
+    private final EmailService emailService;
+
+    @Value("${app.ui.requests-url-base:http://localhost:8082/requests/}")
+    private String requestsUrlBase;
+
+    @Value("${app.mail.from:no-reply@your-domain.com}")
+    private String fromAddress;
+
+    public RequestNotificationService(PresenceService presence, SimpMessagingTemplate messaging, UserRepository userRepository, EmailService emailService) {
+        this.presence = presence; this.messaging = messaging;
+        this.userRepository = userRepository;
+        this.emailService = emailService;
+    }
+
+    public void notifyAdmins(Notificationdto payload) {
+        for (String adminUsername : presence.getOnlineAdmins()) {
+            messaging.convertAndSendToUser(adminUsername, "/queue/requests", payload);
+        }
+
+        var allAdmins = findAllAdmins();
+        for (User admin : allAdmins) {
+            String username = admin.getUsername();
+            if (presence.getOnlineAdmins().contains(username)) continue;
+            String to = safeEmail(admin.getEmail());
+            if (to == null) continue;
+
+            String link = (requestsUrlBase.endsWith("/") ? requestsUrlBase : requestsUrlBase + "/");
+            String subject = "New Request " + payload.getRequestId();
+            String body =
+                    "Hello,\n\n" +
+                            "A new request " + payload.getRequestId() + " (" + payload.getItemCount() + " item" +
+                            (payload.getItemCount() == 1 ? "" : "s") + ") was created by " + payload.getCreatedBy() + ".\n" +
+                            "Open: " + link + "\n\n" +
+                            "You are receiving this because you were offline.\n";
+
+            try {
+
+                emailService.sendEmail(to, subject, body);
+            } catch (Exception e) {
+
+                System.err.println("Email send failed to " + to + ": " + e.getMessage());
+            }
+        }
+    }
+    private List<User> findAllAdmins() {
+
+            return userRepository.findByUserTypeAndAccess("ProductAdministrator", 1);
+
+    }
+    private static boolean equalsIgnoreCase(String a, String b) {
+        return a != null && b != null && a.equalsIgnoreCase(b);
+    }
+    private static String safeEmail(String e) {
+        return (e != null && !e.isBlank()) ? e : null;
+    }
+}
+
