Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ build/

### ENVIRONMENT ###
.env
secret.json
.jdk

### Build ###
target/

### Demo ###
certificate.db
*.pdf
uploads/
*.py
*.csv
86 changes: 82 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.42.0.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
<version>6.2.2.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
Expand Down Expand Up @@ -81,10 +91,6 @@
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
Expand All @@ -99,6 +105,63 @@
<artifactId>commonmark</artifactId>
<version>0.21.0</version>
</dependency>

<!-- PDF Generation (iText) -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>8.0.5</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>layout</artifactId>
<version>8.0.5</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>forms</artifactId>
<version>8.0.5</version>
</dependency>

<!-- Google API client libraries for Gmail API -->
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>1.34.1</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-gmail</artifactId>
<version>v1-rev20220404-2.0.0</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-gson</artifactId>
<version>1.43.3</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.16.0</version>
</dependency>

<!-- CSV Parsing -->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>

<!-- Thymeleaf for templates -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -115,6 +178,21 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ public SecurityConfiguration(UserService userService, JwtFilter jwtFilter, Simpl
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder, UserService userService)
throws Exception {
// TODO: and() is deprecated, replace in future
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userService)
.passwordEncoder(passwordEncoder)
.and().build();
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
return builder.build();
}

@Bean
Expand All @@ -52,6 +51,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
a -> a.requestMatchers("/error").anonymous()
.requestMatchers("/api/certificates/**", "/api/templates/**").permitAll() // Adjust security as needed
.anyRequest().permitAll()
)
.sessionManagement(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.pecacm.backend.controllers;

import com.pecacm.backend.entities.Certificate;
import com.pecacm.backend.entities.Event;
import com.pecacm.backend.repository.EventRepository;
import com.pecacm.backend.services.CertificateService;
import com.pecacm.backend.services.MassMailService;
import com.pecacm.backend.services.TemplateGeneratorService;
import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvValidationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api/certificates")
@RequiredArgsConstructor
@Slf4j
public class CertificateController {

private final CertificateService certificateService;
private final MassMailService massMailService;
private final TemplateGeneratorService generatorService;
private final EventRepository eventRepository;

@GetMapping
public List<Certificate> getAllCertificates(@RequestParam(required = false) Integer eventId) {
if (eventId != null) {
return certificateService.getCertificatesByEventId(eventId);
}
return certificateService.getAllCertificates();
}

@GetMapping("/{id}")
public Certificate getCertificate(@PathVariable Long id) {
return certificateService.getCertificateById(id);
}

@PostMapping
public Certificate createCertificate(@RequestBody Certificate certificate) {
return certificateService.createCertificate(certificate);
}

@PutMapping("/{id}")
public Certificate updateCertificate(@PathVariable Long id, @RequestBody Certificate certificate) {
return certificateService.updateCertificate(id, certificate);
}

@DeleteMapping("/{id}")
public void deleteCertificate(@PathVariable Long id) {
certificateService.deleteCertificate(id);
}

@GetMapping("/{id}/download")
public ResponseEntity<byte[]> downloadCertificate(@PathVariable Long id) {
Certificate certificate = certificateService.getCertificateById(id);
byte[] pdfBytes = generatorService.generateCertificatePdf(certificate);

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"certificate.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(pdfBytes);
}

@PostMapping("/mass-mail/{eventId}")
public ResponseEntity<String> sendMassMail(@PathVariable Integer eventId) {
Event event = eventRepository.findById(eventId)
.orElseThrow(() -> new RuntimeException("Event not found with id: " + eventId));

List<Certificate> certificates = certificateService.getCertificatesByEventId(eventId);

if (certificates.isEmpty()) {
return ResponseEntity.badRequest().body("No certificates found for this event.");
}

massMailService.sendCertificates(event, certificates);
return ResponseEntity.ok("Mass mail job started for " + certificates.size() + " recipients.");
}

@PostMapping("/upload-csv/{eventId}")
public ResponseEntity<String> uploadCsv(@PathVariable Integer eventId, @RequestParam("file") MultipartFile file) {
Event event = eventRepository.findById(eventId)
.orElseThrow(() -> new RuntimeException("Event not found with id: " + eventId));

List<Certificate> certificates = new ArrayList<>();
try (CSVReader reader = new CSVReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
String[] nextLine;
// Skip header: Name, Email
reader.readNext();

while ((nextLine = reader.readNext()) != null) {
if (nextLine.length >= 2) {
String name = nextLine[0].trim();
String email = nextLine[1].trim();
if (!certificateService.existsByEmailAndEvent(email, eventId)) {
Certificate cert = Certificate.builder()
.recipientName(name)
.recipientEmail(email)
.event(event)
.issueDate(LocalDate.now().toString())
.build();
certificates.add(certificateService.createCertificate(cert));
}
}
}
} catch (IOException | CsvValidationException e) {
log.error("CSV processing error", e);
return ResponseEntity.internalServerError().body("Error processing CSV: " + e.getMessage());
}

return ResponseEntity.ok("Successfully imported " + certificates.size() + " participants.");
}
}
Loading
Loading