diff --git a/pom.xml b/pom.xml index 29848fa..5a72f33 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ - ru.tubryansk + ru.mskobaro tdms 0.0.1 pom diff --git a/server/pom.xml b/server/pom.xml index 16f199e..e945b64 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,16 +5,16 @@ 4.0.0 - ru.tubryansk + ru.mskobaro tdms 0.0.1 - ru.tubryansk.tdms + ru.mskobaro server 0.0.1 - TDMS :: SERVER + TDMS::SERVER 17 diff --git a/server/src/main/java/ru/tubryansk/tdms/TdmsApplication.java b/server/src/main/java/ru/mskobaro/tdms/TdmsApplication.java similarity index 62% rename from server/src/main/java/ru/tubryansk/tdms/TdmsApplication.java rename to server/src/main/java/ru/mskobaro/tdms/TdmsApplication.java index 2a28121..3f9368b 100644 --- a/server/src/main/java/ru/tubryansk/tdms/TdmsApplication.java +++ b/server/src/main/java/ru/mskobaro/tdms/TdmsApplication.java @@ -1,15 +1,14 @@ -package ru.tubryansk.tdms; +package ru.mskobaro.tdms; -import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -@Slf4j public class TdmsApplication { public static void main(String[] args) { - SpringApplication.run(TdmsApplication.class, args).start(); + var ctx = SpringApplication.run(TdmsApplication.class, args); + ctx.start(); } } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/DiplomaTopic.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/DiplomaTopic.java similarity index 90% rename from server/src/main/java/ru/tubryansk/tdms/entity/DiplomaTopic.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/DiplomaTopic.java index dc08e04..2675a87 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/DiplomaTopic.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/DiplomaTopic.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.*; diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Group.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Group.java similarity index 94% rename from server/src/main/java/ru/tubryansk/tdms/entity/Group.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/Group.java index e417058..72547ff 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Group.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Group.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.*; diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Role.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Role.java similarity index 77% rename from server/src/main/java/ru/tubryansk/tdms/entity/Role.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/Role.java index 77dd152..496e771 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Role.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Role.java @@ -1,11 +1,13 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.ToString; import org.springframework.security.core.GrantedAuthority; @@ -13,6 +15,8 @@ import org.springframework.security.core.GrantedAuthority; @Getter @ToString @Entity +@AllArgsConstructor +@NoArgsConstructor @Table(name = "`role`") public class Role implements GrantedAuthority { @Id diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Student.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Student.java similarity index 80% rename from server/src/main/java/ru/tubryansk/tdms/entity/Student.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/Student.java index cdde3fe..230230d 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Student.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Student.java @@ -1,10 +1,14 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.ZonedDateTime; @Getter @@ -54,4 +58,11 @@ public class Student { @ManyToOne @JoinColumn(name = "group_id") private Group group; + + @Column(name = "created_at") + @CreationTimestamp + private ZonedDateTime createdAt; + @Column(name = "updated_at") + @UpdateTimestamp + private ZonedDateTime updatedAt; } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Teacher.java similarity index 88% rename from server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/Teacher.java index 48aafba..4bd1938 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/Teacher.java @@ -1,7 +1,9 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.*; import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -9,6 +11,8 @@ import java.time.ZonedDateTime; import java.util.List; @Getter +@Setter +@ToString @Entity @Table(name = "teacher") public class Teacher { diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/User.java b/server/src/main/java/ru/mskobaro/tdms/domain/entity/User.java similarity index 97% rename from server/src/main/java/ru/tubryansk/tdms/entity/User.java rename to server/src/main/java/ru/mskobaro/tdms/domain/entity/User.java index 67a772e..0e1f039 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/User.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/entity/User.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.entity; +package ru.mskobaro.tdms.domain.entity; import jakarta.persistence.*; diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/exception/AccessDeniedException.java b/server/src/main/java/ru/mskobaro/tdms/domain/exception/AccessDeniedException.java new file mode 100644 index 0000000..606a9d1 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/exception/AccessDeniedException.java @@ -0,0 +1,14 @@ +package ru.mskobaro.tdms.domain.exception; + +import ru.mskobaro.tdms.presentation.payload.ErrorDTO; + +public class AccessDeniedException extends BusinessException { + public AccessDeniedException() { + super("Access denied"); + } + + @Override + public ErrorDTO.ErrorCode getErrorCode() { + return ErrorDTO.ErrorCode.ACCESS_DENIED; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/exception/BusinessException.java b/server/src/main/java/ru/mskobaro/tdms/domain/exception/BusinessException.java new file mode 100644 index 0000000..1cc9574 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/exception/BusinessException.java @@ -0,0 +1,13 @@ +package ru.mskobaro.tdms.domain.exception; + +import ru.mskobaro.tdms.presentation.payload.ErrorDTO; + +public class BusinessException extends RuntimeException { + public BusinessException(String message) { + super(message); + } + + public ErrorDTO.ErrorCode getErrorCode() { + return ErrorDTO.ErrorCode.BUSINESS_ERROR; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/exception/NotFoundException.java b/server/src/main/java/ru/mskobaro/tdms/domain/exception/NotFoundException.java new file mode 100644 index 0000000..278081b --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/exception/NotFoundException.java @@ -0,0 +1,34 @@ +package ru.mskobaro.tdms.domain.exception; + +import ru.mskobaro.tdms.presentation.payload.ErrorDTO; + +public class NotFoundException extends BusinessException { + public NotFoundException(Class entityClass, Object id) { + super(entityClass.getSimpleName() + " с идентификатором " + id + " не наеден"); + } + + public NotFoundException(Class entityClass) { + super(entityClass.getSimpleName() + " не найден"); + } + + public NotFoundException() { + super("Не найдено"); + } + + public static void throwIfNull(Object object) { + if (object == null) { + throw new NotFoundException(); + } + } + + public static void throwIfNull(Object object, Class entityClass, Object id) { + if (object == null) { + throw new NotFoundException(entityClass, id); + } + } + + @Override + public ErrorDTO.ErrorCode getErrorCode() { + return ErrorDTO.ErrorCode.NOT_FOUND; + } +} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/AuthenticationService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/AuthenticationService.java similarity index 68% rename from server/src/main/java/ru/tubryansk/tdms/service/AuthenticationService.java rename to server/src/main/java/ru/mskobaro/tdms/domain/service/AuthenticationService.java index cc5822e..4080bc4 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/AuthenticationService.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/AuthenticationService.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.service; +package ru.mskobaro.tdms.domain.service; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; @@ -9,9 +9,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.tubryansk.tdms.entity.User; - -import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; @Service @Slf4j @@ -21,11 +18,6 @@ public class AuthenticationService { @Autowired private AuthenticationManager authenticationManager; - public boolean authenticated() { - var authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication.isAuthenticated() && (authentication.getPrincipal() instanceof User); - } - public void logout() { log.info("Logging out user: {}", SecurityContextHolder.getContext().getAuthentication().getName()); HttpSession session = request.getSession(false); @@ -40,12 +32,9 @@ public class AuthenticationService { public void login(String username, String password) { log.info("Logging in user: {}, ip: {}", username, request.getRemoteAddr()); - var context = SecurityContextHolder.createEmptyContext(); var token = new UsernamePasswordAuthenticationToken(username, password); var authenticated = authenticationManager.authenticate(token); - context.setAuthentication(authenticated); - SecurityContextHolder.setContext(context); - request.getSession(true).setAttribute(SPRING_SECURITY_CONTEXT_KEY, context); + SecurityContextHolder.getContext().setAuthentication(authenticated); log.info("User {} logged in", username); } diff --git a/server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/DiplomaTopicService.java similarity index 78% rename from server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java rename to server/src/main/java/ru/mskobaro/tdms/domain/service/DiplomaTopicService.java index bdf5deb..adccdae 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/DiplomaTopicService.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/DiplomaTopicService.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.service; +package ru.mskobaro.tdms.domain.service; import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; diff --git a/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/GroupService.java similarity index 80% rename from server/src/main/java/ru/tubryansk/tdms/service/GroupService.java rename to server/src/main/java/ru/mskobaro/tdms/domain/service/GroupService.java index e8a2024..f67babe 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/GroupService.java @@ -1,15 +1,15 @@ -package ru.tubryansk.tdms.service; +package ru.mskobaro.tdms.domain.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.tubryansk.tdms.controller.payload.GroupDTO; -import ru.tubryansk.tdms.controller.payload.GroupEditDTO; -import ru.tubryansk.tdms.entity.Group; -import ru.tubryansk.tdms.entity.User; -import ru.tubryansk.tdms.entity.repository.GroupRepository; -import ru.tubryansk.tdms.exception.BusinessException; +import ru.mskobaro.tdms.domain.entity.Group; +import ru.mskobaro.tdms.domain.entity.User; +import ru.mskobaro.tdms.domain.exception.BusinessException; +import ru.mskobaro.tdms.integration.database.GroupRepository; +import ru.mskobaro.tdms.presentation.payload.GroupDTO; +import ru.mskobaro.tdms.presentation.payload.GroupEditDTO; import java.util.Collection; import java.util.List; @@ -21,12 +21,12 @@ public class GroupService { @Autowired private GroupRepository groupRepository; @Autowired - private CallerService callerService; + private UserService userService; public Collection getAllGroups() { log.info("Getting all groups"); List groups = groupRepository.findAll(); - User callerUser = callerService.getCallerUser().orElse(null); + User callerUser = userService.getCallerUser(); List result = groups.stream().map(group -> { GroupDTO groupDTO = new GroupDTO(); diff --git a/server/src/main/java/ru/tubryansk/tdms/service/LifecycleService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/LifecycleService.java similarity index 93% rename from server/src/main/java/ru/tubryansk/tdms/service/LifecycleService.java rename to server/src/main/java/ru/mskobaro/tdms/domain/service/LifecycleService.java index 49dea3c..cd8aae5 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/LifecycleService.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/LifecycleService.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.service; +package ru.mskobaro.tdms.domain.service; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.ContextStartedEvent; diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/service/RoleService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/RoleService.java new file mode 100644 index 0000000..1bdffb7 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/RoleService.java @@ -0,0 +1,57 @@ +package ru.mskobaro.tdms.domain.service; + +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.mskobaro.tdms.domain.entity.Role; +import ru.mskobaro.tdms.integration.database.RoleRepository; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +@Slf4j +public class RoleService { + @RequiredArgsConstructor + @Getter + public enum Authority { + ADMIN("ROLE_ADMINISTRATOR"), + COMM_MEMBER("ROLE_COMMISSION_MEMBER"), + TEACHER("ROLE_TEACHER"), + SECRETARY("ROLE_SECRETARY"), + STUDENT("ROLE_STUDENT"), + ; + + private final String authority; + + public static Authority from(String authority) { + for (Authority value : values()) { + if (value.getAuthority().equals(authority)) { + return value; + } + } + throw new IllegalArgumentException("No such authority: " + authority); + } + } + public transient Map roles; + + @Autowired + private RoleRepository roleRepository; + + @PostConstruct + @Transactional + public void bootstrapRolesCache() { + log.debug("Initializing roles"); + roles = new ConcurrentHashMap<>(); + roleRepository.findAll().forEach(role -> roles.put(role.getAuthority(), role)); + log.info("Roles initialized: {}", roles); + } + + public Role getRoleByAuthority(Authority authority) { + return roles.get(authority.getAuthority()); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/service/StudentService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/StudentService.java new file mode 100644 index 0000000..f64f825 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/StudentService.java @@ -0,0 +1,57 @@ +package ru.mskobaro.tdms.domain.service; + +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.mskobaro.tdms.domain.entity.Student; +import ru.mskobaro.tdms.domain.entity.User; +import ru.mskobaro.tdms.domain.exception.BusinessException; +import ru.mskobaro.tdms.domain.exception.NotFoundException; +import ru.mskobaro.tdms.integration.database.StudentRepository; +import ru.mskobaro.tdms.integration.database.UserRepository; +import ru.mskobaro.tdms.presentation.payload.StudentDTO; + +@Service +@Transactional +@Slf4j +public class StudentService { + @Autowired + private StudentRepository studentRepository; + @Autowired + private UserService userService; + @Autowired + private UserRepository userRepository; + + public Student getCallerStudentThrow() { + userService.sureCallerInAnyRole(RoleService.Authority.STUDENT); + return studentRepository.findByUser(userService.getCallerUser()); + } + + public StudentDTO getCallerStudentDtoThrow() { + Student callerStudent = getCallerStudentThrow(); + if (callerStudent == null) { + throw new BusinessException("Вызывающий пользователь является студентом, но ассоциированный с ним студент не найден"); + } + + return StudentDTO.from(callerStudent); + } + + public StudentDTO getStudentByUserIdThrow(Long id) { + User callerUser = userService.getCallerUser(); + if (callerUser == null) { + throw new NotFoundException(); + } + + if (callerUser.getId().equals(id)) { + return getCallerStudentDtoThrow(); + } else if (userService.isCallerInRole(RoleService.Authority.ADMIN, RoleService.Authority.TEACHER, RoleService.Authority.SECRETARY)) { + User user = userRepository.findByIdThrow(id); + Student student = studentRepository.findByUser(user); + NotFoundException.throwIfNull(student, Student.class, user.getId()); + return StudentDTO.from(student); + } else { + throw new NotFoundException(Student.class, id); + } + } +} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/SysInfoService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/SysInfoService.java similarity index 87% rename from server/src/main/java/ru/tubryansk/tdms/service/SysInfoService.java rename to server/src/main/java/ru/mskobaro/tdms/domain/service/SysInfoService.java index 877c0d3..6e31cf6 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/SysInfoService.java +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/SysInfoService.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.service; +package ru.mskobaro.tdms.domain.service; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; diff --git a/server/src/main/java/ru/mskobaro/tdms/domain/service/UserService.java b/server/src/main/java/ru/mskobaro/tdms/domain/service/UserService.java new file mode 100644 index 0000000..bf6eec2 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/domain/service/UserService.java @@ -0,0 +1,181 @@ +package ru.mskobaro.tdms.domain.service; + +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import ru.mskobaro.tdms.domain.entity.*; +import ru.mskobaro.tdms.domain.exception.AccessDeniedException; +import ru.mskobaro.tdms.domain.exception.NotFoundException; +import ru.mskobaro.tdms.integration.database.GroupRepository; +import ru.mskobaro.tdms.integration.database.StudentRepository; +import ru.mskobaro.tdms.integration.database.TeacherRepository; +import ru.mskobaro.tdms.integration.database.UserRepository; +import ru.mskobaro.tdms.presentation.payload.RegistrationDTO; +import ru.mskobaro.tdms.presentation.payload.UserDTO; + +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@Slf4j +public class UserService implements UserDetailsService { + @Autowired + private UserRepository userRepository; + @Autowired + private GroupRepository groupRepository; + @Autowired + private StudentRepository studentRepository; + @Autowired + private RoleService roleService; + @Autowired + private PasswordEncoder passwordEncoder; + @Autowired + private TeacherRepository teacherRepository; + + @Override + public User loadUserByUsername(String username) throws UsernameNotFoundException { + log.debug("Loading user with username: {}", username); + User user = userRepository.findUserByLogin(username).orElseThrow( + () -> new UsernameNotFoundException("User with login " + username + " not found")); + log.debug("User with login {} loaded", username); + return user; + } + + public List getAllUsers() { + log.debug("Loading all users"); + List users = userRepository.findAll().stream() + .map(UserDTO::from) + .toList(); + log.info("{} users loaded", users.size()); + return users; + } + + public void registerUser(RegistrationDTO registrationDTO) { + log.info("Registering user: {}", registrationDTO); + + User user = transientUser(registrationDTO); + fillRoles(user, registrationDTO); + userRepository.save(user); + + if (userInAnyRole(user, RoleService.Authority.STUDENT)) { + sureCallerInAnyRole(RoleService.Authority.ADMIN, RoleService.Authority.SECRETARY); + Student student = transientStudent(registrationDTO.getStudentData()); + student.setUser(user); + student = studentRepository.save(student); + log.info("New user is student: {}", student); + } else if (userInAnyRole(user, RoleService.Authority.TEACHER)) { + sureCallerInAnyRole(RoleService.Authority.ADMIN, RoleService.Authority.SECRETARY); + Teacher teacher = transientTeacher(registrationDTO.getTeacherData()); + teacher.setUser(user); + teacher = teacherRepository.save(teacher); + log.info("New user is teacher: {}", teacher); + } else if (userInAnyRole(user, RoleService.Authority.ADMIN)) { + sureCallerInAnyRole(RoleService.Authority.ADMIN); + log.info("New user is administrator"); + } else { + throw new UnsupportedOperationException("Role not supported: " + user.getAuthorities()); + } + } + + private Teacher transientTeacher(RegistrationDTO.TeacherRegistrationDTO teacherData) { + if (teacherData == null) + throw new NullPointerException("Teacher data is null"); + if (teacherData.getCuratingGroups() == null) + teacherData.setCuratingGroups(List.of()); + if (teacherData.getAdvisingStudents() == null) + teacherData.setAdvisingStudents(List.of()); + + Teacher teacher = new Teacher(); + + List groups = groupRepository.findAllById(teacherData.getCuratingGroups()); + if (groups.size() != teacherData.getCuratingGroups().size()) { + throw new NotFoundException(Teacher.class); + } + List students = studentRepository.findAllById(teacherData.getAdvisingStudents()); + if (students.size() != teacherData.getAdvisingStudents().size()) { + throw new NotFoundException(Student.class); + } + + teacher.setCuratingGroups(groups); + teacher.setAdvisingStudents(students); + return teacher; + } + + private User transientUser(RegistrationDTO registrationDTO) { + User user = new User(); + user.setLogin(registrationDTO.getLogin()); + user.setPassword(passwordEncoder.encode(registrationDTO.getPassword())); + user.setFullName(registrationDTO.getFullName()); + user.setEmail(registrationDTO.getEmail()); + user.setNumberPhone(registrationDTO.getNumberPhone()); + return user; + } + + private Student transientStudent(RegistrationDTO.StudentRegistrationDTO studentData) { + if (studentData == null) + throw new NullPointerException("Student data is null"); + + Student student = new Student(); + if (studentData.getGroupId() != null) { + student.setGroup(groupRepository.findByIdThrow(studentData.getGroupId())); + } + + return student; + } + + private void fillRoles(User user, RegistrationDTO registrationDTO) { + RoleService.Authority accountType = registrationDTO.getAccountType(); + Role role = roleService.getRoleByAuthority(accountType); + if (role == null) { + throw new IllegalArgumentException("Role not found for authority: " + accountType); + } + user.setRoles(List.of(role)); + } + + public boolean userInAnyRole(User user, RoleService.Authority... authority) { + if (user == null || authority == null) { + return false; + } + + List toCheckAuthorities = List.of(authority); + return user.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .map(RoleService.Authority::from) + .anyMatch(toCheckAuthorities::contains); + } + + public User getCallerUser() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if (!(principal instanceof User)) { + return null; + } + + return (User) principal; + } + + public Optional getCallerOptional() { + return Optional.ofNullable(getCallerUser()); + } + + public UserDTO getCallerUserDTO() { + return getCallerOptional().map(UserDTO::from).orElse(UserDTO.unauthenticated()); + } + + public boolean isCallerInRole(RoleService.Authority... authorities) { + return userInAnyRole(getCallerUser(), authorities); + } + + public void sureCallerInAnyRole(RoleService.Authority... authority) { + if (!isCallerInRole(authority)) { + throw new AccessDeniedException(); + } + } +} + diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java new file mode 100644 index 0000000..b7348bb --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java @@ -0,0 +1,4 @@ +package ru.mskobaro.tdms.integration.database; + +public interface DefenceRepository { +} diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/DiplomaTopicRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java similarity index 70% rename from server/src/main/java/ru/tubryansk/tdms/entity/repository/DiplomaTopicRepository.java rename to server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java index aa9a364..fcfa333 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/DiplomaTopicRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java @@ -1,9 +1,9 @@ -package ru.tubryansk.tdms.entity.repository; +package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.tubryansk.tdms.entity.DiplomaTopic; -import ru.tubryansk.tdms.exception.NotFoundException; +import ru.mskobaro.tdms.domain.entity.DiplomaTopic; +import ru.mskobaro.tdms.domain.exception.NotFoundException; @Repository public interface DiplomaTopicRepository extends JpaRepository { diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/GroupRepository.java similarity index 71% rename from server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java rename to server/src/main/java/ru/mskobaro/tdms/integration/database/GroupRepository.java index c3d7141..bc6d4b7 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/GroupRepository.java @@ -1,9 +1,9 @@ -package ru.tubryansk.tdms.entity.repository; +package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.tubryansk.tdms.entity.Group; -import ru.tubryansk.tdms.exception.NotFoundException; +import ru.mskobaro.tdms.domain.entity.Group; +import ru.mskobaro.tdms.domain.exception.NotFoundException; @Repository public interface GroupRepository extends JpaRepository { diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/RoleRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/RoleRepository.java similarity index 68% rename from server/src/main/java/ru/tubryansk/tdms/entity/repository/RoleRepository.java rename to server/src/main/java/ru/mskobaro/tdms/integration/database/RoleRepository.java index 5d11c42..1020358 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/RoleRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/RoleRepository.java @@ -1,8 +1,8 @@ -package ru.tubryansk.tdms.entity.repository; +package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.tubryansk.tdms.entity.Role; +import ru.mskobaro.tdms.domain.entity.Role; @Repository public interface RoleRepository extends JpaRepository { diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/StudentRepository.java similarity index 58% rename from server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java rename to server/src/main/java/ru/mskobaro/tdms/integration/database/StudentRepository.java index 868631b..03a0cc1 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/StudentRepository.java @@ -1,12 +1,10 @@ -package ru.tubryansk.tdms.entity.repository; +package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.tubryansk.tdms.entity.Student; -import ru.tubryansk.tdms.entity.User; -import ru.tubryansk.tdms.exception.NotFoundException; - -import java.util.Optional; +import ru.mskobaro.tdms.domain.entity.Student; +import ru.mskobaro.tdms.domain.entity.User; +import ru.mskobaro.tdms.domain.exception.NotFoundException; @Repository public interface StudentRepository extends JpaRepository { @@ -14,5 +12,5 @@ public interface StudentRepository extends JpaRepository { return this.findById(id).orElseThrow(() -> new NotFoundException(Student.class, id)); } - Optional findByUser(User user); + Student findByUser(User user); } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/TeacherRepository.java similarity index 69% rename from server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java rename to server/src/main/java/ru/mskobaro/tdms/integration/database/TeacherRepository.java index 6d7f58e..27f212d 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/TeacherRepository.java @@ -1,9 +1,9 @@ -package ru.tubryansk.tdms.entity.repository; +package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.tubryansk.tdms.entity.Teacher; -import ru.tubryansk.tdms.exception.NotFoundException; +import ru.mskobaro.tdms.domain.entity.Teacher; +import ru.mskobaro.tdms.domain.exception.NotFoundException; @Repository public interface TeacherRepository extends JpaRepository { diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/UserRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/UserRepository.java new file mode 100644 index 0000000..17009c8 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/UserRepository.java @@ -0,0 +1,16 @@ +package ru.mskobaro.tdms.integration.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.mskobaro.tdms.domain.entity.User; +import ru.mskobaro.tdms.domain.exception.NotFoundException; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + default User findByIdThrow(Long id) { + return this.findById(id).orElseThrow(() -> new NotFoundException(User.class, id)); + } + Optional findUserByLogin(String login); +} diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java similarity index 85% rename from server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicController.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java index 8f9a6ad..efb764a 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/DiplomaTopicController.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.controller; +package ru.mskobaro.tdms.presentation.controller; import org.springframework.validation.annotation.Validated; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java similarity index 73% rename from server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java index 8714ce7..74dbb7b 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java @@ -1,12 +1,12 @@ -package ru.tubryansk.tdms.controller; +package ru.mskobaro.tdms.presentation.controller; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import ru.tubryansk.tdms.controller.payload.GroupCreateDTO; -import ru.tubryansk.tdms.controller.payload.GroupDTO; -import ru.tubryansk.tdms.controller.payload.GroupEditDTO; -import ru.tubryansk.tdms.service.GroupService; +import ru.mskobaro.tdms.domain.service.GroupService; +import ru.mskobaro.tdms.presentation.payload.GroupCreateDTO; +import ru.mskobaro.tdms.presentation.payload.GroupDTO; +import ru.mskobaro.tdms.presentation.payload.GroupEditDTO; import java.util.Collection; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/StudentController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java similarity index 51% rename from server/src/main/java/ru/tubryansk/tdms/controller/StudentController.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java index d8ef6d2..fda517c 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/StudentController.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java @@ -1,11 +1,12 @@ -package ru.tubryansk.tdms.controller; +package ru.mskobaro.tdms.presentation.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import ru.tubryansk.tdms.controller.payload.StudentDTO; -import ru.tubryansk.tdms.service.StudentService; +import ru.mskobaro.tdms.domain.service.StudentService; +import ru.mskobaro.tdms.presentation.payload.StudentDTO; @RestController @RequestMapping("/api/v1/student/") @@ -15,6 +16,11 @@ public class StudentController { @GetMapping("/current") public StudentDTO getCurrentStudent() { - return studentService.getCallerStudentDTO(); + return studentService.getCallerStudentDtoThrow(); + } + + @GetMapping("/by-user-id") + public StudentDTO getStudentByUserId(@RequestParam Long id) { + return studentService.getStudentByUserIdThrow(id); } } diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java similarity index 83% rename from server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java index 860a337..9e854fa 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java @@ -1,10 +1,10 @@ -package ru.tubryansk.tdms.controller; +package ru.mskobaro.tdms.presentation.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import ru.tubryansk.tdms.service.SysInfoService; +import ru.mskobaro.tdms.domain.service.SysInfoService; @RestController @RequestMapping("/api/v1/sysinfo") diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/UserController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java similarity index 64% rename from server/src/main/java/ru/tubryansk/tdms/controller/UserController.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java index 0132698..e32b7c9 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/UserController.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java @@ -1,15 +1,14 @@ -package ru.tubryansk.tdms.controller; +package ru.mskobaro.tdms.presentation.controller; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import ru.tubryansk.tdms.controller.payload.LoginDTO; -import ru.tubryansk.tdms.controller.payload.RegistrationDTO; -import ru.tubryansk.tdms.controller.payload.UserDTO; -import ru.tubryansk.tdms.service.AuthenticationService; -import ru.tubryansk.tdms.service.CallerService; -import ru.tubryansk.tdms.service.UserService; +import ru.mskobaro.tdms.domain.service.AuthenticationService; +import ru.mskobaro.tdms.domain.service.UserService; +import ru.mskobaro.tdms.presentation.payload.LoginDTO; +import ru.mskobaro.tdms.presentation.payload.RegistrationDTO; +import ru.mskobaro.tdms.presentation.payload.UserDTO; import java.util.List; @@ -20,13 +19,11 @@ public class UserController { @Autowired private AuthenticationService authenticationService; @Autowired - private CallerService callerService; - @Autowired private UserService userService; @GetMapping("/current") public UserDTO getCurrentUser() { - return callerService.getCallerUserDTO(); + return userService.getCallerUserDTO(); } @PostMapping("/logout") @@ -40,7 +37,7 @@ public class UserController { } @PostMapping("/register") - public void post(@RequestBody @Valid RegistrationDTO registrationDTO) { + public void register(@RequestBody @Valid RegistrationDTO registrationDTO) { userService.registerUser(registrationDTO); } diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java b/server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java similarity index 51% rename from server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java index 81c95c3..bfd6b95 100644 --- a/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.exception; +package ru.mskobaro.tdms.presentation.exception; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -10,58 +10,60 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; -import ru.tubryansk.tdms.controller.payload.ErrorResponse; +import ru.mskobaro.tdms.domain.exception.AccessDeniedException; +import ru.mskobaro.tdms.domain.exception.BusinessException; +import ru.mskobaro.tdms.presentation.payload.ErrorDTO; import java.util.UUID; import java.util.stream.Collectors; @RestControllerAdvice @Slf4j -public class GlobalExceptionHandler { +public class ApplicationExceptionHandler { @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleMethodArgumentNotValidException(BindException e) { + public ErrorDTO handleMethodArgumentNotValidException(BindException e) { log.debug("Validation error: {}", e.getMessage()); String validationErrors = e.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining("\n")); - return new ErrorResponse(validationErrors, ErrorResponse.ErrorCode.VALIDATION_ERROR); + return new ErrorDTO(validationErrors, ErrorDTO.ErrorCode.VALIDATION_ERROR); } @ExceptionHandler(BusinessException.class) - public ErrorResponse handleBusinessException(BusinessException e, HttpServletResponse response) { + public ErrorDTO handleBusinessException(BusinessException e, HttpServletResponse response) { log.info("Business error: {}", e.getMessage()); response.setStatus(e.getErrorCode().getHttpStatus().value()); - return new ErrorResponse(e.getMessage(), e.getErrorCode()); + return new ErrorDTO(e.getMessage(), e.getErrorCode()); } - @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class) + @ExceptionHandler({org.springframework.security.access.AccessDeniedException.class, AccessDeniedException.class}) @ResponseStatus(HttpStatus.FORBIDDEN) - public ErrorResponse handleAccessDeniedException(AccessDeniedException e) { + public ErrorDTO handleAccessDeniedException(RuntimeException e) { log.info("Access denied: {}", e.getMessage()); - return new ErrorResponse("Доступ запрещен", ErrorResponse.ErrorCode.ACCESS_DENIED); + return new ErrorDTO("Доступ запрещен", ErrorDTO.ErrorCode.ACCESS_DENIED); } @ExceptionHandler(AuthenticationException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) - public ErrorResponse handleAuthenticationException(AuthenticationException e) { + public ErrorDTO handleAuthenticationException(AuthenticationException e) { log.info("Authentication error: {}", e.getMessage()); - return new ErrorResponse("Неверный логин или пароль", ErrorResponse.ErrorCode.ACCESS_DENIED); + return new ErrorDTO("Неверный логин или пароль", ErrorDTO.ErrorCode.ACCESS_DENIED); } @ExceptionHandler(NoResourceFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorResponse handleNoResourceFoundException(NoResourceFoundException e) { + public ErrorDTO handleNoResourceFoundException(NoResourceFoundException e) { UUID uuid = UUID.randomUUID(); - log.error("Resource not found ({})", uuid, e); - return new ErrorResponse("Идентификатор ошибки: (" + uuid + ")\nРесурс не был наеден, обратитесь к администратору", ErrorResponse.ErrorCode.NOT_FOUND); + log.error("{} ({})", e.getMessage(), uuid); + return new ErrorDTO("Идентификатор ошибки: (" + uuid + ")\nРесурс не был наеден, обратитесь к администратору", ErrorDTO.ErrorCode.NOT_FOUND); } @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleUnexpectedException(Exception e) { + public ErrorDTO handleUnexpectedException(Exception e) { UUID uuid = UUID.randomUUID(); log.error("Unexpected exception ({})", uuid, e); - return new ErrorResponse("Идентификатор ошибки: (" + uuid + ")\nПроизошла непредвиденная ошибка, обратитесь к администратору", ErrorResponse.ErrorCode.INTERNAL_ERROR); + return new ErrorDTO("Идентификатор ошибки: (" + uuid + ")\nПроизошла непредвиденная ошибка, обратитесь к администратору", ErrorDTO.ErrorCode.INTERNAL_ERROR); } } diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/ErrorResponse.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/ErrorDTO.java similarity index 80% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/ErrorResponse.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/ErrorDTO.java index c5f94ff..895fe7b 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/ErrorResponse.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/ErrorDTO.java @@ -1,10 +1,10 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -public record ErrorResponse(String message, ErrorCode errorCode) { +public record ErrorDTO(String message, ErrorCode errorCode) { @RequiredArgsConstructor @Getter public enum ErrorCode { diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupCreateDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupCreateDTO.java similarity index 93% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupCreateDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupCreateDTO.java index 9c643f2..2591364 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupCreateDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupCreateDTO.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupDTO.java similarity index 83% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupDTO.java index 7ee0831..ee07feb 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupDTO.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import lombok.Getter; import lombok.Setter; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupEditDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupEditDTO.java similarity index 94% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupEditDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupEditDTO.java index af36649..1fc54f0 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupEditDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/GroupEditDTO.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import jakarta.validation.constraints.*; import lombok.Getter; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/LoginDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/LoginDTO.java similarity index 95% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/LoginDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/LoginDTO.java index 972332f..fefbc44 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/LoginDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/LoginDTO.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/RegistrationDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/RegistrationDTO.java similarity index 79% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/RegistrationDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/RegistrationDTO.java index 6ad9060..d47a973 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/RegistrationDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/RegistrationDTO.java @@ -1,10 +1,16 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import jakarta.validation.constraints.*; import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import org.hibernate.validator.constraints.Length; +import ru.mskobaro.tdms.domain.service.RoleService.Authority; + +import java.util.List; @Getter +@ToString public class RegistrationDTO { @NotEmpty(message = "Логин не может быть пустым") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Логин должен содержать только латинские буквы, цифры и знак подчеркивания") @@ -24,11 +30,25 @@ public class RegistrationDTO { @NotNull(message = "Номер телефона не может быть пустым") @Pattern(regexp = "^\\+[1-9]\\d{6,14}$", message = "Номер телефона должен начинаться с '+' и содержать от 7 до 15 цифр") private String numberPhone; + @NotNull(message = "Тип аккаунта не может быть пустым") + private Authority accountType; + private StudentRegistrationDTO studentData; + private TeacherRegistrationDTO teacherData; @Getter + @ToString public static class StudentRegistrationDTO { @NotNull(message = "Группа не может быть пустой") private Long groupId; } + + @Getter + @Setter + @ToString + public static class TeacherRegistrationDTO { + private List curatingGroups; + private List advisingStudents; + } + } \ No newline at end of file diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/RoleDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/RoleDTO.java similarity index 70% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/RoleDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/RoleDTO.java index bcf67b1..aacf797 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/RoleDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/RoleDTO.java @@ -1,8 +1,8 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; -import ru.tubryansk.tdms.entity.Role; -import ru.tubryansk.tdms.entity.User; +import ru.mskobaro.tdms.domain.entity.Role; +import ru.mskobaro.tdms.domain.entity.User; import java.util.List; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/payload/StudentDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/StudentDTO.java new file mode 100644 index 0000000..6cc4d74 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/StudentDTO.java @@ -0,0 +1,47 @@ +package ru.mskobaro.tdms.presentation.payload; + + +import lombok.Data; +import ru.mskobaro.tdms.domain.entity.Student; + + +@Data +public class StudentDTO { + private Long id; + private Boolean form; + private Integer protectionOrder; + private String magistracy; + private Boolean digitalFormatPresent; + private Integer markComment; + private Integer markPractice; + private String predefenceComment; + private String normalControl; + private Integer antiPlagiarism; + private String note; + private Boolean recordBookReturned; + private String work; + private UserDTO user; + private String diplomaTopic; + private UserDTO mentorUser; + private GroupDTO group; + + public static StudentDTO from(Student student) { + StudentDTO studentDTO = new StudentDTO(); + studentDTO.setId(student.getId()); + studentDTO.setForm(student.getForm()); + studentDTO.setProtectionOrder(student.getProtectionOrder()); + studentDTO.setMagistracy(student.getMagistracy()); + studentDTO.setDigitalFormatPresent(student.getDigitalFormatPresent()); + studentDTO.setMarkComment(student.getMarkComment()); + studentDTO.setMarkPractice(student.getMarkPractice()); + studentDTO.setPredefenceComment(student.getPredefenceComment()); + studentDTO.setNormalControl(student.getNormalControl()); + studentDTO.setAntiPlagiarism(student.getAntiPlagiarism()); + studentDTO.setNote(student.getNote()); + studentDTO.setRecordBookReturned(student.getRecordBookReturned()); + studentDTO.setWork(student.getWork()); + studentDTO.setUser(UserDTO.from(student.getUser())); + studentDTO.setDiplomaTopic(student.getDiplomaTopic().getName()); + return studentDTO; + } +} diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/UserDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/UserDTO.java similarity index 89% rename from server/src/main/java/ru/tubryansk/tdms/controller/payload/UserDTO.java rename to server/src/main/java/ru/mskobaro/tdms/presentation/payload/UserDTO.java index 11790f9..eb2cbc6 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/UserDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/presentation/payload/UserDTO.java @@ -1,10 +1,10 @@ -package ru.tubryansk.tdms.controller.payload; +package ru.mskobaro.tdms.presentation.payload; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import org.springframework.security.core.GrantedAuthority; -import ru.tubryansk.tdms.entity.User; +import ru.mskobaro.tdms.domain.entity.User; import java.time.ZonedDateTime; import java.util.List; @@ -13,6 +13,7 @@ import java.util.List; @Builder @JsonInclude(JsonInclude.Include.NON_ABSENT) public record UserDTO( + Long id, boolean authenticated, String login, String fullName, @@ -30,6 +31,7 @@ public record UserDTO( public static UserDTO from(User user) { return UserDTO.builder() + .id(user.getId()) .authenticated(true) .login(user.getLogin()) .fullName(user.getFullName()) diff --git a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java b/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java similarity index 80% rename from server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java rename to server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java index cfbefa7..4260e6e 100644 --- a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java +++ b/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java @@ -1,12 +1,13 @@ -package ru.tubryansk.tdms.config; +package ru.mskobaro.tdms.system.config; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; @@ -21,23 +22,37 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; +import ru.mskobaro.tdms.system.web.DevAuthenticationRequestFilter; +import ru.mskobaro.tdms.system.web.LoggingRequestFilter; import java.time.Duration; import java.util.List; +import static ru.mskobaro.tdms.domain.service.RoleService.Authority.*; + -@Configuration @Slf4j -public class SecurityConfiguration { +@Configuration +public class SecurityConfig { + @Autowired + private Environment environment; + @Bean public SecurityFilterChain securityFilterChain( HttpSecurity httpSecurity, AuthenticationManager authenticationManager, - @Qualifier("corsConfig") CorsConfigurationSource cors + CorsConfigurationSource cors ) throws Exception { + if (environment.matchesProfiles("dev")) { + httpSecurity.addFilterBefore(new DevAuthenticationRequestFilter(), AnonymousAuthenticationFilter.class); + } + return httpSecurity + .addFilterBefore(new LoggingRequestFilter(), ExceptionTranslationFilter.class) .authorizeHttpRequests(this::configureHttpAuthorization) .csrf(AbstractHttpConfigurer::disable) /* todo: настроить csrf */ .cors(a -> a.configurationSource(cors)) @@ -50,7 +65,7 @@ public class SecurityConfiguration { } @Bean - @Qualifier("corsConfig") + @Primary public CorsConfigurationSource corsConfigurationProd( @Value("${application.domain}") String domain, @Value("${application.port}") String port, @@ -63,9 +78,11 @@ public class SecurityConfiguration { corsConfiguration.setAllowCredentials(true); corsConfiguration.setMaxAge(Duration.ofDays(1)); corsConfiguration.addAllowedOrigin(StringUtils.join(protocol, "://", domain, ":", port)); + if (environment.matchesProfiles("dev")) { corsConfiguration.addAllowedOrigin("http://localhost:8888"); } + log.info("CORS configuration: [headers: {}, methods: {}, origins: {}, credentials: {}, maxAge: {}]", corsConfiguration.getAllowedHeaders(), corsConfiguration.getAllowedMethods(), corsConfiguration.getAllowedOrigins(), corsConfiguration.getAllowCredentials(), corsConfiguration.getMaxAge()); @@ -89,14 +106,16 @@ public class SecurityConfiguration { httpAuthorization.requestMatchers("/api/v1/user/logout").authenticated(); httpAuthorization.requestMatchers("/api/v1/user/login").anonymous(); httpAuthorization.requestMatchers("/api/v1/user/current").permitAll(); - httpAuthorization.requestMatchers("/api/v1/user/get-all").hasAuthority("ROLE_ADMINISTRATOR"); - httpAuthorization.requestMatchers("/api/v1/user/register").hasAuthority("ROLE_ADMINISTRATOR"); - httpAuthorization.requestMatchers("/api/v1/user/validate-registration").hasAuthority("ROLE_ADMINISTRATOR"); + httpAuthorization.requestMatchers("/api/v1/user/get-all").hasAuthority(ADMIN.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/user/register").hasAuthority(ADMIN.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/user/validate-registration").hasAuthority(ADMIN.getAuthority()); /* StudentController */ httpAuthorization.requestMatchers("/api/v1/student/current").permitAll(); + httpAuthorization.requestMatchers("api/v1/student/by-user-id").hasAnyAuthority( + SECRETARY.getAuthority(), ADMIN.getAuthority(), STUDENT.getAuthority(), TEACHER.getAuthority()); /* GroupController */ httpAuthorization.requestMatchers("/api/v1/group/get-all-groups").permitAll(); - httpAuthorization.requestMatchers("/api/v1/group/create-group").hasAuthority("ROLE_ADMINISTRATOR"); + httpAuthorization.requestMatchers("/api/v1/group/create-group").hasAuthority(ADMIN.getAuthority()); /* deny all other api requests */ httpAuthorization.requestMatchers("/api/**").denyAll(); /* since api already blocked, all other requests are static resources */ diff --git a/server/src/main/java/ru/mskobaro/tdms/system/web/DevAuthenticationRequestFilter.java b/server/src/main/java/ru/mskobaro/tdms/system/web/DevAuthenticationRequestFilter.java new file mode 100644 index 0000000..44e94e9 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/system/web/DevAuthenticationRequestFilter.java @@ -0,0 +1,46 @@ +package ru.mskobaro.tdms.system.web; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.TransientSecurityContext; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.web.filter.OncePerRequestFilter; +import ru.mskobaro.tdms.domain.entity.Role; +import ru.mskobaro.tdms.domain.entity.User; + +import java.io.IOException; +import java.util.List; + +import static ru.mskobaro.tdms.domain.service.RoleService.Authority.ADMIN; + +@Slf4j +public class DevAuthenticationRequestFilter extends OncePerRequestFilter { + public DevAuthenticationRequestFilter() { + log.warn("ANY REQUEST WILL BE AUTHENTICATED AS DEV_ADMIN, IF YOU SEE THIS IN PRODUCTION, CHECK YOUR CONFIGURATION"); + log.warn("ANY REQUEST WILL BE AUTHENTICATED AS DEV_ADMIN, IF YOU SEE THIS IN PRODUCTION, CHECK YOUR CONFIGURATION"); + log.warn("ANY REQUEST WILL BE AUTHENTICATED AS DEV_ADMIN, IF YOU SEE THIS IN PRODUCTION, CHECK YOUR CONFIGURATION"); + log.warn("ANY REQUEST WILL BE AUTHENTICATED AS DEV_ADMIN, IF YOU SEE THIS IN PRODUCTION, CHECK YOUR CONFIGURATION"); + log.warn("ANY REQUEST WILL BE AUTHENTICATED AS DEV_ADMIN, IF YOU SEE THIS IN PRODUCTION, CHECK YOUR CONFIGURATION"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + Role role = new Role(-1L, "dev_admin_role", ADMIN.getAuthority()); + User admin = new User(); + admin.setRoles(List.of(role)); + admin.setId(-1L); + admin.setLogin("dev_admin"); + admin.setEmail("dev_admin@main.mail"); + admin.setFullName("dev_admin"); + admin.setNumberPhone("+79999999999"); + admin.setPassword("{bcrypt}$2a$06$BHHQMjwQB2KI9sDdC9rRHOuYkTskjDt9WAyrscWP/Dcn7my3Jr77K"); + var auth = new PreAuthenticatedAuthenticationToken(admin, null, admin.getAuthorities()); + var context = new TransientSecurityContext(auth); + SecurityContextHolder.setContext(context); + filterChain.doFilter(request, response); + } +} diff --git a/server/src/main/java/ru/tubryansk/tdms/web/LoggingRequestFilter.java b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java similarity index 78% rename from server/src/main/java/ru/tubryansk/tdms/web/LoggingRequestFilter.java rename to server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java index 878d7ad..1bef516 100644 --- a/server/src/main/java/ru/tubryansk/tdms/web/LoggingRequestFilter.java +++ b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java @@ -1,23 +1,23 @@ -package ru.tubryansk.tdms.web; +package ru.mskobaro.tdms.system.web; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -@Component @Slf4j public class LoggingRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { long startTime = System.currentTimeMillis(); + HttpSession session = request.getSession(false); log.info("Request received: {}. user: {}, session: {}, remote ip: {}", - request.getRequestURI(), request.getRemoteUser(), request.getSession().getId(), request.getRemoteAddr()); + request.getRequestURI(), request.getRemoteUser(), session == null ? "no" : session.getId(), request.getRemoteAddr()); try { filterChain.doFilter(request, response); } finally { diff --git a/server/src/main/java/ru/tubryansk/tdms/web/LoggingSessionListener.java b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingSessionListener.java similarity index 94% rename from server/src/main/java/ru/tubryansk/tdms/web/LoggingSessionListener.java rename to server/src/main/java/ru/mskobaro/tdms/system/web/LoggingSessionListener.java index b3b49a9..8421553 100644 --- a/server/src/main/java/ru/tubryansk/tdms/web/LoggingSessionListener.java +++ b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingSessionListener.java @@ -1,4 +1,4 @@ -package ru.tubryansk.tdms.web; +package ru.mskobaro.tdms.system.web; import jakarta.servlet.http.HttpSessionEvent; import jakarta.servlet.http.HttpSessionListener; @@ -8,6 +8,7 @@ import org.springframework.stereotype.Component; @Component @Slf4j + public class LoggingSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/StudentDTO.java b/server/src/main/java/ru/tubryansk/tdms/controller/payload/StudentDTO.java deleted file mode 100644 index 48e144d..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/StudentDTO.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.tubryansk.tdms.controller.payload; - - -import lombok.Data; -import ru.tubryansk.tdms.entity.Student; - - -@Data -public class StudentDTO { - // private Boolean form; - // private Integer protectionOrder; - // private String magistracy; - // private Boolean digitalFormatPresent; - // private Integer markComment; - // private Integer markPractice; - // private String predefenceComment; - // private String normalControl; - // private Integer antiPlagiarism; - // private String note; - // private Boolean recordBookReturned; - // private String work; - // private UserDTO user; - // private String diplomaTopic; - // private UserDTO mentorUser; - // private GroupDTO group; - - public static StudentDTO from(Student student) { - StudentDTO studentDTO = new StudentDTO(); - // studentDTO.setForm(student.getForm()); - // return studentDTO; - // student.getForm(), - // student.getProtectionOrder(), - // student.getMagistracy(), - // student.getDigitalFormatPresent(), - // student.getMarkComment(), - // student.getMarkPractice(), - // student.getPredefenceComment(), - // student.getNormalControl(), - // student.getAntiPlagiarism(), - // student.getNote(), - // student.getRecordBookReturned(), - // student.getWork(), - // UserDTO.from(student.getUser()), - // student.getDiplomaTopic().getName(), - // UserDTO.from(student.getMentorUser()), - // GroupDTO.from(student.getGroup()) - // ); - return studentDTO; - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/DefenceRepository.java b/server/src/main/java/ru/tubryansk/tdms/entity/repository/DefenceRepository.java deleted file mode 100644 index cbe22e5..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/DefenceRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.tubryansk.tdms.entity.repository; - -public interface DefenceRepository { -} diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/UserRepository.java b/server/src/main/java/ru/tubryansk/tdms/entity/repository/UserRepository.java deleted file mode 100644 index 8abd792..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/UserRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.tubryansk.tdms.entity.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import ru.tubryansk.tdms.entity.User; - -import java.util.Optional; - -public interface UserRepository extends JpaRepository { - Optional findUserByLogin(String login); -} diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java b/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java deleted file mode 100644 index 02d2410..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/exception/AccessDeniedException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.tubryansk.tdms.exception; - -import ru.tubryansk.tdms.controller.payload.ErrorResponse; - -public class AccessDeniedException extends BusinessException { - public AccessDeniedException() { - super("Access denied"); - } - - @Override - public ErrorResponse.ErrorCode getErrorCode() { - return ErrorResponse.ErrorCode.ACCESS_DENIED; - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java b/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java deleted file mode 100644 index 652afa7..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/exception/BusinessException.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.tubryansk.tdms.exception; - -import ru.tubryansk.tdms.controller.payload.ErrorResponse; - -public class BusinessException extends RuntimeException { - public BusinessException(String message) { - super(message); - } - - public ErrorResponse.ErrorCode getErrorCode() { - return ErrorResponse.ErrorCode.BUSINESS_ERROR; - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java b/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java deleted file mode 100644 index 19ce606..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/exception/NotFoundException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.tubryansk.tdms.exception; - -import ru.tubryansk.tdms.controller.payload.ErrorResponse; - -public class NotFoundException extends BusinessException { - public NotFoundException(Class entityClass, Object id) { - super(entityClass.getSimpleName() + " с идентификатором " + id + " не наеден"); - } - - @Override - public ErrorResponse.ErrorCode getErrorCode() { - return ErrorResponse.ErrorCode.NOT_FOUND; - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/CallerService.java b/server/src/main/java/ru/tubryansk/tdms/service/CallerService.java deleted file mode 100644 index 7ac9437..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/service/CallerService.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.tubryansk.tdms.service; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; -import ru.tubryansk.tdms.controller.payload.UserDTO; -import ru.tubryansk.tdms.entity.User; - -import java.util.Optional; - -@Service -public class CallerService { - @Autowired - private AuthenticationService authenticationService; - - public Optional getCallerUser() { - if(authenticationService.authenticated()) { - return Optional.of((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); - } - return Optional.empty(); - } - - public UserDTO getCallerUserDTO() { - return getCallerUser().map(UserDTO::from).orElse(UserDTO.unauthenticated()); - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/RoleService.java b/server/src/main/java/ru/tubryansk/tdms/service/RoleService.java deleted file mode 100644 index 3cf6db0..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/service/RoleService.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.tubryansk.tdms.service; - -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.tubryansk.tdms.entity.Role; -import ru.tubryansk.tdms.entity.repository.RoleRepository; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Service -@Slf4j -public class RoleService { - public enum Authority { - ROLE_ADMINISTRATOR, - ROLE_COMMISSION_MEMBER, - ROLE_TEACHER, - ROLE_SECRETARY, - ROLE_STUDENT, - } - - public transient Map roles; - - @Autowired - private RoleRepository roleRepository; - - @PostConstruct - @Transactional - public void init() { - log.debug("Initializing roles"); - roles = new ConcurrentHashMap<>(); - roleRepository.findAll().forEach(role -> roles.put(role.getAuthority(), role)); - log.info("Roles initialized: {}", roles); - } - - public Role getRoleByAuthority(Authority authority) { - return roles.get(authority.name()); - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java b/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java deleted file mode 100644 index b9df908..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java +++ /dev/null @@ -1,32 +0,0 @@ -package ru.tubryansk.tdms.service; - -import jakarta.transaction.Transactional; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import ru.tubryansk.tdms.controller.payload.StudentDTO; -import ru.tubryansk.tdms.entity.Student; -import ru.tubryansk.tdms.entity.repository.StudentRepository; - -import java.util.Optional; - -@Service -@Transactional -public class StudentService { - @Autowired - private StudentRepository studentRepository; - @Autowired - private CallerService callerService; - - public Optional getCallerStudent() { - return studentRepository.findByUser(callerService.getCallerUser().orElse(null)); - } - - public StudentDTO getCallerStudentDTO() { - Student callerStudent = getCallerStudent().orElse(null); - if (callerStudent == null) { - return null; - } - - return StudentDTO.from(callerStudent); - } -} diff --git a/server/src/main/java/ru/tubryansk/tdms/service/UserService.java b/server/src/main/java/ru/tubryansk/tdms/service/UserService.java deleted file mode 100644 index e7356b3..0000000 --- a/server/src/main/java/ru/tubryansk/tdms/service/UserService.java +++ /dev/null @@ -1,98 +0,0 @@ -package ru.tubryansk.tdms.service; - -import jakarta.transaction.Transactional; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import ru.tubryansk.tdms.controller.payload.RegistrationDTO; -import ru.tubryansk.tdms.controller.payload.UserDTO; -import ru.tubryansk.tdms.entity.Role; -import ru.tubryansk.tdms.entity.Student; -import ru.tubryansk.tdms.entity.User; -import ru.tubryansk.tdms.entity.repository.GroupRepository; -import ru.tubryansk.tdms.entity.repository.StudentRepository; -import ru.tubryansk.tdms.entity.repository.UserRepository; - -import java.util.ArrayList; -import java.util.List; - -@Service -@Transactional -@Slf4j -public class UserService implements UserDetailsService { - @Autowired - private UserRepository userRepository; - @Autowired - private GroupRepository groupRepository; - @Autowired - private StudentRepository studentRepository; - @Autowired - private RoleService roleService; - @Autowired - private PasswordEncoder passwordEncoder; - - @Override - public User loadUserByUsername(String username) throws UsernameNotFoundException { - log.debug("Loading user with username: {}", username); - User user = userRepository.findUserByLogin(username).orElseThrow( - () -> new UsernameNotFoundException("User with login " + username + " not found")); - log.debug("User with login {} loaded", username); - return user; - } - - public List getAllUsers() { - log.debug("Loading all users"); - List users = userRepository.findAll().stream() - .map(UserDTO::from) - .toList(); - log.info("{} users loaded", users.size()); - return users; - } - - public void registerUser(RegistrationDTO registrationDTO) { - log.debug("Registering new user with login: {}", registrationDTO.getLogin()); - User user = transientUser(registrationDTO); - Student student = transientStudent(registrationDTO.getStudentData()); - fillRoles(user, registrationDTO); - - log.info("Saving new user: {}", user); - userRepository.save(user); - if (student != null) { - student.setUser(user); - log.info("User is student, saving student: {}", student); - studentRepository.save(student); - } - } - - private User transientUser(RegistrationDTO registrationDTO) { - User user = new User(); - user.setLogin(registrationDTO.getLogin()); - user.setPassword(passwordEncoder.encode(registrationDTO.getPassword())); - user.setFullName(registrationDTO.getFullName()); - user.setEmail(registrationDTO.getEmail()); - user.setNumberPhone(registrationDTO.getNumberPhone()); - return user; - } - - private Student transientStudent(RegistrationDTO.StudentRegistrationDTO studentData) { - if (studentData == null) { - return null; - } - - Student student = new Student(); - student.setGroup(groupRepository.findByIdThrow(studentData.getGroupId())); - return student; - } - - private void fillRoles(User user, RegistrationDTO registrationDTO) { - List roles = new ArrayList<>(); - if (registrationDTO.getStudentData() != null) { - log.debug("User is student, adding role ROLE_STUDENT"); - roles.add(roleService.getRoleByAuthority(RoleService.Authority.ROLE_STUDENT)); - } - user.setRoles(roles); - } -} diff --git a/server/src/main/resources/application-dev.yml b/server/src/main/resources/application-dev.yml index 9db06b5..8fd4241 100644 --- a/server/src/main/resources/application-dev.yml +++ b/server/src/main/resources/application-dev.yml @@ -3,13 +3,3 @@ application: port: 8080 domain: localhost protocol: http -spring: - web: - resources: - static-locations: file:///${user.dir}/web/dist/ - chain: - cache: false - compressed: false -server: - compression: - enabled: false diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index fce5a82..32dff9c 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -1,3 +1,4 @@ + db: url: jdbc:postgresql://localhost:5400/tdms schema: public @@ -29,7 +30,12 @@ spring: password: ${db.password} schemas: ${db.schema} main: - banner-mode: off + banner-mode: console + banner: + location: banner.txt + web: + resources: + static-locations: classpath:/static/ server: port: ${application.port} address: ${application.domain} diff --git a/server/src/main/resources/banner.txt b/server/src/main/resources/banner.txt new file mode 100644 index 0000000..d9160d5 --- /dev/null +++ b/server/src/main/resources/banner.txt @@ -0,0 +1,5 @@ + __________ __ ________ ____ ____ ___ + /_ __/ __ \/ |/ / ___/ _ __ / __ \ / __ \ < / + / / / / / / /|_/ /\__ \ | | / / / / / / / / / / / / + / / / /_/ / / / /___/ / | |/ /_/ /_/ /_/ /_/ /_ / / +/_/ /_____/_/ /_//____/ |___/(_)____/(_)____/(_)_/ \ No newline at end of file diff --git a/server/src/main/resources/db/migration/V00070__Create__student_table.sql b/server/src/main/resources/db/migration/V00070__Create__student_table.sql index ef2ed84..9aa3b63 100644 --- a/server/src/main/resources/db/migration/V00070__Create__student_table.sql +++ b/server/src/main/resources/db/migration/V00070__Create__student_table.sql @@ -2,9 +2,9 @@ create table student ( id bigserial primary key, user_id bigint not null, - diploma_topic_id bigint not null, - adviser_teacher_id bigint not null, - group_id bigint not null, + diploma_topic_id bigint, + adviser_teacher_id bigint, + group_id bigint, form boolean, protection_day int, diff --git a/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql b/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql index c7e0806..30e30e1 100644 --- a/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql +++ b/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql @@ -2,6 +2,11 @@ create table defence ( id bigserial primary key, defence_date timestamptz, + created_at timestamptz not null, updated_at timestamptz -); \ No newline at end of file +); + +-- COMMENTS +comment on table defence is 'Таблица для хранения данных о защитах'; +comment on column defence.defence_date is 'Дата защиты'; \ No newline at end of file diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index 0c3d24d..539813a 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n @@ -9,11 +9,11 @@ logs/app.log false - %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n - + diff --git a/server/src/test/java/ru/tubryansk/tdms/TdmsApplicationTests.java b/server/src/test/java/ru/tubryansk/tdms/TdmsApplicationTests.java deleted file mode 100644 index cec5701..0000000 --- a/server/src/test/java/ru/tubryansk/tdms/TdmsApplicationTests.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.tubryansk.tdms; - - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; - - -@Import(TestcontainersConfiguration.class) -@SpringBootTest -class TdmsApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/server/src/test/java/ru/tubryansk/tdms/TestTdmsApplication.java b/server/src/test/java/ru/tubryansk/tdms/TestTdmsApplication.java deleted file mode 100644 index 42f57f5..0000000 --- a/server/src/test/java/ru/tubryansk/tdms/TestTdmsApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.tubryansk.tdms; - - -import org.springframework.boot.SpringApplication; - - -public class TestTdmsApplication { - - public static void main(String[] args) { - SpringApplication.from(TdmsApplication::main).with(TestcontainersConfiguration.class).run(args); - } - -} diff --git a/server/src/test/java/ru/tubryansk/tdms/TestcontainersConfiguration.java b/server/src/test/java/ru/tubryansk/tdms/TestcontainersConfiguration.java deleted file mode 100644 index f4f3cfc..0000000 --- a/server/src/test/java/ru/tubryansk/tdms/TestcontainersConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.tubryansk.tdms; - - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.context.annotation.Bean; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.DockerImageName; - - -@TestConfiguration(proxyBeanMethods = false) -class TestcontainersConfiguration { - - @Bean - @ServiceConnection - PostgreSQLContainer postgresContainer() { - return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.2-alpine3.19")); - } - -} diff --git a/web/pom.xml b/web/pom.xml index 33af884..0e9b08e 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -4,17 +4,17 @@ 4.0.0 - ru.tubryansk + ru.mskobaro tdms 0.0.1 - ru.tubryansk.tdms + ru.mskobaro.tdms web 0.0.1 pom - TDMS :: WEB + TDMS::WEB diff --git a/web/src/components/NotificationContainer.tsx b/web/src/components/NotificationContainer.tsx index df5a09d..fe8d3b4 100644 --- a/web/src/components/NotificationContainer.tsx +++ b/web/src/components/NotificationContainer.tsx @@ -51,9 +51,14 @@ class NotificationPopup extends ComponentContext<{ notification: Notification, t render() { const hasTitle = !!this.props.notification.title && this.props.notification.title.length > 0; - const closeIcon = ; - const title = this.props.notification.title.split('\n').map((item, key) => {item}
); - const message = this.props.notification.message.split('\n').map((item, key) => {item}
); + + const title = this.props.notification.title?.split('\n').map((item, key) => + {item}
); + const message = this.props.notification.message?.split('\n').map((item, key) => + {item}
); + const closeIcon = + + ; return { @@ -70,7 +75,7 @@ class NotificationPopup extends ComponentContext<{ notification: Notification, t - {message} + {message ?? ''} { !hasTitle && diff --git a/web/src/components/custom/DataTable.css b/web/src/components/custom/DataTable.css new file mode 100644 index 0000000..0e82542 --- /dev/null +++ b/web/src/components/custom/DataTable.css @@ -0,0 +1,4 @@ +._table-header:hover { + background: #f5f5f5; +} + diff --git a/web/src/components/custom/DataTable.tsx b/web/src/components/custom/DataTable.tsx index b19c1c4..bc54750 100644 --- a/web/src/components/custom/DataTable.tsx +++ b/web/src/components/custom/DataTable.tsx @@ -7,6 +7,7 @@ import _ from "lodash"; import {ChangeEvent} from "react"; import {ModalState} from "../../utils/modalState"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import './DataTable.css'; export interface DataTableProps { tableDescriptor: TableDescriptor; @@ -167,7 +168,7 @@ export class DataTable extends ComponentContext> { borderRight: lastColumn ? 'none' : '1px solid var(--bs-table-border-color)', }; - return + return
runInAction(() => { const other = this.descriptor.columns @@ -197,7 +198,6 @@ export class DataTable extends ComponentContext> { } - } @computed diff --git a/web/src/components/custom/controls/ReactiveControls.tsx b/web/src/components/custom/controls/ReactiveControls.tsx index 37f1b1c..c604766 100644 --- a/web/src/components/custom/controls/ReactiveControls.tsx +++ b/web/src/components/custom/controls/ReactiveControls.tsx @@ -1,8 +1,8 @@ -import React from "react"; +import React, {ChangeEvent, Component} from "react"; import {ReactiveValue} from "../../../utils/reactive/reactiveValue"; import {observer} from "mobx-react"; -import {action, makeObservable, observable} from "mobx"; -import {Button, FloatingLabel, FormControl, FormText} from "react-bootstrap"; +import {action, makeObservable, observable, runInAction} from "mobx"; +import {Button, FloatingLabel, FormControl, FormSelect, FormText} from "react-bootstrap"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import './ReactiveControls.css'; @@ -14,12 +14,8 @@ export interface ReactiveInputProps { validateless?: boolean; } -export interface ReactiveSelectInputProps extends ReactiveInputProps { - possibleValues: { value: T, label: string }[]; -} - @observer -export class StringInput extends React.Component> { +export class StringInput extends Component> { constructor(props: any) { super(props); makeObservable(this); @@ -49,7 +45,7 @@ export class StringInput extends React.Component> { } @observer -export class PasswordInput extends React.Component> { +export class PasswordInput extends Component> { @observable showPassword = false; constructor(props: any) { @@ -76,9 +72,8 @@ export class PasswordInput extends React.Component> {
+ disabled={this.props.disabled} onChange={this.onChange} value={this.props.value.value} + className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`}/> + } + { + this.viewMode === "VIEW" && + + } + + + ; + } +} \ No newline at end of file diff --git a/web/src/components/user/UserListPage.tsx b/web/src/components/user/UserListPage.tsx index 469ddcc..25b2c5c 100644 --- a/web/src/components/user/UserListPage.tsx +++ b/web/src/components/user/UserListPage.tsx @@ -5,6 +5,11 @@ import {IAuthenticated} from "../../models/user"; import {DataTable} from "../custom/DataTable"; import {get} from "../../utils/request"; import {Column, TableDescriptor} from "../../utils/tables"; +import {Authorities, getAuthorityByCode} from "../../models/authorities"; +import {datetimeConverter} from "../../utils/converters"; +import {StudentProfileModal} from "./StudentProfileModal"; +import {ModalState} from "../../utils/modalState"; +import {IStudent} from "../../models/student"; @observer export class UserListPage extends Page { @@ -27,13 +32,37 @@ export class UserListPage extends Page { @observable users?: IAuthenticated[]; @observable tableDescriptor?: TableDescriptor; + @observable studentModalState = new ModalState(); + @observable studentFullName: string = ''; + @observable student: IStudent; + userColumns = [ - new Column('login', 'Логин'), - new Column('fullName', 'Полное имя'), - new Column('email', 'Email'), - new Column('phone', 'Телефон'), - new Column('createdAt', 'Дата создания'), - new Column('updatedAt', 'Дата обновления', (value: string) => value ? value : 'Не обновлялось'), + new Column('login', 'Логин'), + new Column('fullName', 'Полное имя'), + new Column('authorities', 'Роли', (value, user) => { + return value.map(getAuthorityByCode).map(authority => { + if (authority.code === Authorities.STUDENT.code) { + return { + console.log(user.id); + get('student/by-user-id', {id: user.id}).then((student) => { + runInAction(() => { + this.studentFullName = user.fullName; + this.student = student; + this.studentModalState.open(); + }); + }); + }}>{authority.name}; + } else { + return {authority.name}; + } + }).reduce((prev, curr) => <> + {prev}, {curr} + ); + }), + new Column('email', 'Email'), + new Column('phone', 'Телефон'), + new Column('createdAt', 'Дата создания', (value: string) => value ? datetimeConverter(value) : 'Не создавалось'), + new Column('updatedAt', 'Дата обновления', (value: string) => value ? datetimeConverter(value) : 'Не обновлялось'), ]; @@ -53,7 +82,14 @@ export class UserListPage extends Page { return <> { this.tableDescriptor && - + <> + + { + this.student && + + } + + } } diff --git a/web/src/components/user/UserProfile.tsx b/web/src/components/user/UserProfile.tsx new file mode 100644 index 0000000..4ef76b9 --- /dev/null +++ b/web/src/components/user/UserProfile.tsx @@ -0,0 +1,14 @@ +import {ComponentContext} from "../../utils/ComponentContext"; +import {IUser} from "../../models/user"; +import {IStudent} from "../../models/student"; +import {ITeacher} from "../../models/teacher"; + +export interface UserProfileProps { + user: IUser; + student?: IStudent; + teacher?: ITeacher; +} + +export class UserProfile extends ComponentContext { + +} \ No newline at end of file diff --git a/web/src/components/user/UserProfilePage.tsx b/web/src/components/user/UserProfilePage.tsx index facb540..890a34c 100644 --- a/web/src/components/user/UserProfilePage.tsx +++ b/web/src/components/user/UserProfilePage.tsx @@ -6,7 +6,7 @@ import {action, makeObservable, observable, runInAction} from "mobx"; import {IAuthenticated} from "../../models/user"; import {ComponentContext} from "../../utils/ComponentContext"; import {getAuthorityByCode} from "../../models/authorities"; -import {dateConverter} from "../../utils/converters"; +import {datetimeConverter} from "../../utils/converters"; @observer class UserInfo extends ComponentContext { @@ -57,11 +57,11 @@ class UserInfo extends ComponentContext { Дата создания - + Дата последней модификации - + @@ -136,7 +136,7 @@ class StudentInfo extends ComponentContext { Куратор - + Форма обучения {/* todo: обсудить с аналитиком */} diff --git a/web/src/components/user/UserRegistrationModal.tsx b/web/src/components/user/UserRegistrationModal.tsx index 6b44902..0663540 100644 --- a/web/src/components/user/UserRegistrationModal.tsx +++ b/web/src/components/user/UserRegistrationModal.tsx @@ -1,10 +1,10 @@ import {observer} from "mobx-react"; -import {action, computed, makeObservable, observable} from "mobx"; +import {action, makeObservable, observable} from "mobx"; import {Button, Col, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle, Row} from "react-bootstrap"; import {UserRegistrationDTO} from "../../models/registration"; import {post} from "../../utils/request"; import {ReactiveValue} from "../../utils/reactive/reactiveValue"; -import {PasswordInput, StringInput} from "../custom/controls/ReactiveControls"; +import {PasswordInput, ReactiveSelectInputSelect, SelectInput, StringInput} from "../custom/controls/ReactiveControls"; import { email, emailChars, @@ -19,11 +19,13 @@ import { passwordMaxLength, phone, phoneChars, - required + required, + selected } from "../../utils/reactive/validators"; import {ComponentContext} from "../../utils/ComponentContext"; import {ModalState} from "../../utils/modalState"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {Authorities} from "../../models/authorities"; export interface UserRegistrationModalProps { modalState: ModalState; @@ -53,13 +55,33 @@ export class UserRegistrationModal extends ComponentContext().addValidator(required); - @observable accountType = new ReactiveValue().addValidator(required).addValidator((value) => { - if (!['student', 'admin'].includes(value)) { - return 'Тип аккаунта должен быть "СТУДЕНТ" или "АДМИНИСТРАТОР"'; + @observable accountType = new ReactiveValue().addValidator(selected).addValidator((value) => { + if (value.value === Authorities.SECRETARY.code || value.value === Authorities.COMMISSION_MEMBER.code) { + return 'не реализовано' } }); - @computed + @observable teacherGroup = new ReactiveValue().addValidator(selected); + @observable teacherStudent = new ReactiveValue().addValidator(selected); + + @observable studentGroup = new ReactiveValue().addValidator(selected); + + possibleAccountTypes(): ReactiveSelectInputSelect[] { + const teacher = {value: Authorities.TEACHER.code, label: Authorities.TEACHER.name} as ReactiveSelectInputSelect; + const student = {value: Authorities.STUDENT.code, label: Authorities.STUDENT.name} as ReactiveSelectInputSelect; + const commissionMember = {value: Authorities.COMMISSION_MEMBER.code, label: Authorities.COMMISSION_MEMBER.name} as ReactiveSelectInputSelect; + const administrator = {value: Authorities.ADMINISTRATOR.code, label: Authorities.ADMINISTRATOR.name} as ReactiveSelectInputSelect; + const secretary = {value: Authorities.SECRETARY.code, label: Authorities.SECRETARY.name} as ReactiveSelectInputSelect; + + if (this.userStore.isAdministrator) { + return [teacher, student, commissionMember, administrator, secretary]; + } else if (this.userStore.isSecretary) { + return [teacher, student, commissionMember]; + } else { + return []; + } + } + get formInvalid() { return this.login.invalid || !this.login.touched || this.password.invalid || !this.password.touched @@ -82,7 +104,14 @@ export class UserRegistrationModal extends ComponentContext { this.notificationStore.success('Пользователь успешно зарегистрирован'); }).catch(() => { @@ -102,11 +131,11 @@ export class UserRegistrationModal extends ComponentContext { thinking && - +
-
+ } { !thinking && @@ -115,6 +144,7 @@ export class UserRegistrationModal extends ComponentContext + @@ -122,6 +152,25 @@ export class UserRegistrationModal extends ComponentContext + + { + this.accountType.value?.value === Authorities.STUDENT.code && + + + + + + } + + { + this.accountType.value?.value === Authorities.TEACHER.code && + + + + + + + } } diff --git a/web/src/models/group.ts b/web/src/models/IGroup.ts similarity index 73% rename from web/src/models/group.ts rename to web/src/models/IGroup.ts index 8bf4786..06e2816 100644 --- a/web/src/models/group.ts +++ b/web/src/models/IGroup.ts @@ -1,4 +1,4 @@ -export interface Group { +export interface IGroup { name: string; curatorName?: string; iAmCurator?: boolean; diff --git a/web/src/models/registration.ts b/web/src/models/registration.ts index c4bd788..65f493f 100644 --- a/web/src/models/registration.ts +++ b/web/src/models/registration.ts @@ -5,8 +5,14 @@ export interface UserRegistrationDTO { email: string, numberPhone: string, studentData?: StudentRegistrationDTO + teacherData?: TeacherRegistrationDTO } export interface StudentRegistrationDTO { groupId: number; +} + +export interface TeacherRegistrationDTO { + curatingGroups: number[]; + advisingStudents: number[]; } \ No newline at end of file diff --git a/web/src/models/student.ts b/web/src/models/student.ts index edd6a6f..cb6d34d 100644 --- a/web/src/models/student.ts +++ b/web/src/models/student.ts @@ -1,11 +1,8 @@ import {IAuthenticated, IUser} from "./user"; - -export interface IGRoup { - name: string; - principalUser: IAuthenticated; -} +import {IGroup} from "./IGroup"; export interface IStudent { + id: number; form: boolean; protectionOrder: number; magistracy: string; @@ -21,5 +18,5 @@ export interface IStudent { user: IUser; diplomaTopic: string; mentorUser: IAuthenticated; - group: IGRoup; + group: IGroup; } \ No newline at end of file diff --git a/web/src/models/teacher.ts b/web/src/models/teacher.ts new file mode 100644 index 0000000..8d471fc --- /dev/null +++ b/web/src/models/teacher.ts @@ -0,0 +1,10 @@ +import {IGroup} from "./IGroup"; +import {IStudent} from "./student"; + +export interface ITeacher { + id: number; + curatingGroups: IGroup[]; + advisingStudents: IStudent[]; + createdAt: string; + updatedAt: string; +} \ No newline at end of file diff --git a/web/src/models/user.ts b/web/src/models/user.ts index a2fbc75..145af20 100644 --- a/web/src/models/user.ts +++ b/web/src/models/user.ts @@ -1,4 +1,5 @@ export interface IAuthenticated { + id: number, authenticated: true, login: string, fullName: string, diff --git a/web/src/store/NotificationStore.ts b/web/src/store/NotificationStore.ts index 43ef7a0..703e843 100644 --- a/web/src/store/NotificationStore.ts +++ b/web/src/store/NotificationStore.ts @@ -27,7 +27,7 @@ export class NotificationStore { @observable warnings: Notification[] = []; @observable infos: Notification[] = []; @observable successes: Notification[] = []; - timeout: number = 5000; + timeout: number = 10000; constructor(rootStore: RootStore) { this.rootStore = rootStore; diff --git a/web/src/utils/converters.ts b/web/src/utils/converters.ts index 3152f1c..117da53 100644 --- a/web/src/utils/converters.ts +++ b/web/src/utils/converters.ts @@ -1,3 +1,11 @@ -export const dateConverter = (date: string) => { +export const datetimeConverter = (date: string) => { return new Date(date).toLocaleString(); -} \ No newline at end of file +} + +export const dateConverter = (date: string) => { + return new Date(date).toLocaleDateString(); +} + +export const timeConverter = (date: string) => { + return new Date(date).toLocaleTimeString(); +} diff --git a/web/src/utils/reactive/validators.ts b/web/src/utils/reactive/validators.ts index ef621ba..de6f5a9 100644 --- a/web/src/utils/reactive/validators.ts +++ b/web/src/utils/reactive/validators.ts @@ -1,6 +1,19 @@ +import {ReactiveSelectInputSelect} from "../../components/custom/controls/ReactiveControls"; + export const required = (value: any, field = 'Поле') => { - if (!value || (typeof value === 'string' && value.trim().length === 0)) { - return `${field} обязательно для заполнения`; + const message = `${field} обязательно для заполнения`; + if (!value) { + return message; + } else if (typeof value === 'string' && (value.trim().length === 0)) { + return message; + } else if (Array.isArray(value) && value.length === 0) { + return message; + } +} + +export const selected = (value: ReactiveSelectInputSelect, field = 'Поле') => { + if (!value.value || value.value === '__unselected__') { + return `${field} должно быть выбрано`; } } diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts index a70eee5..63b925d 100644 --- a/web/src/utils/request.ts +++ b/web/src/utils/request.ts @@ -7,14 +7,14 @@ export const apiUrl = "http://localhost:8080/api/v1/"; export const get = async (url: string, data?: any, doReject = true, showError = true) => await request({ url: url, method: 'GET', - data: data, -}, doReject, showError); + params: data, +} as AxiosRequestConfig, doReject, showError); export const post = async (url: string, data?: any, doReject = true, showError = true) => await request({ url: url, method: 'POST', data: data, -}, doReject, showError); +} as AxiosRequestConfig, doReject, showError); export const request = async (config: AxiosRequestConfig, doReject: boolean, showError: boolean) => { return new Promise((resolve, reject) => { diff --git a/web/src/utils/tables.ts b/web/src/utils/tables.ts index 46d0e57..b6a26ca 100644 --- a/web/src/utils/tables.ts +++ b/web/src/utils/tables.ts @@ -34,7 +34,7 @@ export class Column { constructor( key: string, title: string, - format: (value: any) => string = value => value, + format: (value: C, data: R) => ReactNode = (value: C) => value ? _.toString(value) : 'Пусто', suffix?: (data: R) => ReactNode, sort: Sort = new Sort() ) { diff --git a/web/tsconfig.json b/web/tsconfig.json index 13fec21..4e8ad4d 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -4,11 +4,12 @@ "target": "ES5", "module": "ES6", "jsx": "react-jsx", - "sourceMap": false, + "sourceMap": true, "useDefineForClassFields": true, "moduleResolution": "Bundler", "composite": true, "resolveJsonModule": true, + "incremental": true, /* enabling decorators */ "experimentalDecorators": true, diff --git a/web/webpack.config.js b/web/webpack.config.js index 2366e78..9ffde96 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -2,7 +2,6 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { - mode: 'development', entry: { app: path.join(__dirname, 'src', 'Application.tsx') }, @@ -11,21 +10,32 @@ module.exports = { extensions: ['.ts', '.tsx', '.js'] }, module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: '/node_modules/' - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'], - }, + rules: [{ + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + }, { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, { + test: /\.(png|jpe?g|gif|svg)$/i, + type: 'asset/resource', + }, { + test: /\.(woff|woff2|eot|ttf|otf)$/i, + type: 'asset/resource', + }, ], }, + devtool: 'source-map', output: { - filename: '[name].js', - path: path.resolve(__dirname, 'dist') + filename: '[name].[contenthash].js', + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + optimization: { + splitChunks: { + chunks: 'all', + }, }, devServer: { client: {