This commit is contained in:
Maksim Skobaro 2024-07-18 12:40:05 +03:00
commit 53586dff0a
155 changed files with 14992 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
/logs/app.log
/configurations/Start RDBMS.run.xml

37
README.md Normal file
View File

@ -0,0 +1,37 @@
# Thesis Defense Management System (TDMS)
# Server module
Contains backend server of service
> After building executable JAR file will be in `tdms/server/target` folder
>
> To start:`java -jar <jar-file>`
>
> URL is: `http://localhost:8080`
# Web module
Contains frontend part of service
> go to `tdms/web/` folder
>
> To start: `npm run dev`
>
> URL you will see in console
# How to build
1. Install `Maven`
2. Install `Java 17`
> While building, **web** project compiles frontend, and **server** project automatically copies its to `/server/target/classes/static` and into generated `<jar-file>`
> In IntelliJ Idea you can run `Execute maven goal`, to write further commands
| Description | Command |
|---------------------------|--------------------------------------|
| Clear & Run tests & Build | `mvn clear install` |
| Run tests & build | `mvn install` |
| Skip tests & build | `mvn install -Dmaven.test.skip=true` |
>To use multithreading during build process use `-T <threads>` keyword
### There is final result (add `clear` if you need)
```shell
mvn install -Dmaven.test.skip=true -T 4
```

9
docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
services:
db:
image: postgres:16.2-alpine3.19
environment:
- "POSTGRES_DB=tdms"
- "POSTGRES_PASSWORD=root"
- "POSTGRES_USER=root"
ports:
- "5400:5432"

31
pom.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<name>TDMS</name>
<description>Thesis-Defense-Management-System</description>
<version>0.0.1</version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/>
</parent>
<groupId>ru.mskobaro</groupId>
<artifactId>tdms</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>${name}</name>
<description>${description}</description>
<modules>
<module>web</module>
<module>server</module>
</modules>
</project>

2
server/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/logs/app.log

158
server/pom.xml Normal file
View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.mskobaro</groupId>
<artifactId>tdms</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>server</artifactId>
<version>0.0.1</version>
<name>TDMS::SERVER</name>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.34</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<useIncrementalCompilation>true</useIncrementalCompilation>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
<source>23</source>
<target>23</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>deploy frontend</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/classes/static</outputDirectory>
<resources>
<resource>
<directory>../web/dist</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package ru.mskobaro.tdms;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
@SpringBootApplication
@Slf4j
public class TdmsApplication {
public static void main(String[] args) {
Thread.currentThread().setName("spring-bootstrapper");
ConfigurableApplicationContext ctx = SpringApplication.run(TdmsApplication.class, args);
Environment environment = ctx.getEnvironment();
log.info("Server listening on port: {}", environment.getProperty("server.port"));
ctx.start();
}
}

View File

@ -0,0 +1,21 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.Getter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
@Embeddable
@Getter
public class AuditInfo {
@CreationTimestamp
@Column(name = "created_at")
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,25 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "commission_member_data")
@Getter
@Setter
public class CommissionMemberData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "partic_id", referencedColumnName = "id")
private Participant participant;
private String workPlace;
private String workPosition;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,40 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.util.List;
@Entity
@Table(name = "defense")
@Getter
@Setter
public class Defense {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "defense_commission",
joinColumns = @JoinColumn(name = "defense_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "commission_member_data_id", referencedColumnName = "id"))
private List<CommissionMemberData> commissionMembers;
private LocalDate defenseDate;
@OneToMany(mappedBy = "defense")
private List<Group> groups;
@ManyToMany
@JoinTable(
name = "defense_best_student_works",
joinColumns = @JoinColumn(name = "defense_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "student_data_id", referencedColumnName = "id"))
private List<StudentData> bestWorks;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,35 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@Entity
@Table(name = "diploma_topic")
public class DiplomaTopic {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@ManyToOne
@JoinColumn(name = "teacher_id")
private TeacherData teacher;
@ManyToOne
@JoinColumn(name = "direction_of_preparation_id")
private DirectionOfPreparation directionOfPreparation;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,27 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Entity
@Table(name = "direction_of_preparation")
@Getter
@Setter
public class DirectionOfPreparation {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String code;
@OneToMany(mappedBy = "directionOfPreparation")
private List<DiplomaTopic> diplomaTopic;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,40 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@ToString
@Entity
@Table(name = "`group`")
public class Group {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "group")
private List<StudentData> students = new ArrayList<>();
@ManyToOne
@JoinColumn(name = "direction_of_preparation_id")
private DirectionOfPreparation directionOfPreparation;
@ManyToOne
@JoinColumn(name = "defense_id")
private Defense defense;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,67 @@
package ru.mskobaro.tdms.business.entity;
import io.micrometer.common.util.StringUtils;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Entity
@Table(name = "participant")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Participant {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String middleName;
private String email;
private String numberPhone;
@OneToOne(mappedBy = "participant")
private User user;
private boolean deleted;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "participant_role",
joinColumns = @JoinColumn(name = "partic_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<Role> roles;
@Embedded
private AuditInfo auditInfo;
@Transient
public String getFullName() {
return Stream.of(lastName, firstName, middleName)
.filter(StringUtils::isNotBlank)
.collect(Collectors.joining(" "));
}
@Transient
public String getShortName() {
StringBuilder builder = new StringBuilder();
if (StringUtils.isNotBlank(lastName)) {
builder.append(lastName);
}
if (StringUtils.isNotBlank(firstName)) {
builder.append(" ").append(firstName.charAt(0)).append(".");
}
if (StringUtils.isNotBlank(middleName)) {
builder.append(" ").append(middleName.charAt(0)).append(".");
}
return builder.toString().trim();
}
}

View File

@ -0,0 +1,27 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
@Getter
@ToString
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "`role`")
@EqualsAndHashCode(of = "id")
public class Role implements GrantedAuthority {
@Id
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
@Column(name = "authority")
private String authority;
}

View File

@ -0,0 +1,50 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import ru.mskobaro.tdms.integration.database.TeacherDataRepository;
@Getter
@Setter
@ToString(exclude = "group")
@Entity
@Table(name = "student_data")
public class StudentData {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "partic_id")
private Participant participant;
@ManyToOne
@JoinColumn(name = "group_id")
private Group group;
@ManyToOne
@JoinColumn(name = "study_form_id")
private StudyForm form;
private Integer protectionOrder;
private Integer protectionDay;
private Integer markComment;
private Integer markPractice;
@ManyToOne
@JoinColumn(name = "curator_id")
private TeacherData curator;
@ManyToOne
@JoinColumn(name = "diploma_topic_id")
private DiplomaTopic diplomaTopic;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,21 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "study_form")
@Getter
@Setter
public class StudyForm {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,41 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import ru.mskobaro.tdms.business.taskfields.TaskFields;
@Entity
@Table(name = "task")
@NoArgsConstructor
@Getter
@Setter
public class Task {
public enum Type {
DIPLOMA_TOPIC_AGREEMENT,
}
public enum Status {
WAIT_FOR_TOPIC_AGREEMENT,
DONE,
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
private Type type;
@Enumerated(EnumType.STRING)
private Status status;
@JdbcTypeCode(SqlTypes.JSON)
private TaskFields fields;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,24 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "teacher_data")
@Getter
@Setter
public class TeacherData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "participant_id", referencedColumnName = "id")
private Participant participant;
private String degree;
@Embedded
private AuditInfo auditInfo;
}

View File

@ -0,0 +1,56 @@
package ru.mskobaro.tdms.business.entity;
import jakarta.persistence.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Getter
@Setter
@ToString
@EqualsAndHashCode(of = "id")
@Entity
@Table(name = "`user`")
public class User implements UserDetails {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "login")
private String login;
@Column(name = "password")
private String password;
@OneToOne
@JoinColumn(name = "partic_id")
private Participant participant;
@Embedded
private AuditInfo auditInfo;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return participant.getRoles().stream()
.map(Role::getAuthority)
.map(SimpleGrantedAuthority::new)
.toList();
}
@Override
public boolean isEnabled() {
return !participant.isDeleted();
}
@Override
public String getUsername() {
return login;
}
}

View File

@ -0,0 +1,18 @@
package ru.mskobaro.tdms.business.exception;
import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO;
public class AccessDeniedException extends BusinessException {
public AccessDeniedException() {
super("Access denied");
}
public AccessDeniedException(String message) {
super(message);
}
@Override
public ErrorDTO.ErrorCode getErrorCode() {
return ErrorDTO.ErrorCode.ACCESS_DENIED;
}
}

View File

@ -0,0 +1,13 @@
package ru.mskobaro.tdms.business.exception;
import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO;
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
public ErrorDTO.ErrorCode getErrorCode() {
return ErrorDTO.ErrorCode.BUSINESS_ERROR;
}
}

View File

@ -0,0 +1,34 @@
package ru.mskobaro.tdms.business.exception;
import ru.mskobaro.tdms.presentation.controller.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;
}
}

View File

@ -0,0 +1,61 @@
package ru.mskobaro.tdms.business.service;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Service
@Slf4j
public class AuthenticationService {
@Autowired
private HttpServletRequest request;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SessionRegistry sessionRegistry;
public void logout() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return;
}
String username = authentication.getName();
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
SecurityContextHolder.clearContext();
log.info("User {} logged out", username);
}
public void logout(String username) {
sessionRegistry.getAllSessions(username, false).forEach(session -> {
log.info("Invalidating session for user {}: {}", username, session.getSessionId());
session.expireNow();
sessionRegistry.removeSessionInformation(session.getSessionId());
});
}
@Transactional
public void login(String username, String password) {
var token = new UsernamePasswordAuthenticationToken(username, password);
var authenticated = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticated);
request.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
log.info("User {} logged in, ip: {}", username, request.getRemoteAddr());
}
}

View File

@ -0,0 +1,20 @@
package ru.mskobaro.tdms.business.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mskobaro.tdms.integration.database.DefenceRepository;
import ru.mskobaro.tdms.presentation.controller.payload.DefenceDTO;
import java.util.List;
@Service
@Transactional
public class DefenceService {
@Autowired
private DefenceRepository defenceRepository;
public List<DefenceDTO> getAllDefences() {
return defenceRepository.findAll().stream().map(DefenceDTO::from).toList();
}
}

View File

@ -0,0 +1,55 @@
package ru.mskobaro.tdms.business.service;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.mskobaro.tdms.business.entity.DiplomaTopic;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import ru.mskobaro.tdms.business.entity.TeacherData;
import ru.mskobaro.tdms.integration.database.DiplomaTopicRepository;
import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository;
import ru.mskobaro.tdms.integration.database.TeacherDataRepository;
import ru.mskobaro.tdms.presentation.controller.payload.DiplomaTopicDTO;
import java.util.List;
@Service
@Transactional
public class DiplomaTopicService {
@Autowired
private DiplomaTopicRepository diplomaTopicRepository;
@Autowired
private TeacherDataRepository teacherDataRepository;
@Autowired
private PreparationDirectionRepository preparationDirectionRepository;
public List<DiplomaTopic> findAll() {
return diplomaTopicRepository.findAll();
}
public void save(DiplomaTopicDTO diplomaTopicDTO) {
boolean isEdit = diplomaTopicDTO.getId() != null;
DiplomaTopic diplomaTopic;
if (isEdit) {
diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicDTO.getId());
} else {
diplomaTopic = new DiplomaTopic();
}
diplomaTopic.setName(diplomaTopicDTO.getName());
if (diplomaTopicDTO.getTeacher() != null) {
TeacherData teacherData = teacherDataRepository.findByIdThrow(diplomaTopicDTO.getTeacher().getId());
diplomaTopic.setTeacher(teacherData);
}
if (diplomaTopicDTO.getPreparationDirection() != null) {
DirectionOfPreparation directionOfPreparation = preparationDirectionRepository.findByIdThrow(diplomaTopicDTO.getPreparationDirection().getId());
diplomaTopic.setDirectionOfPreparation(directionOfPreparation);
}
diplomaTopicRepository.save(diplomaTopic);
}
public List<DiplomaTopic> findAllForStudent(Long studentId) {
return diplomaTopicRepository.findAllForStudentId(studentId);
}
}

