GroupCreation, ErrorHandling, Database
This commit is contained in:
parent
c2d19a7724
commit
b1ea3a670e
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,3 +31,4 @@ build/
|
|||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
/logs/app.log
|
/logs/app.log
|
||||||
|
/configurations/Start RDBMS.run.xml
|
||||||
|
|||||||
@ -97,6 +97,7 @@ public class SecurityConfiguration {
|
|||||||
httpAuthorization.requestMatchers("/api/v1/student/current").permitAll();
|
httpAuthorization.requestMatchers("/api/v1/student/current").permitAll();
|
||||||
/* GroupController */
|
/* GroupController */
|
||||||
httpAuthorization.requestMatchers("/api/v1/group/get-all").permitAll();
|
httpAuthorization.requestMatchers("/api/v1/group/get-all").permitAll();
|
||||||
|
httpAuthorization.requestMatchers("api/v1/group/create-group").hasAuthority("ROLE_ADMINISTRATOR");
|
||||||
/* deny all other api requests */
|
/* deny all other api requests */
|
||||||
httpAuthorization.requestMatchers("/api/**").denyAll();
|
httpAuthorization.requestMatchers("/api/**").denyAll();
|
||||||
/* since api already blocked, all other requests are static resources */
|
/* since api already blocked, all other requests are static resources */
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
package ru.tubryansk.tdms.controller;
|
package ru.tubryansk.tdms.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import ru.tubryansk.tdms.controller.payload.GroupDTO;
|
import ru.tubryansk.tdms.controller.payload.GroupDTO;
|
||||||
|
import ru.tubryansk.tdms.controller.payload.GroupRegistrationDTO;
|
||||||
import ru.tubryansk.tdms.service.GroupService;
|
import ru.tubryansk.tdms.service.GroupService;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@RestController("/api/v1/group/")
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/group")
|
||||||
public class GroupController {
|
public class GroupController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private GroupService groupService;
|
private GroupService groupService;
|
||||||
@ -17,4 +19,9 @@ public class GroupController {
|
|||||||
public Collection<GroupDTO> getAllGroups() {
|
public Collection<GroupDTO> getAllGroups() {
|
||||||
return groupService.getAllGroups();
|
return groupService.getAllGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/create-group")
|
||||||
|
public void createGroup(@RequestBody @Valid GroupRegistrationDTO groupRegistrationDTO) {
|
||||||
|
groupService.createGroup(groupRegistrationDTO.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package ru.tubryansk.tdms.controller;
|
package ru.tubryansk.tdms.controller;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -13,9 +12,10 @@ public class SysInfoController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SysInfoService sysInfoService;
|
private SysInfoService sysInfoService;
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@GetMapping("/version")
|
@GetMapping("/version")
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
return sysInfoService.getVersion();
|
return sysInfoService.getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @GetMapping("/status")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,6 @@ import lombok.ToString;
|
|||||||
@ToString
|
@ToString
|
||||||
public class GroupDTO {
|
public class GroupDTO {
|
||||||
private String name;
|
private String name;
|
||||||
private String principalName;
|
private String curatorName;
|
||||||
private Boolean isMePrincipal;
|
private Boolean iAmCurator;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
package ru.tubryansk.tdms.controller.payload;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class GroupRegistrationDTO {
|
||||||
|
@NotEmpty(message = "Имя группы не может быть пустым")
|
||||||
|
@Size(min = 3, max = 50, message = "Имя группы должно быть от 3 до 50 символов")
|
||||||
|
@Pattern(regexp = "^[а-яА-ЯёЁ0-9_-]*$", message = "Имя группы должно содержать только русские буквы, дефис, нижнее подчеркивание и цифры")
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@ -5,13 +5,17 @@ import jakarta.persistence.*;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "group")
|
@Table(name = "`group`")
|
||||||
public class Group {
|
public class Group {
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "id")
|
@Column(name = "id")
|
||||||
@ -20,6 +24,12 @@ public class Group {
|
|||||||
@Column(name = "name")
|
@Column(name = "name")
|
||||||
private String name;
|
private String name;
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "curator_user_id")
|
@JoinColumn(name = "curator_teacher_id")
|
||||||
private User groupCurator;
|
private Teacher groupCurator;
|
||||||
|
@Column(name = "created_at")
|
||||||
|
@CreationTimestamp
|
||||||
|
private ZonedDateTime createdAt;
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
@UpdateTimestamp
|
||||||
|
private ZonedDateTime updatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,9 +47,10 @@ public class Student {
|
|||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "diploma_topic_id")
|
@JoinColumn(name = "diploma_topic_id")
|
||||||
private DiplomaTopic diplomaTopic;
|
private DiplomaTopic diplomaTopic;
|
||||||
|
// Научный руководитель
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "mentor_user_id")
|
@JoinColumn(name = "adviser_teacher_id")
|
||||||
private User mentorUser;
|
private Teacher adviser;
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "group_id")
|
@JoinColumn(name = "group_id")
|
||||||
private Group group;
|
private Group group;
|
||||||
|
|||||||
32
server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java
Normal file
32
server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package ru.tubryansk.tdms.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "teacher")
|
||||||
|
public class Teacher {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
@OneToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private User user;
|
||||||
|
@OneToMany(mappedBy = "groupCurator")
|
||||||
|
private List<Group> curatingGroups;
|
||||||
|
@OneToMany(mappedBy = "adviser")
|
||||||
|
private List<Student> advisingStudents;
|
||||||
|
|
||||||
|
@Column(name = "created_at")
|
||||||
|
@CreationTimestamp
|
||||||
|
private ZonedDateTime createdAt;
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
@UpdateTimestamp
|
||||||
|
private ZonedDateTime updatedAt;
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package ru.tubryansk.tdms.entity;
|
|||||||
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@ -19,6 +20,7 @@ import java.util.List;
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
|
@EqualsAndHashCode(of = "id")
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "`user`")
|
@Table(name = "`user`")
|
||||||
public class User implements UserDetails {
|
public class User implements UserDetails {
|
||||||
|
|||||||
@ -10,4 +10,6 @@ public interface GroupRepository extends JpaRepository<Group, Long> {
|
|||||||
default Group findByIdThrow(Long id) {
|
default Group findByIdThrow(Long id) {
|
||||||
return this.findById(id).orElseThrow(() -> new NotFoundException(Group.class, id));
|
return this.findById(id).orElseThrow(() -> new NotFoundException(Group.class, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean existsByName(String name);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import ru.tubryansk.tdms.exception.NotFoundException;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface StudentRepository extends JpaRepository<Student, Integer> {
|
public interface StudentRepository extends JpaRepository<Student, Long> {
|
||||||
default Student findByIdThrow(Integer id) {
|
default Student findByIdThrow(Long id) {
|
||||||
return this.findById(id).orElseThrow(() -> new NotFoundException(Student.class, id));
|
return this.findById(id).orElseThrow(() -> new NotFoundException(Student.class, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package ru.tubryansk.tdms.entity.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.tubryansk.tdms.entity.Teacher;
|
||||||
|
import ru.tubryansk.tdms.exception.NotFoundException;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface TeacherRepository extends JpaRepository<Teacher, Long> {
|
||||||
|
default Teacher findByIdThrow(Long id) {
|
||||||
|
return this.findById(id).orElseThrow(() -> new NotFoundException(Teacher.class, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
import ru.tubryansk.tdms.controller.payload.ErrorResponse;
|
import ru.tubryansk.tdms.controller.payload.ErrorResponse;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@ -51,7 +52,9 @@ public class GlobalExceptionHandler {
|
|||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public ErrorResponse handleUnexpectedException(Exception e) {
|
public ErrorResponse handleUnexpectedException(Exception e) {
|
||||||
log.error("Unexpected exception.", e);
|
Random random = new Random();
|
||||||
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR);
|
long errorInx = random.nextLong();
|
||||||
|
log.error("Unexpected exception. random: {}", errorInx, e);
|
||||||
|
return new ErrorResponse("Произошла непредвиденная ошибка, обратитесь к администратору. Номер ошибки: " + errorInx, ErrorResponse.ErrorCode.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,53 @@
|
|||||||
package ru.tubryansk.tdms.service;
|
package ru.tubryansk.tdms.service;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import ru.tubryansk.tdms.controller.payload.GroupDTO;
|
import ru.tubryansk.tdms.controller.payload.GroupDTO;
|
||||||
|
import ru.tubryansk.tdms.entity.Group;
|
||||||
|
import ru.tubryansk.tdms.entity.User;
|
||||||
|
import ru.tubryansk.tdms.entity.repository.GroupRepository;
|
||||||
|
import ru.tubryansk.tdms.exception.BusinessException;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class GroupService {
|
public class GroupService {
|
||||||
|
@Autowired
|
||||||
|
private GroupRepository groupRepository;
|
||||||
|
@Autowired
|
||||||
|
private CallerService callerService;
|
||||||
|
|
||||||
public Collection<GroupDTO> getAllGroups() {
|
public Collection<GroupDTO> getAllGroups() {
|
||||||
return null;
|
List<Group> groups = groupRepository.findAll();
|
||||||
|
User callerUser = callerService.getCallerUser().orElse(null);
|
||||||
|
|
||||||
|
return groups.stream()
|
||||||
|
.map(g -> {
|
||||||
|
GroupDTO groupDTO = new GroupDTO();
|
||||||
|
groupDTO.setName(g.getName());
|
||||||
|
|
||||||
|
if (g.getGroupCurator() != null) {
|
||||||
|
groupDTO.setCuratorName(g.getGroupCurator().getUser().getFullName());
|
||||||
|
if (callerUser != null) {
|
||||||
|
groupDTO.setIAmCurator(g.getGroupCurator().getUser().equals(callerUser));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupDTO;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createGroup(String groupName) {
|
||||||
|
boolean existsByName = groupRepository.existsByName(groupName);
|
||||||
|
if (existsByName) {
|
||||||
|
throw new BusinessException("Группа с именем " + groupName + " уже существует");
|
||||||
|
}
|
||||||
|
|
||||||
|
Group group = new Group();
|
||||||
|
group.setName(groupName);
|
||||||
|
groupRepository.save(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import ru.tubryansk.tdms.entity.repository.DiplomaTopicRepository;
|
|||||||
import ru.tubryansk.tdms.entity.repository.StudentRepository;
|
import ru.tubryansk.tdms.entity.repository.StudentRepository;
|
||||||
import ru.tubryansk.tdms.exception.AccessDeniedException;
|
import ru.tubryansk.tdms.exception.AccessDeniedException;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -26,15 +25,15 @@ public class StudentService {
|
|||||||
private CallerService callerService;
|
private CallerService callerService;
|
||||||
|
|
||||||
/** @param studentToDiplomaTopic Map of @{@link Student} id and @{@link DiplomaTopic} id */
|
/** @param studentToDiplomaTopic Map of @{@link Student} id and @{@link DiplomaTopic} id */
|
||||||
public void changeDiplomaTopic(Map<Integer, Integer> studentToDiplomaTopic) {
|
// public void changeDiplomaTopic(Map<Integer, Integer> studentToDiplomaTopic) {
|
||||||
studentToDiplomaTopic.forEach(this::changeDiplomaTopic);
|
// studentToDiplomaTopic.forEach(this::changeDiplomaTopic);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public void changeDiplomaTopic(Integer studentId, Integer diplomaTopicId) {
|
// public void changeDiplomaTopic(Integer studentId, Integer diplomaTopicId) {
|
||||||
Student student = studentRepository.findByIdThrow(studentId);
|
// Student student = studentRepository.findByIdThrow(studentId);
|
||||||
DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
|
// DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
|
||||||
student.setDiplomaTopic(diplomaTopic);
|
// student.setDiplomaTopic(diplomaTopic);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public void changeCallerDiplomaTopic(Integer diplomaTopicId) {
|
public void changeCallerDiplomaTopic(Integer diplomaTopicId) {
|
||||||
DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
|
DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId);
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
create table "group"
|
|
||||||
(
|
|
||||||
id bigserial primary key,
|
|
||||||
|
|
||||||
name text not null unique,
|
|
||||||
curator_user_id bigint,
|
|
||||||
|
|
||||||
created_at timestamptz not null,
|
|
||||||
updated_at timestamptz
|
|
||||||
);
|
|
||||||
|
|
||||||
-- FOREIGN KEY
|
|
||||||
alter table "group"
|
|
||||||
add constraint fk_group_curator_user_id
|
|
||||||
foreign key (curator_user_id) references "user" (id)
|
|
||||||
on delete set null on update cascade;
|
|
||||||
|
|
||||||
-- COMMENTS
|
|
||||||
comment on table "group" is 'Таблица групп студентов';
|
|
||||||
|
|
||||||
comment on column "group".name is 'Название группы';
|
|
||||||
comment on column "group".curator_user_id is 'Идентификатор куратора группы';
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
create table teacher
|
||||||
|
(
|
||||||
|
id bigserial primary key,
|
||||||
|
user_id bigint not null,
|
||||||
|
|
||||||
|
created_at timestamptz not null,
|
||||||
|
updated_at timestamptz
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FOREIGN KEY
|
||||||
|
alter table teacher
|
||||||
|
add constraint fk_teacher_user_id
|
||||||
|
foreign key (user_id) references "user" (id)
|
||||||
|
on delete cascade on update cascade;
|
||||||
|
|
||||||
|
-- COMMENTS
|
||||||
|
comment on table teacher is 'Таблица преподавателей';
|
||||||
|
|
||||||
|
comment on column teacher.user_id is 'Идентификатор пользователя';
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
create table "group"
|
||||||
|
(
|
||||||
|
id bigserial primary key,
|
||||||
|
|
||||||
|
name text not null unique,
|
||||||
|
curator_teacher_id bigint,
|
||||||
|
|
||||||
|
created_at timestamptz not null,
|
||||||
|
updated_at timestamptz
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FOREIGN KEY
|
||||||
|
alter table "group"
|
||||||
|
add constraint fk_group_curator_teacher_id
|
||||||
|
foreign key (curator_teacher_id) references teacher (id)
|
||||||
|
on delete set null on update cascade;
|
||||||
|
|
||||||
|
-- COMMENTS
|
||||||
|
comment on table "group" is 'Таблица групп студентов';
|
||||||
|
|
||||||
|
comment on column "group".name is 'Название группы';
|
||||||
|
comment on column "group".curator_teacher_id is 'Идентификатор куратора группы';
|
||||||
@ -1,18 +1,18 @@
|
|||||||
create table student
|
create table student
|
||||||
(
|
(
|
||||||
id bigserial primary key,
|
id bigserial primary key,
|
||||||
user_id bigint not null,
|
user_id bigint not null,
|
||||||
diploma_topic_id bigint not null,
|
diploma_topic_id bigint not null,
|
||||||
mentor_user_id bigint not null,
|
adviser_teacher_id bigint not null,
|
||||||
group_id bigint not null,
|
group_id bigint not null,
|
||||||
|
|
||||||
form boolean,
|
form boolean,
|
||||||
protection_day int,
|
protection_day int,
|
||||||
protection_order int,
|
protection_order int,
|
||||||
magistracy text,
|
magistracy text,
|
||||||
digital_format_present boolean,
|
digital_format_present boolean,
|
||||||
mark_comment int,
|
mark_comment int,
|
||||||
mark_practice int,
|
mark_practice int,
|
||||||
predefence_comment text,
|
predefence_comment text,
|
||||||
normal_control text,
|
normal_control text,
|
||||||
anti_plagiarism int,
|
anti_plagiarism int,
|
||||||
@ -34,8 +34,8 @@ alter table student
|
|||||||
foreign key (diploma_topic_id) references diploma_topic (id)
|
foreign key (diploma_topic_id) references diploma_topic (id)
|
||||||
on delete set null on update cascade;
|
on delete set null on update cascade;
|
||||||
alter table student
|
alter table student
|
||||||
add constraint fk_student_mentor_user_id
|
add constraint fk_student_adviser_teacher_id
|
||||||
foreign key (mentor_user_id) references "user" (id)
|
foreign key (adviser_teacher_id) references teacher (id)
|
||||||
on delete set null on update cascade;
|
on delete set null on update cascade;
|
||||||
alter table student
|
alter table student
|
||||||
add constraint fk_student_group_id
|
add constraint fk_student_group_id
|
||||||
@ -47,7 +47,7 @@ comment on table student is 'Таблица студентов';
|
|||||||
|
|
||||||
comment on column student.user_id is 'Идентификатор пользователя';
|
comment on column student.user_id is 'Идентификатор пользователя';
|
||||||
comment on column student.diploma_topic_id is 'Идентификатор темы дипломной работы';
|
comment on column student.diploma_topic_id is 'Идентификатор темы дипломной работы';
|
||||||
comment on column student.mentor_user_id is 'Идентификатор научного руководителя';
|
comment on column student.adviser_teacher_id is 'Идентификатор научного руководителя';
|
||||||
comment on column student.group_id is 'Идентификатор группы';
|
comment on column student.group_id is 'Идентификатор группы';
|
||||||
|
|
||||||
comment on column student.form is 'Форма обучения';
|
comment on column student.form is 'Форма обучения';
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import {ComponentContext} from "../utils/ComponentContext";
|
import {ComponentContext} from "../utils/ComponentContext";
|
||||||
import {observer} from "mobx-react";
|
import {observer} from "mobx-react";
|
||||||
import {Notification, NotificationType} from "../store/NotificationStore";
|
import {Notification, NotificationType} from "../store/NotificationStore";
|
||||||
import {Card, CardBody, CardHeader, CardText, CardTitle, Col, Row} from "react-bootstrap";
|
import {Card, CardBody, CardHeader, CardText, CardTitle} from "react-bootstrap";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {action, makeObservable} from "mobx";
|
import {action, makeObservable} from "mobx";
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export class NotificationContainer extends ComponentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div style={{position: 'fixed', left: '50%', transform: 'translateX(-50%)', zIndex: 1000}}>
|
return <div style={{position: 'fixed', left: '50%', transform: 'translateX(-50%)', zIndex: 2000}}>
|
||||||
{this.forEachNotificationRender(this.notificationStore.errors, NotificationType.ERROR)}
|
{this.forEachNotificationRender(this.notificationStore.errors, NotificationType.ERROR)}
|
||||||
{this.forEachNotificationRender(this.notificationStore.successes, NotificationType.SUCCESS)}
|
{this.forEachNotificationRender(this.notificationStore.successes, NotificationType.SUCCESS)}
|
||||||
{this.forEachNotificationRender(this.notificationStore.warnings, NotificationType.WARNING)}
|
{this.forEachNotificationRender(this.notificationStore.warnings, NotificationType.WARNING)}
|
||||||
@ -51,37 +51,25 @@ class NotificationPopup extends ComponentContext<{ notification: Notification, t
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const hasTitle = !!this.props.notification.title && this.props.notification.title.length > 0;
|
const hasTitle = !!this.props.notification.title && this.props.notification.title.length > 0;
|
||||||
const closeIcon = <FontAwesomeIcon icon={'close'} onClick={this.close}/>;
|
const closeIcon = <span className={'ms-2'}><FontAwesomeIcon icon={'close'} onClick={this.close}/></span>;
|
||||||
|
|
||||||
return <Card className={`position-relative mt-3 opacity-75 ${this.cardClassName}`}>
|
return <Card className={`position-relative mt-3 opacity-75 ${this.cardClassName}`}>
|
||||||
{
|
{
|
||||||
hasTitle &&
|
hasTitle &&
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle className={'d-flex justify-content-between align-items-start'}>
|
||||||
<Row>
|
{this.props.notification.title}
|
||||||
<Col sm={11}>
|
{closeIcon}
|
||||||
{this.props.notification.title}
|
|
||||||
</Col>
|
|
||||||
<Col className={'text-end'}>
|
|
||||||
{closeIcon}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
}
|
}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<CardText>
|
<CardText className={'d-flex justify-content-between align-items-start'}>
|
||||||
<Row>
|
{this.props.notification.message}
|
||||||
<Col sm={11}>
|
{
|
||||||
{this.props.notification.message}
|
!hasTitle &&
|
||||||
</Col>
|
closeIcon
|
||||||
{
|
}
|
||||||
!hasTitle &&
|
|
||||||
<Col className={'text-end'}>
|
|
||||||
{closeIcon}
|
|
||||||
</Col>
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
</CardText>
|
</CardText>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -74,7 +74,7 @@ export class PasswordInput extends React.Component<ReactiveInputProps<string>> {
|
|||||||
className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`}
|
className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`}
|
||||||
onChange={this.onChange} value={this.props.value.value}/>
|
onChange={this.onChange} value={this.props.value.value}/>
|
||||||
</FloatingLabel>
|
</FloatingLabel>
|
||||||
<Button onClick={this.toggleShowPassword} variant={"outline-secondary"}>
|
<Button onClick={this.toggleShowPassword} variant={"outline-secondary"} className={'ms-2'}>
|
||||||
<FontAwesomeIcon icon={this.showPassword ? 'eye-slash' : 'eye'}/>
|
<FontAwesomeIcon icon={this.showPassword ? 'eye-slash' : 'eye'}/>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
55
web/src/components/group/CreateGroupModal.tsx
Normal file
55
web/src/components/group/CreateGroupModal.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {ComponentContext} from "../../utils/ComponentContext";
|
||||||
|
import {observer} from "mobx-react";
|
||||||
|
import {ModalState} from "../../utils/modalState";
|
||||||
|
import {action, computed, makeObservable, observable} from "mobx";
|
||||||
|
import {ReactiveValue} from "../../utils/reactive/reactiveValue";
|
||||||
|
import {required, strLength, strPattern} from "../../utils/reactive/validators";
|
||||||
|
import {Button, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle} from "react-bootstrap";
|
||||||
|
import {StringInput} from "../custom/controls/ReactiveControls";
|
||||||
|
import {post} from "../../utils/request";
|
||||||
|
|
||||||
|
export interface CreateGroupModalProps {
|
||||||
|
modalState: ModalState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class CreateGroupModal extends ComponentContext<CreateGroupModalProps> {
|
||||||
|
@observable name = new ReactiveValue<string>()
|
||||||
|
.addValidator(required)
|
||||||
|
.addValidator(strLength(3, 50))
|
||||||
|
.addValidator(strPattern(/^[а-яА-ЯёЁ0-9_-]*$/, "Имя группы должно содержать только русские буквы, дефис, нижнее подчеркивание и цифры"));
|
||||||
|
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
makeObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action.bound
|
||||||
|
creationRequest() {
|
||||||
|
post<void>('/group/create-group', {name: this.name.value}).then(() => {
|
||||||
|
this.notificationStore.success(`Группа ${this.name.value} создана`);
|
||||||
|
}).finally(() => {
|
||||||
|
this.props.modalState.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get formInvalid() {
|
||||||
|
return this.name.invalid || !this.name.touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Modal show={this.props.modalState.isOpen}>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Создание группы</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<StringInput value={this.name} label={'Имя группы'}/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onClick={this.creationRequest} disabled={this.formInvalid}>Создать</Button>
|
||||||
|
<Button onClick={this.props.modalState.close}>Закрыть</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,14 +6,18 @@ import {observer} from "mobx-react";
|
|||||||
import {post} from "../../utils/request";
|
import {post} from "../../utils/request";
|
||||||
import {LoginModal} from "../user/LoginModal";
|
import {LoginModal} from "../user/LoginModal";
|
||||||
import {ModalState} from "../../utils/modalState";
|
import {ModalState} from "../../utils/modalState";
|
||||||
import {action, makeObservable} from "mobx";
|
import {action, makeObservable, observable} from "mobx";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {ComponentContext} from "../../utils/ComponentContext";
|
import {ComponentContext} from "../../utils/ComponentContext";
|
||||||
|
import {CreateGroupModal} from "../group/CreateGroupModal";
|
||||||
|
import {UserRegistrationModal} from "../user/UserRegistrationModal";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Header extends ComponentContext {
|
class Header extends ComponentContext {
|
||||||
|
|
||||||
loginModalState = new ModalState();
|
@observable loginModalState = new ModalState();
|
||||||
|
@observable createGroupModalState = new ModalState();
|
||||||
|
@observable userRegistrationModalState = new ModalState();
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -37,10 +41,17 @@ class Header extends ComponentContext {
|
|||||||
user.authenticated && userStore.isAdministrator() &&
|
user.authenticated && userStore.isAdministrator() &&
|
||||||
<NavDropdown title="Пользователи">
|
<NavDropdown title="Пользователи">
|
||||||
<NavDropdown.Item as={RouterLink} routeName={'userList'} children={'Список'}/>
|
<NavDropdown.Item as={RouterLink} routeName={'userList'} children={'Список'}/>
|
||||||
<NavDropdown.Item as={RouterLink} routeName={'userRegistration'}
|
<NavDropdown.Item onClick={this.userRegistrationModalState.open}
|
||||||
children={'Зарегистрировать'}/>
|
children={'Зарегистрировать'}/>
|
||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
user.authenticated && userStore.isAdministrator() &&
|
||||||
|
<NavDropdown title="Группы">
|
||||||
|
<NavDropdown.Item as={RouterLink} routeName={'groupList'} children={'Список'}/>
|
||||||
|
<NavDropdown.Item onClick={this.createGroupModalState.open} children={'Добавить'}/>
|
||||||
|
</NavDropdown>
|
||||||
|
}
|
||||||
</Nav>
|
</Nav>
|
||||||
|
|
||||||
<Nav className="ms-auto">
|
<Nav className="ms-auto">
|
||||||
@ -63,6 +74,8 @@ class Header extends ComponentContext {
|
|||||||
</Navbar>
|
</Navbar>
|
||||||
</header>
|
</header>
|
||||||
<LoginModal modalState={this.loginModalState}/>
|
<LoginModal modalState={this.loginModalState}/>
|
||||||
|
<CreateGroupModal modalState={this.createGroupModalState}/>
|
||||||
|
<UserRegistrationModal modalState={this.userRegistrationModalState}/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import {observer} from "mobx-react";
|
import {observer} from "mobx-react";
|
||||||
import {DefaultPage} from "../layout/DefaultPage";
|
|
||||||
import {action, computed, makeObservable, observable} from "mobx";
|
import {action, computed, makeObservable, observable} from "mobx";
|
||||||
import {Button, Col, Form, Row} from "react-bootstrap";
|
import {Button, Col, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle, Row} from "react-bootstrap";
|
||||||
import {UserRegistrationDTO} from "../../models/registration";
|
import {UserRegistrationDTO} from "../../models/registration";
|
||||||
import {post} from "../../utils/request";
|
import {post} from "../../utils/request";
|
||||||
import {ReactiveValue} from "../../utils/reactive/reactiveValue";
|
import {ReactiveValue} from "../../utils/reactive/reactiveValue";
|
||||||
@ -17,9 +16,15 @@ import {
|
|||||||
phone,
|
phone,
|
||||||
required
|
required
|
||||||
} from "../../utils/reactive/validators";
|
} from "../../utils/reactive/validators";
|
||||||
|
import {ComponentContext} from "../../utils/ComponentContext";
|
||||||
|
import {ModalState} from "../../utils/modalState";
|
||||||
|
|
||||||
|
export interface UserRegistrationModalProps {
|
||||||
|
modalState: ModalState;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class UserRegistration extends DefaultPage {
|
export class UserRegistrationModal extends ComponentContext<UserRegistrationModalProps> {
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
@ -31,7 +36,6 @@ export class UserRegistration extends DefaultPage {
|
|||||||
@observable email = new ReactiveValue<string>().addValidator(required).addValidator(email);
|
@observable email = new ReactiveValue<string>().addValidator(required).addValidator(email);
|
||||||
@observable numberPhone = new ReactiveValue<string>().addValidator(required).addValidator(phone).setAuto('+7');
|
@observable numberPhone = new ReactiveValue<string>().addValidator(required).addValidator(phone).setAuto('+7');
|
||||||
|
|
||||||
|
|
||||||
@observable accountType = new ReactiveValue<string>().addValidator(required).addValidator((value) => {
|
@observable accountType = new ReactiveValue<string>().addValidator(required).addValidator((value) => {
|
||||||
if (!['student', 'admin'].includes(value)) {
|
if (!['student', 'admin'].includes(value)) {
|
||||||
return 'Тип аккаунта должен быть "СТУДЕНТ" или "АДМИНИСТРАТОР"';
|
return 'Тип аккаунта должен быть "СТУДЕНТ" или "АДМИНИСТРАТОР"';
|
||||||
@ -64,9 +68,13 @@ export class UserRegistration extends DefaultPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get page() {
|
render() {
|
||||||
return <div className={'w-75 ms-auto me-auto'}>
|
return <Modal show={this.props.modalState.isOpen} size={'lg'}>
|
||||||
<Form>
|
<ModalHeader>
|
||||||
|
<ModalTitle>Регистрация пользователя</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<StringInput value={this.login} label={"Логин"}/>
|
<StringInput value={this.login} label={"Логин"}/>
|
||||||
@ -78,10 +86,12 @@ export class UserRegistration extends DefaultPage {
|
|||||||
<StringInput value={this.numberPhone} label={"Телефон"}/>
|
<StringInput value={this.numberPhone} label={"Телефон"}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<SelectButtonInput value={this.accountType} label={'Тип аккаунта'}/>
|
<SelectButtonInput value={this.accountType} label={'Тип аккаунта'}/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
<Button disabled={this.formInvalid} onClick={this.submit}>Зарегистрировать</Button>
|
<Button disabled={this.formInvalid} onClick={this.submit}>Зарегистрировать</Button>
|
||||||
</Form>
|
<Button onClick={this.props.modalState.close} variant={'secondary'}>Закрыть</Button>
|
||||||
</div>
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
web/src/models/error.ts
Normal file
12
web/src/models/error.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export enum ErrorCode {
|
||||||
|
BUSINESS_ERROR = 'BUSINESS_ERROR',
|
||||||
|
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||||
|
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
||||||
|
NOT_FOUND = 'NOT_FOUND',
|
||||||
|
ACCESS_DENIED = 'ACCESS_DENIED',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorResponse {
|
||||||
|
message: string;
|
||||||
|
errorCode: ErrorCode;
|
||||||
|
}
|
||||||
@ -9,9 +9,6 @@ export const routes: Route[] = [{
|
|||||||
}, {
|
}, {
|
||||||
name: 'userList',
|
name: 'userList',
|
||||||
pattern: '/users',
|
pattern: '/users',
|
||||||
}, {
|
|
||||||
name: 'userRegistration',
|
|
||||||
pattern: '/user-registration',
|
|
||||||
}, {
|
}, {
|
||||||
name: 'error',
|
name: 'error',
|
||||||
pattern: '/error',
|
pattern: '/error',
|
||||||
|
|||||||
@ -3,12 +3,10 @@ import Home from "../components/layout/Home";
|
|||||||
import Error from "../components/layout/Error";
|
import Error from "../components/layout/Error";
|
||||||
import UserProfilePage from "../components/user/UserProfilePage";
|
import UserProfilePage from "../components/user/UserProfilePage";
|
||||||
import {UserList} from "../components/user/UserList";
|
import {UserList} from "../components/user/UserList";
|
||||||
import {UserRegistration} from "../components/user/UserRegistration";
|
|
||||||
|
|
||||||
export const viewMap: ViewMap = {
|
export const viewMap: ViewMap = {
|
||||||
root: <Home/>,
|
root: <Home/>,
|
||||||
profile: <UserProfilePage/>,
|
profile: <UserProfilePage/>,
|
||||||
userList: <UserList/>,
|
userList: <UserList/>,
|
||||||
userRegistration: <UserRegistration/>,
|
|
||||||
error: <Error/>,
|
error: <Error/>,
|
||||||
}
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
import {action, computed, makeObservable, observable, reaction} from "mobx";
|
import {action, computed, makeObservable, observable, reaction} from "mobx";
|
||||||
import React from "react";
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
|
|
||||||
export class ReactiveValue<T> {
|
export class ReactiveValue<T> {
|
||||||
@ -95,33 +93,33 @@ export class ReactiveValue<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NumberField extends ReactiveValue<number> {
|
// export class NumberField extends ReactiveValue<number> {
|
||||||
constructor(fireImmediately: boolean = false) {
|
// constructor(fireImmediately: boolean = false) {
|
||||||
super(fireImmediately);
|
// super(fireImmediately);
|
||||||
makeObservable(this);
|
// makeObservable(this);
|
||||||
this.addValidator(value => {
|
// this.addValidator(value => {
|
||||||
if (_.isNaN(value)) {
|
// if (_.isNaN(value)) {
|
||||||
return 'Должно быть числом';
|
// return 'Должно быть числом';
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return null;
|
// return null;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@action.bound
|
// @action.bound
|
||||||
onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
// onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
this.set(_.toNumber(event.currentTarget.value));
|
// this.set(_.toNumber(event.currentTarget.value));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
export class BooleanField extends ReactiveValue<boolean> {
|
// export class BooleanField extends ReactiveValue<boolean> {
|
||||||
constructor(fireImmediately: boolean = false) {
|
// constructor(fireImmediately: boolean = false) {
|
||||||
super(fireImmediately);
|
// super(fireImmediately);
|
||||||
makeObservable(this);
|
// makeObservable(this);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@action.bound
|
// @action.bound
|
||||||
onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
// onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
this.set(event.currentTarget.checked);
|
// this.set(event.currentTarget.checked);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -3,17 +3,6 @@ export const required = (value: any, field = 'Поле') => {
|
|||||||
return `${field} обязательно для заполнения`;
|
return `${field} обязательно для заполнения`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const greaterThan = (min: number) => (value: number, field = 'Значение') => {
|
|
||||||
if (value && value <= min) {
|
|
||||||
return `${field} должно быть больше ${min}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const lessThan = (max: number) => (value: number, field = 'Значение') => {
|
|
||||||
if (value && value >= max) {
|
|
||||||
return `${field} должно быть меньше ${max}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const equals = (expected: any) => (value: any, field = 'Поле') => {
|
export const equals = (expected: any) => (value: any, field = 'Поле') => {
|
||||||
if (value && value !== expected) {
|
if (value && value !== expected) {
|
||||||
@ -21,13 +10,31 @@ export const equals = (expected: any) => (value: any, field = 'Поле') => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const length = (min: number, max: number) => (value: string, field = 'Поле') => {
|
export const number = (value: string, field = 'Поле') => {
|
||||||
|
if (!/^\d+$/.test(value)) {
|
||||||
|
return `${field} должно быть числом`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const numGreaterThan = (min: number) => (value: number, field = 'Значение') => {
|
||||||
|
if (value && value <= min) {
|
||||||
|
return `${field} должно быть больше ${min}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const numLessThan = (max: number) => (value: number, field = 'Значение') => {
|
||||||
|
if (value && value >= max) {
|
||||||
|
return `${field} должно быть меньше ${max}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const strLength = (min: number, max: number) => (value: string, field = 'Поле') => {
|
||||||
if (value && (value.length < min || value.length > max)) {
|
if (value && (value.length < min || value.length > max)) {
|
||||||
return `${field} должно содержать от ${min} до ${max} символов`;
|
return `${field} должно содержать от ${min} до ${max} символов`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pattern = (regexp: RegExp, message: string) => (value: string, field = '') => {
|
export const strPattern = (regexp: RegExp, message: string) => (value: string, field = '') => {
|
||||||
if (!regexp.test(value)) {
|
if (!regexp.test(value)) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -81,12 +88,6 @@ export const nameLength = (value: string, field = 'Поле') => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const number = (value: string, field = 'Поле') => {
|
|
||||||
if (!/^\d+$/.test(value)) {
|
|
||||||
return `${field} должно быть числом`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const date = (value: string, field = 'Поле') => {
|
export const date = (value: string, field = 'Поле') => {
|
||||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||||
return `${field} должно быть датой в формате ГГГГ-ММ-ДД`;
|
return `${field} должно быть датой в формате ГГГГ-ММ-ДД`;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import axios, {AxiosError, AxiosRequestConfig} from "axios";
|
import axios, {AxiosError, AxiosRequestConfig} from "axios";
|
||||||
import {NotificationService} from "../services/NotificationService";
|
import {NotificationService} from "../services/NotificationService";
|
||||||
|
import {ErrorResponse} from "../models/error";
|
||||||
|
|
||||||
export const apiUrl = "http://localhost:8080/api/v1/";
|
export const apiUrl = "http://localhost:8080/api/v1/";
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ export const request = async <R>(config: AxiosRequestConfig<any>, doReject: bool
|
|||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
}).catch((error: AxiosError) => {
|
}).catch((error: AxiosError) => {
|
||||||
if (showError) {
|
if (showError) {
|
||||||
if (error.response) NotificationService.error(JSON.stringify(error.response.data), 'Сервер вернул ошибку, код статуса: ' + error.response.status);
|
if (error.response) NotificationService.error((error.response.data as ErrorResponse).message, 'Сервер вернул ошибку, код статуса: ' + error.response.status);
|
||||||
else if (error.request) NotificationService.error(error.request, 'Не удалось получить ответ от сервера');
|
else if (error.request) NotificationService.error(error.request, 'Не удалось получить ответ от сервера');
|
||||||
else NotificationService.error(error.message, 'Запрос отправить не удалось');
|
else NotificationService.error(error.message, 'Запрос отправить не удалось');
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user