Changeset 4d97b63 for jobvista-backend


Ignore:
Timestamp:
08/30/24 15:44:27 (4 weeks ago)
Author:
223021 <daniel.ilievski.2@…>
Branches:
main
Parents:
0f0add0
Message:

Implemented Google login, additional file uploads, response messages and email notifications

Location:
jobvista-backend
Files:
20 added
15 edited

Legend:

Unmodified
Added
Removed
  • jobvista-backend/.gitignore

    r0f0add0 r4d97b63  
    3232### VS Code ###
    3333.vscode/
     34
     35.env
  • jobvista-backend/pom.xml

    r0f0add0 r4d97b63  
    6363                        <scope>test</scope>
    6464                </dependency>
     65                <!-- other -->
     66                <dependency>
     67                        <groupId>org.springframework.boot</groupId>
     68                        <artifactId>spring-boot-starter-oauth2-client</artifactId>
     69                </dependency>
    6570
    66                 <!-- other -->
     71                <dependency>
     72                        <groupId>org.springframework.boot</groupId>
     73                        <artifactId>spring-boot-starter-mail</artifactId>
     74                </dependency>
     75
     76                <dependency>
     77                        <groupId>org.springframework.security</groupId>
     78                        <artifactId>spring-security-oauth2-jose</artifactId>
     79                </dependency>
     80
     81                <dependency>
     82                        <groupId>com.google.api-client</groupId>
     83                        <artifactId>google-api-client</artifactId>
     84                        <version>2.5.1</version>
     85                </dependency>
     86
     87                <!-- https://mvnrepository.com/artifact/com.google.oauth-client/google-oauth-client -->
     88                <dependency>
     89                        <groupId>com.google.oauth-client</groupId>
     90                        <artifactId>google-oauth-client-jetty</artifactId>
     91                        <version>1.34.1</version>
     92                </dependency>
     93
     94                <dependency>
     95                        <groupId>com.google.http-client</groupId>
     96                        <artifactId>google-http-client-jackson2</artifactId>
     97                        <version>1.32.1</version>
     98                </dependency>
     99
     100
     101
     102
     103
     104
    67105                <!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
    68106                <dependency>
     
    71109                        <version>3.0.2</version>
    72110                </dependency>
    73 
    74 
    75111
    76112                <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/JobvistaBackendApplication.java

    r0f0add0 r4d97b63  
    2727                        admin.setEmail("admin@admin.com");
    2828                        admin.setHasAccess(true);
    29 //                      admin.setName("admin");
    30 //                      admin.setSurname("admin");
    3129                        admin.setPassword(new BCryptPasswordEncoder().encode("admin"));
    3230                        userRepository.save(admin);
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java

    r0f0add0 r4d97b63  
    3333                        .requestMatchers(
    3434                                "/api/auth/**",
     35                                "/oauth2/**",
    3536                                "/api/job-advertisements/**",
    3637                                "/api/applications/**",
    3738                                "/api/recruiter/**",
    38                                 "/api/job-seeker/**"
     39                                "/api/job-seeker/**",
     40                                "/uploads/**"
    3941                        ).permitAll()
    4042                        .requestMatchers("/api/admin/**").hasAnyAuthority(Role.ROLE_ADMIN.name())
     
    4648                        .requestMatchers("/api/job-advertisements/edit/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
    4749                        .requestMatchers("/api/job-advertisements/delete/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
    48                         .requestMatchers("/api/applications/{id}/update").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     50                        .requestMatchers("/api/applications/{id}/update").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
     51                        .requestMatchers("/api/applications/update").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     52                        .requestMatchers("/uploads/applications/**").hasAnyAuthority(Role.ROLE_RECRUITER.name())
    4953                        .requestMatchers("/api/job-advertisements/{advertisement_id}/applications").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     54                        .requestMatchers("/api/job-advertisements/{advertisement_id}/applications/filtered").hasAnyAuthority(Role.ROLE_RECRUITER.name())
    5055                        .requestMatchers("/api/applications/submit").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
    5156                        .requestMatchers("/api/my-applications/{id}").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
    52                         .anyRequest().authenticated())
     57                        .requestMatchers("/api/my-applications/{id}/filtered").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
     58                .anyRequest().authenticated())
    5359                .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
    5460                .authenticationProvider(authenticationProvider()).addFilterBefore(
    55                         jwtAuthFilter, UsernamePasswordAuthenticationFilter.class
    56                 );
     61                        jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
     62                .oauth2Login(oauth2 -> oauth2
     63                        .defaultSuccessUrl("/api/auth/google", true)
     64                )
     65        ;
     66
    5767        return http.build();
    5868    }
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java

    r0f0add0 r4d97b63  
    3030    }
    3131
     32    @PostMapping("/my-applications/{id}/filtered")
     33    public ResponseEntity<?> filterApplicationsByJobSeekerId(@PathVariable Long id, @RequestBody String status) {
     34        List<ApplicationDetailsDTO> applicationList = applicationService.filterByJobSeekerId(id, status);
     35        return new ResponseEntity<>(applicationList, HttpStatus.OK);
     36    }
     37
    3238    @GetMapping("/job-advertisements/{advertisement_id}/applications")
    3339    public ResponseEntity<?> findAllApplicationsByJobAdvertisementId(@PathVariable("advertisement_id") Long advertisementId) {
     
    3642    }
    3743
    38     @PostMapping("/applications/{id}/update")
     44    @PostMapping("/job-advertisements/{advertisement_id}/applications/filtered")
     45    public ResponseEntity<?> filterApplicationsByJobAdvertisementId(@PathVariable("advertisement_id") Long advertisementId, @RequestBody String status) {
     46        List<ApplicationDetailsDTO> applicationList = applicationService.filterByJobAdvertisementId(advertisementId, status);
     47         return new ResponseEntity<>(applicationList, HttpStatus.OK);
     48    }
     49
     50    @PostMapping("/applications/{id}/update/NOT-IN-USE")
    3951    public ResponseEntity<?> updateApplicationStatus(@PathVariable("id") Long applicaitonId, @RequestBody ApplicationStatusDTO appStatusDTO) {
    4052        ApplicationStatusDTO applicationStatusDTO = applicationService.updateApplicationStatus(applicaitonId,appStatusDTO.getStatus());
    4153        return new ResponseEntity<>(applicationStatusDTO, HttpStatus.OK);
     54    }
     55
     56    @PostMapping("/applications/update")
     57    public ResponseEntity<?> updateApplications(@RequestBody List<ApplicationStatusDTO> changes) {
     58       List<ApplicationStatusDTO> updatedApplications = applicationService.updateApplications(changes);
     59       return new ResponseEntity<>(updatedApplications, HttpStatus.OK);
    4260    }
    4361
     
    6684        return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK);
    6785    }
     86
     87    @PostMapping("/applications/{id}/update")
     88    public ResponseEntity<ApplicationDetailsDTO> updateApplication(
     89            @PathVariable("id") Long applicationId,
     90            @RequestParam("additionalFiles") MultipartFile[] additionalFiles) {
     91        ApplicationDetailsDTO applicationDetailsDTO = applicationService.updateApplication(applicationId, additionalFiles);
     92        return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK);
     93    }
     94
     95    @GetMapping("/applications/{id}/download-additional-files")
     96    public ResponseEntity<List<String>> getAdditionalFilesUrls(@PathVariable("id") Long applicationId) {
     97        List<String> fileUrls = applicationService.loadAdditionalFilesAsUrls(applicationId);
     98        return ResponseEntity.ok(fileUrls);
     99    }
     100
    68101}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java

    r0f0add0 r4d97b63  
    77import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.JobSeekerMapper;
    88import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.RecruiterMapper;
     9
    910import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.AuthService;
     11
    1012import org.springframework.http.HttpStatus;
    1113import org.springframework.http.ResponseEntity;
    1214import org.springframework.web.bind.annotation.*;
     15
     16import java.util.Map;
     17
     18
    1319
    1420@RestController
     
    4248        return ResponseEntity.ok(authenticationService.refreshToken(refreshTokenRequest));
    4349    }
     50
     51    @PostMapping("/google")
     52    public ResponseEntity<?> googleSignIn(@RequestBody Map<String, String> token) {
     53        return ResponseEntity.ok(authenticationService.googleSignIn(token));
     54    }
    4455}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java

    r0f0add0 r4d97b63  
    99import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.JobAdvertisement;
    1010import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.JobSeeker;
    11 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
    1211
    1312import java.time.LocalDateTime;
    14 import java.util.HashMap;
     13import java.util.ArrayList;
    1514import java.util.List;
    1615
     
    4544    private ApplicationStatus status;
    4645
     46    private String response;
     47
     48    @ElementCollection
     49    private List<String> additionalFilePaths;
     50
    4751    public Application(JobSeeker jobSeeker, JobAdvertisement jobAdvertisement, List<String> answers, String message) {
    4852        this.jobSeeker = jobSeeker;
     
    5357        submittedOn = LocalDateTime.now();
    5458        this.status = ApplicationStatus.PROPOSED;
     59        this.response = "";
     60        this.additionalFilePaths = new ArrayList<>();
    5561    }
    5662
     
    7278                application.getMessage(),
    7379                application.getSubmittedOn(),
    74                 application.getStatus().name()
     80                application.getStatus().name(),
     81                application.getResponse(),
     82                application.getAdditionalFilePaths()
    7583        );
    7684    }
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationDetailsDTO.java

    r0f0add0 r4d97b63  
    2929    private LocalDateTime submittedOn;
    3030    private String status;
     31    private String response;
     32    private List<String> additionalFileNames;
    3133}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/DTO/ApplicationStatusDTO.java

    r0f0add0 r4d97b63  
    99    Long id;
    1010    String status;
     11    String response;
    1112}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/JobSeekerRepository.java

    r0f0add0 r4d97b63  
    44import org.springframework.data.jpa.repository.JpaRepository;
    55
     6import java.util.Optional;
     7
    68public interface JobSeekerRepository extends JpaRepository<JobSeeker, Long> {
     9    Optional<JobSeeker> findByEmail(String email);
    710}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java

    r0f0add0 r4d97b63  
    66import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.JobSeeker;
    77import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.JobSeekerRepository;
     8import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.EmailSenderService;
    89import org.springframework.core.io.Resource;
    910import org.springframework.core.io.UrlResource;
     
    1920import org.springframework.beans.factory.annotation.Value;
    2021import org.springframework.stereotype.Service;
     22import org.springframework.web.multipart.MultipartFile;
     23import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
    2124
    2225import java.io.IOException;
     26import java.net.MalformedURLException;
    2327import java.nio.file.Files;
    2428import java.nio.file.Path;
     
    3943
    4044    @Autowired
     45    private EmailSenderService emailSenderService;
     46
     47    @Autowired
    4148    public ApplicationServiceImpl(@Value("${file.upload-dir}") String uploadDir, UserRepository userRepository, ApplicationRepository applicationRepository, JobAdvertisementRepository jobAdvertisementRepository,
    4249                                  JobSeekerRepository jobSeekerRepository) {
     
    95102
    96103    @Override
     104    public ApplicationDetailsDTO updateApplication(Long applicationId, MultipartFile[] additionalFiles) {
     105        Application application = applicationRepository.findById(applicationId).orElse(null);
     106        if(application== null) {
     107            throw new RuntimeException("Application not found.");
     108        }
     109
     110        for (MultipartFile additionalFile : additionalFiles) {
     111            if (additionalFile.isEmpty()) {
     112                throw new RuntimeException("Failed to store empty file.");
     113            }
     114        }
     115
     116        Path filesPath = this.fileStorageLocation.resolve(String.valueOf(application.getId())).resolve("additional_files");
     117        for(MultipartFile additionalFile: additionalFiles) {
     118            Path targetLocation = filesPath.resolve(additionalFile.getOriginalFilename());
     119
     120            try {
     121                Files.createDirectories(filesPath);
     122                Files.copy(additionalFile.getInputStream(), targetLocation);
     123            } catch (IOException e) {
     124                throw new RuntimeException(e);
     125            }
     126
     127            String relativePath = Paths.get("uploads","applications",String.valueOf(application.getId()),
     128                    "additional_files", additionalFile.getOriginalFilename()).toString();
     129            List<String> currentAdditionalFilePaths = application.getAdditionalFilePaths();
     130            currentAdditionalFilePaths.add(relativePath);
     131            application.setAdditionalFilePaths(currentAdditionalFilePaths);
     132            application = applicationRepository.save(application);
     133        }
     134        return Application.mapToApplicationDetailsDTO(application);
     135    }
     136
     137    @Override
    97138    public List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId) {
    98139        List<Application> applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
     
    101142
    102143    @Override
     144    public List<ApplicationDetailsDTO> filterByJobAdvertisementId(Long jobId, String status) {
     145        List<Application> applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
     146        String statusTrimmed = status.subSequence(0, status.length()-1).toString();
     147
     148        if(statusTrimmed.equals("ALL")) {
     149            applications =  applicationRepository.findAllByJobAdvertisementId(jobId);
     150        } else {
     151            applications = applications.stream().filter(application -> application.getStatus().name().equals(statusTrimmed)).toList();
     152        }
     153        return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
     154    }
     155
     156    @Override
    103157    public List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId) {
    104158       List<Application> applications = applicationRepository.findAllByJobSeekerId(jobSeekerId);
    105159       return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
     160    }
     161
     162    @Override
     163    public List<ApplicationDetailsDTO> filterByJobSeekerId(Long jobSeekerId, String status) {
     164        List<Application> applications = applicationRepository.findAllByJobSeekerId(jobSeekerId);
     165        String statusTrimmed = status.subSequence(0, status.length()-1).toString();
     166        if(statusTrimmed.equals("ALL")) {
     167            applications =  applicationRepository.findAllByJobSeekerId(jobSeekerId);
     168        } else {
     169            applications = applications.stream().filter(application -> application.getStatus().name().equals(statusTrimmed)).toList();
     170        }
     171        return applications.stream().map(Application::mapToApplicationDetailsDTO).toList();
    106172    }
    107173
     
    126192    }
    127193
     194    public List<String> loadAdditionalFilesAsUrls(Long applicationId) {
     195        Application application = applicationRepository.findById(applicationId)
     196                .orElseThrow(() -> new IllegalArgumentException("Application not found"));
     197
     198        List<String> fileUrls = new ArrayList<>();
     199        List<String> relativeFilePaths = application.getAdditionalFilePaths();
     200
     201        for (String relativeFilePath : relativeFilePaths) {
     202            //TO DO: refactor
     203            Path filePath = Paths.get(fileStorageLocation.getParent().getParent().toString(), relativeFilePath).normalize();
     204            String relativePath = filePath.toString().replace("\\", "/").replaceFirst("^.+uploads", "uploads");
     205
     206            String fileUrl = ServletUriComponentsBuilder.fromCurrentContextPath()
     207                    .path("/")
     208                    .path(relativePath)
     209                    .toUriString();
     210            fileUrls.add(fileUrl);
     211        }
     212
     213        return fileUrls;
     214    }
     215
     216   /* @Override
     217    public List<Resource> loadAdditionalFilesAsZippedResource(Long applicationId) {
     218        Application application = applicationRepository.findById(applicationId).
     219                orElseThrow(() -> new IllegalArgumentException("Application not found"));
     220
     221        List<Resource> resources = new ArrayList<>();
     222
     223        List<String> relativeFilePaths = application.getAdditionalFilePaths();
     224        for(String relativeFilePath: relativeFilePaths) {
     225            Path filePath = fileStorageLocation.getParent().getParent().resolve(relativeFilePath).normalize();
     226
     227            try {
     228                Resource resource = new UrlResource(filePath.toUri());
     229                if (resource.exists()) {
     230                    resources.add(resource);
     231                }
     232            } catch (MalformedURLException e) {
     233                throw new RuntimeException(e);
     234            }
     235        }
     236        return resources;
     237    }*/
     238
     239    @Override
     240    public List<ApplicationStatusDTO> updateApplications(List<ApplicationStatusDTO> updates) {
     241        List<ApplicationStatusDTO> updatedApplications = new ArrayList<>();
     242
     243        for(ApplicationStatusDTO applicationStatusDTO : updates) {
     244            Application application = applicationRepository.findById(applicationStatusDTO.getId()).orElse(null);
     245            if(application != null) {
     246                application.setStatus(ApplicationStatus.valueOf(applicationStatusDTO.getStatus()));
     247                application.setResponse(applicationStatusDTO.getResponse());
     248                applicationRepository.save(application);
     249                updatedApplications.add(applicationStatusDTO);
     250
     251                //email notification
     252                String email = application.getJobSeeker().getEmail();
     253                String subject = application.getJobAdvertisement().getRecruiter().getName() + ": " + application.getJobAdvertisement().getTitle() + " - STATUS UPDATE";
     254                String text = "Dear " + application.getJobSeeker().getName() + ",\n\n";
     255
     256                switch (applicationStatusDTO.getStatus()) {
     257                    case "ACCEPTED":
     258                        text += "Great news! Your application has been accepted.\n\n";
     259                        break;
     260                    case "DENIED":
     261                        text += "We regret to inform you that your application has been denied. We appreciate your interest and effort.\n\n";
     262                        break;
     263                    case "PROPOSED":
     264                        text += "Your application status has been updated to 'Proposed'. We're considering your application for the next phase.\n\n";
     265                        break;
     266                    case "UNDER_REVIEW":
     267                        text += "Your application is currently under review.\n\n";
     268                        break;
     269                }
     270
     271
     272                if(!applicationStatusDTO.getResponse().isEmpty()) {
     273                    text += "Response: " + applicationStatusDTO.getResponse() + "\n\n";
     274                }
     275               text += "Thank you.";
     276           emailSenderService.sendEmail(email, subject, text);
     277            }
     278        }
     279        return updatedApplications;
     280    }
     281
    128282    @Override
    129283    public ApplicationStatusDTO updateApplicationStatus(Long id, String status) {
     
    132286       application.setStatus(ApplicationStatus.valueOf(status));
    133287       applicationRepository.save(application);
    134        return new ApplicationStatusDTO(id, status);
     288       return new ApplicationStatusDTO(id, status, "");
    135289    }
    136290}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java

    r0f0add0 r4d97b63  
    11package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.impl;
    22
     3import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
     4import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
     5import com.google.api.client.http.javanet.NetHttpTransport;
     6import com.google.api.client.json.jackson2.JacksonFactory;
    37import lombok.RequiredArgsConstructor;
     8import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.config.GoogleOAuth2Properties;
     9import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.controllers.AuthController;
     10import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.Role;
    411import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.SignInDTO;
    512import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.JwtAuthResponse;
     
    1219import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.UserRepository;
    1320import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.AuthService;
     21import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JobSeekerService;
    1422import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JwtService;
    1523import org.springframework.security.authentication.AuthenticationManager;
    1624import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     25import org.springframework.security.core.authority.SimpleGrantedAuthority;
    1726import org.springframework.security.crypto.password.PasswordEncoder;
     27import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
     28import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
     29import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
     30import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
     31import org.springframework.security.oauth2.core.user.OAuth2User;
    1832import org.springframework.stereotype.Service;
    19 
     33import org.springframework.web.multipart.MultipartFile;
     34
     35import javax.imageio.ImageIO;
     36import java.awt.image.BufferedImage;
     37import java.io.ByteArrayInputStream;
     38import java.io.ByteArrayOutputStream;
     39import java.io.IOException;
     40import java.io.InputStream;
     41import java.net.URL;
    2042import java.time.LocalDateTime;
     43import java.util.Collections;
    2144import java.util.HashMap;
     45import java.util.Map;
     46import java.util.Optional;
    2247
    2348@Service
     
    3055    private final AuthenticationManager authenticationManager;
    3156    private final UserRepository userRepository;
     57    private final JobSeekerService jobSeekerService;
    3258    private final JwtService jwtService;
     59    private final GoogleOAuth2Properties googleOAuth2Properties;
    3360
    3461    @Override
     
    5683        return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), user.isHasAccess(), jwt, refreshJwt);
    5784    }
    58    
     85
    5986    public JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest) {
    6087        String userEmail = jwtService.extractUsername(refreshTokenRequest.getToken());
    6188        User user = userRepository.findByEmail(userEmail).orElseThrow();
    62         if(jwtService.isTokenValid(refreshTokenRequest.getToken(), user)) {
     89        if (jwtService.isTokenValid(refreshTokenRequest.getToken(), user)) {
    6390            String jwt = jwtService.generateToken(user);
    6491
     
    6794        return null;
    6895    }
     96
     97    @Override
     98    public JwtAuthResponse googleSignIn(Map<String, String> token) {
     99        OAuth2AuthenticationToken authentication = getAuthentication(token.get("tokenId"));
     100
     101        OAuth2User oAuth2User = authentication.getPrincipal();
     102        String email = oAuth2User.getAttribute("email");
     103
     104        JobSeeker jobSeeker = jobSeekerRepository.findByEmail(email)
     105                .orElseGet(() -> {
     106                    JobSeeker newJobSeeker = new JobSeeker();
     107                    newJobSeeker.setEmail(email);
     108                    newJobSeeker.setFirstName(oAuth2User.getAttribute("given_name"));
     109                    newJobSeeker.setLastName(oAuth2User.getAttribute("family_name"));
     110                    newJobSeeker.setPassword("");
     111                    newJobSeeker.setRole(Role.ROLE_JOBSEEKER);
     112                    newJobSeeker.setHasAccess(true);
     113                    jobSeekerRepository.save(newJobSeeker);
     114
     115                    String googleProfilePicUrl = oAuth2User.getAttribute("picture");
     116                    submitGoogleProfilePic(newJobSeeker.getId(), googleProfilePicUrl);
     117
     118                    return newJobSeeker;
     119                });
     120
     121        String jwt = jwtService.generateToken(jobSeeker);
     122
     123        return new JwtAuthResponse(
     124                jobSeeker.getId(),
     125                jobSeeker.getEmail(),
     126                jobSeeker.getFirstName() + " " + jobSeeker.getLastName(),
     127                jobSeeker.getRole().name(),
     128                jobSeeker.isHasAccess(),
     129                jwt,
     130                null
     131        );
     132    }
     133
     134    public OAuth2AuthenticationToken getAuthentication(String tokenId) {
     135        try {
     136            GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
     137                    .setAudience(Collections.singletonList(googleOAuth2Properties.getClientId()))
     138                    .build();
     139
     140            GoogleIdToken idToken = verifier.verify(tokenId);
     141            if (idToken != null) {
     142                GoogleIdToken.Payload payload = idToken.getPayload();
     143
     144                String userId = payload.getSubject();
     145                String email = payload.getEmail();
     146                boolean emailVerified = Boolean.TRUE.equals(payload.getEmailVerified());
     147                String name = (String) payload.get("name");
     148                String pictureUrl = (String) payload.get("picture");
     149                String familyName = Optional.ofNullable((String) payload.get("family_name")).orElse("");
     150                String givenName = (String) payload.get("given_name");
     151
     152                Map<String, Object> attributes = Map.of(
     153                        "sub", userId,
     154                        "email", email,
     155                        "email_verified", emailVerified,
     156                        "name", name,
     157                        "picture", pictureUrl,
     158                        "family_name", familyName,
     159                        "given_name", givenName
     160                );
     161
     162                OAuth2User oAuth2User = new DefaultOAuth2User(
     163                        Collections.singleton(new SimpleGrantedAuthority("ROLE_JOBSEEKER")),
     164                        attributes,
     165                        "sub"
     166                );
     167
     168                return new OAuth2AuthenticationToken(oAuth2User, oAuth2User.getAuthorities(), "google");
     169            } else {
     170                throw new IllegalArgumentException("Invalid ID token");
     171            }
     172        } catch (Exception e) {
     173            throw new RuntimeException("Failed to verify token", e);
     174        }
     175    }
     176
     177    public void submitGoogleProfilePic(Long jobSeekerId, String googleProfilePicUrl) {
     178        try {
     179            URL url = new URL(googleProfilePicUrl);
     180            BufferedImage image = ImageIO.read(url);
     181
     182            // Convert BufferedImage to byte array
     183            ByteArrayOutputStream baos = new ByteArrayOutputStream();
     184            ImageIO.write(image, "jpg", baos);
     185            byte[] imageBytes = baos.toByteArray();
     186
     187            // Convert byte array to MultipartFile
     188            MultipartFile multipartFile = new InMemoryMultipartFile("profilePicFile", "google-profile-pic.jpg", "image/jpeg", imageBytes);
     189
     190            jobSeekerService.submitProfilePic(jobSeekerId, multipartFile);
     191        } catch (IOException e) {
     192            e.printStackTrace();
     193        }
     194    }
     195
     196    class InMemoryMultipartFile implements MultipartFile {
     197
     198        private final String name;
     199        private final String originalFilename;
     200        private final String contentType;
     201        private final byte[] content;
     202
     203        public InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
     204            this.name = name;
     205            this.originalFilename = originalFilename;
     206            this.contentType = contentType;
     207            this.content = content;
     208        }
     209
     210        @Override
     211        public String getName() {
     212            return name;
     213        }
     214
     215        @Override
     216        public String getOriginalFilename() {
     217            return originalFilename;
     218        }
     219
     220        @Override
     221        public String getContentType() {
     222            return contentType;
     223        }
     224
     225        @Override
     226        public boolean isEmpty() {
     227            return content.length == 0;
     228        }
     229
     230        @Override
     231        public long getSize() {
     232            return content.length;
     233        }
     234
     235        @Override
     236        public byte[] getBytes() throws IOException {
     237            return content;
     238        }
     239
     240        @Override
     241        public InputStream getInputStream() throws IOException {
     242            return new ByteArrayInputStream(content);
     243        }
     244
     245        @Override
     246        public void transferTo(java.io.File dest) throws IOException {
     247            throw new UnsupportedOperationException("This method is not implemented");
     248        }
     249    }
    69250}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java

    r0f0add0 r4d97b63  
    77import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.applications.DTO.ApplicationStatusDTO;
    88import org.springframework.core.io.Resource;
     9import org.springframework.web.multipart.MultipartFile;
    910
    1011import java.util.List;
     
    1213public interface ApplicationService {
    1314    ApplicationDetailsDTO submitApplication(ApplicationDTO applicationDTO);
     15    ApplicationDetailsDTO updateApplication(Long applicationId, MultipartFile[] additionalFiles);
    1416    List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId);
     17    List<ApplicationDetailsDTO> filterByJobAdvertisementId(Long jobId, String status);
    1518    List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId);
     19    List<ApplicationDetailsDTO> filterByJobSeekerId(Long jobSeekerId, String status);
    1620    Resource loadResumeAsResource(Long applicationId);
     21    List<String> loadAdditionalFilesAsUrls(Long applicationId);
    1722    ApplicationStatusDTO updateApplicationStatus(Long id, String status);
     23    List<ApplicationStatusDTO> updateApplications(List<ApplicationStatusDTO> updates);
    1824}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/AuthService.java

    r0f0add0 r4d97b63  
    77import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
    88import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
     9import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
     10
     11import java.util.Map;
    912
    1013public interface AuthService {
     
    1316    JwtAuthResponse signIn(SignInDTO signInDTO);
    1417    JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest);
     18
     19    JwtAuthResponse googleSignIn(Map<String, String> token);
     20    OAuth2AuthenticationToken getAuthentication(String tokenId);
     21    void submitGoogleProfilePic(Long jobSeekerId, String googleProfilePicUrl);
    1522}
  • jobvista-backend/src/main/resources/application.properties

    r0f0add0 r4d97b63  
    22
    33spring.datasource.driver-class-name=org.postgresql.Driver
    4 spring.datasource.url=jdbc:postgresql://localhost:5432/jobvistaDB
    5 spring.datasource.username=postgres
    6 spring.datasource.password=postgres
     4spring.datasource.url=${db_url}
     5spring.datasource.username=${db_username}
     6spring.datasource.password=${db_password}
    77
    88#spring.jpa.hibernate.ddl-auto=create-drop
     
    1212spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
    1313
    14 file.upload-dir=./uploads
     14file.upload-dir=${file_upload_dir}
    1515
    1616spring.servlet.multipart.enabled=true
    1717spring.servlet.multipart.max-file-size=2MB
    1818spring.servlet.multipart.max-request-size=2MB
     19
     20spring.security.oauth2.client.registration.google.client-id=${google_id}
     21spring.security.oauth2.client.registration.google.client-secret=${google_secret}
     22spring.security.oauth2.client.registration.google.scope=profile, email
     23spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:3000/login/oauth2/code/google
     24spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
     25spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
     26spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
     27spring.security.oauth2.client.provider.google.user-name-attribute=sub
     28
     29spring.mail.host=smtp.gmail.com
     30spring.mail.port=587
     31spring.mail.username=${mail_username}
     32spring.mail.password=${mail_password}
     33custom.mail.sender.email=${mail_sender_email}
     34custom.mail.sender.name=${mail_sender_name}
     35spring.mail.properties.mail.smtp.auth=true
     36spring.mail.properties.mail.smtp.starttls.enable=true
     37
     38
Note: See TracChangeset for help on using the changeset viewer.