View File

@ -0,0 +1,78 @@
package ru.mskobaro.tdms.business.service;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import ru.mskobaro.tdms.business.entity.Group;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.exception.BusinessException;
import ru.mskobaro.tdms.integration.database.GroupRepository;
import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository;
import ru.mskobaro.tdms.integration.database.StudentDataRepository;
import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO;
import ru.mskobaro.tdms.presentation.controller.payload.StudentDataDTO;
import java.util.Collection;
import java.util.List;
@Service
@Transactional
@Slf4j
public class GroupService {
@Autowired
private GroupRepository groupRepository;
@Autowired
private RoleService roleService;
@Autowired
private StudentDataRepository studentDataRepository;
@Autowired
private PreparationDirectionRepository preparationDirectionRepository;
public Collection<GroupDTO> getAllGroups() {
List<Group> groups = groupRepository.findAll();
return groups.stream().map(g -> GroupDTO.from(g, true)).toList();
}
public void save(@Valid GroupDTO groupDTO) {
boolean editMode = groupDTO.getId() != null;
log.info("Saving group: {}. Edit?: {}", groupDTO, editMode);
if (!editMode && groupRepository.existsByName(groupDTO.getName())) {
throw new BusinessException("Группа с именем " + groupDTO.getName() + " уже существует");
}
Group group = editMode ? groupRepository.findByIdThrow(groupDTO.getId()) : new Group();
group.setName(groupDTO.getName());
studentDataRepository.findAllByGroup_Id(group.getId()).forEach(s -> {
s.setGroup(null);
studentDataRepository.save(s);
});
group.getStudents().clear();
List<Long> studentIds = groupDTO.getStudents().stream().map(StudentDataDTO::getId).toList();
if (!CollectionUtils.isEmpty(studentIds)) {
List<StudentData> students = studentDataRepository.findAllById(studentIds);
students.stream()
.filter(s -> roleService.isParticInAuthority(s.getParticipant(), RoleService.Authority.STUDENT))
.forEach(s -> {
group.getStudents().add(s);
s.setGroup(group);
});
}
if (groupDTO.getPreparationDirection() != null) {
DirectionOfPreparation directionOfPreparation = preparationDirectionRepository.findByIdThrow(groupDTO.getPreparationDirection().getId());
group.setDirectionOfPreparation(directionOfPreparation);
}
groupRepository.save(group);
}
public void deleteGroup(Long groupId) {
groupRepository.deleteById(groupId);
}
}

View File

@ -0,0 +1,220 @@
package ru.mskobaro.tdms.business.service;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mskobaro.tdms.business.entity.*;
import ru.mskobaro.tdms.business.exception.AccessDeniedException;
import ru.mskobaro.tdms.business.exception.BusinessException;
import ru.mskobaro.tdms.integration.database.*;
import ru.mskobaro.tdms.presentation.controller.payload.ParticipantSaveDTO;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
@Slf4j
public class ParticipantService {
@Autowired
private ParticipantRepository participantRepository;
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserRepository userRepository;
@Autowired
private GroupRepository groupRepository;
@Autowired
private StudentDataRepository studentDataRepository;
@Autowired
private AuthenticationService authenticationService;
@Autowired
private TeacherDataRepository teacherDataRepository;
public ParticipantService(ParticipantRepository participantRepository) {
this.participantRepository = participantRepository;
}
public Collection<Participant> getAllParticipants() {
return participantRepository.findAll();
}
public void saveParticipant(ParticipantSaveDTO participantSaveDTO) {
boolean editMode = participantSaveDTO.getId() != null;
log.info("Saving participant: {}. Edit: {}", participantSaveDTO, editMode);
Participant existingParticipant = null;
if (editMode)
existingParticipant = participantRepository.findByIdThrow(participantSaveDTO.getId());
persistParticipant(participantSaveDTO, existingParticipant, editMode);
}
private void persistParticipant(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode) {
Participant participant = !editMode ? new Participant() : existingParticipant;
User callerUser = userService.getCallerUser();
if (callerUser == null)
throw new AccessDeniedException();
participant.setFirstName(participantSaveDTO.getFirstName());
participant.setLastName(participantSaveDTO.getLastName());
participant.setMiddleName(participantSaveDTO.getMiddleName());
participant.setNumberPhone(participantSaveDTO.getNumberPhone());
participant.setEmail(participantSaveDTO.getEmail());
List<Role> roles = persistRoles(participantSaveDTO, existingParticipant, editMode, callerUser, participant);
boolean credentialsChanged = persistUserData(participantSaveDTO, existingParticipant, editMode, participant);
persistStudentData(participantSaveDTO, existingParticipant, editMode, roles, participant);
persistTeacherData(participantSaveDTO, existingParticipant, editMode, roles, participant);
// TODO: notification task
Participant saved = participantRepository.save(participant);
log.info("Participant saved: {}", saved.getFullName());
if (credentialsChanged) {
log.info("User {} changed credentials, logging out", saved.getUser().getUsername());
authenticationService.logout(saved.getUser().getUsername());
}
}
private List<Role> persistRoles(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode, User callerUser, Participant participant) {
boolean isAdmin = roleService.isParticInAuthority(callerUser.getParticipant(), RoleService.Authority.ADMIN);
boolean isSecretary = roleService.isParticInAuthority(callerUser.getParticipant(), RoleService.Authority.SECRETARY);
boolean isOwner = existingParticipant != null && existingParticipant.getUser() != null
&& existingParticipant.getUser().getId().equals(callerUser.getId());
if (!isAdmin && !isSecretary && !isOwner)
throw new AccessDeniedException();
if (participantSaveDTO.getAuthorities() != null && participantSaveDTO.getAuthorities().contains(RoleService.Authority.ADMIN) && !isAdmin)
throw new AccessDeniedException("Недостаточно прав для назначения роли администратора");
List<Role> roles = participantSaveDTO.getAuthorities() != null
? participantSaveDTO.getAuthorities()
.stream()
.map(roleService::getRoleByAuthority)
.collect(Collectors.toList())
: null;
if (editMode && isOwner && !isAdmin && roles != null && !ListUtils.isEqualList(roles, existingParticipant.getRoles())) {
throw new AccessDeniedException("Вы не можете изменять свои роли");
} else if (roles != null) {
participant.setRoles(roles);
}
return roles;
}
private boolean persistUserData(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode, Participant participant) {
boolean credentialsChanged = false;
if (participantSaveDTO.getUserData() == null) {
return credentialsChanged;
}
User user;
boolean wasBefore = false;
if (editMode && existingParticipant.getUser() != null) {
user = existingParticipant.getUser();
wasBefore = true;
} else {
user = new User();
}
String login = participantSaveDTO.getUserData().getLogin();
if (StringUtils.isNotBlank(login)) {
user.setLogin(login);
credentialsChanged = true;
} else if (!wasBefore) {
throw new BusinessException("Логин не может быть пустым");
}
String password = participantSaveDTO.getUserData().getPassword();
if (StringUtils.isNotBlank(password)) {
user.setPassword(passwordEncoder.encode(password));
credentialsChanged = true;
} else if (!wasBefore) {
throw new BusinessException("Пароль не может быть пустым");
}
user = userRepository.save(user);
participant.setUser(user);
user.setParticipant(participant);
return credentialsChanged;
}
private void persistTeacherData(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode, List<Role> roles, Participant participant) {
boolean shouldPersistTeacherData = participantSaveDTO.getTeacherData() != null && roles != null
&& CollectionUtils.containsAny(roles, roleService.getRoleByAuthority(RoleService.Authority.TEACHER));
if (!shouldPersistTeacherData) {
return;
}
boolean alreadyExists = editMode && teacherDataRepository.existsByParticipant_IdAndParticipant_DeletedFalse(existingParticipant.getId());
TeacherData teacherData;
if (alreadyExists) {
teacherData = teacherDataRepository.findByParticipant_Id(existingParticipant.getId());
} else {
teacherData = new TeacherData();
}
teacherData.setDegree(participantSaveDTO.getTeacherData().getDegree());
teacherData = teacherDataRepository.save(teacherData);
teacherData.setParticipant(participant);
}
private void persistStudentData(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode, List<Role> roles, Participant participant) {
boolean shouldPersistStudentData = participantSaveDTO.getStudentData() != null && roles != null
&& CollectionUtils.containsAny(roles, roleService.getRoleByAuthority(RoleService.Authority.STUDENT));
if (!shouldPersistStudentData) {
return;
}
boolean alreadyExists = editMode && studentDataRepository.existsByParticipant_IdAndParticipant_DeletedFalse(existingParticipant.getId());
StudentData studentData;
if (alreadyExists) {
studentData = studentDataRepository.findStudentDataByParticipant_Id(existingParticipant.getId());
} else {
studentData = new StudentData();
}
if (participantSaveDTO.getStudentData().getGroupId() != null) {
Group group = groupRepository.findByIdThrow(participantSaveDTO.getStudentData().getGroupId());
studentData.setGroup(group);
if (!group.getStudents().contains(studentData))
group.getStudents().add(studentData);
} else {
if (editMode) {
Group group = groupRepository.findByStudentsContaining(Collections.singletonList(studentData));
if (group != null)
group.getStudents().remove(studentData);
}
studentData.setGroup(null);
}
if (participantSaveDTO.getStudentData().getCuratorId() != null) {
TeacherData teacherData = teacherDataRepository.findByIdThrow(participantSaveDTO.getStudentData().getCuratorId());
studentData.setCurator(teacherData);
} else {
studentData.setCurator(null);
}
studentData = studentDataRepository.save(studentData);
studentData.setParticipant(participant);
}
public void deleteParticipant(Long id) {
if (id == 1) {
throw new AccessDeniedException();
}
Participant partic = participantRepository.findByIdThrow(id);
partic.setDeleted(true);
}
}

View File

@ -0,0 +1,34 @@
package ru.mskobaro.tdms.business.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository;
import ru.mskobaro.tdms.presentation.controller.payload.PreparationDirectionDTO;
import java.util.List;
@Service
@Transactional
public class PreparationDirectionService {
@Autowired
private PreparationDirectionRepository preparationDirectionRepository;
public List<DirectionOfPreparation> getAll() {
return preparationDirectionRepository.findAll();
}
public void save(PreparationDirectionDTO preparationDirectionDTO) {
boolean editMode = preparationDirectionDTO.getId() != null;
DirectionOfPreparation preparationDirection;
if (editMode) {
preparationDirection = preparationDirectionRepository.findByIdThrow(preparationDirectionDTO.getId());
} else {
preparationDirection = new DirectionOfPreparation();
}
preparationDirection.setName(preparationDirectionDTO.getName());
preparationDirection.setCode(preparationDirectionDTO.getCode());
preparationDirectionRepository.save(preparationDirection);
}
}

View File

@ -0,0 +1,82 @@
package ru.mskobaro.tdms.business.service;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
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.business.entity.Participant;
import ru.mskobaro.tdms.business.entity.Role;
import ru.mskobaro.tdms.integration.database.RoleRepository;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
@Slf4j
public class RoleService {
@RequiredArgsConstructor
public enum Authority {
ADMIN("ROLE_ADMINISTRATOR"),
COMM_MEMBER("ROLE_COMMISSION_MEMBER"),
TEACHER("ROLE_TEACHER"),
PLAGIARISM_CHECKER("ROLE_PLAGIARISM_CHECKER"),
SECRETARY("ROLE_SECRETARY"),
STUDENT("ROLE_STUDENT"),
;
private final String authority;
@JsonValue
public String getAuthority() {
return authority;
}
@JsonCreator
public static Authority from(String authority) {
for (Authority value : values()) {
if (value.getAuthority().equals(authority)) {
return value;
}
}
throw new IllegalArgumentException("No such authority: " + authority);
}
}
private final Map<String, Role> roles = new ConcurrentHashMap<>();
@Autowired
private RoleRepository roleRepository;
@PostConstruct
@Transactional
public void bootstrapRolesCache() {
roleRepository.findAll().forEach(role -> roles.put(role.getAuthority(), role));
log.info("Roles initialized: {}", roles);
}
public Role getRoleByAuthority(Authority authority) {
return roles.get(authority.getAuthority());
}
public boolean isParticInAuthority(Participant participant, Authority authority) {
return participant.getRoles().stream()
.anyMatch(role -> role.getAuthority().equals(authority.getAuthority()));
}
public boolean isParticInAnyAuthority(Participant participant, Authority... authority) {
return participant.getRoles().stream()
.anyMatch(role -> {
for (Authority auth : authority) {
if (role.getAuthority().equals(auth.getAuthority())) {
return true;
}
}
return false;
});
}
}

