Dev/v1.0 (#1)

Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local>
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-07-23 10:51:04 +08:00
parent 3189e3a1e3
commit 85b31e9528
106 changed files with 19492 additions and 401 deletions
@@ -0,0 +1,35 @@
package ba.unsa.etf.si.bbqms.admin_service.api;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface AdvertisementService {
Advertisement create(String tenantCode,
MultipartFile file,
String title,
Integer durationSeconds,
Integer sortOrder,
Boolean active) throws Exception;
List<Advertisement> listByTenant(String tenantCode);
List<Advertisement> listActiveByTenant(String tenantCode);
Advertisement update(String tenantCode,
long adId,
String title,
Integer durationSeconds,
Integer sortOrder,
Boolean active) throws Exception;
void delete(String tenantCode, long adId) throws Exception;
Advertisement getForTenant(String tenantCode, long adId);
Advertisement findById(long adId);
Resource loadMedia(long adId) throws Exception;
}
@@ -0,0 +1,214 @@
package ba.unsa.etf.si.bbqms.admin_service.implementation;
import ba.unsa.etf.si.bbqms.admin_service.api.AdvertisementService;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import ba.unsa.etf.si.bbqms.domain.AdvertisementMediaType;
import ba.unsa.etf.si.bbqms.domain.Tenant;
import ba.unsa.etf.si.bbqms.repository.AdvertisementRepository;
import ba.unsa.etf.si.bbqms.tenant_service.api.TenantService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
@Service
public class DefaultAdvertisementService implements AdvertisementService {
private static final int DEFAULT_DURATION_SECONDS = 10;
private static final Set<String> IMAGE_TYPES = Set.of(
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
);
private static final Set<String> VIDEO_TYPES = Set.of(
"video/mp4",
"video/webm"
);
private final AdvertisementRepository advertisementRepository;
private final TenantService tenantService;
private final Path uploadRoot;
public DefaultAdvertisementService(final AdvertisementRepository advertisementRepository,
final TenantService tenantService,
@Value("${ads.upload-dir:uploads/ads}") final String uploadDir) {
this.advertisementRepository = advertisementRepository;
this.tenantService = tenantService;
this.uploadRoot = Path.of(uploadDir).toAbsolutePath().normalize();
}
@Override
public Advertisement create(final String tenantCode,
final MultipartFile file,
final String title,
final Integer durationSeconds,
final Integer sortOrder,
final Boolean active) throws Exception {
if (file == null || file.isEmpty()) {
throw new IllegalArgumentException("File is required.");
}
final String contentType = normalizeContentType(file.getContentType());
final AdvertisementMediaType mediaType = resolveMediaType(contentType);
final Tenant tenant = this.tenantService.findByCode(tenantCode);
final String originalName = file.getOriginalFilename() != null ? file.getOriginalFilename() : "upload";
final String safeFileName = sanitizeFileName(originalName);
final String storedName = UUID.randomUUID() + "-" + safeFileName;
final Path tenantDir = this.uploadRoot.resolve(tenantCode).normalize();
Files.createDirectories(tenantDir);
final Path destination = tenantDir.resolve(storedName).normalize();
if (!destination.startsWith(tenantDir)) {
throw new IllegalArgumentException("Invalid file path.");
}
try {
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
} catch (final IOException exception) {
throw new IllegalStateException("Failed to store advertisement file.", exception);
}
final String relativePath = this.uploadRoot.relativize(destination).toString().replace('\\', '/');
final int nextSortOrder = sortOrder != null
? sortOrder
: this.advertisementRepository.findByTenant_CodeOrderBySortOrderAscIdAsc(tenantCode).stream()
.mapToInt(Advertisement::getSortOrder)
.max()
.orElse(-1) + 1;
final Advertisement advertisement = new Advertisement(
tenant,
title,
mediaType,
originalName,
contentType,
relativePath,
nextSortOrder,
active == null || active,
durationSeconds != null ? durationSeconds : DEFAULT_DURATION_SECONDS,
Instant.now()
);
return this.advertisementRepository.save(advertisement);
}
@Override
public List<Advertisement> listByTenant(final String tenantCode) {
this.tenantService.findByCode(tenantCode);
return this.advertisementRepository.findByTenant_CodeOrderBySortOrderAscIdAsc(tenantCode);
}
@Override
public List<Advertisement> listActiveByTenant(final String tenantCode) {
this.tenantService.findByCode(tenantCode);
return this.advertisementRepository.findByTenant_CodeAndActiveTrueOrderBySortOrderAscIdAsc(tenantCode);
}
@Override
public Advertisement update(final String tenantCode,
final long adId,
final String title,
final Integer durationSeconds,
final Integer sortOrder,
final Boolean active) {
final Advertisement advertisement = getForTenant(tenantCode, adId);
if (title != null) {
advertisement.setTitle(title);
}
if (durationSeconds != null) {
advertisement.setDurationSeconds(durationSeconds);
}
if (sortOrder != null) {
advertisement.setSortOrder(sortOrder);
}
if (active != null) {
advertisement.setActive(active);
}
return this.advertisementRepository.save(advertisement);
}
@Override
public void delete(final String tenantCode, final long adId) throws Exception {
final Advertisement advertisement = getForTenant(tenantCode, adId);
final Path filePath = resolveStoredPath(advertisement.getStoragePath());
this.advertisementRepository.delete(advertisement);
try {
Files.deleteIfExists(filePath);
} catch (final IOException exception) {
// Row is already removed; log-worthy but don't fail the API for orphan files.
}
}
@Override
public Advertisement getForTenant(final String tenantCode, final long adId) {
final Advertisement advertisement = this.advertisementRepository.get(adId);
if (!advertisement.getTenant().getCode().equals(tenantCode)) {
throw new EntityNotFoundException("Advertisement not found for tenant: " + tenantCode);
}
return advertisement;
}
@Override
public Advertisement findById(final long adId) {
return this.advertisementRepository.get(adId);
}
@Override
public Resource loadMedia(final long adId) throws Exception {
final Advertisement advertisement = this.advertisementRepository.get(adId);
final Path filePath = resolveStoredPath(advertisement.getStoragePath());
final Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
throw new EntityNotFoundException("Advertisement media file not found.");
}
return resource;
}
private Path resolveStoredPath(final String storagePath) {
final Path resolved = this.uploadRoot.resolve(storagePath).normalize();
if (!resolved.startsWith(this.uploadRoot)) {
throw new IllegalArgumentException("Invalid storage path.");
}
return resolved;
}
private static String normalizeContentType(final String contentType) {
if (contentType == null || contentType.isBlank()) {
throw new IllegalArgumentException("Missing content type.");
}
return contentType.toLowerCase(Locale.ROOT).split(";")[0].trim();
}
private static AdvertisementMediaType resolveMediaType(final String contentType) {
if (IMAGE_TYPES.contains(contentType)) {
return AdvertisementMediaType.IMAGE;
}
if (VIDEO_TYPES.contains(contentType)) {
return AdvertisementMediaType.VIDEO;
}
throw new IllegalArgumentException("Unsupported media type: " + contentType);
}
private static String sanitizeFileName(final String originalName) {
final String name = Path.of(originalName).getFileName().toString();
final String sanitized = name.replaceAll("[^a-zA-Z0-9._-]", "_");
return sanitized.isBlank() ? "upload" : sanitized;
}
}
@@ -0,0 +1,167 @@
package ba.unsa.etf.si.bbqms.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.Instant;
@Entity
@Table(name = "advertisement")
public class Advertisement {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@ManyToOne(optional = false)
@JoinColumn(name = "tenant_id", referencedColumnName = "id")
private Tenant tenant;
@Column(name = "title")
private String title;
@Enumerated(EnumType.STRING)
@Column(name = "media_type", nullable = false)
private AdvertisementMediaType mediaType;
@Column(name = "file_name", nullable = false)
private String fileName;
@Column(name = "content_type", nullable = false)
private String contentType;
@Column(name = "storage_path", nullable = false)
private String storagePath;
@Column(name = "sort_order", nullable = false)
private int sortOrder;
@Column(name = "active", nullable = false)
private boolean active;
@Column(name = "duration_seconds", nullable = false)
private int durationSeconds;
@Column(name = "created_at", nullable = false)
private Instant createdAt;
public Advertisement() {
}
public Advertisement(final Tenant tenant,
final String title,
final AdvertisementMediaType mediaType,
final String fileName,
final String contentType,
final String storagePath,
final int sortOrder,
final boolean active,
final int durationSeconds,
final Instant createdAt) {
this.tenant = tenant;
this.title = title;
this.mediaType = mediaType;
this.fileName = fileName;
this.contentType = contentType;
this.storagePath = storagePath;
this.sortOrder = sortOrder;
this.active = active;
this.durationSeconds = durationSeconds;
this.createdAt = createdAt;
}
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(final Tenant tenant) {
this.tenant = tenant;
}
public String getTitle() {
return title;
}
public void setTitle(final String title) {
this.title = title;
}
public AdvertisementMediaType getMediaType() {
return mediaType;
}
public void setMediaType(final AdvertisementMediaType mediaType) {
this.mediaType = mediaType;
}
public String getFileName() {
return fileName;
}
public void setFileName(final String fileName) {
this.fileName = fileName;
}
public String getContentType() {
return contentType;
}
public void setContentType(final String contentType) {
this.contentType = contentType;
}
public String getStoragePath() {
return storagePath;
}
public void setStoragePath(final String storagePath) {
this.storagePath = storagePath;
}
public int getSortOrder() {
return sortOrder;
}
public void setSortOrder(final int sortOrder) {
this.sortOrder = sortOrder;
}
public boolean isActive() {
return active;
}
public void setActive(final boolean active) {
this.active = active;
}
public int getDurationSeconds() {
return durationSeconds;
}
public void setDurationSeconds(final int durationSeconds) {
this.durationSeconds = durationSeconds;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(final Instant createdAt) {
this.createdAt = createdAt;
}
}
@@ -0,0 +1,6 @@
package ba.unsa.etf.si.bbqms.domain;
public enum AdvertisementMediaType {
IMAGE,
VIDEO
}
@@ -0,0 +1,11 @@
package ba.unsa.etf.si.bbqms.repository;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import java.util.List;
public interface AdvertisementRepository extends BaseRepository<Advertisement, Long> {
List<Advertisement> findByTenant_CodeOrderBySortOrderAscIdAsc(String tenantCode);
List<Advertisement> findByTenant_CodeAndActiveTrueOrderBySortOrderAscIdAsc(String tenantCode);
}
@@ -0,0 +1,153 @@
package ba.unsa.etf.si.bbqms.ws.controllers;
import ba.unsa.etf.si.bbqms.admin_service.api.AdvertisementService;
import ba.unsa.etf.si.bbqms.auth_service.api.AuthService;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import ba.unsa.etf.si.bbqms.ws.models.AdvertisementDto;
import ba.unsa.etf.si.bbqms.ws.models.SimpleMessageDto;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
@RequestMapping("/api/v1/ads")
public class AdvertisementController {
private final AdvertisementService advertisementService;
private final AuthService authService;
public AdvertisementController(final AdvertisementService advertisementService,
final AuthService authService) {
this.advertisementService = advertisementService;
this.authService = authService;
}
@GetMapping("/media/{adId}")
public ResponseEntity streamMedia(@PathVariable final long adId) {
try {
final Advertisement advertisement = this.advertisementService.findById(adId);
final Resource resource = this.advertisementService.loadMedia(adId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + advertisement.getFileName() + "\"")
.contentType(MediaType.parseMediaType(advertisement.getContentType()))
.body(resource);
} catch (final Exception exception) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{tenantCode}/active")
public ResponseEntity listActiveAdvertisements(@PathVariable final String tenantCode) {
try {
final List<AdvertisementDto> ads = this.advertisementService.listActiveByTenant(tenantCode).stream()
.map(AdvertisementDto::fromEntity)
.toList();
return ResponseEntity.ok().body(ads);
} catch (final Exception exception) {
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
}
}
@PostMapping("/{tenantCode}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity createAdvertisement(@PathVariable final String tenantCode,
@RequestParam("file") final MultipartFile file,
@RequestParam(value = "title", required = false) final String title,
@RequestParam(value = "durationSeconds", required = false) final Integer durationSeconds,
@RequestParam(value = "sortOrder", required = false) final Integer sortOrder,
@RequestParam(value = "active", required = false) final Boolean active) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final Advertisement created = this.advertisementService.create(
tenantCode,
file,
title,
durationSeconds,
sortOrder,
active
);
return ResponseEntity.ok().body(AdvertisementDto.fromEntity(created));
} catch (final Exception exception) {
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
}
}
@GetMapping("/{tenantCode}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity listAdvertisements(@PathVariable final String tenantCode) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final List<AdvertisementDto> ads = this.advertisementService.listByTenant(tenantCode).stream()
.map(AdvertisementDto::fromEntity)
.toList();
return ResponseEntity.ok().body(ads);
} catch (final Exception exception) {
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
}
}
@PutMapping("/{tenantCode}/{adId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity updateAdvertisement(@PathVariable final String tenantCode,
@PathVariable final long adId,
@RequestBody final AdvertisementUpdateRequest request) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final Advertisement updated = this.advertisementService.update(
tenantCode,
adId,
request.title(),
request.durationSeconds(),
request.sortOrder(),
request.active()
);
return ResponseEntity.ok().body(AdvertisementDto.fromEntity(updated));
} catch (final Exception exception) {
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
}
}
@DeleteMapping("/{tenantCode}/{adId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity deleteAdvertisement(@PathVariable final String tenantCode,
@PathVariable final long adId) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
this.advertisementService.delete(tenantCode, adId);
return ResponseEntity.ok().body(new SimpleMessageDto("Deleted advertisement with id: " + adId));
} catch (final Exception exception) {
return ResponseEntity.badRequest().body(new SimpleMessageDto(exception.getMessage()));
}
}
public record AdvertisementUpdateRequest(String title,
Integer durationSeconds,
Integer sortOrder,
Boolean active) {
}
}
@@ -27,6 +27,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -186,6 +187,7 @@ public class BranchController {
@GetMapping("/{tenantCode}/{branchId}/queue")
public ResponseEntity getBranchQueue(@PathVariable final String tenantCode,
@PathVariable final String branchId,
@RequestParam(defaultValue = "false") final boolean activeOnly,
final QueueStateParams queueStateParams,
final Sort sort) {
final Branch branch = this.branchService.findById(Long.parseLong(branchId)).orElseThrow();
@@ -193,6 +195,9 @@ public class BranchController {
final TicketRepository ticketRepository = this.ticketService.unwrap(TicketRepository.class);
Specification<Ticket> filter = TicketSpecs.branchIdEquals(branch.getId());
if (activeOnly) {
filter = filter.and(TicketSpecs.deletedEquals(false));
}
if (queueStateParams.serviceId() != null && queueStateParams.serviceId().isPresent()) {
filter = filter.and(TicketSpecs.serviceIdEquals(queueStateParams.serviceId().get()));
}
@@ -10,7 +10,6 @@ import ba.unsa.etf.si.bbqms.ticket_service.api.TicketService;
import ba.unsa.etf.si.bbqms.ws.models.DisplayDto;
import ba.unsa.etf.si.bbqms.ws.models.ErrorResponseDto;
import ba.unsa.etf.si.bbqms.ws.models.ServiceDto;
import ba.unsa.etf.si.bbqms.ws.models.ServiceResponseDto;
import ba.unsa.etf.si.bbqms.ws.models.TicketDto;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -28,8 +27,8 @@ public class TellerStationController {
private final TicketService ticketService;
public TellerStationController(final StationService stationService,
final AuthService authService,
final TicketService ticketService) {
final AuthService authService,
final TicketService ticketService) {
this.stationService = stationService;
this.authService = authService;
this.ticketService = ticketService;
@@ -37,7 +36,7 @@ public class TellerStationController {
@GetMapping("/{tenantCode}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity getAll(@PathVariable final String tenantCode){
public ResponseEntity getAll(@PathVariable final String tenantCode) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
@@ -56,43 +55,45 @@ public class TellerStationController {
@GetMapping("/{tenantCode}/{stationId}/services")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity getServices(@PathVariable final String tenantCode,
@PathVariable final String stationId,
@RequestParam(defaultValue = "true") final boolean assigned) {
@PathVariable final String stationId,
@RequestParam(defaultValue = "true") final boolean assigned) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final Set<Service> serviceSet = this.stationService.getServicesByAssigned(Long.parseLong(stationId),assigned);
final Set<ServiceResponseDto> serviceResponseDtoSet = serviceSet.stream()
.map(ServiceResponseDto::fromEntity)
final Set<Service> serviceSet = this.stationService.getServicesByAssigned(Long.parseLong(stationId),
assigned);
final Set<ServiceDto> serviceDtoSet = serviceSet.stream()
.map(ServiceDto::fromEntity)
.collect(Collectors.toSet());
return ResponseEntity.ok().body(serviceResponseDtoSet);
}
catch (final Exception e) {
return ResponseEntity.ok().body(serviceDtoSet);
} catch (final Exception e) {
return ResponseEntity.badRequest().body(new ErrorResponseDto(e.getMessage()));
}
}
@GetMapping("/{tenantCode}/{stationId}/services/assignable")
public ResponseEntity findAssignableServices(@PathVariable final String stationId,
@PathVariable final String tenantCode) {
@PathVariable final String tenantCode) {
return ResponseEntity.ok().body(
this.stationService.findAssignableServices(Long.parseLong(stationId))
);
this.stationService.findAssignableServices(Long.parseLong(stationId)).stream()
.map(ServiceDto::fromEntity)
.collect(Collectors.toList()));
}
@PutMapping("/{tenantCode}/{stationId}/services/{serviceId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity addTellerStationService(@PathVariable final String tenantCode,
@PathVariable final String stationId,
@PathVariable final String serviceId) {
@PathVariable final String stationId,
@PathVariable final String serviceId) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final TellerStation updatedTellerStation = this.stationService.addTellerStationService(Long.parseLong(stationId), Long.parseLong(serviceId));
final TellerStation updatedTellerStation = this.stationService
.addTellerStationService(Long.parseLong(stationId), Long.parseLong(serviceId));
return ResponseEntity.ok().body(TellerStationResponseDto.fromEntity(updatedTellerStation));
} catch (Exception e) {
return ResponseEntity.badRequest().build();
@@ -102,14 +103,15 @@ public class TellerStationController {
@DeleteMapping("/{tenantCode}/{stationId}/services/{serviceId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity deleteTellerStationService(@PathVariable final String tenantCode,
@PathVariable final String stationId,
@PathVariable final String serviceId) {
@PathVariable final String stationId,
@PathVariable final String serviceId) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final TellerStation updatedTellerStation = this.stationService.deleteTellerStationService(Long.parseLong(stationId), Long.parseLong(serviceId));
final TellerStation updatedTellerStation = this.stationService
.deleteTellerStationService(Long.parseLong(stationId), Long.parseLong(serviceId));
return ResponseEntity.ok().body(TellerStationResponseDto.fromEntity(updatedTellerStation));
} catch (Exception e) {
return ResponseEntity.badRequest().body(new ErrorResponseDto(e.getMessage()));
@@ -119,16 +121,17 @@ public class TellerStationController {
@PutMapping("/{tenantCode}/{stationId}/displays/{displayId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity addDisplayToStation(@PathVariable final String tenantCode,
@PathVariable final String stationId,
@PathVariable final String displayId) {
@PathVariable final String stationId,
@PathVariable final String displayId) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final TellerStation updatedTellerStation = this.stationService.addTellerStationDisplay(Long.parseLong(stationId), Long.parseLong(displayId));
final TellerStation updatedTellerStation = this.stationService
.addTellerStationDisplay(Long.parseLong(stationId), Long.parseLong(displayId));
return ResponseEntity.ok().body(TellerStationResponseDto.fromEntity(updatedTellerStation));
} catch(final Exception exception) {
} catch (final Exception exception) {
return ResponseEntity.badRequest().build();
}
}
@@ -136,29 +139,29 @@ public class TellerStationController {
@DeleteMapping("/{tenantCode}/{stationId}/displays/{displayId}")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN', 'ROLE_BRANCH_ADMIN')")
public ResponseEntity removeDisplayFromStation(@PathVariable final String tenantCode,
@PathVariable final String stationId,
@PathVariable final String displayId) {
@PathVariable final String stationId,
@PathVariable final String displayId) {
if (!this.authService.canChangeTenant(tenantCode)) {
return ResponseEntity.badRequest().build();
}
try {
final TellerStation updatedTellerStation = this.stationService.deleteTellerStationDisplay(Long.parseLong(stationId), Long.parseLong(displayId));
final TellerStation updatedTellerStation = this.stationService
.deleteTellerStationDisplay(Long.parseLong(stationId), Long.parseLong(displayId));
return ResponseEntity.ok().body(TellerStationResponseDto.fromEntity(updatedTellerStation));
} catch(final Exception exception) {
} catch (final Exception exception) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/{tenantCode}/{branchId}")
public ResponseEntity getBranchStations(@PathVariable final String tenantCode,
@PathVariable final String branchId) {
try{
@PathVariable final String branchId) {
try {
return ResponseEntity.ok().body(
this.stationService.getAllByBranch(Long.parseLong(branchId)).stream()
.map(TellerStationResponseDto::fromEntity)
.toList()
);
.toList());
} catch (final Exception exception) {
return ResponseEntity.badRequest().build();
}
@@ -168,7 +171,8 @@ public class TellerStationController {
public ResponseEntity getTicketsForTellerStation(@PathVariable final long stationId) {
try {
final List<TicketDto> ticketDtos = this.ticketService.findWithStation(stationId).stream()
.filter(ticket -> ticket.getTellerStation() == null || ticket.getTellerStation().getId() == stationId)
.filter(ticket -> ticket.getTellerStation() == null
|| ticket.getTellerStation().getId() == stationId)
.map(TicketDto::fromEntity)
.toList();
@@ -178,15 +182,14 @@ public class TellerStationController {
}
}
public record TellerStationResponseDto(long id, String name, DisplayDto display, Set<ServiceResponseDto> services) {
public record TellerStationResponseDto(long id, String name, DisplayDto display, Set<ServiceDto> services) {
public static TellerStationResponseDto fromEntity(final TellerStation tellerStation) {
final Set<Service> serviceSet = tellerStation.getServices();
return new TellerStationResponseDto(
tellerStation.getId(),
tellerStation.getName(),
tellerStation.getDisplay() != null ? DisplayDto.fromEntity(tellerStation.getDisplay()) : null,
serviceSet.stream().map(ServiceResponseDto::fromEntity).collect(Collectors.toSet())
);
serviceSet.stream().map(ServiceDto::fromEntity).collect(Collectors.toSet()));
}
}
}
@@ -32,9 +32,9 @@ public class TenantController {
private final AuthService authService;
public TenantController(final TenantService tenantService,
final BranchService branchService,
final GroupService groupService,
final AuthService authService) {
final BranchService branchService,
final GroupService groupService,
final AuthService authService) {
this.tenantService = tenantService;
this.branchService = branchService;
this.groupService = groupService;
@@ -57,7 +57,8 @@ public class TenantController {
@PutMapping("/{code}")
@PreAuthorize("isAuthenticated()")
public ResponseEntity updateTenant(@PathVariable final String code, @RequestBody final TenantDto request) throws Exception {
public ResponseEntity updateTenant(@PathVariable final String code, @RequestBody final TenantDto request)
throws Exception {
if (!this.authService.canChangeTenant(code)) {
return ResponseEntity.notFound().build();
}
@@ -71,7 +72,8 @@ public class TenantController {
@PostMapping("/{code}/services")
@PreAuthorize("hasAnyRole('ROLE_BRANCH_ADMIN', 'ROLE_SUPER_ADMIN')")
public ResponseEntity addService(@PathVariable(name = "code") final String code, @RequestBody final ServiceRequestDto serviceRequestDto) {
public ResponseEntity addService(@PathVariable(name = "code") final String code,
@RequestBody final ServiceRequestDto serviceRequestDto) {
if (!this.authService.canChangeTenant(code)) {
return ResponseEntity.notFound().build();
}
@@ -92,7 +94,8 @@ public class TenantController {
}
@GetMapping("/{code}/services/group/{groupId}")
public ResponseEntity listGroupAssignableServices(@PathVariable final String code, @PathVariable final String groupId) {
public ResponseEntity listGroupAssignableServices(@PathVariable final String code,
@PathVariable final String groupId) {
final List<Service> possibleServices = this.tenantService.getAllServicesByTenant(code);
final List<Service> assignedServices = this.groupService.get(Long.parseLong(groupId))
.getServices().stream().toList();
@@ -105,8 +108,8 @@ public class TenantController {
@PutMapping("/{code}/services/{id}")
@PreAuthorize("hasAnyRole('ROLE_BRANCH_ADMIN', 'ROLE_SUPER_ADMIN')")
public ResponseEntity updateService(@PathVariable(name = "code") final String code,
@PathVariable(name = "id") final Long id,
@RequestBody final ServiceRequestDto request) {
@PathVariable(name = "id") final Long id,
@RequestBody final ServiceRequestDto request) {
if (!this.authService.canChangeTenant(code)) {
return ResponseEntity.notFound().build();
}
@@ -120,7 +123,8 @@ public class TenantController {
@DeleteMapping("/{code}/services/{id}")
@PreAuthorize("hasAnyRole('ROLE_BRANCH_ADMIN', 'ROLE_SUPER_ADMIN')")
public ResponseEntity deleteService(@PathVariable(name = "code") final String code, @PathVariable(name = "id") final Long id) {
public ResponseEntity deleteService(@PathVariable(name = "code") final String code,
@PathVariable(name = "id") final Long id) {
if (!this.authService.canChangeTenant(code)) {
return ResponseEntity.notFound().build();
}
@@ -0,0 +1,34 @@
package ba.unsa.etf.si.bbqms.ws.models;
import ba.unsa.etf.si.bbqms.domain.Advertisement;
import ba.unsa.etf.si.bbqms.domain.AdvertisementMediaType;
import java.time.Instant;
public record AdvertisementDto(
long id,
String title,
AdvertisementMediaType mediaType,
String fileName,
String contentType,
String mediaUrl,
int sortOrder,
boolean active,
int durationSeconds,
Instant createdAt
) {
public static AdvertisementDto fromEntity(final Advertisement advertisement) {
return new AdvertisementDto(
advertisement.getId(),
advertisement.getTitle(),
advertisement.getMediaType(),
advertisement.getFileName(),
advertisement.getContentType(),
"/api/v1/ads/media/" + advertisement.getId(),
advertisement.getSortOrder(),
advertisement.isActive(),
advertisement.getDurationSeconds(),
advertisement.getCreatedAt()
);
}
}
+12 -6
View File
@@ -1,14 +1,18 @@
spring:
application:
name: bbqms
name: qms
jpa:
database: mysql
hibernate:
ddl-auto: none
datasource:
url: jdbc:mysql://localhost:3306/bbqms
url: jdbc:mysql://localhost:3306/qms
username: root
password: password
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
security:
oauth2:
client:
@@ -16,19 +20,21 @@ spring:
google:
client-id: dummy-google-client-id
flyway:
schemas: bbqms
schemas: qms
jwt:
header-title: Authorization
token-prefix: Bearer
secret-key: dummy-jwt-secret-key-for-local-dev-only-min-32-chars
secret-key: a68uiaDQ0V3iLjF4DqMuS13GAVwkut55dlFbGCLyXTF
authorities-key: USER_AUTHORITIES
token-validity-time: PT30M
tfa:
label: BBQMS
issuer: BBQMS
label: QMS
issuer: QMS
tenancy:
default-code: DFLT
notifications:
expo-url: https://exp.host/--/api/v2/push/send
mock: true
ads:
upload-dir: uploads/ads
@@ -0,0 +1,19 @@
BEGIN;
CREATE TABLE IF NOT EXISTS advertisement
(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
tenant_id INTEGER NOT NULL,
title VARCHAR(255) NULL,
media_type VARCHAR(32) NOT NULL,
file_name VARCHAR(512) NOT NULL,
content_type VARCHAR(128) NOT NULL,
storage_path VARCHAR(1024) NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
active BOOL NOT NULL DEFAULT TRUE,
duration_seconds INTEGER NOT NULL DEFAULT 10,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT FK_advertisement_tenant FOREIGN KEY (tenant_id) REFERENCES tenant (id)
);
COMMIT;