Hero and Service tile added newly
This commit is contained in:
@ -4,10 +4,7 @@ public class FileConstant {
|
||||
|
||||
public static final String USER_IMAGE_PATH = "/user/image/";
|
||||
public static final String JPG_EXTENSION = "jpg";
|
||||
|
||||
// ✅ CHANGED: From System.getProperty("user.home") to /app/uploads
|
||||
public static final String USER_FOLDER = "/app/uploads/user/";
|
||||
|
||||
public static final String DIRECTORY_CREATED = "Created directory for: ";
|
||||
public static final String DEFAULT_USER_IMAGE_URI_PATTERN = "/user/%s/profile-image";
|
||||
public static final String USER_IMAGE_FILENAME = "avatar.jpg";
|
||||
@ -17,9 +14,15 @@ public class FileConstant {
|
||||
public static final String NOT_AN_IMAGE_FILE = " is not an image file. Please upload an image file";
|
||||
public static final String TEMP_PROFILE_IMAGE_BASE_URL = "https://robohash.org/";
|
||||
|
||||
// ✅ CHANGED: Professor-specific constants
|
||||
// Professor-specific constants
|
||||
public static final String PROFESSOR_IMAGE_PATH = "/professor/image/";
|
||||
public static final String PROFESSOR_FOLDER = "/app/uploads/professor/";
|
||||
public static final String DEFAULT_PROFESSOR_IMAGE_URI_PATTERN = "/professor/%s/profile-image";
|
||||
public static final String PROFESSOR_IMAGE_FILENAME = "avatar.jpg";
|
||||
|
||||
// ✅ NEW: Hero Image constants
|
||||
public static final String HERO_IMAGE_PATH = "/hero/image/";
|
||||
public static final String HERO_FOLDER = "/app/uploads/hero/";
|
||||
public static final String DEFAULT_HERO_IMAGE_URI_PATTERN = "/hero/%s/image";
|
||||
public static final String HERO_IMAGE_PREFIX = "hero_";
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HeroImage;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HttpResponse;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.HeroImageService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import static net.shyshkin.study.fullstack.supportportal.backend.constant.FileConstant.HERO_FOLDER;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/hero")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
public class HeroImageResource {
|
||||
|
||||
private final HeroImageService heroImageService;
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyAuthority('user:create')")
|
||||
public ResponseEntity<HeroImage> addHeroImage(
|
||||
@RequestParam(required = false) String title,
|
||||
@RequestParam(required = false) String subtitle,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false) MultipartFile image) throws IOException {
|
||||
|
||||
log.info("Adding new hero image with title: {}", title);
|
||||
HeroImage heroImage = heroImageService.addHeroImage(title, subtitle, description, image);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(heroImage);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<HeroImage> updateHeroImage(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) String title,
|
||||
@RequestParam(required = false) String subtitle,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false) MultipartFile image) throws IOException {
|
||||
|
||||
log.info("Updating hero image with id: {}", id);
|
||||
HeroImage heroImage = heroImageService.updateHeroImage(id, title, subtitle, description, image);
|
||||
return ResponseEntity.ok(heroImage);
|
||||
}
|
||||
|
||||
@GetMapping("/active")
|
||||
public ResponseEntity<HeroImage> getActiveHeroImage() {
|
||||
log.info("Getting active hero image");
|
||||
HeroImage heroImage = heroImageService.getActiveHeroImage();
|
||||
return ResponseEntity.ok(heroImage);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<HeroImage> getHeroImageById(@PathVariable Long id) {
|
||||
log.info("Getting hero image with id: {}", id);
|
||||
HeroImage heroImage = heroImageService.getHeroImageById(id);
|
||||
return ResponseEntity.ok(heroImage);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<HeroImage>> getAllHeroImages() {
|
||||
log.info("Getting all hero images");
|
||||
List<HeroImage> heroImages = heroImageService.getAllHeroImages();
|
||||
return ResponseEntity.ok(heroImages);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('user:delete')")
|
||||
public ResponseEntity<HttpResponse> deleteHeroImage(@PathVariable Long id) {
|
||||
log.info("Deleting hero image with id: {}", id);
|
||||
heroImageService.deleteHeroImage(id);
|
||||
return ResponseEntity.ok(
|
||||
HttpResponse.builder()
|
||||
.httpStatusCode(HttpStatus.OK.value())
|
||||
.httpStatus(HttpStatus.OK)
|
||||
.reason(HttpStatus.OK.getReasonPhrase())
|
||||
.message("Hero image deleted successfully")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{id}/activate")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<HeroImage> setActiveHeroImage(@PathVariable Long id) {
|
||||
log.info("Setting hero image as active with id: {}", id);
|
||||
HeroImage heroImage = heroImageService.setActiveHeroImage(id);
|
||||
return ResponseEntity.ok(heroImage);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/image/{filename}", produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_GIF_VALUE})
|
||||
public byte[] getHeroImage(@PathVariable String filename) throws IOException {
|
||||
log.info("Getting hero image file: {}", filename);
|
||||
Path imagePath = Paths.get(HERO_FOLDER).resolve(filename);
|
||||
return Files.readAllBytes(imagePath);
|
||||
}
|
||||
}
|
||||
@ -7,12 +7,20 @@ import net.shyshkin.study.fullstack.supportportal.backend.repository.JobReposito
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.JobApplicationRepository;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@ -26,6 +34,9 @@ public class JobApplicationController {
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
// Base path for resume storage - adjust this to your actual path
|
||||
private final String RESUME_UPLOAD_DIR = "uploads/resumes/";
|
||||
|
||||
// Get all applications (for admin)
|
||||
@GetMapping
|
||||
public List<JobApplication> getAllApplications() {
|
||||
@ -92,4 +103,68 @@ public class JobApplicationController {
|
||||
})
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
// Download/View Resume
|
||||
@GetMapping("/resume/{filename:.+}")
|
||||
public ResponseEntity<Resource> downloadResume(@PathVariable String filename) {
|
||||
try {
|
||||
// Construct the file path
|
||||
Path filePath = Paths.get(RESUME_UPLOAD_DIR).resolve(filename).normalize();
|
||||
Resource resource = new UrlResource(filePath.toUri());
|
||||
|
||||
if (resource.exists() && resource.isReadable()) {
|
||||
// Determine content type
|
||||
String contentType = "application/octet-stream";
|
||||
try {
|
||||
contentType = Files.probeContentType(filePath);
|
||||
if (contentType == null) {
|
||||
contentType = "application/pdf"; // Default to PDF
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
contentType = "application/pdf";
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"inline; filename=\"" + resource.getFilename() + "\"")
|
||||
.body(resource);
|
||||
} else {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative endpoint for forced download (not inline view)
|
||||
@GetMapping("/resume/download/{filename:.+}")
|
||||
public ResponseEntity<Resource> forceDownloadResume(@PathVariable String filename) {
|
||||
try {
|
||||
Path filePath = Paths.get(RESUME_UPLOAD_DIR).resolve(filename).normalize();
|
||||
Resource resource = new UrlResource(filePath.toUri());
|
||||
|
||||
if (resource.exists() && resource.isReadable()) {
|
||||
String contentType = "application/octet-stream";
|
||||
try {
|
||||
contentType = Files.probeContentType(filePath);
|
||||
if (contentType == null) {
|
||||
contentType = "application/pdf";
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
contentType = "application/pdf";
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + resource.getFilename() + "\"")
|
||||
.body(resource);
|
||||
} else {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HttpResponse;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.ServiceTileService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/service-tiles")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
public class ServiceTileResource {
|
||||
|
||||
private final ServiceTileService serviceTileService;
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyAuthority('user:create')")
|
||||
public ResponseEntity<ServiceTile> addServiceTile(
|
||||
@RequestParam String title,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false) Integer displayOrder) {
|
||||
|
||||
log.info("Adding new service tile with title: {}", title);
|
||||
ServiceTile serviceTile = serviceTileService.addServiceTile(title, description, displayOrder);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(serviceTile);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<ServiceTile> updateServiceTile(
|
||||
@PathVariable Long id,
|
||||
@RequestParam String title,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam(required = false) Integer displayOrder) {
|
||||
|
||||
log.info("Updating service tile with id: {}", id);
|
||||
ServiceTile serviceTile = serviceTileService.updateServiceTile(id, title, description, displayOrder);
|
||||
return ResponseEntity.ok(serviceTile);
|
||||
}
|
||||
|
||||
@GetMapping("/active")
|
||||
public ResponseEntity<List<ServiceTile>> getActiveServiceTiles() {
|
||||
log.info("Getting active service tiles");
|
||||
List<ServiceTile> serviceTiles = serviceTileService.getActiveServiceTiles();
|
||||
return ResponseEntity.ok(serviceTiles);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ServiceTile> getServiceTileById(@PathVariable Long id) {
|
||||
log.info("Getting service tile with id: {}", id);
|
||||
ServiceTile serviceTile = serviceTileService.getServiceTileById(id);
|
||||
return ResponseEntity.ok(serviceTile);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyAuthority('user:read')")
|
||||
public ResponseEntity<List<ServiceTile>> getAllServiceTiles() {
|
||||
log.info("Getting all service tiles");
|
||||
List<ServiceTile> serviceTiles = serviceTileService.getAllServiceTiles();
|
||||
return ResponseEntity.ok(serviceTiles);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('user:delete')")
|
||||
public ResponseEntity<HttpResponse> deleteServiceTile(@PathVariable Long id) {
|
||||
log.info("Deleting service tile with id: {}", id);
|
||||
serviceTileService.deleteServiceTile(id);
|
||||
return ResponseEntity.ok(
|
||||
HttpResponse.builder()
|
||||
.httpStatusCode(HttpStatus.OK.value())
|
||||
.httpStatus(HttpStatus.OK)
|
||||
.reason(HttpStatus.OK.getReasonPhrase())
|
||||
.message("Service tile deleted successfully")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/{id}/toggle-active")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<ServiceTile> toggleActiveStatus(@PathVariable Long id) {
|
||||
log.info("Toggling active status for service tile with id: {}", id);
|
||||
ServiceTile serviceTile = serviceTileService.toggleActiveStatus(id);
|
||||
return ResponseEntity.ok(serviceTile);
|
||||
}
|
||||
|
||||
@PutMapping("/reorder")
|
||||
@PreAuthorize("hasAnyAuthority('user:update')")
|
||||
public ResponseEntity<HttpResponse> reorderServiceTiles(@RequestBody List<Long> orderedIds) {
|
||||
log.info("Reordering service tiles");
|
||||
serviceTileService.reorderServiceTiles(orderedIds);
|
||||
return ResponseEntity.ok(
|
||||
HttpResponse.builder()
|
||||
.httpStatusCode(HttpStatus.OK.value())
|
||||
.httpStatus(HttpStatus.OK)
|
||||
.reason(HttpStatus.OK.getReasonPhrase())
|
||||
.message("Service tiles reordered successfully")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,12 @@ public class CourseApplication extends BaseEntity {
|
||||
@Column(nullable = false)
|
||||
private String phone;
|
||||
|
||||
@Column(name = "resume_url")
|
||||
private String resumeUrl;
|
||||
|
||||
@Column(name = "resume_path")
|
||||
private String resumePath;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String qualification;
|
||||
|
||||
@ -38,12 +44,13 @@ public class CourseApplication extends BaseEntity {
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String coverLetter;
|
||||
|
||||
private String resumeUrl;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ApplicationStatus status = ApplicationStatus.PENDING;
|
||||
|
||||
public enum ApplicationStatus {
|
||||
PENDING, REVIEWED, SHORTLISTED, ACCEPTED, REJECTED, ENROLLED
|
||||
}
|
||||
public String getResumePath() {
|
||||
return resumePath != null ? resumePath : resumeUrl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "hero_images")
|
||||
public class HeroImage implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(nullable = false, updatable = false)
|
||||
private Long id;
|
||||
|
||||
private String title;
|
||||
private String subtitle;
|
||||
private String description;
|
||||
private String imageUrl;
|
||||
private String imageFilename;
|
||||
|
||||
@JsonProperty("isActive")
|
||||
@Column(name = "is_active")
|
||||
private boolean isActive;
|
||||
|
||||
private Date uploadDate;
|
||||
private Date lastModified;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
uploadDate = new Date();
|
||||
lastModified = new Date();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
lastModified = new Date();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "service_tiles")
|
||||
public class ServiceTile implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(nullable = false, updatable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column(length = 1000)
|
||||
private String description;
|
||||
|
||||
@JsonProperty("isActive")
|
||||
@Column(name = "is_active")
|
||||
private boolean isActive;
|
||||
|
||||
@Column(name = "display_order")
|
||||
private Integer displayOrder;
|
||||
|
||||
private Date createdDate;
|
||||
private Date lastModified;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdDate = new Date();
|
||||
lastModified = new Date();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
lastModified = new Date();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.exception.domain;
|
||||
|
||||
public class HeroImageNotFoundException extends RuntimeException {
|
||||
|
||||
public HeroImageNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.exception.domain;
|
||||
|
||||
public class ServiceTileNotFoundException extends RuntimeException {
|
||||
|
||||
public ServiceTileNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.repository;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HeroImage;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface HeroImageRepository extends JpaRepository<HeroImage, Long> {
|
||||
|
||||
@Query("SELECT h FROM HeroImage h WHERE h.isActive = true")
|
||||
Optional<HeroImage> findByIsActiveTrue();
|
||||
|
||||
Optional<HeroImage> findByImageFilename(String imageFilename);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.repository;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ServiceTileRepository extends JpaRepository<ServiceTile, Long> {
|
||||
|
||||
@Query("SELECT s FROM ServiceTile s WHERE s.isActive = true ORDER BY s.displayOrder ASC, s.id ASC")
|
||||
List<ServiceTile> findByIsActiveTrueOrderByDisplayOrder();
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HeroImage;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public interface HeroImageService {
|
||||
|
||||
HeroImage addHeroImage(String title, String subtitle, String description, MultipartFile image) throws IOException;
|
||||
|
||||
HeroImage updateHeroImage(Long id, String title, String subtitle, String description, MultipartFile image) throws IOException;
|
||||
|
||||
HeroImage getActiveHeroImage();
|
||||
|
||||
HeroImage getHeroImageById(Long id);
|
||||
|
||||
List<HeroImage> getAllHeroImages();
|
||||
|
||||
void deleteHeroImage(Long id);
|
||||
|
||||
HeroImage setActiveHeroImage(Long id);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ServiceTileService {
|
||||
|
||||
ServiceTile addServiceTile(String title, String description, Integer displayOrder);
|
||||
|
||||
ServiceTile updateServiceTile(Long id, String title, String description, Integer displayOrder);
|
||||
|
||||
List<ServiceTile> getActiveServiceTiles();
|
||||
|
||||
ServiceTile getServiceTileById(Long id);
|
||||
|
||||
List<ServiceTile> getAllServiceTiles();
|
||||
|
||||
void deleteServiceTile(Long id);
|
||||
|
||||
ServiceTile toggleActiveStatus(Long id);
|
||||
|
||||
void reorderServiceTiles(List<Long> orderedIds);
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.HeroImage;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.HeroImageNotFoundException;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.HeroImageRepository;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.HeroImageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
import static net.shyshkin.study.fullstack.supportportal.backend.constant.FileConstant.*;
|
||||
import static org.springframework.http.MediaType.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class HeroImageServiceImpl implements HeroImageService {
|
||||
|
||||
private final HeroImageRepository heroImageRepository;
|
||||
|
||||
@Override
|
||||
public HeroImage addHeroImage(String title, String subtitle, String description, MultipartFile image) throws IOException {
|
||||
log.info("Adding new hero image with title: {}", title);
|
||||
|
||||
HeroImage heroImage = new HeroImage();
|
||||
heroImage.setTitle(title);
|
||||
heroImage.setSubtitle(subtitle);
|
||||
heroImage.setDescription(description);
|
||||
heroImage.setActive(false); // New images are inactive by default
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
String filename = saveHeroImage(image);
|
||||
heroImage.setImageFilename(filename);
|
||||
heroImage.setImageUrl(getHeroImageUrl(filename));
|
||||
}
|
||||
|
||||
return heroImageRepository.save(heroImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeroImage updateHeroImage(Long id, String title, String subtitle, String description, MultipartFile image) throws IOException {
|
||||
log.info("Updating hero image with id: {}", id);
|
||||
|
||||
HeroImage heroImage = getHeroImageById(id);
|
||||
heroImage.setTitle(title);
|
||||
heroImage.setSubtitle(subtitle);
|
||||
heroImage.setDescription(description);
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
// Delete old image if exists
|
||||
if (heroImage.getImageFilename() != null) {
|
||||
deleteHeroImageFile(heroImage.getImageFilename());
|
||||
}
|
||||
|
||||
String filename = saveHeroImage(image);
|
||||
heroImage.setImageFilename(filename);
|
||||
heroImage.setImageUrl(getHeroImageUrl(filename));
|
||||
}
|
||||
|
||||
return heroImageRepository.save(heroImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public HeroImage getActiveHeroImage() {
|
||||
return heroImageRepository.findByIsActiveTrue()
|
||||
.orElseThrow(() -> new HeroImageNotFoundException("No active hero image found"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public HeroImage getHeroImageById(Long id) {
|
||||
return heroImageRepository.findById(id)
|
||||
.orElseThrow(() -> new HeroImageNotFoundException("Hero image not found with id: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<HeroImage> getAllHeroImages() {
|
||||
return heroImageRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteHeroImage(Long id) {
|
||||
log.info("Deleting hero image with id: {}", id);
|
||||
|
||||
HeroImage heroImage = getHeroImageById(id);
|
||||
|
||||
if (heroImage.isActive()) {
|
||||
throw new IllegalStateException("Cannot delete active hero image. Please set another image as active first.");
|
||||
}
|
||||
|
||||
if (heroImage.getImageFilename() != null) {
|
||||
deleteHeroImageFile(heroImage.getImageFilename());
|
||||
}
|
||||
|
||||
heroImageRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeroImage setActiveHeroImage(Long id) {
|
||||
log.info("Setting hero image as active with id: {}", id);
|
||||
|
||||
// Deactivate current active image
|
||||
heroImageRepository.findByIsActiveTrue().ifPresent(currentActive -> {
|
||||
currentActive.setActive(false);
|
||||
heroImageRepository.save(currentActive);
|
||||
});
|
||||
|
||||
// Activate new image
|
||||
HeroImage heroImage = getHeroImageById(id);
|
||||
heroImage.setActive(true);
|
||||
return heroImageRepository.save(heroImage);
|
||||
}
|
||||
|
||||
private String saveHeroImage(MultipartFile image) throws IOException {
|
||||
if (!isImageFile(image)) {
|
||||
throw new IOException(image.getOriginalFilename() + NOT_AN_IMAGE_FILE);
|
||||
}
|
||||
|
||||
Path heroFolder = Paths.get(HERO_FOLDER).toAbsolutePath().normalize();
|
||||
if (!Files.exists(heroFolder)) {
|
||||
Files.createDirectories(heroFolder);
|
||||
log.info(DIRECTORY_CREATED + heroFolder);
|
||||
}
|
||||
|
||||
String filename = HERO_IMAGE_PREFIX + System.currentTimeMillis() + DOT + getFileExtension(image);
|
||||
Path targetLocation = heroFolder.resolve(filename);
|
||||
|
||||
Files.copy(image.getInputStream(), targetLocation, REPLACE_EXISTING);
|
||||
log.info(FILE_SAVED_IN_FILE_SYSTEM + filename);
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
private void deleteHeroImageFile(String filename) {
|
||||
try {
|
||||
Path filePath = Paths.get(HERO_FOLDER).resolve(filename);
|
||||
Files.deleteIfExists(filePath);
|
||||
log.info("Deleted hero image file: {}", filename);
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting hero image file: {}", filename, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getHeroImageUrl(String filename) {
|
||||
return ServletUriComponentsBuilder.fromCurrentContextPath()
|
||||
.path(HERO_IMAGE_PATH + filename)
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private boolean isImageFile(MultipartFile file) {
|
||||
String contentType = file.getContentType();
|
||||
return contentType != null && (
|
||||
contentType.equals(IMAGE_JPEG_VALUE) ||
|
||||
contentType.equals(IMAGE_PNG_VALUE) ||
|
||||
contentType.equals(IMAGE_GIF_VALUE)
|
||||
);
|
||||
}
|
||||
|
||||
private String getFileExtension(MultipartFile file) {
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename != null && originalFilename.contains(DOT)) {
|
||||
return originalFilename.substring(originalFilename.lastIndexOf(DOT) + 1);
|
||||
}
|
||||
return JPG_EXTENSION;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.ServiceTile;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.ServiceTileNotFoundException;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.ServiceTileRepository;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.ServiceTileService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class ServiceTileServiceImpl implements ServiceTileService {
|
||||
|
||||
private final ServiceTileRepository serviceTileRepository;
|
||||
|
||||
@Override
|
||||
public ServiceTile addServiceTile(String title, String description, Integer displayOrder) {
|
||||
log.info("Adding new service tile with title: {}", title);
|
||||
|
||||
ServiceTile serviceTile = new ServiceTile();
|
||||
serviceTile.setTitle(title);
|
||||
serviceTile.setDescription(description);
|
||||
serviceTile.setDisplayOrder(displayOrder != null ? displayOrder : 0);
|
||||
serviceTile.setActive(true); // New tiles are active by default
|
||||
|
||||
return serviceTileRepository.save(serviceTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceTile updateServiceTile(Long id, String title, String description, Integer displayOrder) {
|
||||
log.info("Updating service tile with id: {}", id);
|
||||
|
||||
ServiceTile serviceTile = getServiceTileById(id);
|
||||
serviceTile.setTitle(title);
|
||||
serviceTile.setDescription(description);
|
||||
|
||||
if (displayOrder != null) {
|
||||
serviceTile.setDisplayOrder(displayOrder);
|
||||
}
|
||||
|
||||
return serviceTileRepository.save(serviceTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ServiceTile> getActiveServiceTiles() {
|
||||
return serviceTileRepository.findByIsActiveTrueOrderByDisplayOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ServiceTile getServiceTileById(Long id) {
|
||||
return serviceTileRepository.findById(id)
|
||||
.orElseThrow(() -> new ServiceTileNotFoundException("Service tile not found with id: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ServiceTile> getAllServiceTiles() {
|
||||
return serviceTileRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteServiceTile(Long id) {
|
||||
log.info("Deleting service tile with id: {}", id);
|
||||
|
||||
ServiceTile serviceTile = getServiceTileById(id);
|
||||
serviceTileRepository.delete(serviceTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceTile toggleActiveStatus(Long id) {
|
||||
log.info("Toggling active status for service tile with id: {}", id);
|
||||
|
||||
ServiceTile serviceTile = getServiceTileById(id);
|
||||
serviceTile.setActive(!serviceTile.isActive());
|
||||
|
||||
return serviceTileRepository.save(serviceTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reorderServiceTiles(List<Long> orderedIds) {
|
||||
log.info("Reordering {} service tiles", orderedIds.size());
|
||||
|
||||
for (int i = 0; i < orderedIds.size(); i++) {
|
||||
Long id = orderedIds.get(i);
|
||||
ServiceTile serviceTile = getServiceTileById(id);
|
||||
serviceTile.setDisplayOrder(i);
|
||||
serviceTileRepository.save(serviceTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,9 +51,9 @@ file:
|
||||
app:
|
||||
base-url: ${APP_BASE_URL:http://localhost:8080}
|
||||
# Fixed public URLs with correct wildcard patterns
|
||||
public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active,/api/milestones,/api/milestones/**,/api/testimonials,/api/testimonials/**
|
||||
public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications/**,/api/job-applications/resume/**,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active,/api/milestones,/api/milestones/**,/api/testimonials,/api/testimonials/**,/hero/image/**,/hero/active/**,/hero/**,/service-tiles/active,/service-tiles/active/**
|
||||
cors:
|
||||
allowed-origins: http://localhost:4200,http://localhost:3000,https://maincmc.rootxwire.com,https://dashboad.cmctrauma.com,https://www.dashboad.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com
|
||||
allowed-origins: http://localhost:4200,http://localhost:3000,https://maincmc.rootxwire.com,https://dashboard.cmctrauma.com,https://www.dashboard.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com
|
||||
jwt:
|
||||
secret: custom_text
|
||||
|
||||
@ -69,7 +69,7 @@ file:
|
||||
app:
|
||||
base-url: https://cmcbackend.rootxwire.com
|
||||
cors:
|
||||
allowed-origins: https://maincmc.rootxwire.com,https://dashboad.cmctrauma.com,https://www.dashboad.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com
|
||||
allowed-origins: https://maincmc.rootxwire.com,https://dashboard.cmctrauma.com,https://www.dashboard.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com
|
||||
|
||||
---
|
||||
# Development file upload configuration with custom directory
|
||||
|
||||
Reference in New Issue
Block a user