View File

@ -0,0 +1,29 @@
package ru.mskobaro.tdms.business.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.business.entity.Participant;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.integration.database.StudentDataRepository;
import ru.mskobaro.tdms.integration.database.UserRepository;
import java.util.Collection;
import java.util.List;
@Service
@Transactional
@Slf4j
public class StudentDataService {
@Autowired
private StudentDataRepository studentDataRepository;
public StudentData getStudentByParticIdThrow(Long particId) {
return studentDataRepository.findStudentDataByParticipant_Id(particId);
}
public Collection<StudentData> getAllStudentsWithoutGroup() {
return studentDataRepository.findByGroupIsNull();
}
}

View File

@ -0,0 +1,14 @@
package ru.mskobaro.tdms.business.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class SysInfoService {
@Value("${application.version}")
private String version;
public String getVersion() {
return version;
}
}

View File

@ -0,0 +1,63 @@
package ru.mskobaro.tdms.business.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.entity.Task;
import ru.mskobaro.tdms.business.entity.User;
import ru.mskobaro.tdms.business.taskfields.DiplomaTopicAgreementTaskFields;
import ru.mskobaro.tdms.business.taskfields.TaskFields;
import ru.mskobaro.tdms.integration.database.StudentDataRepository;
import ru.mskobaro.tdms.integration.database.TaskRepository;
import ru.mskobaro.tdms.presentation.controller.TaskController;
import java.util.List;
@Service
@Transactional
public class TaskService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private UserService userService;
@Autowired
private StudentDataRepository studentDataRepository;
public void createTask(Task.Type type, Task.Status status, TaskFields taskFields) {
Task task = new Task();
task.setType(type);
task.setStatus(status);
task.setFields(taskFields);
taskRepository.save(task);
}
public Task findDiplomaTopicAgreementTaskCallerMaker() {
User user = userService.getCallerUser();
List<Task> diplomaTopicAgreementTaskByMakerId = taskRepository.findDiplomaTopicAgreementTaskByMakerId(
user.getParticipant().getId(), Task.Type.DIPLOMA_TOPIC_AGREEMENT
);
if (diplomaTopicAgreementTaskByMakerId.isEmpty()) {
return null;
}
if (diplomaTopicAgreementTaskByMakerId.size() > 1) {
throw new IllegalStateException();
}
return diplomaTopicAgreementTaskByMakerId.get(0);
}
public void createDiplomaAgreementTask(TaskController.DiplomaTopicAgreementDTO diplomaTopicAgreementDTO) {
DiplomaTopicAgreementTaskFields taskFields = new DiplomaTopicAgreementTaskFields();
User user = userService.getCallerUser();
StudentData studentData = studentDataRepository.findStudentDataByParticipant_Id(user.getParticipant().getId());
taskFields.setCheckerParticipantId(user.getParticipant().getId());
taskFields.setDiplomaTopicId(diplomaTopicAgreementDTO.getDiplomaTopicId());
taskFields.setDiplomaTopicName(diplomaTopicAgreementDTO.getDiplomaTopicName());
taskFields.setCheckerParticipantId(studentData.getCurator().getId());
createTask(Task.Type.DIPLOMA_TOPIC_AGREEMENT, Task.Status.WAIT_FOR_TOPIC_AGREEMENT, taskFields);
}
}

View File

@ -0,0 +1,30 @@
package ru.mskobaro.tdms.business.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mskobaro.tdms.business.entity.TeacherData;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import ru.mskobaro.tdms.integration.database.TeacherDataRepository;
import java.util.List;
@Service
@Transactional
public class TeacherDataService {
@Autowired
private TeacherDataRepository teacherDataRepository;
public List<TeacherData> findAll() {
return teacherDataRepository.findAll();
}
public TeacherData getTeacherDataByParticipantId(Long participantId) {
TeacherData teacher = teacherDataRepository.findByParticipant_Id(participantId);
if (teacher == null) {
throw new NotFoundException(TeacherData.class, participantId);
}
return teacher;
}
}

View File

@ -0,0 +1,50 @@
package ru.mskobaro.tdms.business.service;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import ru.mskobaro.tdms.business.entity.User;
import ru.mskobaro.tdms.integration.database.UserRepository;
import ru.mskobaro.tdms.presentation.controller.payload.UserDTO;
import java.util.List;
@Service
@Transactional
@Slf4j
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@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<UserDTO> getAllUsers() {
log.debug("Loading all users");
List<UserDTO> users = userRepository.findAll().stream()
.map(UserDTO::fromEntity)
.toList();
log.info("{} users loaded", users.size());
return users;
}
public User getCallerUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!(principal instanceof User)) {
return null;
}
return (User) principal;
}
}

View File

@ -0,0 +1,11 @@
package ru.mskobaro.tdms.business.taskfields;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DiplomaTopicAgreementTaskFields extends MakerCheckerTaskFields {
private String diplomaTopicName;
private Long diplomaTopicId;
}

View File

@ -0,0 +1,13 @@
package ru.mskobaro.tdms.business.taskfields;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class MakerCheckerTaskFields extends MakerTaskFields {
private Long checkerParticipantId;
private LocalDateTime approvedAt;
}

View File

@ -0,0 +1,10 @@
package ru.mskobaro.tdms.business.taskfields;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MakerTaskFields extends TaskFields {
private Long makerParticipantId;
}

View File

@ -0,0 +1,10 @@
package ru.mskobaro.tdms.business.taskfields;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
public class TaskFields {
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,25 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.Defense;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.Optional;
@Repository
public interface DefenceRepository extends JpaRepository<Defense, Long> {
default Defense findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(Defense.class, id));
}
@Override
@Query("SELECT d from Defense d " +
"left join fetch d.bestWorks bw " +
"left join fetch d.commissionMembers cm " +
"where d.id = :id " +
"and bw.participant.deleted = false " +
"and cm.participant.deleted = false")
Optional<Defense> findById(Long id);
}

View File

@ -0,0 +1,29 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.DiplomaTopic;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
import java.util.Optional;
@Repository
public interface DiplomaTopicRepository extends JpaRepository<DiplomaTopic, Long> {
default DiplomaTopic findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id));
}
@Override
@Query("SELECT d FROM DiplomaTopic d WHERE d.id = :id AND d.teacher.participant.deleted = false")
Optional<DiplomaTopic> findById(Long id);
@Query("SELECT d FROM DiplomaTopic d " +
"inner join d.directionOfPreparation dp " +
"inner join StudentData sd on sd.id = :studentIdwhere " +
"where dp = sd.group.directionOfPreparation")
List<DiplomaTopic> findAllForStudentId(Long studentId);
}

View File

@ -0,0 +1,27 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.Group;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
import java.util.Optional;
@Repository
public interface GroupRepository extends JpaRepository<Group, Long> {
default Group findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(Group.class, id));
}
@Override
@Query("SELECT g FROM Group g left join fetch g.students sd WHERE g.id = :id")
Optional<Group> findById(Long id);
boolean existsByName(String name);
@Query("SELECT g FROM Group g left join fetch g.students sd WHERE sd IN :students")
Group findByStudentsContaining(List<StudentData> students);
}

View File

@ -0,0 +1,27 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.DiplomaTopic;
import ru.mskobaro.tdms.business.entity.Participant;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
import java.util.Optional;
@Repository
public interface ParticipantRepository extends JpaRepository<Participant, Long> {
default Participant findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id));
}
@Override
@Query("SELECT p FROM Participant p WHERE p.id = :id AND p.deleted = false")
Optional<Participant> findById(Long id);
@Override
@Query("SELECT p from Participant p where p.deleted = false")
List<Participant> findAll();
}

View File

@ -0,0 +1,13 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import ru.mskobaro.tdms.business.exception.NotFoundException;
@Repository
public interface PreparationDirectionRepository extends JpaRepository<DirectionOfPreparation, Long> {
default DirectionOfPreparation findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(DirectionOfPreparation.class, id));
}
}

View File

@ -0,0 +1,9 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.Role;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}

View File

@ -0,0 +1,39 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
import java.util.Optional;
@Repository
public interface StudentDataRepository extends JpaRepository<StudentData, Long> {
default StudentData findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(StudentData.class, id));
}
@EntityGraph(type = EntityGraph.EntityGraphType.LOAD, attributePaths = {"group.students"})
@Query("SELECT s FROM StudentData s join fetch s.participant p WHERE p.id = :id AND p.deleted = false")
StudentData findStudentDataByParticipant_Id(Long id);
boolean existsByParticipant_IdAndParticipant_DeletedFalse(Long id);
@Override
@EntityGraph(type = EntityGraph.EntityGraphType.LOAD, attributePaths = {"participant.roles"})
@Query("SELECT s FROM StudentData s join fetch s.participant p WHERE s.id in :ids AND p.deleted = false")
List<StudentData> findAllById(Iterable<Long> ids);
@Query("SELECT s FROM StudentData s join fetch s.participant p WHERE s.group is null and p.deleted = false")
List<StudentData> findByGroupIsNull();
@Query("SELECT s FROM StudentData s join fetch s.participant p join fetch s.group g WHERE g.id = :id AND p.deleted = false")
List<StudentData> findAllByGroup_Id(Long id);
@Override
@Query("SELECT s FROM StudentData s join fetch s.participant p WHERE s.id = :id AND p.deleted = false")
Optional<StudentData> findById(Long id);
}

View File

@ -0,0 +1,21 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.Task;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
default Task findByIdThrow(Long id) {
return findById(id).orElseThrow(() -> new NotFoundException(Task.class, id));
}
@Query(value = "SELECT t FROM task t " +
"WHERE t.type = :type " +
"and t.fields->>'makerParticipantId' = :id", nativeQuery = true)
List<Task> findDiplomaTopicAgreementTaskByMakerId(Long id, Task.Type type);
}

View File

@ -0,0 +1,30 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.TeacherData;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.List;
import java.util.Optional;
@Repository
public interface TeacherDataRepository extends JpaRepository<TeacherData, Long> {
default TeacherData findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(TeacherData.class, id));
}
@Override
@Query("SELECT t FROM TeacherData t WHERE t.id = :id AND t.participant.deleted = false")
Optional<TeacherData> findById(Long id);
boolean existsByParticipant_IdAndParticipant_DeletedFalse(Long id);
@Query("SELECT t FROM TeacherData t WHERE t.participant.id = :id AND t.participant.deleted = false")
TeacherData findByParticipant_Id(Long id);
@Override
@Query("select t from TeacherData t where t.participant.deleted = false")
List<TeacherData> findAll();
}

View File

@ -0,0 +1,23 @@
package ru.mskobaro.tdms.integration.database;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.mskobaro.tdms.business.entity.User;
import ru.mskobaro.tdms.business.exception.NotFoundException;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
default User findByIdThrow(Long id) {
return this.findById(id).orElseThrow(() -> new NotFoundException(User.class, id));
}
@Override
@Query("SELECT u from User u join fetch u.participant p where u.id = :id and p.deleted = false")
Optional<User> findById(Long id);
@Query("SELECT u from User u join fetch u.participant p where u.login = :login and p.deleted = false")
Optional<User> findUserByLogin(String login);
}

View File

@ -0,0 +1,22 @@
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.mskobaro.tdms.business.service.DefenceService;
import ru.mskobaro.tdms.presentation.controller.payload.DefenceDTO;
import java.util.List;
@RestController
@RequestMapping("/api/v1/defence")
public class DefenceController {
@Autowired
private DefenceService defenceService;
@GetMapping("/all")
public List<DefenceDTO> getAllDefences() {
return defenceService.getAllDefences();
}
}

View File

