diff --git a/src/main/java/app/ApplicationConfig.java b/src/main/java/app/ApplicationConfig.java index ba3ccba..7fb7aa3 100644 --- a/src/main/java/app/ApplicationConfig.java +++ b/src/main/java/app/ApplicationConfig.java @@ -5,6 +5,7 @@ import app.dto.company.CompanyResponseDTO; import app.dto.company.CreateCompanyRequestDTO; import app.dto.company.UpdateCompanyRequestDTO; +import app.dto.login.LoginResponseDTO; import app.dto.randomuser.RandomUserViewDTO; import app.dto.user.CreateUserRequestDTO; import app.dto.user.UpdateUserRequestDTO; @@ -12,11 +13,9 @@ import app.entities.Company; import app.entities.User; import app.exceptions.ApiErrorResponse; +import app.exceptions.ConflictException; import app.exceptions.UnauthorizedException; -import app.services.AuthService; -import app.services.JwtService; -import app.services.PasswordService; -import app.services.RandomUserService; +import app.services.*; import io.javalin.Javalin; import jakarta.persistence.EntityManagerFactory; @@ -34,10 +33,21 @@ public static Javalin startApp(int port, EntityManagerFactory emf) PasswordService passwordService = new PasswordService(); JwtService jwtService = new JwtService(); AuthService authService = new AuthService(userDAO, passwordService, jwtService); + UserServiceImpl userService = new UserServiceImpl(userDAO, companyDAO, passwordService); RandomUserService randomUserService = new RandomUserService(); Javalin app = Javalin.create(config -> { + config.bundledPlugins.enableCors(cors -> + { + cors.addRule(it -> + { + it.allowHost( + "https://membersystem.obli.dk", + "http://localhost:5173" + ); + }); + }); config.router.apiBuilder(() -> { // TODO: Split routes into separate controller classes later. @@ -63,11 +73,18 @@ public static Javalin startApp(int port, EntityManagerFactory emf) ctx.json(new ApiErrorResponse(400, e.getMessage())); }); - app.exception(UnauthorizedException.class, (e, ctx) -> { + app.exception(UnauthorizedException.class, (e, ctx) -> + { ctx.status(401); ctx.json(new app.exceptions.ApiErrorResponse(401, e.getMessage())); }); + app.exception(ConflictException.class, (e, ctx) -> + { + ctx.status(409); + ctx.json(new ApiErrorResponse(409, e.getMessage())); + }); + app.exception(Exception.class, (e, ctx) -> { e.printStackTrace(); @@ -82,13 +99,14 @@ public static Javalin startApp(int port, EntityManagerFactory emf) // -------------------- // TODO: Add authentication endpoints like /login. - app.post("/login", ctx -> { + app.post("/login", ctx -> + { var request = ctx.bodyAsClass(app.dto.login.LoginRequestDTO.class); String token = authService.login(request.email(), request.password()); ctx.status(200); - ctx.json(new app.dto.login.LoginResponseDTO(token)); + ctx.json(new LoginResponseDTO(token)); }); // -------------------- @@ -126,6 +144,11 @@ public static Javalin startApp(int port, EntityManagerFactory emf) CreateCompanyRequestDTO request = ctx.bodyAsClass(CreateCompanyRequestDTO.class); + if (companyDAO.findByName(request.name()).isPresent()) + { + throw new ConflictException("Company already exists with name: " + request.name()); + } + Company company = Company.builder() .name(request.name()) .build(); @@ -222,34 +245,7 @@ public static Javalin startApp(int port, EntityManagerFactory emf) requireAuth(ctx, jwtService); CreateUserRequestDTO request = ctx.bodyAsClass(CreateUserRequestDTO.class); - - Company company = companyDAO.getById(request.companyId()); - - String hashedPassword = passwordService.hashPassword(request.password()); - - User user = User.builder() - .company(company) - .email(request.email()) - .firstname(request.firstname()) - .lastname(request.lastname()) - .dob(request.dob()) - .role(request.role()) - .passwordHash(hashedPassword) - .build(); - - User created = userDAO.create(user); - User createdWithCompany = userDAO.getByIdWithCompany(created.getId()); - - UserResponseDTO response = new UserResponseDTO( - createdWithCompany.getId(), - createdWithCompany.getEmail(), - createdWithCompany.getFirstname(), - createdWithCompany.getLastname(), - createdWithCompany.getDob(), - createdWithCompany.getRole(), - createdWithCompany.getCompany().getId(), - createdWithCompany.getCompany().getName() - ); + UserResponseDTO response = userService.create(request); ctx.status(201); ctx.json(response); @@ -343,10 +339,12 @@ public static Javalin startApp(int port, EntityManagerFactory emf) // Helper methods // -------------------- - private static void requireAuth(io.javalin.http.Context ctx, JwtService jwtService) { + private static void requireAuth(io.javalin.http.Context ctx, JwtService jwtService) + { String authHeader = ctx.header("Authorization"); - if (authHeader == null || !authHeader.startsWith("Bearer ")) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) + { throw new UnauthorizedException("Missing or invalid Authorization header"); } diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index cff5977..8af137b 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -7,7 +7,9 @@ import app.entities.Role; import app.entities.User; import app.services.PasswordService; +import app.utils.Utils; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.EntityNotFoundException; import java.time.LocalDate; @@ -31,6 +33,7 @@ private static void seedBootstrapAdmin(EntityManagerFactory emf) PasswordService passwordService = new PasswordService(); String adminEmail = "admin@obli.dk"; + String companyName = "Membersystem Bootstrap Company"; if (userDAO.findByEmail(adminEmail).isPresent()) { @@ -38,18 +41,24 @@ private static void seedBootstrapAdmin(EntityManagerFactory emf) return; } - Company company = companyDAO.create( - Company.builder() - .name("Membersystem Bootstrap Company") - .build() - ); + Company company = companyDAO.findByName(companyName) + .orElseGet(() -> companyDAO.create( + Company.builder() + .name(companyName) + .build() + )); -// String password = "Test1234!"; String password = System.getenv("BOOTSTRAP_ADMIN_PASSWORD"); + if (password == null || password.isBlank()) + { + password = Utils.getPropertyValue("PASSWORD", "config.properties"); + } + if (password == null || password.isBlank()) { throw new IllegalStateException("BOOTSTRAP_ADMIN_PASSWORD is not set"); } + String hashedPassword = passwordService.hashPassword(password); User admin = User.builder() @@ -74,6 +83,6 @@ private static int getPort() { return Integer.parseInt(portEnv); } - return 7000; + return 7070; } } \ No newline at end of file diff --git a/src/main/java/app/daos/CompanyDAO.java b/src/main/java/app/daos/CompanyDAO.java index 15f2d79..b960a96 100644 --- a/src/main/java/app/daos/CompanyDAO.java +++ b/src/main/java/app/daos/CompanyDAO.java @@ -7,19 +7,24 @@ import jakarta.persistence.EntityNotFoundException; import java.util.HashSet; +import java.util.Optional; import java.util.Set; -public class CompanyDAO implements IDAO { +public class CompanyDAO implements IDAO +{ private final EntityManagerFactory emf; - public CompanyDAO(EntityManagerFactory emf) { + public CompanyDAO(EntityManagerFactory emf) + { this.emf = emf; } @Override - public Company create(Company company) { - try (EntityManager em = emf.createEntityManager()) { + public Company create(Company company) + { + try (EntityManager em = emf.createEntityManager()) + { em.getTransaction().begin(); em.persist(company); em.getTransaction().commit(); @@ -28,8 +33,10 @@ public Company create(Company company) { } @Override - public Set getAll() { - try (EntityManager em = emf.createEntityManager()) { + public Set getAll() + { + try (EntityManager em = emf.createEntityManager()) + { return new HashSet<>( em.createQuery("SELECT c FROM Company c", Company.class) .getResultList() @@ -38,21 +45,41 @@ public Set getAll() { } @Override - public Company getById(Long id) { - try (EntityManager em = emf.createEntityManager()) { + public Company getById(Long id) + { + try (EntityManager em = emf.createEntityManager()) + { Company company = em.find(Company.class, id); - if (company == null) { + if (company == null) + { throw new EntityNotFoundException("Company not found with id: " + id); } return company; } } + public Optional findByName(String name) + { + try (EntityManager em = emf.createEntityManager()) + { + return em.createQuery( + "SELECT c FROM Company c WHERE c.name = :name", + Company.class + ) + .setParameter("name", name) + .getResultStream() + .findFirst(); + } + } + @Override - public Company update(Company company) { - try (EntityManager em = emf.createEntityManager()) { + public Company update(Company company) + { + try (EntityManager em = emf.createEntityManager()) + { Company found = em.find(Company.class, company.getId()); - if (found == null) { + if (found == null) + { throw new EntityNotFoundException("Company not found with id: " + company.getId()); } @@ -64,10 +91,13 @@ public Company update(Company company) { } @Override - public Long delete(Company company) { - try (EntityManager em = emf.createEntityManager()) { + public Long delete(Company company) + { + try (EntityManager em = emf.createEntityManager()) + { Company found = em.find(Company.class, company.getId()); - if (found == null) { + if (found == null) + { throw new EntityNotFoundException("Company not found with id: " + company.getId()); } diff --git a/src/main/java/app/exceptions/ConflictException.java b/src/main/java/app/exceptions/ConflictException.java new file mode 100644 index 0000000..2a1ccb3 --- /dev/null +++ b/src/main/java/app/exceptions/ConflictException.java @@ -0,0 +1,7 @@ +package app.exceptions; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/app/interfaces/IUserService.java b/src/main/java/app/interfaces/IUserService.java new file mode 100644 index 0000000..f7da439 --- /dev/null +++ b/src/main/java/app/interfaces/IUserService.java @@ -0,0 +1,20 @@ +package app.interfaces; + +import app.dto.user.CreateUserRequestDTO; +import app.dto.user.UpdateUserRequestDTO; +import app.dto.user.UserResponseDTO; + +import java.util.List; + +public interface IUserService +{ + UserResponseDTO create(CreateUserRequestDTO request); + + UserResponseDTO getById(Long id); + + List getAll(); + + UserResponseDTO update(Long id, UpdateUserRequestDTO request); + + void delete(Long id); +} \ No newline at end of file diff --git a/src/main/java/app/services/UserServiceImpl.java b/src/main/java/app/services/UserServiceImpl.java new file mode 100644 index 0000000..df7fdc2 --- /dev/null +++ b/src/main/java/app/services/UserServiceImpl.java @@ -0,0 +1,93 @@ +package app.services; + +import app.daos.CompanyDAO; +import app.daos.UserDAO; +import app.dto.user.CreateUserRequestDTO; +import app.dto.user.UpdateUserRequestDTO; +import app.dto.user.UserResponseDTO; +import app.entities.Company; +import app.entities.User; +import app.exceptions.ConflictException; +import app.interfaces.IUserService; + +import java.util.List; + +public class UserServiceImpl implements IUserService +{ + private final UserDAO userDAO; + private final CompanyDAO companyDAO; + private final PasswordService passwordService; + + public UserServiceImpl(UserDAO userDAO, CompanyDAO companyDAO, PasswordService passwordService) + { + this.userDAO = userDAO; + this.companyDAO = companyDAO; + this.passwordService = passwordService; + } + + @Override + public UserResponseDTO create(CreateUserRequestDTO request) + { + if (userDAO.findByEmail(request.email()).isPresent()) + { + throw new ConflictException("User already exists with email: " + request.email()); + } + + Company company = companyDAO.getById(request.companyId()); + + String hashedPassword = passwordService.hashPassword(request.password()); + + User user = User.builder() + .company(company) + .email(request.email()) + .firstname(request.firstname()) + .lastname(request.lastname()) + .dob(request.dob()) + .role(request.role()) + .passwordHash(hashedPassword) + .build(); + + User created = userDAO.create(user); + User createdWithCompany = userDAO.getByIdWithCompany(created.getId()); + + return mapToResponseDTO(createdWithCompany); + } + + @Override + public UserResponseDTO getById(Long id) + { + return null; + } + + @Override + public List getAll() + { + return List.of(); + } + + @Override + public UserResponseDTO update(Long id, UpdateUserRequestDTO request) + { + return null; + } + + @Override + public void delete(Long id) + { + + } + + private UserResponseDTO mapToResponseDTO(User user) + { + return new UserResponseDTO( + user.getId(), + user.getEmail(), + user.getFirstname(), + user.getLastname(), + user.getDob(), + user.getRole(), + user.getCompany().getId(), + user.getCompany().getName() + ); + } +}