Skip to content

Commit 86850d9

Browse files
authored
Merge pull request #32 from thughari/codex/implement-resource-caching-and-file-upload
Codex/implement resource caching and file upload
2 parents 30e9552 + 1c4ae53 commit 86850d9

19 files changed

+1241
-30
lines changed

backend/src/main/java/com/thughari/jobtrackerpro/config/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.beans.factory.annotation.Value;
99
import org.springframework.context.annotation.Bean;
1010
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.http.HttpMethod;
1112
import org.springframework.security.authentication.AuthenticationManager;
1213
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
1314
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -56,6 +57,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
5657
.csrf(AbstractHttpConfigurer::disable)
5758
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
5859
.authorizeHttpRequests(auth -> auth
60+
.requestMatchers(HttpMethod.GET, "/api/resources", "/api/resources/**").permitAll()
5961
.requestMatchers(publicEndpoints).permitAll()
6062
.anyRequest().authenticated()
6163
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.thughari.jobtrackerpro.controller;
2+
3+
import com.thughari.jobtrackerpro.dto.CareerResourceDTO;
4+
import com.thughari.jobtrackerpro.dto.CareerResourcePageResponse;
5+
import com.thughari.jobtrackerpro.dto.CreateCareerResourceRequest;
6+
import com.thughari.jobtrackerpro.service.CareerResourceService;
7+
import org.springframework.http.MediaType;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.security.authentication.AnonymousAuthenticationToken;
10+
import org.springframework.security.core.Authentication;
11+
import org.springframework.security.core.context.SecurityContextHolder;
12+
import org.springframework.web.bind.annotation.DeleteMapping;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.PathVariable;
15+
import org.springframework.web.bind.annotation.PostMapping;
16+
import org.springframework.web.bind.annotation.RequestBody;
17+
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
19+
import org.springframework.web.bind.annotation.RestController;
20+
import org.springframework.web.multipart.MultipartFile;
21+
22+
import java.util.UUID;
23+
24+
@RestController
25+
@RequestMapping("/api/resources")
26+
public class CareerResourceController {
27+
28+
private final CareerResourceService careerResourceService;
29+
30+
public CareerResourceController(CareerResourceService careerResourceService) {
31+
this.careerResourceService = careerResourceService;
32+
}
33+
34+
@GetMapping
35+
public ResponseEntity<CareerResourcePageResponse> getResources(
36+
@RequestParam(defaultValue = "0") int page,
37+
@RequestParam(defaultValue = "20") int size
38+
) {
39+
return ResponseEntity.ok(careerResourceService.getResourcePage(page, size, getAuthenticatedEmailOrNull()));
40+
}
41+
42+
@PostMapping
43+
public ResponseEntity<CareerResourceDTO> addResource(@RequestBody CreateCareerResourceRequest request) {
44+
String email = getAuthenticatedEmail();
45+
return ResponseEntity.ok(careerResourceService.createResource(email, request));
46+
}
47+
48+
@PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
49+
public ResponseEntity<CareerResourceDTO> uploadResource(
50+
@RequestParam String title,
51+
@RequestParam String category,
52+
@RequestParam(required = false) String description,
53+
@RequestParam MultipartFile file
54+
) {
55+
String email = getAuthenticatedEmail();
56+
return ResponseEntity.ok(careerResourceService.createResourceFromFile(email, title, category, description, file));
57+
}
58+
59+
@DeleteMapping("/{id}")
60+
public ResponseEntity<Void> deleteResource(@PathVariable UUID id) {
61+
String email = getAuthenticatedEmail();
62+
careerResourceService.deleteResource(email, id);
63+
return ResponseEntity.noContent().build();
64+
}
65+
66+
private String getAuthenticatedEmail() {
67+
return ((String) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).toLowerCase();
68+
}
69+
70+
private String getAuthenticatedEmailOrNull() {
71+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
72+
if (authentication == null || !authentication.isAuthenticated() || authentication instanceof AnonymousAuthenticationToken) {
73+
return null;
74+
}
75+
76+
Object principal = authentication.getPrincipal();
77+
if (principal instanceof String email && !"anonymousUser".equalsIgnoreCase(email)) {
78+
return email.toLowerCase();
79+
}
80+
81+
return null;
82+
}
83+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.thughari.jobtrackerpro.dto;
2+
3+
import lombok.Data;
4+
5+
import java.time.LocalDateTime;
6+
import java.util.UUID;
7+
8+
@Data
9+
public class CareerResourceDTO {
10+
private UUID id;
11+
private String title;
12+
private String url;
13+
private String category;
14+
private String description;
15+
private String resourceType;
16+
private String originalFileName;
17+
private Long fileSizeBytes;
18+
private boolean ownedByCurrentUser;
19+
private String submittedByName;
20+
private LocalDateTime createdAt;
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.thughari.jobtrackerpro.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
import java.util.List;
7+
8+
@Data
9+
@AllArgsConstructor
10+
public class CareerResourcePageResponse {
11+
private List<CareerResourceDTO> content;
12+
private int page;
13+
private int size;
14+
private long totalElements;
15+
private int totalPages;
16+
private boolean hasNext;
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.thughari.jobtrackerpro.dto;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class CreateCareerResourceRequest {
7+
private String title;
8+
private String url;
9+
private String category;
10+
private String description;
11+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.thughari.jobtrackerpro.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.GeneratedValue;
6+
import jakarta.persistence.GenerationType;
7+
import jakarta.persistence.Id;
8+
import jakarta.persistence.PrePersist;
9+
import jakarta.persistence.Table;
10+
import lombok.Data;
11+
12+
import java.time.LocalDateTime;
13+
import java.util.UUID;
14+
15+
@Data
16+
@Entity
17+
@Table(name = "career_resources")
18+
public class CareerResource {
19+
20+
@Id
21+
@GeneratedValue(strategy = GenerationType.UUID)
22+
@Column(columnDefinition = "uuid")
23+
private UUID id;
24+
25+
@Column(nullable = false, length = 180)
26+
private String title;
27+
28+
@Column(nullable = false, length = 2048)
29+
private String url;
30+
31+
@Column(nullable = false, length = 16)
32+
private String resourceType = "LINK";
33+
34+
@Column(nullable = false, length = 80)
35+
private String category;
36+
37+
@Column(length = 1200)
38+
private String description;
39+
40+
@Column(length = 255)
41+
private String originalFileName;
42+
43+
private Long fileSizeBytes;
44+
45+
@Column(nullable = false)
46+
private String submittedByEmail;
47+
48+
@Column(nullable = false)
49+
private String submittedByName;
50+
51+
@Column(nullable = false)
52+
private LocalDateTime createdAt;
53+
54+
@PrePersist
55+
public void prePersist() {
56+
if (createdAt == null) {
57+
createdAt = LocalDateTime.now();
58+
}
59+
}
60+
}

backend/src/main/java/com/thughari/jobtrackerpro/interfaces/StorageService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public interface StorageService {
66

77
String uploadFile(MultipartFile file, String userId);
88
String uploadFromUrl(String externalUrl, String userId);
9+
String uploadResourceFile(MultipartFile file, String userId);
910
void deleteFile(String fileUrl);
1011

1112
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.thughari.jobtrackerpro.repo;
2+
3+
import com.thughari.jobtrackerpro.entity.CareerResource;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
7+
8+
import java.util.List;
9+
import java.util.UUID;
10+
11+
public interface CareerResourceRepository extends JpaRepository<CareerResource, UUID> {
12+
List<CareerResource> findAllByOrderByCreatedAtDesc();
13+
14+
Page<CareerResource> findAllByOrderByCreatedAtDesc(Pageable pageable);
15+
16+
boolean existsByUrl(String url);
17+
}

0 commit comments

Comments
 (0)