@ -0,0 +1,34 @@
package ru.mskobaro.tdms.presentation.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.mskobaro.tdms.business.service.DiplomaTopicService;
import ru.mskobaro.tdms.presentation.controller.payload.DiplomaTopicDTO;
import java.util.List;
@RestController
@RequestMapping("/api/v1/diploma-topic/")
@Validated
public class DiplomaTopicController {
@Autowired
private DiplomaTopicService diplomaTopicService;
@GetMapping("/all")
public List<DiplomaTopicDTO> getAll() {
return diplomaTopicService.findAll().stream().map(DiplomaTopicDTO::from).toList();
}
@PostMapping("/save")
public void save(@RequestBody DiplomaTopicDTO diplomaTopicDTO) {
diplomaTopicService.save(diplomaTopicDTO);
}
@GetMapping("/all-for-student")
public List<DiplomaTopicDTO> getAllForStudent(@RequestParam Long studentId) {
return diplomaTopicService.findAllForStudent(studentId).stream().map(DiplomaTopicDTO::from).toList();
}
}

View File

@ -0,0 +1,31 @@
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.mskobaro.tdms.business.service.GroupService;
import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO;
import java.util.Collection;
@RestController
@RequestMapping("/api/v1/group")
public class GroupController {
@Autowired
private GroupService groupService;
@GetMapping("/get-all-groups")
public Collection<GroupDTO> getAllGroups() {
return groupService.getAllGroups();
}
@PostMapping("/save")
public void save(@RequestBody @Valid GroupDTO groupDTO) {
groupService.save(groupDTO);
}
@PostMapping("/delete")
public void delete(@RequestParam(value = "id") Long groupId) {
groupService.deleteGroup(groupId);
}
}

View File

@ -0,0 +1,35 @@
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.mskobaro.tdms.business.service.ParticipantService;
import ru.mskobaro.tdms.presentation.controller.payload.IdDto;
import ru.mskobaro.tdms.presentation.controller.payload.ParticipantDTO;
import ru.mskobaro.tdms.presentation.controller.payload.ParticipantSaveDTO;
import java.util.Collection;
@RestController
@RequestMapping("/api/v1/participant")
public class ParticipantController {
@Autowired
private ParticipantService participantService;
@GetMapping("/get-all-participants")
public Collection<ParticipantDTO> getAllParticipants() {
return participantService.getAllParticipants().stream()
.map(ParticipantDTO::fromEntity)
.toList();
}
@PostMapping("/save")
public void registerParticipant(@Valid @RequestBody ParticipantSaveDTO participantSaveDTO) {
participantService.saveParticipant(participantSaveDTO);
}
@PostMapping("/delete")
public void deleteParticipant(@RequestBody IdDto id) {
participantService.deleteParticipant(id.getId());
}
}

View File

@ -0,0 +1,26 @@
package ru.mskobaro.tdms.presentation.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.mskobaro.tdms.business.service.PreparationDirectionService;
import ru.mskobaro.tdms.presentation.controller.payload.PreparationDirectionDTO;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/v1/prep-direction")
public class PreparationDirectionController {
@Autowired
private PreparationDirectionService preparationDirectionService;
@GetMapping("/get-all")
public List<PreparationDirectionDTO> getAll() {
return preparationDirectionService.getAll().stream().map(PreparationDirectionDTO::from).collect(Collectors.toList());
}
@PostMapping("save")
public void save(@RequestBody PreparationDirectionDTO preparationDirectionDTO) {
preparationDirectionService.save(preparationDirectionDTO);
}
}

View File

@ -0,0 +1,33 @@
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.mskobaro.tdms.business.entity.StudentData;
import ru.mskobaro.tdms.business.service.StudentDataService;
import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO;
import ru.mskobaro.tdms.presentation.controller.payload.StudentDataDTO;
import java.util.Collection;
@RestController
@RequestMapping("/api/v1/student/")
public class StudentController {
@Autowired
private StudentDataService studentDataService;
@GetMapping(value = "/by-partic-id")
public StudentDataDTO getCurrentStudent(@RequestParam(value = "id") Long particId) {
StudentData studentData = studentDataService.getStudentByParticIdThrow(particId);
return StudentDataDTO.from(studentData, true);
}
@GetMapping(value = "all-without-group")
public Collection<StudentDataDTO> getAllStudentsWithoutGroup() {
return studentDataService.getAllStudentsWithoutGroup().stream()
.map(sd -> StudentDataDTO.from(sd, true))
.toList();
}
}

View File

@ -0,0 +1,21 @@
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.mskobaro.tdms.business.service.SysInfoService;
@RestController
@RequestMapping("/api/v1/sysinfo")
public class SysInfoController {
@Autowired
private SysInfoService sysInfoService;
@GetMapping("/version")
public String getVersion() {
return sysInfoService.getVersion();
}
// @GetMapping("/status")
}

View File

@ -0,0 +1,34 @@
package ru.mskobaro.tdms.presentation.controller;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.mskobaro.tdms.business.service.TaskService;
import ru.mskobaro.tdms.presentation.controller.payload.TaskDto;
@RestController
@RequestMapping("/api/v1/task")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/diploma-agreement-maker")
public TaskDto diplomaTopicAgreementMaker() {
return TaskDto.from(taskService.findDiplomaTopicAgreementTaskCallerMaker());
}
@PostMapping("/create-topic-agreement")
public void createDiplomaTopicAgreement(@RequestBody DiplomaTopicAgreementDTO diplomaTopicAgreementDTO) {
taskService.createDiplomaAgreementTask(diplomaTopicAgreementDTO);
}
@NoArgsConstructor
@Getter
@Setter
public static class DiplomaTopicAgreementDTO {
private String diplomaTopicName;
private Long diplomaTopicId;
}
}

View File

@ -0,0 +1,28 @@
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.mskobaro.tdms.business.service.TeacherDataService;
import ru.mskobaro.tdms.presentation.controller.payload.TeacherDataDTO;
import java.util.List;
@RestController
@RequestMapping("/api/v1/teacher-data")
public class TeacherDataController {
@Autowired
private TeacherDataService teacherDataService;
@GetMapping("/get-all")
public List<TeacherDataDTO> getAllTeacherData() {
return teacherDataService.findAll().stream().map(TeacherDataDTO::from).toList();
}
@GetMapping("/by-partic-id")
public TeacherDataDTO getTeacherDataByParticipantId(@RequestParam Long id) {
return TeacherDataDTO.from(teacherDataService.getTeacherDataByParticipantId(id));
}
}

View File

@ -0,0 +1,41 @@
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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.mskobaro.tdms.business.entity.User;
import ru.mskobaro.tdms.business.service.AuthenticationService;
import ru.mskobaro.tdms.business.service.UserService;
import ru.mskobaro.tdms.presentation.controller.payload.LoginDTO;
import ru.mskobaro.tdms.presentation.controller.payload.UserDTO;
@RestController
@RequestMapping("/api/v1/user")
@Slf4j
public class UserController {
@Autowired
private AuthenticationService authenticationService;
@Autowired
private UserService userService;
@GetMapping("/current")
public ResponseEntity<UserDTO> getCurrentUser() {
User user = userService.getCallerUser();
return user == null
? ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
: ResponseEntity.ok(UserDTO.fromEntity(user));
}
@PostMapping("/logout")
public void logout() {
authenticationService.logout();
}
@PostMapping("/login")
public void login(@RequestBody @Valid LoginDTO loginDTO) {
authenticationService.login(loginDTO.getLogin(), loginDTO.getPassword());
}
}

View File

@ -0,0 +1,31 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.mskobaro.tdms.business.entity.CommissionMemberData;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
public class CommissionMemberDTO {
private Long id;
private ParticipantDTO participant;
private String workPlace;
private String workPosition;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static CommissionMemberDTO from(CommissionMemberData commissionMemberData) {
CommissionMemberDTO dto = new CommissionMemberDTO();
dto.setId(commissionMemberData.getId());
dto.setParticipant(ParticipantDTO.fromEntity(commissionMemberData.getParticipant()));
dto.setWorkPlace(commissionMemberData.getWorkPlace());
dto.setWorkPosition(commissionMemberData.getWorkPosition());
dto.setCreatedAt(commissionMemberData.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(commissionMemberData.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,39 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import ru.mskobaro.tdms.business.entity.Defense;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@Setter
public class DefenceDTO {
private Long id;
private List<CommissionMemberDTO> commissionMembers;
private List<GroupDTO> groups;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static DefenceDTO from(Defense defense) {
DefenceDTO dto = new DefenceDTO();
dto.setId(defense.getId());
if (CollectionUtils.isNotEmpty(defense.getCommissionMembers())) {
dto.setCommissionMembers(
defense.getCommissionMembers().stream()
.map(CommissionMemberDTO::from)
.collect(Collectors.toList())
);
}
dto.setGroups(defense.getGroups().stream().map(g -> GroupDTO.from(g, true)).toList());
dto.setCreatedAt(defense.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(defense.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,37 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import ru.mskobaro.tdms.business.entity.DiplomaTopic;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@ToString
public class DiplomaTopicDTO {
private Long id;
private String name;
private TeacherDataDTO teacher;
private PreparationDirectionDTO preparationDirection;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static DiplomaTopicDTO from(DiplomaTopic diplomaTopic) {
DiplomaTopicDTO dto = new DiplomaTopicDTO();
dto.setId(diplomaTopic.getId());
dto.setName(diplomaTopic.getName());
if (diplomaTopic.getTeacher() != null) {
dto.setTeacher(TeacherDataDTO.from(diplomaTopic.getTeacher()));
}
if (diplomaTopic.getDirectionOfPreparation() != null) {
dto.setPreparationDirection(PreparationDirectionDTO.from(diplomaTopic.getDirectionOfPreparation()));
}
dto.setCreatedAt(diplomaTopic.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(diplomaTopic.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,20 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
public record ErrorDTO(String message, ErrorCode errorCode) {
@RequiredArgsConstructor
@Getter
public enum ErrorCode {
BUSINESS_ERROR(HttpStatus.BAD_REQUEST),
VALIDATION_ERROR(HttpStatus.BAD_REQUEST),
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
NOT_FOUND(HttpStatus.NOT_FOUND),
ACCESS_DENIED(HttpStatus.FORBIDDEN),
;
private final HttpStatus httpStatus;
}
}

View File

@ -0,0 +1,47 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import ru.mskobaro.tdms.business.entity.Group;
import java.time.LocalDateTime;
import java.util.List;
@Getter
@Setter
@ToString
public class GroupDTO {
private Long id;
@NotEmpty(message = "Имя группы не может быть пустым")
@Size(min = 3, max = 50, message = "Имя группы должно быть от 3 до 50 символов")
@Pattern(regexp = "^[а-яА-ЯёЁ0-9_-]*$", message = "Имя группы должно содержать только русские буквы, дефис, нижнее подчеркивание и цифры")
private String name;
private List<StudentDataDTO> students;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private PreparationDirectionDTO preparationDirection;
public static GroupDTO from(Group group, boolean includeStudents) {
GroupDTO dto = new GroupDTO();
dto.setId(group.getId());
dto.setName(group.getName());
if (includeStudents && group.getStudents() != null) {
dto.setStudents(
group.getStudents()
.stream()
.filter(sd -> !sd.getParticipant().isDeleted())
.map(sd -> StudentDataDTO.from(sd, false))
.toList());
}
if (group.getDirectionOfPreparation() != null) {
dto.setPreparationDirection(PreparationDirectionDTO.from(group.getDirectionOfPreparation()));
}
dto.setCreatedAt(group.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(group.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,14 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@Getter
@Setter
@ToString
public class IdDto {
private Long id;
}

View File

@ -0,0 +1,18 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;
@Getter
public class LoginDTO {
@NotEmpty(message = "Логин не может быть пустым")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Логин должен содержать только латинские буквы, цифры и знак подчеркивания")
@Size(min = 5, message = "Логин должен содержать минимум 5 символов")
@Size(max = 32, message = "Логин должен содержать максимум 32 символов")
private String login;
@NotEmpty(message = "Пароль не может быть пустым")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", message = "Пароль должен содержать хотя бы одну цифру, одну заглавную и одну строчную букву, минимум 8 символов")
private String password;
}

View File

@ -0,0 +1,48 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import ru.mskobaro.tdms.business.entity.Participant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
@Getter
@Setter
@ToString
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ParticipantDTO {
private Long id;
private String firstName;
private String lastName;
private String middleName;
private String email;
private String numberPhone;
private UserDTO user;
private List<RoleDTO> roles;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static ParticipantDTO fromEntity(Participant participant) {
ParticipantDTO participantDTO = new ParticipantDTO();
participantDTO.setId(participant.getId());
participantDTO.setFirstName(participant.getFirstName());
participantDTO.setLastName(participant.getLastName());
participantDTO.setMiddleName(participant.getMiddleName());
participantDTO.setEmail(participant.getEmail());
participantDTO.setNumberPhone(participant.getNumberPhone());
participantDTO.setRoles(RoleDTO.from(participant));
participantDTO.setCreatedAt(participant.getAuditInfo().getCreatedAt());
participantDTO.setUpdatedAt(participant.getAuditInfo().getUpdatedAt());
if (participant.getUser() != null) {
participantDTO.setUser(UserDTO.fromEntity(participant.getUser(), participantDTO));
}
return participantDTO;
}
}

View File

@ -0,0 +1,64 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.ToString;
import ru.mskobaro.tdms.business.service.RoleService.Authority;
import java.util.List;
@Getter
@ToString
public class ParticipantSaveDTO {
private Long id;
@NotEmpty(message = "Имя не может быть пустым")
@Pattern(regexp = "^[a-zA-Zа-яА-ЯёЁ\\s-]+$", message = "Имя должно содержать только буквы английского или русского алфавита, пробелы и дефис")
private String firstName;
@NotEmpty(message = "Фамилия не может быть пустой")
@Pattern(regexp = "^[a-zA-Zа-яА-ЯёЁ\\s-]+$", message = "Фамилия должна содержать только буквы английского или русского алфавита, пробелы и дефис")
private String lastName;
private String middleName;
@NotNull(message = "Почта не может быть пустой")
@Email(message = "Почта должна быть валидным адресом электронной почты")
private String email;
@NotNull(message = "Номер телефона не может быть пустым")
@Pattern(regexp = "^\\+[1-9]\\d{6,14}$", message = "Номер телефона должен начинаться с '+' и содержать от 7 до 15 цифр")
private String numberPhone;
private List<Authority> authorities;
@Valid
private UserRegistrationDTO userData;
@Valid
private StudentRegistrationDTO studentData;
@Valid
private TeacherRegistrationDTO teacherData;
@Getter
@ToString
public static class UserRegistrationDTO {
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Логин должен содержать только латинские буквы, цифры и знак подчеркивания")
@Size(min = 5, message = "Логин должен содержать минимум 5 символов")
@Size(max = 32, message = "Логин должен содержать максимум 32 символов")
private String login;
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", message = "Пароль должен содержать хотя бы одну цифру, одну заглавную и одну строчную букву, минимум 8 символов")
private String password;
}
@Getter
@ToString
public static class StudentRegistrationDTO {
private Long groupId;
private Long curatorId;
private Long diplomaTopicId;
}
@Getter
@ToString
public static class TeacherRegistrationDTO {
private List<Long> curatingGroups;
private List<Long> advisingStudents;
private String degree;
}
}

View File

@ -0,0 +1,29 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.mskobaro.tdms.business.entity.DirectionOfPreparation;
import java.time.LocalDateTime;
@NoArgsConstructor
@Getter
@Setter
public class PreparationDirectionDTO {
private Long id;
private String name;
private String code;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static PreparationDirectionDTO from(DirectionOfPreparation preparationDirection) {
PreparationDirectionDTO dto = new PreparationDirectionDTO();
dto.setId(preparationDirection.getId());
dto.setName(preparationDirection.getName());
dto.setCode(preparationDirection.getCode());
dto.setCreatedAt(preparationDirection.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(preparationDirection.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,19 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import ru.mskobaro.tdms.business.entity.Participant;
import ru.mskobaro.tdms.business.entity.Role;
import java.util.List;
public record RoleDTO(String name, String code) {
public static RoleDTO from(Role role) {
return new RoleDTO(role.getName(), role.getAuthority());
}
public static List<RoleDTO> from(Participant participant) {
return participant.getRoles().stream().map(RoleDTO::from).toList();
}
}

View File

@ -0,0 +1,31 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.Setter;
import ru.mskobaro.tdms.business.entity.StudentData;
@Setter
@Getter
public class StudentDataDTO {
private Long id;
private GroupDTO group;
private ParticipantDTO participant;
private TeacherDataDTO curator;
private DiplomaTopicDTO diplomaTopic;
public static StudentDataDTO from(StudentData studentData, boolean includeGroup) {
StudentDataDTO dto = new StudentDataDTO();
dto.setId(studentData.getId());
if (includeGroup && studentData.getGroup() != null) {
dto.setGroup(GroupDTO.from(studentData.getGroup(), false));
}
dto.setParticipant(ParticipantDTO.fromEntity(studentData.getParticipant()));
if (studentData.getCurator() != null) {
dto.setCurator(TeacherDataDTO.from(studentData.getCurator()));
}
if (studentData.getDiplomaTopic() != null) {
dto.setDiplomaTopic(DiplomaTopicDTO.from(studentData.getDiplomaTopic()));
}
return dto;
}
}

View File

@ -0,0 +1,33 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.mskobaro.tdms.business.entity.Task;
import ru.mskobaro.tdms.business.taskfields.TaskFields;
import java.time.LocalDateTime;
@NoArgsConstructor
@Getter
@Setter
public class TaskDto {
private Long id;
private Task.Type type;
private Task.Status status;
private TaskFields fields;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static TaskDto from(Task task) {
TaskDto dto = new TaskDto();
dto.setId(task.getId());
dto.setType(task.getType());
dto.setStatus(task.getStatus());
dto.setFields(task.getFields());
dto.setCreatedAt(task.getAuditInfo().getCreatedAt());
dto.setUpdatedAt(task.getAuditInfo().getUpdatedAt());
return dto;
}
}

View File

@ -0,0 +1,27 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.mskobaro.tdms.business.entity.TeacherData;
@Getter
@Setter
@NoArgsConstructor
public class TeacherDataDTO {
private Long id;
private ParticipantDTO participant;
private String degree;
private String createdAt;
private String updatedAt;
public static TeacherDataDTO from(TeacherData teacherData) {
TeacherDataDTO dto = new TeacherDataDTO();
dto.setId(teacherData.getId());
dto.setParticipant(ParticipantDTO.fromEntity(teacherData.getParticipant()));
dto.setDegree(teacherData.getDegree());
dto.setCreatedAt(teacherData.getAuditInfo().getCreatedAt().toString());
dto.setUpdatedAt(teacherData.getAuditInfo().getUpdatedAt().toString());
return dto;
}
}

View File

@ -0,0 +1,41 @@
package ru.mskobaro.tdms.presentation.controller.payload;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Builder;
import ru.mskobaro.tdms.business.entity.User;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
@Builder
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public record UserDTO(
Long id,
String login,
ParticipantDTO participant,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static UserDTO fromEntity(User user) {
return UserDTO.builder()
.id(user.getId())
.login(user.getLogin())
.participant(ParticipantDTO.fromEntity(user.getParticipant()))
.createdAt(user.getAuditInfo().getCreatedAt())
.updatedAt(user.getAuditInfo().getUpdatedAt())
.build();
}
public static UserDTO fromEntity(User user, ParticipantDTO participant) {
return UserDTO.builder()
.id(user.getId())
.login(user.getLogin())
.participant(participant)
.createdAt(user.getAuditInfo().getCreatedAt())
.updatedAt(user.getAuditInfo().getUpdatedAt())
.build();
}
}

View File

@ -0,0 +1,73 @@
package ru.mskobaro.tdms.presentation.exception;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
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.mskobaro.tdms.business.exception.AccessDeniedException;
import ru.mskobaro.tdms.business.exception.BusinessException;
import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO.ErrorCode.*;
@RestControllerAdvice
@Slf4j
public class ApplicationExceptionHandler {
@ExceptionHandler(BindException.class)
@ResponseStatus(BAD_REQUEST)
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 ErrorDTO(validationErrors, VALIDATION_ERROR);
}
@ExceptionHandler(BusinessException.class)
public ErrorDTO handleBusinessException(BusinessException e, HttpServletResponse response) {
log.warn("Business error: {}", e.getMessage());
response.setStatus(e.getErrorCode().getHttpStatus().value());
return new ErrorDTO(e.getMessage(), e.getErrorCode());
}
@ExceptionHandler({org.springframework.security.access.AccessDeniedException.class, AccessDeniedException.class})
@ResponseStatus(FORBIDDEN)
public ErrorDTO handleAccessDeniedException(RuntimeException e) {
log.warn("Access denied: {}", e.getMessage());
return new ErrorDTO("Доступ запрещен", ACCESS_DENIED);
}
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(UNAUTHORIZED)
public ErrorDTO handleAuthenticationException(AuthenticationException e) {
log.warn("Authentication error: {}", e.getMessage());
return new ErrorDTO("Неверный логин или пароль", ACCESS_DENIED);
}
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(NOT_FOUND)
public ErrorDTO handleNoResourceFoundException(NoResourceFoundException e) {
UUID uuid = UUID.randomUUID();
String message = e.getMessage().substring(0, e.getMessage().length() - 1);
log.error("{} ({})", message, uuid);
return new ErrorDTO("Идентификатор ошибки: (" + uuid + ")\nРесурс не был наеден, обратитесь к администратору", ErrorDTO.ErrorCode.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(INTERNAL_SERVER_ERROR)
public ErrorDTO handleUnexpectedException(Exception e) {
UUID uuid = UUID.randomUUID();
log.error("Unexpected exception ({})", uuid, e);
return new ErrorDTO("Идентификатор ошибки: (" + uuid + ")\роизошла непредвиденная ошибка, обратитесь к администратору", INTERNAL_ERROR);
}
}

View File

@ -0,0 +1,142 @@
package ru.mskobaro.tdms.system.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
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.context.SecurityContextHolderFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import ru.mskobaro.tdms.system.web.LoggingRequestFilter;
import java.time.Duration;
import java.util.List;
import static ru.mskobaro.tdms.business.service.RoleService.Authority.ADMIN;
import static ru.mskobaro.tdms.business.service.RoleService.Authority.SECRETARY;
@Slf4j
@Configuration
public class SecurityConfig {
@Autowired
private Environment environment;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,
AuthenticationManager authenticationManager,
CorsConfigurationSource cors
) throws Exception {
return httpSecurity
.addFilterAfter(new LoggingRequestFilter(), SecurityContextHolderFilter.class)
.authorizeHttpRequests(this::configureHttpAuthorization)
// .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.csrf(AbstractHttpConfigurer::disable)
.cors(a -> a.configurationSource(cors))
.authenticationManager(authenticationManager)
.sessionManagement(cfg -> {
cfg.sessionCreationPolicy(SessionCreationPolicy.NEVER);
cfg.maximumSessions(1);
})
.build();
}
@Bean
@Primary
public CorsConfigurationSource corsConfiguration(
@Value("${application.domain}") String domain,
@Value("${application.port}") String port,
@Value("${application.protocol}") String protocol
) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedMethods(List.of(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.OPTIONS.name()));
corsConfiguration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
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());
return request -> corsConfiguration;
}
@Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return new ProviderManager(provider);
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
private void configureHttpAuthorization(
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry httpAuthorization
) {
httpAuthorization.requestMatchers("/api/v1/sysinfo/**").permitAll();
httpAuthorization.requestMatchers("/api/v1/user/logout").authenticated();
httpAuthorization.requestMatchers("/api/v1/user/login").permitAll();
httpAuthorization.requestMatchers("/api/v1/user/current").permitAll();
httpAuthorization.requestMatchers("/api/v1/participant/get-all-participants").permitAll();
httpAuthorization.requestMatchers("/api/v1/participant/get-possible-curators").permitAll();
// Сложная логика авторизации, так что проверяем явно в ParticipantService
httpAuthorization.requestMatchers("/api/v1/participant/save").authenticated();
httpAuthorization.requestMatchers("/api/v1/participant/delete").hasAuthority(ADMIN.getAuthority());
httpAuthorization.requestMatchers("/api/v1/group/get-all-groups").permitAll();
httpAuthorization.requestMatchers("/api/v1/group/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority());
httpAuthorization.requestMatchers("/api/v1/group/delete").hasAnyAuthority(ADMIN.getAuthority());
httpAuthorization.requestMatchers("/api/v1/student/by-partic-id").permitAll();
httpAuthorization.requestMatchers("/api/v1/student/all-without-group").permitAll();
httpAuthorization.requestMatchers("/api/v1/teacher-data/get-all").permitAll();
httpAuthorization.requestMatchers("/api/v1/teacher-data/by-partic-id").permitAll();
httpAuthorization.requestMatchers("/api/v1/prep-direction/get-all").permitAll();
httpAuthorization.requestMatchers("/api/v1/prep-direction/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority());
httpAuthorization.requestMatchers("/api/v1/defence/all").permitAll();
httpAuthorization.requestMatchers("/api/v1/diploma-topic/all").permitAll();
httpAuthorization.requestMatchers("/api/v1/diploma-topic/all-for-student").permitAll();
httpAuthorization.requestMatchers("/api/v1/diploma-topic/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority());
httpAuthorization.requestMatchers("/api/**").denyAll();
httpAuthorization.requestMatchers("/**").permitAll();
}
}

View File

@ -0,0 +1,28 @@
package ru.mskobaro.tdms.system.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException;
import org.springframework.core.io.Resource;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable()
? requestedResource
: location.createRelative("index.html");
}
});
}
}

View File

@ -0,0 +1,20 @@
package ru.mskobaro.tdms.system.web;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Component
public class AccessDeniedExceptionHandler implements AccessDeniedHandler {
@Autowired
private HandlerExceptionResolver handlerExceptionResolver;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
handlerExceptionResolver.resolveException(request, response, null, new ru.mskobaro.tdms.business.exception.AccessDeniedException());
}
}

View File

@ -0,0 +1,35 @@
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.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.UUID;
@Slf4j
public class LoggingRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UUID uuid = UUID.randomUUID();
long startTime = System.currentTimeMillis();
String username = SecurityContextHolder.getContext().getAuthentication() != null ?
SecurityContextHolder.getContext().getAuthentication().getName() : "anonymousUser";
HttpSession session = request.getSession(false);
log.info("Request received: [{}] {} user: {}, session: {}, remote ip: {} [{}]",
request.getMethod(), request.getRequestURI(), username,
session == null ? "no" : session.getId(), request.getRemoteAddr(), uuid);
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - startTime;
log.info("Response with {} status duration: {} ms [{}]", response.getStatus(), duration, uuid);
}
}
}

View File

@ -0,0 +1,23 @@
package ru.mskobaro.tdms.system.web;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LoggingSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
log.debug("Session created: {}, user {}", se.getSession().getId(),
SecurityContextHolder.getContext().getAuthentication().getName());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
log.debug("Session destroyed: {}", se.getSession().getId());
}
}

View File

@ -0,0 +1,53 @@
{
"properties": [
{
"name": "db.url",
"type": "java.lang.String",
"description": "Database url."
},
{
"name": "db.user",
"type": "java.lang.String",
"description": "Database user."
},
{
"name": "db.password",
"type": "java.lang.String",
"description": "Database password."
},
{
"name": "db.schema",
"type": "java.lang.String",
"description": "Database schema."
},
{
"name": "application.name",
"type": "java.lang.String",
"description": "Service name."
},
{
"name": "application.version",
"type": "java.lang.String",
"description": "Service version."
},
{
"name": "application.type",
"type": "java.lang.String",
"description": "Service build type."
},
{
"name": "application.domain",
"type": "java.lang.String",
"description": "Service domain."
},
{
"name": "application.port",
"type": "java.lang.Integer",
"description": "Service port."
},
{
"name": "application.protocol",
"type": "java.lang.String",
"description": "Service protocol."
}
] }

View File

@ -0,0 +1,5 @@
application:
type: development
port: 8080
domain: localhost
protocol: http

View File

@ -0,0 +1,42 @@
db:
url: jdbc:postgresql://localhost:5400/tdms
schema: public
user: root
password: root
application:
name: @name@
version: @version@
type: production
port: 443
domain: tdms.tu-bryansk.ru
protocol: https
spring:
application:
name: ${application.name}
datasource:
url: ${db.url}
username: ${db.user}
password: ${db.password}
hikari:
schema: ${db.schema}
jpa:
open-in-view: false
hibernate.ddl-auto: validate
flyway:
url: ${db.url}
user: ${db.user}
password: ${db.password}
schemas: ${db.schema}
banner:
location: banner.txt
server:
port: ${application.port}
address: ${application.domain}
compression:
enabled: true
management:
endpoints:
web:
exposure:
exclude: "*"

View File

@ -0,0 +1,5 @@
__________ __ ________ ____ ____ ___
/_ __/ __ \/ |/ / ___/ _ __ / __ \ / __ \ < /
/ / / / / / /|_/ /\__ \ | | / / / / / / / / / / / /
/ / / /_/ / / / /___/ / | |/ /_/ /_/ /_/ /_/ /_ / /
/_/ /_____/_/ /_//____/ |___/(_)____/(_)____/(_)_/

View File

@ -0,0 +1,280 @@
CREATE TABLE defense
(
id BIGSERIAL PRIMARY KEY,
defense_date DATE,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE defense_best_student_works
(
defense_id BIGINT,
student_data_id BIGINT
);
CREATE TABLE defense_commission
(
defense_id BIGINT,
commission_member_data_id BIGINT
);
CREATE TABLE diploma_topic
(
id BIGSERIAL PRIMARY KEY,
name TEXT,
teacher_id BIGINT,
direction_of_preparation_id BIGINT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE direction_of_preparation
(
id BIGSERIAL PRIMARY KEY,
name TEXT,
code TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE "group"
(
id BIGSERIAL PRIMARY KEY,
name TEXT,
defense_id BIGINT,
direction_of_preparation_id BIGINT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE participant
(
id BIGSERIAL PRIMARY KEY,
first_name TEXT,
last_name TEXT,
middle_name TEXT,
email TEXT,
number_phone TEXT,
deleted BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE participant_role
(
partic_id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role
(
id BIGINT PRIMARY KEY,
name TEXT,
authority TEXT
);
/* todo */
CREATE TABLE student_data
(
id BIGSERIAL PRIMARY KEY,
partic_id BIGINT,
study_form_id BIGINT,
curator_id BIGINT,
protection_order INTEGER,
protection_day INTEGER,
mark_comment INTEGER,
mark_practice INTEGER,
predefnese_comment TEXT,
normal_control BOOLEAN,
anti_plagiarism INTEGER,
record_book_returned BOOLEAN,
work TEXT,
diploma_topic_id BIGINT,
adviser_teacher_partic_id BIGINT,
group_id BIGINT,
marks_3 BIGINT,
marks_4 BIGINT,
marks_5 BIGINT,
commission_mark BIGINT,
estimated BOOLEAN,
diploma_with_honors BOOLEAN,
magistracy_recommendation BOOLEAN,
magistracy_wanted BOOLEAN,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
/* not implemented */
create table questionnaire
(
id BIGSERIAL PRIMARY KEY,
student_data_id BIGINT,
data TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
create table study_form
(
id BIGSERIAL PRIMARY KEY,
name TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
/* todo */
create table stud_comment
(
id BIGSERIAL PRIMARY KEY,
comment TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
/* todo */
create table student_data_comment
(
id BIGSERIAL PRIMARY KEY,
student_data_id BIGINT,
stud_comment_id BIGINT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE teacher_data
(
id BIGSERIAL PRIMARY KEY,
participant_id BIGINT,
degree TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE commission_member_data
(
id BIGSERIAL PRIMARY KEY,
partic_id BIGINT,
work_place TEXT,
work_position TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE "user"
(
id BIGSERIAL PRIMARY KEY,
login TEXT,
password TEXT,
partic_id BIGINT,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE task
(
id BIGSERIAL PRIMARY KEY,
type TEXT,
status TEXT,
fields jsonb,
created_at TIMESTAMP WITHOUT TIME ZONE,
updated_at TIMESTAMP WITHOUT TIME ZONE
);
ALTER TABLE defense_commission
ADD CONSTRAINT FK_DEFCOM_ON_DEFNESE FOREIGN KEY (defense_id) REFERENCES defense (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE defense_commission
ADD CONSTRAINT FK_DEFCOM_ON_COMMEMD FOREIGN KEY (commission_member_data_id) REFERENCES commission_member_data (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "group"
ADD CONSTRAINT FK_GROUP_ON_defnese FOREIGN KEY (defense_id) REFERENCES defense (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "group"
ADD CONSTRAINT FK_GROUP_ON_DOP FOREIGN KEY (direction_of_preparation_id) REFERENCES direction_of_preparation (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE participant_role
ADD CONSTRAINT FK_PARROL_ON_PARTICIPANT FOREIGN KEY (partic_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE participant_role
ADD CONSTRAINT FK_PARROL_ON_ROLE FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT UC_STUDENT_DATA_PARTIC UNIQUE (partic_id);
ALTER TABLE student_data
ADD CONSTRAINT FK_STUDENT_DATA_ON_ADVISER_TEACHER_PARTIC FOREIGN KEY (adviser_teacher_partic_id) REFERENCES participant (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT FK_STUDENT_DATA_ON_DIPLOMA_TOPIC FOREIGN KEY (diploma_topic_id) REFERENCES diploma_topic (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT FK_STUDENT_DATA_ON_GROUP FOREIGN KEY (group_id) REFERENCES "group" (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT FK_STUDENT_DATA_ON_PARTIC FOREIGN KEY (partic_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE participant
ADD CONSTRAINT UC_PARTICIPANT_EMAIL UNIQUE (email);
ALTER TABLE participant
ADD CONSTRAINT UC_PARTICIPANT_NUMBER_PHONE UNIQUE (number_phone);
ALTER TABLE "user"
ADD CONSTRAINT UC_USER_LOGIN UNIQUE (login);
ALTER TABLE "user"
ADD CONSTRAINT UC_USER_PARTIC UNIQUE (partic_id);
ALTER TABLE "user"
ADD CONSTRAINT FK_USER_ON_PARTIC FOREIGN KEY (partic_id) REFERENCES participant (id);
ALTER TABLE teacher_data
ADD CONSTRAINT UC_TEACHER_DATA_PARTIC UNIQUE (participant_id);
ALTER TABLE teacher_data
ADD CONSTRAINT FK_TEACHER_DATA_ON_PARTIC FOREIGN KEY (participant_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE commission_member_data
ADD CONSTRAINT UC_COMMISION_MEMBER_DATA_PARTIC UNIQUE (partic_id);
ALTER TABLE commission_member_data
ADD CONSTRAINT FK_COMMISION_MEMBER_DATA_ON_PARTIC FOREIGN KEY (partic_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE direction_of_preparation
ADD CONSTRAINT UC_DIRECTION_OF_PREPARATION_NAME UNIQUE (name);
ALTER TABLE direction_of_preparation
ADD CONSTRAINT UC_DIRECTION_OF_PREPARATION_CODE UNIQUE (code);
ALTER TABLE diploma_topic
ADD CONSTRAINT UC_DIPLOMA_TOPIC_NAME FOREIGN KEY (direction_of_preparation_id) REFERENCES direction_of_preparation (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE defense_best_student_works
ADD CONSTRAINT FK_DEFENSE_BEST_STUDENT_WORKS_ON_DEFENSE FOREIGN KEY (defense_id) REFERENCES defense (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE defense_best_student_works
ADD CONSTRAINT FK_DEFENSE_BEST_STUDENT_WORKS_ON_STUDENT_DATA FOREIGN KEY (student_data_id) REFERENCES student_data (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE stud_comment
ADD CONSTRAINT UC_STUD_COMMENT_COMMENT UNIQUE (comment);
ALTER TABLE student_data
ADD CONSTRAINT UC_STUDENT_DATA_DIPLOMA_TOPIC FOREIGN KEY (diploma_topic_id) REFERENCES diploma_topic (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT UC_STUDENT_DATA_STUDY_FORM FOREIGN KEY (study_form_id) REFERENCES study_form (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE student_data
ADD CONSTRAINT UC_STUDENT_DATA_CURATOR FOREIGN KEY (curator_id) REFERENCES teacher_data (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE study_form
ADD CONSTRAINT UC_STUDY_FORM_NAME UNIQUE (name);
alter table questionnaire
add constraint UC_QUESTIONNAIRE_STUDENT_DATA_ID unique (student_data_id);
alter table questionnaire
add constraint FK_QUESTIONNAIRE_STUDENT_DATA foreign key (student_data_id) references student_data (id) on delete cascade on update cascade;

View File

@ -0,0 +1,7 @@
insert into role (id, name, authority)
values (1, 'Преподаватель', 'ROLE_TEACHER'),
(2, 'Студент', 'ROLE_STUDENT'),
(3, 'Член комиссии ГЭК', 'ROLE_COMMISSION_MEMBER'),
(4, 'Администратор', 'ROLE_ADMINISTRATOR'),
(5, 'Секретарь', 'ROLE_SECRETARY'),
(6, 'Проверяющий на плагиат', 'ROLE_PLAGIARISM_CHECKER');

View File

@ -0,0 +1,21 @@
insert into participant (id, first_name, email, number_phone, created_at, updated_at)
values (1,
'Администратор',
'admin@tdms.tu-bryansk.ru',
'+74832580058',
now(),
now());
insert into "user" (id, login, password, partic_id, created_at, updated_at)
values (1,
'admin',
'{noop}Admin000',
1,
now(),
now());
insert into participant_role (partic_id, role_id)
values (1, 4);
select setval('participant_id_seq', (select max(id) from participant));
select setval('user_id_seq', (select max(id) from "user"));

View File

@ -0,0 +1,12 @@
create table defense
(
id bigserial primary key,
defence_date timestamptz,
created_at timestamptz not null,
updated_at timestamptz
);
-- COMMENTS
comment on table defense is 'Таблица для хранения данных о защитах';
comment on column defense.defence_date is 'Дата защиты';

View File

@ -0,0 +1,40 @@
INSERT INTO diploma_topic (name)
VALUES ('Мобильное приложение для заказа автозапчастей на платформе ABCP'),
('Подсистема уведомления пользователей для программного комплекса "РискПроф. Учебный центр"'),
('Веб-приложение "Таск-менеджер"'),
('Интернет-магазин электроники'),
('Информационная система для студентов БГТУ в Telegram'),
('Автоматизированная система тестирования сотрудников для определения их компетенций: модуль подготовки тестов и учета результатов'),
('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема управления поведением внутриигровых агентов'),
('Корпоративное веб-приложение по управлению задачами'),
('Подсистемы визуализации и аналитики для онлайн-сервиса поддержки scrum-доски'),
('Интерактивная система обучения приемам работы с криптовалютой'),
('Автоматизированная система учета деятельности салона по продаже автомобилей'),
('Обучающее мобильное android-приложение'),
('Автоматизированная система учета зуботехнической CAD/CAM лаборатории'),
('Автоматизированная система тестирования сотрудников для определения их компетенций: модуль регистрации и управления кабинетами'),
('Система мониторинга успеваемости обучающихся и посещаемости занятий образовательного учреждения'),
('Мобильное приложение для организации работы репетитора'),
('Модуль "Редактор рисков" для программного комплекса управления профессиональными рисками "1С. ЕОС ПБ"'),
('Онлайн-сервис по продаже товаров (на примере ООО «ЭПК-2»'),
('Веб-сервис по обучению Microsoft Excel'),
('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема генерации наполнения помещений'),
('Подсистема интеграции с федеральной государственной информационной системой по охране труда для программного комплекса «РискПроф. Учебный центр»'),
('Разработка приложения голосовой авторизации на основе нейросетевого подхода'),
('Автоматизированная система учета деятельности спортивного клуба'),
('Модуль "Специальная оценка условий труда" для программного комплекса управления профессиональными рисками "1С. ЕОС ПБ"'),
('Интернет-магазин по продаже спортивной обуви'),
('Приложение интерактивной исторической карты'),
('Миграция системы учёта с 1С:УТ на 1С:КА'),
('Программный комплекс для автоматизированного проведения опросов и тестирования пользователей'),
('Автоматизированное тестирование программного комплекса «Федеральная государственная информационная система специальной оценки условий труда»'),
('Модуль "Единая информационно-справочная подсистема" для федеральной государственной информационной системы по охране труда'),
('Подсистема сопровождения обучения по охране труда для программного комплекса «РискПроф. Учебный центр»'),
('Программное обеспечение системы обучения и тестирования в мессенджере Telegram'),
('Онлайн-калькулятор индексов влияния в играх взвешенного голосования'),
('Telegram-бот с функциями обработки изображений'),
('Подсистема администрирования автоматизированной системы для проведения платежей и перевода денежных средств'),
('Подсистема администрирования для программного комплекса распределения студентов по руководителям выпускных квалификационных работ'),
('Библиотека контроля качества данных в базах и хранилищах данных для программного комплекса MetaControl'),
('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема генерации карты помещений'),
('Программная система решения транспортной задачи методом генетического алгоритма для торговой сети');

View File

@ -0,0 +1,3 @@
INSERT INTO "group" (name, curator_teacher_id, created_at, updated_at)
VALUES ('ИВТ-1', 40, now(), now()),
('ИВТ-2', 40, now(), now());

View File

@ -0,0 +1,98 @@
do
$$
begin
INSERT INTO studentData (form,
protection_order,
magistracy,
digital_format_present,
mark_comment,
mark_practice,
predefence_comment,
normal_control,
anti_plagiarism,
note,
record_book_returned,
work,
user_id,
diploma_topic_id,
mentor_user_id,
group_id,
created_at)
VALUES (true, 1100, 'ПРИ, ИВТ или другой вуз', true, 5, 5, 'ок', 'Подписано', 7862, 'Акт о внедрении', true,
'нет', 1, 1, 40, 3, now()),
(true, 1110, 'Да, но не уверен, в БГТУ ли', true, 5, 5, 'ок', 'Подписано', 8196,
'Заявка, Акт о внедрении', true, 'ООО "ЦИРОБЗ"', 2, 2, 40, 3, now()),
(true, 3500, 'Нет', true, 3, 3,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7141, 'Иниц',
true, 'нет', 3, 3, 41, 3, now()),
(true, 1800, 'Да, но не уверен, в БГТУ ли', true, 4, 5, 'Усилить работу', 'Подписано', 5381, 'Иниц',
true, 'нет', 4, 4, 42, 3, now()),
(true, 2100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8146, 'Заявка, Акт о внедрении', true, 'нет', 5,
5, 43, 3, now()),
(true, 1100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7965, 'Иниц', true, 'нет', 6, 6, 41, 3, now()),
(true, 1200, 'нет', true, 5, 4, 'Усилить работу', 'Подписано', 8583, Null, true,
'Газ Энерго Комплект (ГЭК)', 7, 7, 44, 3, now()),
(true, 1700, 'Нет', true, 5, 5,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 6647, Null,
true, 'нет', 8, 8, 45, 3, now()),
(true, 3300, 'ИВТ, ПРИ или другой вуз', true, 5, 5, 'Усилить работу', 'Подписано', 6741,
'Заявка, Акт о внедрении', true, 'нет', 9, 9, 46, 3, now()),
(true, 1200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7645, 'Заявка, Акт о внедрении', true, 'нет', 10, 10,
40, 3, now()),
(true, 3700, 'нет', true, 3, 3,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', '1', 3093, 'Иниц', true,
'нет', 11, 11, 45, 3, now()),
(true, 1900, 'Нет', true, 5, 5, 'ок', 'Подписано', 8175, 'Заявка, Акт о внедрении', true, 'нет', 12, 12,
42, 3, now()),
(true, 3200, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 7805, 'Акт', true, 'нет', 13, 13, 46, 3,
now()),
(true, 1200, 'ИВТ, ПРИ', true, 4, 4, 'ок', 'рек', 7590, 'Иниц', true, 'нет', 14, 14, 45, 3, now()),
(true, 3900, 'нет', true, 5, 5, 'Усилить работу', 'Подписано', 6463, 'Заявка, Акт о внедрении', true,
'нет', 15, 15, 40, 3, now()),
(true, 2200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7441, Null, true, 'ООО "ЦИРОБЗ"', 16, 16, 44, 3,
now()),
(true, 1130, 'Нет', true, 5, 4,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7319,
'Заявка, Акт о внедрении', true, 'нет', 17, 17, 40, 3, now()),
(true, 2400, 'ИВТ', true, 4, 5, 'ок', 'Подписано', 6436, Null, true, 'нет', 18, 18, 45, 3, now()),
(true, 1600, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 6227, 'Исслед', true, 'АО "БЭМЗ"', 19, 19,
47, 3, now()),
(true, 1400, 'Нет', true, 5, 5, 'ок', 'Подписано', 8935, 'Иниц', true, 'ООО "ЦИРОБЗ"', 20, 20, 44, 4,
now()),
(true, 2900, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7971, 'Заявка, Акт о внедрении', true, 'нет', 21, 21,
40, 4, now()),
(true, 2600, 'ИВТ, ПРИ', true, 3, 3, 'ок', 'Подписано', 7284, Null, true, 'нет', 22, 22, 48, 4, now()),
(true, 3100, 'ИВТ, ПРИ', true, 5, 5, 'Усилить работу', 'Подписано', 4966, 'Заявка, Акт о внедрении',
true, 'нет', 23, 23, 45, 4, now()),
(true, 3110, 'Нет', true, 5, 5, 'ок', 'Подписано', 7174, Null, true, 'нет', 24, 24, 40, 4, now()),
(true, 3800, 'Нет', true, 5, 5, 'ок', 'Подписано', 7233, Null, true, 'нет', 25, 25, 45, 4, now()),
(true, 3300, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7133, Null, true, 'нет', 26, 26, 43, 4, now()),
(true, 3130, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8083, 'Заявка, Акт о внедрении', true, 'нет', 27, 27,
49, 4, now()),
(true, 3400, 'Нет', true, 5, 4,
'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7968, 'Иниц',
true, 'нет', 28, 28, 50, 4, now()),
(true, 3120, 'ИВТ или ПРИ', true, 5, 5, 'ок', 'Подписано', 7940, 'Исслед', true, 'ООО "ЦИРОБЗ"', 29, 29,
40, 4, now()),
(true, 2800, 'Нет (возможно)', true, 4, 4, 'Усилить работу', 'рек', 6775, 'Заявка, Акт о внедрении',
true, 'нет', 30, 30, 40, 4, now()),
(true, 2100, 'Нет (возможно)', true, 5, 5, 'ок', 'Подписано', 7637, 'Заявка, Акт о внедрении', true,
'нет', 31, 31, 40, 4, now()),
(true, 2700, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8544, 'Заявка, Акт о внедрении', true, 'нет', 32, 32,
45, 4, now()),
(true, 2130, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7166, 'Заявка, Акт о внедрении', true, 'нет', 33,
33, 51, 4, now()),
(true, 2110, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6075, 'Заявка, Акт о внедрении', true, 'нет', 34, 34,
52, 4, now()),
(true, 3100, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 35, 35,
50, 4, now()),
(true, 2120, 'В БГТУ на другой кафедре (38.04.01 Экономика или 27.04.05. Инноватика)', true, 5, 5, 'ок',
'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 36, 36, 51, 4, now()),
(true, 2500, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6583, 'Заявка, Акт о внедрении', true, 'нет', 37, 37,
53, 4, now()),
(true, 1300, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8444, 'Заявка, Акт о внедрении', true, 'нет', 38,
38, 44, 4, now()),
(true, 3600, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7631, 'Заявка, Акт о внедрении', true, 'нет', 39, 39,
45, 4, now());
end
$$;

View File

@ -0,0 +1,82 @@
INSERT INTO "user" (login, password, full_name, mail, number_phone, created_at)
VALUES ('akulenko_mikhail', '{noop}1', 'Акуленко Михаил Вячеславович', 'akulenko.mikhail@example.com',
'+79110000001', NOW()),
('borovikov_artem', '{noop}1', 'Боровиков Артём Викторович', 'borovikov.artem@example.com', '+79110000002',
NOW()),
('bykonya_alexey', '{noop}1', 'Быконя Алексей Николаевич', 'bykonya.alexey@example.com', '+79110000003',
NOW()),
('ermakov_alexander', '{noop}1', 'Ермаков Александр Сергеевич', 'ermakov.alexander@example.com',
'+79110000004', NOW()),
('zgursky_evgeny', '{noop}1', 'Згурский Евгений Олегович', 'zgursky.evgeny@example.com', '+79110000005',
NOW()),
('ibishov_tural', '{noop}1', 'Ибишов Турал Садай оглы', 'ibishov.tural@example.com', '+79110000006', NOW()),
('ignatenko_vladimir', '{noop}1', 'Игнатенко Владимир Алексеевич', 'ignatenko.vladimir@example.com',
'+79110000007', NOW()),
('lazukin_danila', '{noop}1', 'Лазукин Данила Дмитриевич', 'lazukin.danila@example.com', '+79110000008',
NOW()),
('mitiaev_danila', '{noop}1', 'Митяев Данила Алексеевич', 'mitiaev.danila@example.com', '+79110000009',
NOW()),
('neshkov_daniil', '{noop}1', 'Нешков Даниил Владимирович', 'neshkov.daniil@example.com', '+79110000010',
NOW()),
('petrov_pavel', '{noop}1', 'Петров Павел Сергеевич', 'petrov.pavel@example.com', '+79110000011', NOW()),
('sazonov_andrey', '{noop}1', 'Сазонов Андрей Андреевич', 'sazonov.andrey@example.com', '+79110000012',
NOW()),
('solokhin_maxim', '{noop}1', 'Солохин Максим Николаевич', 'solokhin.maxim@example.com', '+79110000013',
NOW()),
('sochinsky_artem', '{noop}1', 'Сочинский Артем Александрович', 'sochinsky.artem@example.com',
'+79110000014', NOW()),
('trisvyatsky_kirill', '{noop}1', 'Трисвятский Кирилл Андреевич', 'trisvyatsky.kirill@example.com',
'+79110000015', NOW()),
('turov_alexander', '{noop}1', 'Туров Александр Сергеевич', 'turov.alexander@example.com', '+79110000016',
NOW()),
('shevtsova_alexandra', '{noop}1', 'Шевцова Александра Валерьевна', 'shevtsova.alexandra@example.com',
'+79110000017', NOW()),
('kibalyuk_artem', '{noop}1', 'Кибалюк Артем Сергеевич', 'kibalyuk.artem@example.com', '+79110000018', NOW()),
('shulindin_artem', '{noop}1', 'Шулындин Артём Андреевич', 'shulindin.artem@example.com', '+79110000019',
NOW()),
('belyaev_egor', '{noop}1', 'Беляев Егор Андреевич', 'belyaev.egor@example.com', '+79110000020', NOW()),
('berezhnoy_igor', '{noop}1', 'Бережной Игорь Александрович', 'berezhnoy.igor@example.com', '+79110000021',
NOW()),
('bogun_pavel', '{noop}1', 'Богун Павел Сергеевич', 'bogun.pavel@example.com', '+79110000022', NOW()),
('vaseykin_nikita', '{noop}1', 'Васейкин Никита Павлович', 'vaseykin.nikita@example.com', '+79110000023',
NOW()),
('gomonov_nikita', '{noop}1', 'Гомонов Никита Алексеевич', 'gomonov.nikita@example.com', '+79110000024',
NOW()),
('druyan_oleg', '{noop}1', 'Друян Олег Викторович', 'druyan.oleg@example.com', '+79110000025', NOW()),
('ivanov_kirill', '{noop}1', 'Иванов Кирилл Эдуардович', 'ivanov.kirill@example.com', '+79110000026', NOW()),
('ivanova_veronika', '{noop}1', 'Иванова Вероника Евгеньевна', 'ivanova.veronika@example.com',
'+79110000027', NOW()),
('izotov_ivan', '{noop}1', 'Изотов Иван Алексеевич', 'izotov.ivan@example.com', '+79110000028', NOW()),
('isakov_zahar', '{noop}1', 'Исаков Захар Александрович', 'isakov.zahar@example.com', '+79110000029', NOW()),
('iskritsky_daniil', '{noop}1', 'Искрицкий Даниил Павлович', 'iskritsky.daniil@example.com', '+79110000030',
NOW()),
('linko_daria', '{noop}1', 'Линько Дарья Андреевна', 'linko.daria@example.com', '+79110000031', NOW()),
('logutov_kirill', '{noop}1', 'Логутов Кирилл Александрович', 'logutov.kirill@example.com', '+79110000032',
NOW()),
('nekrassov_sergey', '{noop}1', 'Некрасов Сергей Игоревич', 'nekrassov.sergey@example.com', '+79110000033',
NOW()),
('sinyagin_ilya', '{noop}1', 'Синягин Илья Александрович', 'sinyagin.ilya@example.com', '+79110000034',
NOW()),
('sopriko_daniil', '{noop}1', 'Соприко Даниил Сергеевич', 'sopriko.daniil@example.com', '+79110000035',
NOW()),
('turovsky_ivan', '{noop}1', 'Туровский Иван Алексеевич', 'turovsky.ivan@example.com', '+79110000036', NOW()),
('frantsev_sergey', '{noop}1', 'Францев Сергей Дмитриевич', 'frantsev.sergey@example.com', '+79110000037',
NOW()),
('chepurnoy_maxim', '{noop}1', 'Чепурной Максим Романович', 'chepurnoy.maxim@example.com', '+79110000038',
NOW()),
('schemelinin_dmitry', '{noop}1', 'Щемелинин Дмитрий Михайлович', 'schemelinin.dmitry@example.com',
'+79110000039', NOW()),
('bulatitsky_d_i_1', '{noop}1', 'Булатицкий Д. И.', 'bulatitsky.d.i.1@example.com', '+79110000040', NOW()),
('kopeliovich_d_i_1', '{noop}1', 'Копелиович Д. И.', 'kopeliovich.d.i.1@example.com', '+79110000041', NOW()),
('dergachev_k_v', '{noop}1', 'Дергачев К. В.', 'dergachev.k.v@example.com', '+79110000042', NOW()),
('trubakov_e_o', '{noop}1', 'Трубаков Е. О.', 'trubakov.e.o@example.com', '+79110000043', NOW()),
('radchenko_a_o', '{noop}1', 'Радченко А. О.', 'radchenko.a.o@example.com', '+79110000044', NOW()),
('zimin_s_n_1', '{noop}1', 'Зимин С. Н.', 'zimin.s.n.1@example.com', '+79110000045', NOW()),
('koptenok_e_v', '{noop}1', 'Коптенок Е. В.', 'koptenok.e.v@example.com', '+79110000046', NOW()),
('mikhaleva_o_a', '{noop}1', 'Михалева О. А.', 'mikhaleva.o.a@example.com', '+79110000047', NOW()),
('gulakov_k_v', '{noop}1', 'Гулаков К. В.', 'gulakov.k.v@example.com', '+79110000048', NOW()),
('titarev_d_v', '{noop}1', 'Титарёв Д. В.', 'titarev.d.v@example.com', '+79110000049', NOW()),
('izrailev_v_ya_1', '{noop}1', 'Израилев В. Я.', 'izrailev.v.ya.1@example.com', '+79110000050', NOW()),
('podvesovsky_a_g_1', '{noop}1', 'Подвесовский А. Г.', 'podvesovsky.a.g.1@example.com', '+79110000051', NOW()),
('trubakov_a_o', '{noop}1', 'Трубаков А. О.', 'trubakov.a.o@example.com', '+79110000059', NOW()),
('lageriev_d_g', '{noop}1', 'Лагерев Д. Г.', 'lageriev.d.g@example.com', '+79110000052', NOW());

View File

@ -0,0 +1,58 @@
do
$$
declare
teacher_id bigint := 1;
student_id bigint := 2;
commission_member_id bigint := 3;
administrator_id bigint := 4;
secretary_id bigint := 5;
begin
INSERT INTO user_role (user_id, role_id)
VALUES (1, student_id),
(2, student_id),
(3, student_id),
(4, student_id),
(5, student_id),
(6, student_id),
(7, student_id),
(8, student_id),
(9, student_id),
(10, student_id),
(11, student_id),
(12, student_id),
(13, student_id),
(14, student_id),
(15, student_id),
(16, student_id),
(17, student_id),
(18, student_id),
(19, student_id),
(20, student_id),
(21, student_id),
(22, student_id),
(23, student_id),
(24, student_id),
(25, student_id),
(26, student_id),
(27, student_id),
(28, student_id),
(29, student_id),
(30, student_id),
(31, student_id),
(32, student_id),
(33, student_id),
(34, student_id),
(35, student_id),
(36, student_id),
(37, teacher_id),
(37, administrator_id),
(38, commission_member_id),
(39, teacher_id),
(40, teacher_id),
(41, teacher_id),
(42, teacher_id),
(43, teacher_id),
(44, secretary_id),
(45, secretary_id);
end
$$;

View File

@ -0,0 +1,22 @@
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<append>false</append>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="ru.mskobaro.tdms" level="debug"/>
<root level="warn">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>

26
web/.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

12
web/babel.config.json Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
["@babel/preset-env", {"modules": false}],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties"],
["@babel/plugin-transform-typescript"]
]
}

7045
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
web/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "web",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --mode development"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@types/lodash": "^4.17.15",
"axios": "^1.7.7",
"bootstrap": "^5.3.3",
"lodash": "^4.17.21",
"mobx": "^6.13.1",
"mobx-react": "^9.1.1",
"mobx-state-router": "^6.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.4",
"react-dom": "^18.2.0",
"uuid": "^11.0.5"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.25.7",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/webpack": "^5.28.5",
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.2",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.1",
"typescript": "^5.6.3",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
}
}

87
web/pom.xml Normal file
View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.mskobaro</groupId>
<artifactId>tdms</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>web</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>TDMS::WEB</name>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<executions>
<execution>
<id>install nodejs and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v20.12.0</nodeVersion>
<npmVersion>10.5.0</npmVersion>
</configuration>
</execution>
<execution>
<id>install dependencies</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>build frontend</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
<configuration>
<nodeVersion>v20.12.0</nodeVersion>
<workingDirectory>./</workingDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<filesets>
<fileset>
<directory>./dist</directory>
<includes>
<include>**</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
<fileset>
<directory>./node</directory>
<includes>
<include>**</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
</project>

34
web/src/Application.tsx Normal file
View File

@ -0,0 +1,34 @@
import ReactDOM from 'react-dom/client'
import {RouterContext, RouterView, ViewMap} from "mobx-state-router";
import {initApp} from "./utils/init";
import React from "react";
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css'
import {RootStoreContext} from './utils/context';
import {Home} from "./components/layout/Home";
import {GroupListPage} from "./components/group/GroupListPage";
import {Error} from "./components/layout/Error";
import {ParticipantListPage} from "./components/participant/ParticipantListPage";
import {DefenceListPage} from "./components/defence/DefenceListPage";
import {PreparationDirectionListPage} from "./components/dictionary/PreparationDirectionList";
import {DiplomaTopicListPage} from "./components/dictionary/DiplomaTopicList";
const viewMap: ViewMap = {
home: <Home/>,
participantList: <ParticipantListPage/>,
groupList: <GroupListPage/>,
defenceList: <DefenceListPage/>,
themeList: <DiplomaTopicListPage/>,
preparationDirectionList: <PreparationDirectionListPage/>,
error: <Error/>,
}
const rootStore = initApp();
ReactDOM.createRoot(document.getElementById('root')!).render(
<RootStoreContext.Provider value={rootStore}>
<RouterContext.Provider value={rootStore.routerStore}>
<RouterView viewMap={viewMap}/>
</RouterContext.Provider>
</RootStoreContext.Provider>
);

Some files were not shown because too many files have changed in this diff Show More