diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3ff3e3..b064dce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,30 +1,28 @@ -image: ubuntu:18.04 - -cache: - key: "$CI_PROJECT_ID" - paths: - - .gradle/ +image: romanov73/is:ng-tracker-container-11 variables: GRADLE_OPTS: "-Dorg.gradle.daemon=false" before_script: - - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - - apt-get install openjdk-8-jdk git -y - - eval $(ssh-agent -s) - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null - - mkdir -p ~/.ssh - - chmod 700 ~/.ssh + - service postgresql stop + - service postgresql start + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - git log --pretty="%cn;%cd;%s" > src/main/resources/commits.log build: stage: build script: ./gradlew assemble - cache: - key: "$CI_PROJECT_ID" - policy: push - paths: - - build - - .gradle + +checkRun: + stage: test + script: ./gradlew bootRun -Dng-tracker.check-run=true + +checkStyle: + stage: test + script: ./gradlew check -x test deploy: stage: deploy @@ -32,9 +30,6 @@ deploy: - sh deploy/gdccloud/deploy.sh only: - dev - cache: - key: "$CI_PROJECT_ID" - policy: pull - paths: - - build - - .gradle \ No newline at end of file + environment: + name: staging + url: http://193.110.3.124:8080 diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md new file mode 100644 index 0000000..c9e1757 --- /dev/null +++ b/.gitlab/issue_templates/feature.md @@ -0,0 +1,46 @@ +## Краткое описание задачи + +Что требуется сделать + + +## `Опционально` Список верстаемых страниц + +Будут затронуты страницы: +* page1.html +* page2.html +* page3.html + +## `Опционально` Список затрагиваемых модулей + +При реализации задачи потребуется также реализовать методы контроллера + + +## `Опционально` Список реализуемых функций + +После выполнения задачи станет доступным: +* просмотр `entity_name` +* редактирование `entity_name` +* валидация `entity_name` + +## `Опционально` Сценарии работы + +Сценарий просмотра: +1. Зайти на главную страницу приложения +2. Перейти в раздел `section_name` +3. Перейти к списку `entity_name` +4. Выбрать нужную `entity_name` и нажать на нее + +Сценарий редактирования: +1. Зайти на главную страницу приложения +2. Перейти в раздел `section_name` +3. Перейти к списку `entity_name` +4. Выбрать нужную `entity_name` и нажать на нее +5. Внести нужные правки в поля и сохранить + +## Описание конечного результата, дающего возможность проверки выполнения задачи: компоненты проекта, сценарии работы + +* Сверстаны страницы page1.hml, page2.hml, page3.hml +* Реализован контроллер для обслуживания страниц +* Сохранение в БД еще не реализовано +* Валидация происходит по полям `field1, field2` +* Сценарий просмотра проверяется при ручном внечении записей в БД diff --git a/README.md b/README.md index ca4db83..f4f6ddd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ 2. Создать новую функцию автоматизированной системы управления задчами - интеллектуальную постановку задач исполнителям. 3. Получить платформу для обкатки научных проектов магистрантов по созданию интеллектуальны систем. 4. Получить проект для обучения бакалавров современым технологиям разработки. +5. Создать систему хранения и трансляции опыта между участниками научной группы. [Демо версия доступна здесь](http://193.110.3.124:8080) diff --git a/build.gradle b/build.gradle index 4e0e9cb..0edeb92 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - versionSpringBoot = '1.5.10.RELEASE' + versionSpringBoot = '2.1.3.RELEASE' } repositories { @@ -30,6 +30,10 @@ bootRun.dependsOn checkstyleMain sourceCompatibility = 1.8 targetCompatibility = 1.8 +bootRun { + systemProperties = System.properties +} + checkstyle { project.ext.checkstyleVersion = '8.8' @@ -101,18 +105,16 @@ dependencies { compile group: 'org.springframework.boot', name: 'spring-boot-starter-jetty' compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa' compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf' - compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity4' + compile group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect' + compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity5' compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5' - compile group: 'postgresql', name: 'postgresql', version: '9.1-901.jdbc4' + compile group: 'org.postgresql', name: 'postgresql', version: '42.2.5' - compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3' + compile group: 'org.liquibase', name: 'liquibase-core', version: '3.6.3' compile group: 'com.mattbertolini', name: 'liquibase-slf4j', version: '2.0.0' - compile group: 'org.apache.poi', name: 'poi', version: '3.9' - compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.9' - compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7' compile group: 'org.webjars', name: 'bootstrap', version: '4.1.0' @@ -121,8 +123,10 @@ dependencies { compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1' compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0' - compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0' - compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0' + compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.0' + compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.0' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test' + testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.3.1' + } \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml index 5877ec9..828cdaa 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -100,7 +100,7 @@ - + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 568c50b..663c448 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip diff --git a/src/main/java/ru/ulstu/NgTrackerApplication.java b/src/main/java/ru/ulstu/NgTrackerApplication.java index 5e6ee4f..fbe3a2f 100644 --- a/src/main/java/ru/ulstu/NgTrackerApplication.java +++ b/src/main/java/ru/ulstu/NgTrackerApplication.java @@ -2,13 +2,30 @@ package ru.ulstu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import ru.ulstu.configuration.ApplicationProperties; import ru.ulstu.core.repository.JpaDetachableRepositoryImpl; @SpringBootApplication @EnableJpaRepositories(repositoryBaseClass = JpaDetachableRepositoryImpl.class) public class NgTrackerApplication { + private final ApplicationProperties applicationProperties; + + public NgTrackerApplication(ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; + } + public static void main(String[] args) { SpringApplication.run(NgTrackerApplication.class, args); } + + @EventListener(ApplicationReadyEvent.class) + public void doSomethingAfterStartup() { + System.out.println("hello world, I have just started up"); + if (applicationProperties.isCheckRun()) { + System.exit(0); + } + } } diff --git a/src/main/java/ru/ulstu/conference/controller/ConferenceController.java b/src/main/java/ru/ulstu/conference/controller/ConferenceController.java new file mode 100644 index 0000000..a416345 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/controller/ConferenceController.java @@ -0,0 +1,156 @@ +package ru.ulstu.conference.controller; + + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.conference.model.ConferenceDto; +import ru.ulstu.conference.model.ConferenceFilterDto; +import ru.ulstu.conference.model.ConferenceUser; +import ru.ulstu.conference.service.ConferenceService; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.user.model.User; +import springfox.documentation.annotations.ApiIgnore; + +import javax.validation.Valid; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; +import static ru.ulstu.core.controller.Navigation.CONFERENCES_PAGE; +import static ru.ulstu.core.controller.Navigation.CONFERENCE_PAGE; +import static ru.ulstu.core.controller.Navigation.REDIRECT_TO; + + +@Controller() +@RequestMapping(value = "/conferences") +@ApiIgnore +public class ConferenceController { + + private final ConferenceService conferenceService; + + public ConferenceController(ConferenceService conferenceService) { + this.conferenceService = conferenceService; + } + + @GetMapping("/conferences") + public void getConferences(ModelMap modelMap) { + modelMap.put("filteredConferences", new ConferenceFilterDto(conferenceService.findAllDto())); + } + + @PostMapping("/conferences") + public void filterConferences(@Valid ConferenceFilterDto conferenceFilterDto, ModelMap modelMap) { + modelMap.put("filteredConferences", new ConferenceFilterDto(conferenceService.filter(conferenceFilterDto), + conferenceFilterDto.getFilterUserId(), + conferenceFilterDto.getYear())); + } + + @GetMapping("/dashboard") + public void getDashboard(ModelMap modelMap) { + modelMap.put("conferences", conferenceService.findAllActiveDto()); + } + + @GetMapping("/conference") + public void getConference(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + modelMap.put("conferenceDto", conferenceService.getExistConferenceById(id)); + } else { + modelMap.put("conferenceDto", conferenceService.getNewConference()); + } + } + + @PostMapping(value = "/conference", params = "save") + public String save(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException { + filterEmptyDeadlines(conferenceDto); + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.save(conferenceDto); + return String.format(REDIRECT_TO, CONFERENCES_PAGE); + + } + + @GetMapping("/delete/{conference-id}") + public String delete(@PathVariable("conference-id") Integer conferenceId) throws IOException { + conferenceService.delete(conferenceId); + return String.format(REDIRECT_TO, CONFERENCES_PAGE); + } + + @PostMapping(value = "/conference", params = "addDeadline") + public String addDeadline(@Valid ConferenceDto conferenceDto, Errors errors) { + filterEmptyDeadlines(conferenceDto); + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceDto.getDeadlines().add(new Deadline()); + return CONFERENCE_PAGE; + } + + @PostMapping(value = "/conference", params = "removeDeadline") + public String removeDeadline(@Valid ConferenceDto conferenceDto, Errors errors, + @RequestParam(value = "removeDeadline") Integer deadlineIndex) throws IOException { + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.removeDeadline(conferenceDto, deadlineIndex); + return CONFERENCE_PAGE; + } + + @PostMapping(value = "/conference", params = "removePaper") + public String removePaper(@Valid ConferenceDto conferenceDto, Errors errors, + @RequestParam(value = "removePaper") Integer paperIndex) throws IOException { + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.removePaper(conferenceDto, paperIndex); + return CONFERENCE_PAGE; + } + + @PostMapping(value = "/conference", params = "takePart") + public String takePart(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException { + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.takePart(conferenceDto); + return CONFERENCE_PAGE; + } + + @ModelAttribute("allParticipation") + public List getAllParticipation() { + return conferenceService.getAllParticipations(); + } + + @ModelAttribute("allDeposit") + public List getAllDeposit() { + return conferenceService.getAllDeposit(); + } + + @ModelAttribute("allUsers") + public List getAllUsers() { + return conferenceService.getAllUsers(); + } + + @ModelAttribute("allYears") + public List getAllYears() { + List years = new ArrayList<>(); + for (int i = Calendar.getInstance().get(Calendar.YEAR); i > 2010; i--) { + years.add(i); + } + return years; + } + + private void filterEmptyDeadlines(ConferenceDto conferenceDto) { + conferenceDto.setDeadlines(conferenceDto.getDeadlines().stream() + .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/ru/ulstu/conference/model/Conference.java b/src/main/java/ru/ulstu/conference/model/Conference.java new file mode 100644 index 0000000..9831a0e --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/Conference.java @@ -0,0 +1,138 @@ +package ru.ulstu.conference.model; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.springframework.format.annotation.DateTimeFormat; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Entity +@Table(name = "conference") +public class Conference extends BaseEntity { + + @NotBlank + private String title; + + private String description; + + private String url; + + private int ping; + + @Column(name = "begin_date") + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date beginDate; + + @Column(name = "end_date") + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date endDate; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "conference_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + @OrderBy("date") + private List deadlines = new ArrayList<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "paper_conference", + joinColumns = {@JoinColumn(name = "conference_id")}, + inverseJoinColumns = {@JoinColumn(name = "paper_id")}) + private List papers = new ArrayList<>(); + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "conference_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + private List users = new ArrayList<>(); + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getPing() { + return ping; + } + + public void setPing(int ping) { + this.ping = ping; + } + + public Date getBeginDate() { + return beginDate; + } + + public void setBeginDate(Date beginDate) { + this.beginDate = beginDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public List getPapers() { + return papers; + } + + public void setPapers(List papers) { + this.papers = papers; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} diff --git a/src/main/java/ru/ulstu/conference/model/ConferenceDto.java b/src/main/java/ru/ulstu/conference/model/ConferenceDto.java new file mode 100644 index 0000000..6264f62 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/ConferenceDto.java @@ -0,0 +1,222 @@ +package ru.ulstu.conference.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.format.annotation.DateTimeFormat; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; + +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static ru.ulstu.core.util.StreamApiUtils.convert; + +public class ConferenceDto { + + private final static String BEGIN_DATE = "Начало: "; + private final static String END_DATE = "Конец: "; + + private Integer id; + @NotEmpty + @Size(min = 2, max = 400) + private String title; + @Size(max = 500) + private String description = ""; + @Size(max = 255) + private String url = ""; + private int ping = 0; + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date beginDate = new Date(); + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date endDate = new Date(); + private List deadlines = new ArrayList<>(); + private List removedDeadlineIds = new ArrayList<>(); + private List userIds = new ArrayList<>(); + private List paperIds = new ArrayList<>(); + private List papers = new ArrayList<>(); + private List notSelectedPapers = new ArrayList<>(); + private List users = new ArrayList<>(); + private boolean disabledTakePart = false; + + public ConferenceDto() { + } + + @JsonCreator + public ConferenceDto(@JsonProperty("id") Integer id, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("url") String url, + @JsonProperty("ping") Integer ping, + @JsonProperty("beginDate") Date beginDate, + @JsonProperty("endDate") Date endDate, + @JsonProperty("deadlines") List deadlines, + @JsonProperty("userIds") List userIds, + @JsonProperty("paperIds") List paperIds, + @JsonProperty("users") List users, + @JsonProperty("papers") List papers, + @JsonProperty("notSelectedPapers") List notSelectedPapers, + @JsonProperty("notSelectedPapers") Boolean disabledTakePart) { + this.id = id; + this.title = title; + this.description = description; + this.url = url; + this.ping = ping; + this.beginDate = beginDate; + this.endDate = endDate; + this.deadlines = deadlines; + this.userIds = userIds; + this.paperIds = paperIds; + this.users = users; + this.papers = papers; + this.notSelectedPapers = notSelectedPapers; + this.disabledTakePart = disabledTakePart; + } + + public ConferenceDto(Conference conference) { + this.id = conference.getId(); + this.title = conference.getTitle(); + this.description = conference.getDescription(); + this.url = conference.getUrl(); + this.ping = conference.getPing(); + this.beginDate = conference.getBeginDate(); + this.endDate = conference.getEndDate(); + this.deadlines = conference.getDeadlines(); + this.userIds = convert(conference.getUsers(), BaseEntity::getId); + this.paperIds = convert(conference.getPapers(), BaseEntity::getId); + this.users = conference.getUsers(); + this.papers = conference.getPapers(); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getPing() { + return ping; + } + + public void setPing(int ping) { + this.ping = ping; + } + + public Date getBeginDate() { + return beginDate; + } + + public void setBeginDate(Date beginDate) { + this.beginDate = beginDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + public List getPaperIds() { + return paperIds; + } + + public void setPaperIds(List paperIds) { + this.paperIds = paperIds; + } + + public List getPapers() { + return papers; + } + + public void setPapers(List papers) { + this.papers = papers; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public boolean isDisabledTakePart() { + return disabledTakePart; + } + + public void setDisabledTakePart(boolean disabledTakePart) { + this.disabledTakePart = disabledTakePart; + } + + public List getRemovedDeadlineIds() { + return removedDeadlineIds; + } + + public void setRemovedDeadlineIds(List removedDeadlineIds) { + this.removedDeadlineIds = removedDeadlineIds; + } + + public List getNotSelectedPapers() { + return notSelectedPapers; + } + + public void setNotSelectedPapers(List notSelectedPapers) { + this.notSelectedPapers = notSelectedPapers; + } + + public String getDatesString() { + return BEGIN_DATE + beginDate.toString().split(" ")[0] + " " + END_DATE + endDate.toString().split(" ")[0]; + } + +} diff --git a/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java b/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java new file mode 100644 index 0000000..8503b40 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java @@ -0,0 +1,47 @@ +package ru.ulstu.conference.model; + +import java.util.List; + +public class ConferenceFilterDto { + + private List conferences; + private Integer filterUserId; + private Integer year; + + public ConferenceFilterDto() { + } + + public ConferenceFilterDto(List conferenceDtos, Integer filterUserId, Integer year) { + this.conferences = conferenceDtos; + this.filterUserId = filterUserId; + this.year = year; + } + + public ConferenceFilterDto(List conferenceDtos) { + this(conferenceDtos, null, null); + } + + public List getConferences() { + return conferences; + } + + public void setConferences(List conferences) { + this.conferences = conferences; + } + + public Integer getFilterUserId() { + return filterUserId; + } + + public void setFilterUserId(Integer filterUserId) { + this.filterUserId = filterUserId; + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } +} diff --git a/src/main/java/ru/ulstu/conference/model/ConferenceUser.java b/src/main/java/ru/ulstu/conference/model/ConferenceUser.java new file mode 100644 index 0000000..63eeb6d --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/ConferenceUser.java @@ -0,0 +1,110 @@ +package ru.ulstu.conference.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.user.model.User; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +@Entity +@Table(name = "users_conference") +public class ConferenceUser extends BaseEntity { + + public enum Participation { + INTRAMURAL("Очная"), + EXTRAMURAL("Заочная"); + + private String participationName; + + Participation(String name) { + this.participationName = name; + } + + public String getParticipation() { + return participationName; + } + } + + public enum Deposit { + ARTICLE("Статья"), + REPORT("Доклад"), + PRESENTATION("Презентация"); + + private String depositName; + + Deposit(String name) { + this.depositName = name; + } + + public String getDeposit() { + return depositName; + } + } + + + @NotNull + @Column(name = "participation", nullable = false) + @Enumerated(value = EnumType.STRING) + private Participation participation = Participation.INTRAMURAL; + + @NotNull + @Column(name = "deposit", nullable = false) + @Enumerated(value = EnumType.STRING) + private Deposit deposit = Deposit.ARTICLE; + + @ManyToOne(optional = false) + @JoinColumn(name = "users_id") + private User user; + + public ConferenceUser() { + } + + public ConferenceUser(User user) { + this.user = user; + } + + @JsonCreator + public ConferenceUser(@JsonProperty("id") Integer id, + @JsonProperty("deposit") Participation participation, + @JsonProperty("deposit") Deposit deposit, + @JsonProperty("user") User user) { + this.setId(id); + this.participation = participation; + this.deposit = deposit; + this.user = user; + } + + + public Participation getParticipation() { + return participation; + } + + public void setParticipation(Participation participation) { + this.participation = participation; + } + + public Deposit getDeposit() { + return deposit; + } + + public void setDeposit(Deposit deposit) { + this.deposit = deposit; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + +} diff --git a/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java b/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java new file mode 100644 index 0000000..cb8a488 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java @@ -0,0 +1,19 @@ +package ru.ulstu.conference.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.ulstu.conference.model.Conference; +import ru.ulstu.user.model.User; + +import java.util.Date; +import java.util.List; + +public interface ConferenceRepository extends JpaRepository { + @Query("SELECT c FROM Conference c LEFT JOIN c.users u WHERE (:user IS NULL OR u.user = :user) " + + "AND (YEAR(c.beginDate) = :year OR :year IS NULL) ORDER BY begin_date DESC") + List findByUserAndYear(@Param("user") User user, @Param("year") Integer year); + + @Query("SELECT c FROM Conference c WHERE c.beginDate > :date") + List findAllActive(@Param("date") Date date); +} diff --git a/src/main/java/ru/ulstu/conference/repository/ConferenceUserRepository.java b/src/main/java/ru/ulstu/conference/repository/ConferenceUserRepository.java new file mode 100644 index 0000000..5e654d5 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/repository/ConferenceUserRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.conference.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.conference.model.ConferenceUser; + +public interface ConferenceUserRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java b/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java new file mode 100644 index 0000000..ff9f69a --- /dev/null +++ b/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java @@ -0,0 +1,7 @@ +package ru.ulstu.conference.service; + +import org.springframework.stereotype.Service; + +@Service +public class ConferenceNotificationService { +} diff --git a/src/main/java/ru/ulstu/conference/service/ConferenceService.java b/src/main/java/ru/ulstu/conference/service/ConferenceService.java new file mode 100644 index 0000000..dfd0f8a --- /dev/null +++ b/src/main/java/ru/ulstu/conference/service/ConferenceService.java @@ -0,0 +1,176 @@ +package ru.ulstu.conference.service; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.conference.model.Conference; +import ru.ulstu.conference.model.ConferenceDto; +import ru.ulstu.conference.model.ConferenceFilterDto; +import ru.ulstu.conference.model.ConferenceUser; +import ru.ulstu.conference.repository.ConferenceRepository; +import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.paper.model.Paper; +import ru.ulstu.paper.service.PaperService; +import ru.ulstu.user.model.User; +import ru.ulstu.user.service.UserService; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; + +@Service +public class ConferenceService { + private final static int MAX_DISPLAY_SIZE = 40; + + private final ConferenceRepository conferenceRepository; + private final ConferenceUserService conferenceUserService; + private final DeadlineService deadlineService; + private final PaperService paperService; + private final UserService userService; + + public ConferenceService(ConferenceRepository conferenceRepository, + ConferenceUserService conferenceUserService, + DeadlineService deadlineService, + PaperService paperService, + UserService userService) { + this.conferenceRepository = conferenceRepository; + this.conferenceUserService = conferenceUserService; + this.deadlineService = deadlineService; + this.paperService = paperService; + this.userService = userService; + } + + public ConferenceDto getExistConferenceById(Integer id) { + ConferenceDto conferenceDto = findOneDto(id); + conferenceDto.setNotSelectedPapers(getNotSelectPapers(conferenceDto.getPaperIds())); + conferenceDto.setDisabledTakePart(isCurrentUserParticipant(conferenceDto.getUsers())); + return conferenceDto; + } + + public ConferenceDto getNewConference() { + ConferenceDto conferenceDto = new ConferenceDto(); + conferenceDto.setNotSelectedPapers(getNotSelectPapers(new ArrayList())); + return conferenceDto; + } + + + public List findAll() { + return conferenceRepository.findAll(new Sort(Sort.Direction.DESC, "beginDate")); + } + + public List findAllDto() { + List conferences = convert(findAll(), ConferenceDto::new); + conferences.forEach(conferenceDto -> conferenceDto.setTitle(StringUtils.abbreviate(conferenceDto.getTitle(), MAX_DISPLAY_SIZE))); + return conferences; + } + + public ConferenceDto findOneDto(Integer id) { + return new ConferenceDto(conferenceRepository.getOne(id)); + } + + public void save(ConferenceDto conferenceDto) throws IOException { + if (isEmpty(conferenceDto.getId())) { + create(conferenceDto); + } else { + update(conferenceDto); + } + } + + @Transactional + public Integer create(ConferenceDto conferenceDto) throws IOException { + Conference newConference = copyFromDto(new Conference(), conferenceDto); + newConference = conferenceRepository.save(newConference); + return newConference.getId(); + } + + @Transactional + public Integer update(ConferenceDto conferenceDto) throws IOException { + Conference conference = conferenceRepository.getOne(conferenceDto.getId()); + conferenceRepository.save(copyFromDto(conference, conferenceDto)); + conferenceDto.getRemovedDeadlineIds().forEach(deadlineService::remove); + return conference.getId(); + } + + @Transactional + public void delete(Integer conferenceId) { + if (conferenceRepository.existsById(conferenceId)) { + conferenceRepository.deleteById(conferenceId); + } + } + + public void removeDeadline(ConferenceDto conferenceDto, Integer deadlineIndex) throws IOException { + if (conferenceDto.getDeadlines().get(deadlineIndex).getId() != null) { + conferenceDto.getRemovedDeadlineIds().add(conferenceDto.getDeadlines().get(deadlineIndex).getId()); + } + conferenceDto.getDeadlines().remove((int) deadlineIndex); + } + + public void removePaper(ConferenceDto conferenceDto, Integer paperIndex) throws IOException { + Paper removedPaper = conferenceDto.getPapers().remove((int) paperIndex); + conferenceDto.getNotSelectedPapers().add(removedPaper); + } + + public void takePart(ConferenceDto conferenceDto) throws IOException { + conferenceDto.getUsers().add(new ConferenceUser(userService.getCurrentUser())); + conferenceDto.setDisabledTakePart(true); + } + + public List getNotSelectPapers(List paperIds) { + return paperService.findAllNotSelect(paperIds); + } + + public List getAllUsers() { + return userService.findAll(); + } + + public List getAllParticipations() { + return Arrays.asList(ConferenceUser.Participation.values()); + } + + public List getAllDeposit() { + return Arrays.asList(ConferenceUser.Deposit.values()); + } + + private Conference copyFromDto(Conference conference, ConferenceDto conferenceDto) throws IOException { + conference.setTitle(conferenceDto.getTitle()); + conference.setDescription(conferenceDto.getDescription()); + conference.setUrl(conferenceDto.getUrl()); + conference.setPing(0); + conference.setBeginDate(conferenceDto.getBeginDate()); + conference.setEndDate(conferenceDto.getEndDate()); + conference.setPapers(conferenceDto.getPapers()); + conference.setDeadlines(deadlineService.saveOrCreate(conferenceDto.getDeadlines())); + conference.setUsers(conferenceUserService.saveOrCreate(conferenceDto.getUsers())); + if (conferenceDto.getPaperIds() != null && !conferenceDto.getPaperIds().isEmpty()) { + conferenceDto.getPaperIds().forEach(paperId -> + conference.getPapers().add(paperService.findEntityById(paperId))); + } + return conference; + } + + + public boolean isCurrentUserParticipant(List conferenceUsers) { + return conferenceUsers.stream().anyMatch(participant -> participant.getUser().equals(userService.getCurrentUser())); + } + + public List filter(ConferenceFilterDto conferenceFilterDto) { + return convert(conferenceRepository.findByUserAndYear( + conferenceFilterDto.getFilterUserId() == null ? null : userService.findById(conferenceFilterDto.getFilterUserId()), + conferenceFilterDto.getYear()), ConferenceDto::new); + + } + + public List findAllActiveDto() { + return convert(findAllActive(), ConferenceDto::new); + } + + public List findAllActive() { + return conferenceRepository.findAllActive(new Date()); + } +} diff --git a/src/main/java/ru/ulstu/conference/service/ConferenceUserService.java b/src/main/java/ru/ulstu/conference/service/ConferenceUserService.java new file mode 100644 index 0000000..269e7df --- /dev/null +++ b/src/main/java/ru/ulstu/conference/service/ConferenceUserService.java @@ -0,0 +1,48 @@ +package ru.ulstu.conference.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.conference.model.ConferenceUser; +import ru.ulstu.conference.repository.ConferenceUserRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ConferenceUserService { + + private final ConferenceUserRepository conferenceUserRepository; + + public ConferenceUserService(ConferenceUserRepository conferenceUserRepository) { + this.conferenceUserRepository = conferenceUserRepository; + } + + public List saveOrCreate(List users) { + return users + .stream() + .map(user -> { + return user.getId() != null ? update(user) : create(user); + }).collect(Collectors.toList()); + } + + @Transactional + public ConferenceUser update(ConferenceUser user) { + ConferenceUser updateUser = conferenceUserRepository.getOne(user.getId()); + updateUser.setDeposit(user.getDeposit()); + updateUser.setParticipation(user.getParticipation()); + conferenceUserRepository.save(updateUser); + return updateUser; + } + + @Transactional + public ConferenceUser create(ConferenceUser user) { + ConferenceUser newUser = new ConferenceUser(); + newUser.setDeposit(user.getDeposit()); + newUser.setParticipation(user.getParticipation()); + newUser.setUser(user.getUser()); + newUser = conferenceUserRepository.save(newUser); + return newUser; + } + + +} diff --git a/src/main/java/ru/ulstu/configuration/ApplicationProperties.java b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java index 8615cb2..0b09821 100644 --- a/src/main/java/ru/ulstu/configuration/ApplicationProperties.java +++ b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java @@ -1,22 +1,27 @@ package ru.ulstu.configuration; -import org.hibernate.validator.constraints.NotBlank; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; +import javax.validation.constraints.NotBlank; + @Component @ConfigurationProperties(prefix = "ng-tracker") @Validated public class ApplicationProperties { @NotBlank private String baseUrl; + @NotBlank private String undeadUserLogin; + private boolean devMode; private boolean useHttps; + private boolean checkRun; + public boolean isUseHttps() { return useHttps; } @@ -48,4 +53,12 @@ public class ApplicationProperties { public void setDevMode(boolean devMode) { this.devMode = devMode; } + + public boolean isCheckRun() { + return checkRun; + } + + public void setCheckRun(boolean checkRun) { + this.checkRun = checkRun; + } } diff --git a/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java b/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java index bcbcab8..514319d 100644 --- a/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java @@ -2,18 +2,18 @@ package ru.ulstu.configuration; import org.eclipse.jetty.server.ServerConnector; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; -import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; -import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer; +import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; +import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; +import org.springframework.boot.web.server.ConfigurableWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Configuration; @Configuration -public class HttpListenerConfiguration implements EmbeddedServletContainerCustomizer { +public class HttpListenerConfiguration implements WebServerFactoryCustomizer { @Value("${server.http.port}") private int httpPort; - private void configureJetty(JettyEmbeddedServletContainerFactory jettyFactory) { + private void configureJetty(JettyServletWebServerFactory jettyFactory) { jettyFactory.addServerCustomizers((JettyServerCustomizer) server -> { ServerConnector serverConnector = new ServerConnector(server); serverConnector.setPort(httpPort); @@ -22,9 +22,9 @@ public class HttpListenerConfiguration implements EmbeddedServletContainerCustom } @Override - public void customize(ConfigurableEmbeddedServletContainer container) { - if (container instanceof JettyEmbeddedServletContainerFactory) { - configureJetty((JettyEmbeddedServletContainerFactory) container); + public void customize(ConfigurableWebServerFactory factory) { + if (factory instanceof JettyServletWebServerFactory) { + configureJetty((JettyServletWebServerFactory) factory); } } } diff --git a/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java b/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java index addf7ac..31de65c 100644 --- a/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java @@ -3,8 +3,8 @@ package ru.ulstu.configuration; import nz.net.ultraq.thymeleaf.LayoutDialect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect; -import org.thymeleaf.spring4.SpringTemplateEngine; +import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect; +import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver; @@ -17,7 +17,6 @@ public class MailTemplateConfiguration { final SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.addTemplateResolver(templateResolver); templateEngine.addTemplateResolver(emailTemplateResolver()); - templateEngine.addTemplateResolver(mvcTemplateResolver()); templateEngine.addDialect(new LayoutDialect()); templateEngine.addDialect(sec); return templateEngine; @@ -25,22 +24,10 @@ public class MailTemplateConfiguration { public ClassLoaderTemplateResolver emailTemplateResolver() { ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver(); - emailTemplateResolver.setPrefix("mail_templates/"); - emailTemplateResolver.setTemplateMode("HTML5"); + emailTemplateResolver.setPrefix("/mail_templates/"); + emailTemplateResolver.setTemplateMode("HTML"); emailTemplateResolver.setSuffix(".html"); - emailTemplateResolver.setOrder(1); emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); return emailTemplateResolver; } - - public ClassLoaderTemplateResolver mvcTemplateResolver() { - ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver(); - emailTemplateResolver.setPrefix("templates"); - emailTemplateResolver.setTemplateMode("HTML5"); - emailTemplateResolver.setSuffix(".html"); - emailTemplateResolver.setOrder(2); - emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); - return emailTemplateResolver; - } - } diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java index 8543847..35e9ad2 100644 --- a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java @@ -3,16 +3,18 @@ package ru.ulstu.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class MvcConfiguration extends WebMvcConfigurerAdapter { +public class MvcConfiguration implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{articlename:\\w+}"); - //registry.addViewController("/admin/{articlename:\\w+}"); + registry.addViewController("/admin/{articlename:\\w+}"); registry.addViewController("/papers/{articlename:\\w+}"); registry.addViewController("/grants/{articlename:\\w+}"); + registry.addViewController("/conferences/{articlename:\\w+}"); + registry.addViewController("/students/{articlename:\\w+}"); registry.addRedirectViewController("/", "/index"); registry.addRedirectViewController("/default", "/index"); } diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java index da498fa..591fa0c 100644 --- a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java @@ -58,7 +58,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .anyRequest() .permitAll(); http.anonymous() - .principal("developer") + .principal("admin") .authorities(UserRoleConstants.ADMIN); } else { log.debug("Security enabled"); diff --git a/src/main/java/ru/ulstu/core/controller/AdviceController.java b/src/main/java/ru/ulstu/core/controller/AdviceController.java index 18c25dc..8238797 100644 --- a/src/main/java/ru/ulstu/core/controller/AdviceController.java +++ b/src/main/java/ru/ulstu/core/controller/AdviceController.java @@ -1,100 +1,110 @@ -package ru.ulstu.core.controller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestController; -import ru.ulstu.core.error.EntityIdIsNullException; -import ru.ulstu.core.model.ErrorConstants; -import ru.ulstu.core.model.response.Response; -import ru.ulstu.core.model.response.ResponseExtended; -import ru.ulstu.user.error.UserActivationError; -import ru.ulstu.user.error.UserEmailExistsException; -import ru.ulstu.user.error.UserIdExistsException; -import ru.ulstu.user.error.UserIsUndeadException; -import ru.ulstu.user.error.UserLoginExistsException; -import ru.ulstu.user.error.UserNotActivatedException; -import ru.ulstu.user.error.UserNotFoundException; -import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; -import ru.ulstu.user.error.UserResetKeyError; - -import java.util.Set; -import java.util.stream.Collectors; - -@RestController -@ControllerAdvice -public class AdviceController { - private final Logger log = LoggerFactory.getLogger(AdviceController.class); - - private Response handleException(ErrorConstants error) { - log.warn(error.toString()); - return new Response<>(error); - } - - private ResponseExtended handleException(ErrorConstants error, E errorData) { - log.warn(error.toString()); - return new ResponseExtended<>(error, errorData); - } - - @ExceptionHandler(EntityIdIsNullException.class) - public Response handleEntityIdIsNullException(Throwable e) { - return handleException(ErrorConstants.ID_IS_NULL); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseExtended> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - final Set errors = e.getBindingResult().getAllErrors().stream() - .filter(error -> error instanceof FieldError) - .map(error -> ((FieldError) error).getField()) - .collect(Collectors.toSet()); - return handleException(ErrorConstants.VALIDATION_ERROR, errors); - } - - @ExceptionHandler(UserIdExistsException.class) - public Response handleUserIdExistsException(Throwable e) { - return handleException(ErrorConstants.USER_ID_EXISTS); - } - - @ExceptionHandler(UserActivationError.class) - public ResponseExtended handleUserActivationError(Throwable e) { - return handleException(ErrorConstants.USER_ACTIVATION_ERROR, e.getMessage()); - } - - @ExceptionHandler(UserLoginExistsException.class) - public ResponseExtended handleUserLoginExistsException(Throwable e) { - return handleException(ErrorConstants.USER_LOGIN_EXISTS, e.getMessage()); - } - - @ExceptionHandler(UserEmailExistsException.class) - public ResponseExtended handleUserEmailExistsException(Throwable e) { - return handleException(ErrorConstants.USER_EMAIL_EXISTS, e.getMessage()); - } - - @ExceptionHandler(UserPasswordsNotValidOrNotMatchException.class) - public Response handleUserPasswordsNotValidOrNotMatchException(Throwable e) { - return handleException(ErrorConstants.USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH); - } - - @ExceptionHandler(UserNotFoundException.class) - public ResponseExtended handleUserNotFoundException(Throwable e) { - return handleException(ErrorConstants.USER_NOT_FOUND, e.getMessage()); - } - - @ExceptionHandler(UserNotActivatedException.class) - public Response handleUserNotActivatedException(Throwable e) { - return handleException(ErrorConstants.USER_NOT_ACTIVATED); - } - - @ExceptionHandler(UserResetKeyError.class) - public ResponseExtended handleUserResetKeyError(Throwable e) { - return handleException(ErrorConstants.USER_RESET_ERROR, e.getMessage()); - } - - @ExceptionHandler(UserIsUndeadException.class) - public ResponseExtended handleUserIsUndeadException(Throwable e) { - return handleException(ErrorConstants.USER_UNDEAD_ERROR, e.getMessage()); - } -} +package ru.ulstu.core.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ModelAttribute; +import ru.ulstu.core.error.EntityIdIsNullException; +import ru.ulstu.core.model.ErrorConstants; +import ru.ulstu.core.model.response.Response; +import ru.ulstu.core.model.response.ResponseExtended; +import ru.ulstu.user.error.UserActivationError; +import ru.ulstu.user.error.UserEmailExistsException; +import ru.ulstu.user.error.UserIdExistsException; +import ru.ulstu.user.error.UserIsUndeadException; +import ru.ulstu.user.error.UserLoginExistsException; +import ru.ulstu.user.error.UserNotActivatedException; +import ru.ulstu.user.error.UserNotFoundException; +import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; +import ru.ulstu.user.error.UserResetKeyError; +import ru.ulstu.user.service.UserService; + +import java.util.Set; +import java.util.stream.Collectors; + +@ControllerAdvice +public class AdviceController { + private final Logger log = LoggerFactory.getLogger(AdviceController.class); + private final UserService userService; + + public AdviceController(UserService userService) { + this.userService = userService; + } + + @ModelAttribute("currentUser") + public String getCurrentUser() { + return userService.getCurrentUser().getUserAbbreviate(); + } + + private Response handleException(ErrorConstants error) { + log.warn(error.toString()); + return new Response<>(error); + } + + private ResponseExtended handleException(ErrorConstants error, E errorData) { + log.warn(error.toString()); + return new ResponseExtended<>(error, errorData); + } + + @ExceptionHandler(EntityIdIsNullException.class) + public Response handleEntityIdIsNullException(Throwable e) { + return handleException(ErrorConstants.ID_IS_NULL); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseExtended> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + final Set errors = e.getBindingResult().getAllErrors().stream() + .filter(error -> error instanceof FieldError) + .map(error -> ((FieldError) error).getField()) + .collect(Collectors.toSet()); + return handleException(ErrorConstants.VALIDATION_ERROR, errors); + } + + @ExceptionHandler(UserIdExistsException.class) + public Response handleUserIdExistsException(Throwable e) { + return handleException(ErrorConstants.USER_ID_EXISTS); + } + + @ExceptionHandler(UserActivationError.class) + public ResponseExtended handleUserActivationError(Throwable e) { + return handleException(ErrorConstants.USER_ACTIVATION_ERROR, e.getMessage()); + } + + @ExceptionHandler(UserLoginExistsException.class) + public ResponseExtended handleUserLoginExistsException(Throwable e) { + return handleException(ErrorConstants.USER_LOGIN_EXISTS, e.getMessage()); + } + + @ExceptionHandler(UserEmailExistsException.class) + public ResponseExtended handleUserEmailExistsException(Throwable e) { + return handleException(ErrorConstants.USER_EMAIL_EXISTS, e.getMessage()); + } + + @ExceptionHandler(UserPasswordsNotValidOrNotMatchException.class) + public Response handleUserPasswordsNotValidOrNotMatchException(Throwable e) { + return handleException(ErrorConstants.USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH); + } + + @ExceptionHandler(UserNotFoundException.class) + public ResponseExtended handleUserNotFoundException(Throwable e) { + return handleException(ErrorConstants.USER_NOT_FOUND, e.getMessage()); + } + + @ExceptionHandler(UserNotActivatedException.class) + public Response handleUserNotActivatedException(Throwable e) { + return handleException(ErrorConstants.USER_NOT_ACTIVATED); + } + + @ExceptionHandler(UserResetKeyError.class) + public ResponseExtended handleUserResetKeyError(Throwable e) { + return handleException(ErrorConstants.USER_RESET_ERROR, e.getMessage()); + } + + @ExceptionHandler(UserIsUndeadException.class) + public ResponseExtended handleUserIsUndeadException(Throwable e) { + return handleException(ErrorConstants.USER_UNDEAD_ERROR, e.getMessage()); + } +} diff --git a/src/main/java/ru/ulstu/core/controller/Navigation.java b/src/main/java/ru/ulstu/core/controller/Navigation.java new file mode 100644 index 0000000..caea429 --- /dev/null +++ b/src/main/java/ru/ulstu/core/controller/Navigation.java @@ -0,0 +1,19 @@ +package ru.ulstu.core.controller; + +import org.springframework.validation.Errors; + +public class Navigation { + public static final String REDIRECT_TO = "redirect:%s"; + public static final String GRANTS_PAGE = "/grants/grants"; + public static final String GRANT_PAGE = "/grants/grant"; + + public static final String CONFERENCES_PAGE = "/conferences/conferences"; + public static final String CONFERENCE_PAGE = "/conferences/conference"; + + public static String hasErrors(Errors errors, String page) { + if (errors.hasErrors()) { + return page; + } + return null; + } +} diff --git a/src/main/java/ru/ulstu/core/error/XlsLoadException.java b/src/main/java/ru/ulstu/core/error/XlsLoadException.java deleted file mode 100644 index 372731f..0000000 --- a/src/main/java/ru/ulstu/core/error/XlsLoadException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.ulstu.core.error; - -public class XlsLoadException extends Exception { - public XlsLoadException(String s) { - super(s); - } -} diff --git a/src/main/java/ru/ulstu/core/error/XlsParseException.java b/src/main/java/ru/ulstu/core/error/XlsParseException.java deleted file mode 100644 index fe8c4b3..0000000 --- a/src/main/java/ru/ulstu/core/error/XlsParseException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.ulstu.core.error; - -public class XlsParseException extends Exception { - public XlsParseException(String s) { - super(s); - } -} diff --git a/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java b/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java index 388e9a1..af3be5c 100644 --- a/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java +++ b/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java @@ -6,19 +6,19 @@ import org.springframework.data.domain.Sort; import java.io.Serializable; public class OffsetablePageRequest implements Pageable, Serializable { - private final int offset; + private final long offset; private final int count; private final Sort sort; - public OffsetablePageRequest(int offset, int count) { + public OffsetablePageRequest(long offset, int count) { this(offset, count, null); } - public OffsetablePageRequest(int offset, int count, Sort.Direction direction, String... properties) { + public OffsetablePageRequest(long offset, int count, Sort.Direction direction, String... properties) { this(offset, count, new Sort(direction, properties)); } - public OffsetablePageRequest(int offset, int count, Sort sort) { + public OffsetablePageRequest(long offset, int count, Sort sort) { if (offset < 0) { throw new IllegalArgumentException("Offset value must not be less than zero!"); } @@ -42,11 +42,11 @@ public class OffsetablePageRequest implements Pageable, Serializable { @Override public int getPageNumber() { - return offset / count; + return (int) (offset / count); } @Override - public int getOffset() { + public long getOffset() { return offset; } @@ -89,9 +89,9 @@ public class OffsetablePageRequest implements Pageable, Serializable { @Override public int hashCode() { final int prime = 31; - int result = 1; + long result = 1; result = prime * result + offset; result = prime * result + count; - return result; + return (int) result; } } diff --git a/src/main/java/ru/ulstu/core/model/BaseEntity.java b/src/main/java/ru/ulstu/core/model/BaseEntity.java index bd0f1e4..d3dfa76 100644 --- a/src/main/java/ru/ulstu/core/model/BaseEntity.java +++ b/src/main/java/ru/ulstu/core/model/BaseEntity.java @@ -1,6 +1,10 @@ package ru.ulstu.core.model; -import javax.persistence.*; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; import java.io.Serializable; @MappedSuperclass diff --git a/src/main/java/ru/ulstu/core/service/TreeService.java b/src/main/java/ru/ulstu/core/service/TreeService.java deleted file mode 100644 index 1e3d4a0..0000000 --- a/src/main/java/ru/ulstu/core/service/TreeService.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.ulstu.core.service; - -import org.springframework.stereotype.Service; -import ru.ulstu.core.model.TreeDto; -import ru.ulstu.core.model.TreeEntity; - -import java.util.List; -import java.util.function.Predicate; - -@Service -public class TreeService { - public TreeDto getTree(String rootName, List rootItems) { - return addChildNode(new TreeDto(rootName), rootItems, element -> true); - } - - public TreeDto getTree(String rootName, List rootItems, Predicate filterPredicate) { - return addChildNode(new TreeDto(rootName), rootItems, filterPredicate); - } - - private TreeDto addChildNode(TreeDto currentRoot, List children, Predicate filterPredicate) { - if (children != null) { - children.stream() - .filter(filterPredicate) - .forEach(item -> { - TreeDto newNode = new TreeDto(item); - currentRoot.getChildren().add(addChildNode(newNode, item.getChildren(), filterPredicate)); - }); - } - return currentRoot; - } -} diff --git a/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java b/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java deleted file mode 100644 index d70d86e..0000000 --- a/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java +++ /dev/null @@ -1,207 +0,0 @@ -package ru.ulstu.core.service; - -import org.apache.poi.POIXMLDocument; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.ss.util.RegionUtil; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import ru.ulstu.core.error.XlsParseException; - -import java.io.*; - -public class XlsDocumentBuilder { - private static final int DEFAULT_SHEET_NUM = 0; - private File documentFile; - private Workbook workBook; - private Sheet currentSheet; - - /** - * Constructor for reading and writing data from/to *.[xls|xlsx] document - * - * @param file contains existing document for reading or new document to save - */ - public XlsDocumentBuilder(File file) throws IOException, XlsParseException { - this.documentFile = file; - if (file.exists()) { - workBook = getWorkBook(file); - currentSheet = workBook.getSheetAt(DEFAULT_SHEET_NUM); - } else { - workBook = new XSSFWorkbook(); - currentSheet = workBook.createSheet(); - } - } - - private Workbook getWorkBook(File file) throws XlsParseException, IOException { - InputStream inputStream = new PushbackInputStream(new FileInputStream(file), 4096); - if (isXlsx(inputStream)) { - return new XSSFWorkbook(inputStream); - } else if (isExcel(inputStream)) { - return new HSSFWorkbook(inputStream); - } - throw new XlsParseException("Wrong document format"); - } - - /** - * Change active sheet to write or read data - * - * @param index index of sheet to activate - */ - public XlsDocumentBuilder setActiveSheet(int index) { - workBook.setActiveSheet(index); - currentSheet = workBook.getSheetAt(index); - return this; - } - - /** - * Create new sheet in document and set it active - * - * @param sheetName - */ - public XlsDocumentBuilder insertNewSheet(String sheetName) { - currentSheet = workBook.createSheet(sheetName); - workBook.setActiveSheet(getSheetCount() - 1); - return this; - } - - public XlsDocumentBuilder insertNewSheet(String sheetName, int order) { - insertNewSheet(sheetName); - workBook.setSheetOrder(sheetName, order); - return this; - } - - /** - * Returns number of sheet in document - * - * @return sheets count - */ - public int getSheetCount() { - return workBook.getNumberOfSheets(); - } - - /** - * Returns number of rows in sheet - * - * @return rows count - */ - public int getRowCount() { - return currentSheet.getLastRowNum(); - } - - /** - * Returns number of columns in sheet - * - * @return columns count - */ - public int getColumnCount() { - Row row = currentSheet.getRow(getRowCount()); - if (row == null) { - return 0; - } - return row.getLastCellNum() - 1; - } - - /** - * Returns converted to string representation of cell value - * - * @param rowIndex row index of current sheet - * @param colIndex column index of current sheet - * @return string value of cell - */ - public String getCellAsString(int rowIndex, int colIndex) { - Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex); - cell.setCellType(Cell.CELL_TYPE_STRING); - return cell.getStringCellValue(); - } - - /** - * Sets string cell value - * - * @param rowIndex row index of current sheet - * @param colIndex column index of current sheet - */ - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, String value) { - if (currentSheet.getRow(rowIndex) == null) { - currentSheet.createRow(rowIndex); - } - if (currentSheet.getRow(rowIndex).getCell(colIndex) == null) { - currentSheet.getRow(rowIndex).createCell(colIndex); - } - Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex); - cell.setCellValue(value); - setColumnAutosize(colIndex, colIndex); - return this; - } - - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, int value) { - return setCellValue(rowIndex, colIndex, String.valueOf(value)); - } - - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, long value) { - return setCellValue(rowIndex, colIndex, String.valueOf(value)); - } - - /** - * Save current file - */ - public XlsDocumentBuilder save() throws IOException { - OutputStream out = new FileOutputStream(documentFile); - workBook.write(out); - return this; - } - - private boolean isExcel(InputStream i) throws IOException { - return (POIFSFileSystem.hasPOIFSHeader(i) || POIXMLDocument.hasOOXMLHeader(i)); - } - - private boolean isXlsx(InputStream i) throws IOException { - return POIXMLDocument.hasOOXMLHeader(i); - } - - public int getActiveSheetIndex() { - return workBook.getActiveSheetIndex(); - } - - public XlsDocumentBuilder mergeCells(int rowFrom, int rowTo, int colFrom, int colTo) { - currentSheet.addMergedRegion(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo)); - return this; - } - - public void setRegionBorderWithMedium(int rowFrom, int rowTo, int colFrom, int colTo) { - for (int row = rowFrom; row < rowTo; row++) { - for (int col = colFrom; col <= colTo; col++) { - CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, col, col); - RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderRight(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderTop(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - } - } - } - - public XlsDocumentBuilder setColumnAutosize(int from, int to) { - for (int col = from; col <= to; col++) { - currentSheet.autoSizeColumn(col, true); - } - return this; - } - - public XlsDocumentBuilder setRowAutosize(int from, int to) { - CellStyle style = workBook.createCellStyle(); - style.setWrapText(true); - for (int row = from; row <= to; row++) { - for (int col = 0; col <= currentSheet.getRow(row).getLastCellNum(); col++) { - if (currentSheet.getRow(row).getCell(col) != null) { - currentSheet.getRow(row).getCell(col).setCellStyle(style); - } - } - } - return this; - } - - public XlsDocumentBuilder deleteSheet(int index) { - workBook.removeSheetAt(index); - return this; - } -} diff --git a/src/main/java/ru/ulstu/core/util/DateUtils.java b/src/main/java/ru/ulstu/core/util/DateUtils.java index b24b09c..6122583 100644 --- a/src/main/java/ru/ulstu/core/util/DateUtils.java +++ b/src/main/java/ru/ulstu/core/util/DateUtils.java @@ -1,6 +1,11 @@ package ru.ulstu.core.util; -import java.time.*; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -24,7 +29,7 @@ public class DateUtils { return cal; } - public static List getMonths () { + public static List getMonths() { return Arrays.asList(Month.values()); } diff --git a/src/main/java/ru/ulstu/deadline/model/Deadline.java b/src/main/java/ru/ulstu/deadline/model/Deadline.java index 1a73e59..404e5c8 100644 --- a/src/main/java/ru/ulstu/deadline/model/Deadline.java +++ b/src/main/java/ru/ulstu/deadline/model/Deadline.java @@ -1,5 +1,8 @@ package ru.ulstu.deadline.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.format.annotation.DateTimeFormat; import ru.ulstu.core.model.BaseEntity; import javax.persistence.Entity; @@ -13,6 +16,7 @@ public class Deadline extends BaseEntity { private String description; @Temporal(value = TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; public Deadline() { @@ -23,6 +27,15 @@ public class Deadline extends BaseEntity { this.description = description; } + @JsonCreator + public Deadline(@JsonProperty("id") Integer id, + @JsonProperty("description") String description, + @JsonProperty("date") Date date) { + this.setId(id); + this.description = description; + this.date = date; + } + public String getDescription() { return description; } diff --git a/src/main/java/ru/ulstu/deadline/model/DeadlineDto.java b/src/main/java/ru/ulstu/deadline/model/DeadlineDto.java deleted file mode 100644 index 7f724aa..0000000 --- a/src/main/java/ru/ulstu/deadline/model/DeadlineDto.java +++ /dev/null @@ -1,58 +0,0 @@ -package ru.ulstu.deadline.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.format.annotation.DateTimeFormat; - -import java.util.Date; - -public class DeadlineDto { - private Integer id; - - private String description; - - @DateTimeFormat(pattern = "yyyy-MM-dd") - private Date date; - - public DeadlineDto() { - } - - @JsonCreator - public DeadlineDto(@JsonProperty("id") Integer id, - @JsonProperty("description") String description, - @JsonProperty("date") Date date) { - this.id = id; - this.description = description; - this.date = date; - } - - public DeadlineDto(Deadline deadline) { - this.id = deadline.getId(); - this.description = deadline.getDescription(); - this.date = deadline.getDate(); - } - - public Integer getId() { - return id; - } - - public String getDescription() { - return description; - } - - public Date getDate() { - return date; - } - - public void setId(Integer id) { - this.id = id; - } - - public void setDescription(String description) { - this.description = description; - } - - public void setDate(Date date) { - this.date = date; - } -} diff --git a/src/main/java/ru/ulstu/deadline/service/DeadlineService.java b/src/main/java/ru/ulstu/deadline/service/DeadlineService.java index c371d87..cb80bfe 100644 --- a/src/main/java/ru/ulstu/deadline/service/DeadlineService.java +++ b/src/main/java/ru/ulstu/deadline/service/DeadlineService.java @@ -3,7 +3,6 @@ package ru.ulstu.deadline.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.ulstu.deadline.model.Deadline; -import ru.ulstu.deadline.model.DeadlineDto; import ru.ulstu.deadline.repository.DeadlineRepository; import java.util.List; @@ -17,29 +16,34 @@ public class DeadlineService { this.deadlineRepository = deadlineRepository; } - public List saveOrCreate(List deadlines) { - return deadlines.stream().map(deadlineDto -> { - return deadlineDto.getId() != null ? update(deadlineDto) : create(deadlineDto); - }).collect(Collectors.toList()); + public List saveOrCreate(List deadlines) { + return deadlines + .stream() + .map(deadline -> { + return deadline.getId() != null ? update(deadline) : create(deadline); + }).collect(Collectors.toList()); } @Transactional - public Deadline update(DeadlineDto deadlineDto) { - Deadline deadline = deadlineRepository.findOne(deadlineDto.getId()); - deadlineRepository.save(copyFromDto(deadline, deadlineDto)); - return deadline; + public Deadline update(Deadline deadline) { + Deadline updateDeadline = deadlineRepository.getOne(deadline.getId()); + updateDeadline.setDate(deadline.getDate()); + updateDeadline.setDescription(deadline.getDescription()); + deadlineRepository.save(updateDeadline); + return updateDeadline; } @Transactional - public Deadline create(DeadlineDto deadlineDto) { - Deadline newDeadline = copyFromDto(new Deadline(), deadlineDto); + public Deadline create(Deadline deadline) { + Deadline newDeadline = new Deadline(); + newDeadline.setDate(deadline.getDate()); + newDeadline.setDescription(deadline.getDescription()); newDeadline = deadlineRepository.save(newDeadline); return newDeadline; } - private Deadline copyFromDto(Deadline deadline, DeadlineDto deadlineDto) { - deadline.setDate(deadlineDto.getDate()); - deadline.setDescription(deadlineDto.getDescription()); - return deadline; + @Transactional + public void remove(Integer deadlineId) { + deadlineRepository.deleteById(deadlineId); } } diff --git a/src/main/java/ru/ulstu/file/FileController.java b/src/main/java/ru/ulstu/file/FileController.java index e60626b..ac08ac1 100644 --- a/src/main/java/ru/ulstu/file/FileController.java +++ b/src/main/java/ru/ulstu/file/FileController.java @@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile; import ru.ulstu.configuration.Constants; import ru.ulstu.core.model.response.Response; import ru.ulstu.file.model.FileData; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.service.FileService; import java.io.IOException; @@ -51,7 +52,7 @@ public class FileController { } @PostMapping("/uploadTmpFile") - public Response upload(@RequestParam("file") MultipartFile multipartFile) throws IOException { - return new Response(fileService.uploadToTmpDir(multipartFile)); + public Response upload(@RequestParam("file") MultipartFile multipartFile) throws IOException { + return new Response(fileService.createFromMultipartFile(multipartFile)); } } diff --git a/src/main/java/ru/ulstu/file/model/FileData.java b/src/main/java/ru/ulstu/file/model/FileData.java index e5b3277..3f97130 100644 --- a/src/main/java/ru/ulstu/file/model/FileData.java +++ b/src/main/java/ru/ulstu/file/model/FileData.java @@ -19,6 +19,9 @@ public class FileData extends BaseEntity { private byte[] data; + @Column(name = "is_latex_attach") + private Boolean isLatexAttach; + public String getName() { return name; } @@ -50,4 +53,12 @@ public class FileData extends BaseEntity { public void setData(byte[] data) { this.data = data; } + + public Boolean isLatexAttach() { + return isLatexAttach; + } + + public void setLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } } diff --git a/src/main/java/ru/ulstu/file/model/FileDataDto.java b/src/main/java/ru/ulstu/file/model/FileDataDto.java new file mode 100644 index 0000000..e83bf12 --- /dev/null +++ b/src/main/java/ru/ulstu/file/model/FileDataDto.java @@ -0,0 +1,95 @@ +package ru.ulstu.file.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FileDataDto { + private Integer id; + private String name; + private String fileName; + private String tmpFileName; + private boolean deleted; + private Boolean isLatexAttach; + + public FileDataDto() { + } + + @JsonCreator + public FileDataDto(@JsonProperty("id") Integer id, + @JsonProperty("name") String name, + @JsonProperty("isLatexAttach") Boolean isLatexAttach, + @JsonProperty("fileName") String fileName, + @JsonProperty("tmpFileName") String tmpFileName) { + this.id = id; + this.name = name; + this.fileName = fileName; + this.tmpFileName = tmpFileName; + this.isLatexAttach = isLatexAttach; + } + + public FileDataDto(FileData fileData) { + this.id = fileData.getId(); + this.name = fileData.getName(); + this.isLatexAttach = fileData.isLatexAttach(); + } + + public FileDataDto(String fileName, String tmpFileName) { + this.fileName = fileName; + this.tmpFileName = tmpFileName; + } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getTmpFileName() { + return tmpFileName; + } + + public void setTmpFileName(String tmpFileName) { + this.tmpFileName = tmpFileName; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public Boolean isLatexAttach() { + return isLatexAttach; + } + + public Boolean getIsLatexAttach() { + return isLatexAttach; + } + + public void setLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } + + public void setIsLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } +} diff --git a/src/main/java/ru/ulstu/file/service/FileService.java b/src/main/java/ru/ulstu/file/service/FileService.java index 2f1fb6d..a8e705a 100644 --- a/src/main/java/ru/ulstu/file/service/FileService.java +++ b/src/main/java/ru/ulstu/file/service/FileService.java @@ -1,15 +1,24 @@ package ru.ulstu.file.service; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import ru.ulstu.file.model.FileData; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.repostory.FileRepository; +import ru.ulstu.paper.model.PaperDto; +import java.io.BufferedWriter; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import static java.nio.charset.StandardCharsets.UTF_8; @@ -29,13 +38,13 @@ public class FileService { public FileData createFileFromTmp(String tmpFileName) throws IOException { FileData fileData = new FileData(); fileData.setData(getTmpFile(tmpFileName)); - fileData.setName(getTmpFileName(tmpFileName)); + fileData.setSize(getTmpFileSize(tmpFileName)); fileData.setCreateDate(new Date()); return fileRepository.save(fileData); } public String uploadToTmpDir(MultipartFile multipartFile) throws IOException { - String tmpFileName = String.valueOf(System.currentTimeMillis()); + String tmpFileName = String.valueOf(System.currentTimeMillis()) + UUID.randomUUID(); Files.write(getTmpFilePath(tmpFileName), multipartFile.getBytes()); String meta = multipartFile.getOriginalFilename() + "\n" + multipartFile.getSize(); Files.write(getTmpFileMetaPath(tmpFileName), meta.getBytes(UTF_8)); @@ -60,7 +69,7 @@ public class FileService { } public FileData getFile(Integer fileId) { - return fileRepository.findOne(fileId); + return fileRepository.getOne(fileId); } public void deleteTmpFile(String tmpFileName) throws IOException { @@ -78,4 +87,63 @@ public class FileService { public void deleteFile(FileData fileData) { fileRepository.delete(fileData); } + + public List saveOrCreate(List fileDtos) throws IOException { + List files = new ArrayList<>(); + for (FileDataDto file : fileDtos) { + files.add(file.getId() != null ? update(file) : create(file)); + } + return files; + } + + @Transactional + public FileData update(FileDataDto fileDataDto) { + FileData file = fileRepository.getOne(fileDataDto.getId()); + return fileRepository.save(copyFromDto(file, fileDataDto)); + } + + @Transactional + public FileData create(FileDataDto fileDataDto) throws IOException { + FileData newFile = createFileFromTmp(fileDataDto.getTmpFileName()); + copyFromDto(newFile, fileDataDto); + return fileRepository.save(newFile); + } + + private FileData copyFromDto(FileData fileData, FileDataDto fileDataDto) { + fileData.setName(fileDataDto.getName()); + fileData.setLatexAttach(fileDataDto.isLatexAttach()); + return fileData; + } + + @Transactional + public void delete(Integer fileId) { + fileRepository.delete(fileRepository.getOne(fileId)); + } + + public FileDataDto createFromMultipartFile(MultipartFile multipartFile) throws IOException { + return new FileDataDto(multipartFile.getOriginalFilename(), uploadToTmpDir(multipartFile)); + } + + public void createLatexAttachs(PaperDto paper) throws IOException { + for (FileDataDto fileDataDto : paper.getFiles() + .stream() + .filter(f -> (f.isLatexAttach() != null && f.isLatexAttach()) && !f.isDeleted()) + .collect(Collectors.toList())) { + if (fileDataDto.getId() == null) { + File oldFile = getTmpFilePath(fileDataDto.getTmpFileName()).toFile(); + File renamed = getTmpFilePath(fileDataDto.getName()).toFile(); + oldFile.renameTo(renamed); + } else { + Files.write(getTmpFilePath(fileDataDto.getName()), fileRepository.getOne(fileDataDto.getId()).getData()); + } + } + } + + public File createLatexFile(PaperDto paper) throws IOException { + BufferedWriter writer = Files.newBufferedWriter(getTmpFilePath(paper.getTitle() + ".tex")); + writer.write(paper.getLatexText()); + writer.close(); + + return getTmpFilePath(paper.getTitle() + ".tex").toFile(); + } } diff --git a/src/main/java/ru/ulstu/grant/controller/GrantController.java b/src/main/java/ru/ulstu/grant/controller/GrantController.java index 415dd0b..a0bb916 100644 --- a/src/main/java/ru/ulstu/grant/controller/GrantController.java +++ b/src/main/java/ru/ulstu/grant/controller/GrantController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import ru.ulstu.deadline.model.DeadlineDto; +import ru.ulstu.deadline.model.Deadline; import ru.ulstu.grant.model.Grant; import ru.ulstu.grant.model.GrantDto; import ru.ulstu.grant.service.GrantService; +import ru.ulstu.user.model.User; +import springfox.documentation.annotations.ApiIgnore; import javax.validation.Valid; import java.io.IOException; @@ -20,10 +22,14 @@ import java.util.List; import java.util.stream.Collectors; import static org.springframework.util.StringUtils.isEmpty; +import static ru.ulstu.core.controller.Navigation.GRANTS_PAGE; +import static ru.ulstu.core.controller.Navigation.GRANT_PAGE; +import static ru.ulstu.core.controller.Navigation.REDIRECT_TO; @Controller() @RequestMapping(value = "/grants") +@ApiIgnore public class GrantController { private final GrantService grantService; @@ -51,41 +57,50 @@ public class GrantController { } @PostMapping(value = "/grant", params = "save") - public String save(@Valid GrantDto grantDto, Errors errors) throws IOException { + public String save(@Valid GrantDto grantDto, Errors errors) + throws IOException { filterEmptyDeadlines(grantDto); if (grantDto.getDeadlines().isEmpty()) { - errors.rejectValue("deadlines", "errorCode", "Не может быть пустым"); + errors.rejectValue("deadlines", "errorCode", "Не может быть пусто"); + } + if (grantDto.getLeaderId().equals(-1)) { + errors.rejectValue("leaderId", "errorCode", "Укажите руководителя"); } if (errors.hasErrors()) { - return "/grants/grant"; + return GRANT_PAGE; } grantService.save(grantDto); - return "redirect:/grants/grants"; + return String.format(REDIRECT_TO, GRANTS_PAGE); + } + + @PostMapping(value = "/grant", params = "filterUsers") + public String filterUsers() { + return GRANT_PAGE; } @PostMapping(value = "/grant", params = "addDeadline") public String addDeadline(@Valid GrantDto grantDto, Errors errors) { filterEmptyDeadlines(grantDto); if (errors.hasErrors()) { - return "/grants/grant"; + return GRANT_PAGE; } - grantDto.getDeadlines().add(new DeadlineDto()); - return "/grants/grant"; + grantDto.getDeadlines().add(new Deadline()); + return GRANT_PAGE; } @PostMapping(value = "/grant", params = "createProject") - public String createProject(@Valid GrantDto grantDto, Errors errors) { + public String createProject(@Valid GrantDto grantDto, Errors errors) throws IOException { if (errors.hasErrors()) { - return "/grants/grant"; + return GRANT_PAGE; } grantService.createProject(grantDto); - return "/grants/grant"; + return GRANT_PAGE; } @GetMapping("/delete/{grant-id}") public String delete(@PathVariable("grant-id") Integer grantId) throws IOException { grantService.delete(grantId); - return "redirect:/grants/grants"; + return String.format(REDIRECT_TO, GRANTS_PAGE); } @ModelAttribute("allStatuses") @@ -93,6 +108,11 @@ public class GrantController { return grantService.getGrantStatuses(); } + @ModelAttribute("allAuthors") + public List getAllAuthors(GrantDto grantDto) { + return grantService.getGrantAuthors(grantDto); + } + private void filterEmptyDeadlines(GrantDto grantDto) { grantDto.setDeadlines(grantDto.getDeadlines().stream() .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) diff --git a/src/main/java/ru/ulstu/grant/model/Grant.java b/src/main/java/ru/ulstu/grant/model/Grant.java index 8d40e3a..30d9fb3 100644 --- a/src/main/java/ru/ulstu/grant/model/Grant.java +++ b/src/main/java/ru/ulstu/grant/model/Grant.java @@ -1,36 +1,44 @@ package ru.ulstu.grant.model; -import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.core.model.UserContainer; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.file.model.FileData; import ru.ulstu.project.model.Project; +import ru.ulstu.user.model.User; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; @Entity @Table(name = "grants") -public class Grant extends BaseEntity { +public class Grant extends BaseEntity implements UserContainer { public enum GrantStatus { APPLICATION("Заявка"), ON_COMPETITION("Отправлен на конкурс"), SUCCESSFUL_PASSAGE("Успешное прохождение"), IN_WORK("В работе"), COMPLETED("Завершен"), - FAILED("Провалены сроки"); + FAILED("Провалены сроки"), + LOADED_FROM_KIAS("Загружен автоматически"); private String statusName; @@ -51,6 +59,7 @@ public class Grant extends BaseEntity { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "grant_id") + @OrderBy("date") private List deadlines = new ArrayList<>(); //Описание гранта @@ -66,6 +75,14 @@ public class Grant extends BaseEntity { @JoinColumn(name = "project_id") private Project project; + @ManyToMany(fetch = FetchType.EAGER) + private Set authors = new HashSet<>(); + + @NotNull + @ManyToOne + @JoinColumn(name = "leader_id") + private User leader; + public GrantStatus getStatus() { return status; } @@ -114,6 +131,27 @@ public class Grant extends BaseEntity { this.project = project; } + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } + + @Override + public Set getUsers() { + return getAuthors(); + } + + public User getLeader() { + return leader; + } + + public void setLeader(User leader) { + this.leader = leader; + } + public Optional getNextDeadline() { return deadlines .stream() diff --git a/src/main/java/ru/ulstu/grant/model/GrantDto.java b/src/main/java/ru/ulstu/grant/model/GrantDto.java index e56896a..d836dfb 100644 --- a/src/main/java/ru/ulstu/grant/model/GrantDto.java +++ b/src/main/java/ru/ulstu/grant/model/GrantDto.java @@ -2,36 +2,54 @@ package ru.ulstu.grant.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotEmpty; -import ru.ulstu.deadline.model.DeadlineDto; +import org.apache.commons.lang3.StringUtils; +import ru.ulstu.deadline.model.Deadline; import ru.ulstu.project.model.ProjectDto; +import ru.ulstu.user.model.UserDto; +import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static ru.ulstu.core.util.StreamApiUtils.convert; public class GrantDto { + private final static int MAX_AUTHORS_LENGTH = 60; + private Integer id; @NotEmpty private String title; private Grant.GrantStatus status; - private List deadlines = new ArrayList<>(); + private List deadlines = new ArrayList<>(); private String comment; private String applicationFileName; private ProjectDto project; + private Set authorIds; + private Set authors; + private Integer leaderId; + private boolean wasLeader; + private boolean hasAge; + private boolean hasDegree; public GrantDto() { - deadlines.add(new DeadlineDto()); + deadlines.add(new Deadline()); } @JsonCreator public GrantDto(@JsonProperty("id") Integer id, @JsonProperty("title") String title, @JsonProperty("status") Grant.GrantStatus status, - @JsonProperty("deadlines") List deadlines, + @JsonProperty("deadlines") List deadlines, @JsonProperty("comment") String comment, - @JsonProperty("project") ProjectDto project) { + @JsonProperty("project") ProjectDto project, + @JsonProperty("authorIds") Set authorIds, + @JsonProperty("authors") Set authors, + @JsonProperty("leader") Integer leaderId, + @JsonProperty("wasLeader") boolean wasLeader, + @JsonProperty("hasAge") boolean hasAge, + @JsonProperty("hasDegree") boolean hasDegree) { this.id = id; this.title = title; this.status = status; @@ -39,16 +57,27 @@ public class GrantDto { this.comment = comment; this.applicationFileName = null; this.project = project; + this.authors = authors; + this.leaderId = leaderId; + this.wasLeader = wasLeader; + this.hasAge = hasAge; + this.hasDegree = hasDegree; } public GrantDto(Grant grant) { this.id = grant.getId(); this.title = grant.getTitle(); this.status = grant.getStatus(); - this.deadlines = convert(grant.getDeadlines(), DeadlineDto::new); + this.deadlines = grant.getDeadlines(); this.comment = grant.getComment(); this.project = grant.getProject() == null ? null : new ProjectDto(grant.getProject()); this.applicationFileName = grant.getApplication() == null ? null : grant.getApplication().getName(); + this.authorIds = convert(grant.getAuthors(), user -> user.getId()); + this.authors = convert(grant.getAuthors(), UserDto::new); + this.leaderId = grant.getLeader().getId(); + this.wasLeader = false; + this.hasAge = false; + this.hasDegree = false; } public Integer getId() { @@ -75,11 +104,11 @@ public class GrantDto { this.status = status; } - public List getDeadlines() { + public List getDeadlines() { return deadlines; } - public void setDeadlines(List deadlines) { + public void setDeadlines(List deadlines) { this.deadlines = deadlines; } @@ -106,4 +135,59 @@ public class GrantDto { public void setApplicationFileName(String applicationFileName) { this.applicationFileName = applicationFileName; } + + public Set getAuthorIds() { + return authorIds; + } + + public void setAuthorIds(Set authorIds) { + this.authorIds = authorIds; + } + + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } + + public String getAuthorsString() { + return StringUtils.abbreviate(authors + .stream() + .map(author -> author.getLastName()) + .collect(Collectors.joining(", ")), MAX_AUTHORS_LENGTH); + } + + public Integer getLeaderId() { + return leaderId; + } + + public void setLeaderId(Integer leaderId) { + this.leaderId = leaderId; + } + + public boolean isWasLeader() { + return wasLeader; + } + + public void setWasLeader(boolean wasLeader) { + this.wasLeader = wasLeader; + } + + public boolean isHasAge() { + return hasAge; + } + + public void setHasAge(boolean hasAge) { + this.hasAge = hasAge; + } + + public boolean isHasDegree() { + return hasDegree; + } + + public void setHasDegree(boolean hasDegree) { + this.hasDegree = hasDegree; + } } diff --git a/src/main/java/ru/ulstu/grant/model/GrantStatusDto.java b/src/main/java/ru/ulstu/grant/model/GrantStatusDto.java deleted file mode 100644 index 34676d6..0000000 --- a/src/main/java/ru/ulstu/grant/model/GrantStatusDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.ulstu.grant.model; - -public class GrantStatusDto { - private final String id; - private final String name; - - public GrantStatusDto(Grant.GrantStatus status) { - this.id = status.name(); - this.name = status.getStatusName(); - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/ru/ulstu/grant/repository/GrantRepository.java b/src/main/java/ru/ulstu/grant/repository/GrantRepository.java index 92dec43..44c2cc0 100644 --- a/src/main/java/ru/ulstu/grant/repository/GrantRepository.java +++ b/src/main/java/ru/ulstu/grant/repository/GrantRepository.java @@ -3,6 +3,9 @@ package ru.ulstu.grant.repository; import org.springframework.data.jpa.repository.JpaRepository; import ru.ulstu.grant.model.Grant; +import java.util.List; + public interface GrantRepository extends JpaRepository { + List findByStatus(Grant.GrantStatus status); } diff --git a/src/main/java/ru/ulstu/grant/service/GrantService.java b/src/main/java/ru/ulstu/grant/service/GrantService.java index d5beec3..a6e255f 100644 --- a/src/main/java/ru/ulstu/grant/service/GrantService.java +++ b/src/main/java/ru/ulstu/grant/service/GrantService.java @@ -12,11 +12,14 @@ import ru.ulstu.grant.repository.GrantRepository; import ru.ulstu.project.model.Project; import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.service.ProjectService; +import ru.ulstu.user.model.User; +import ru.ulstu.user.service.UserService; import java.io.IOException; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static org.springframework.util.ObjectUtils.isEmpty; import static ru.ulstu.core.util.StreamApiUtils.convert; @@ -30,15 +33,18 @@ public class GrantService { private final ProjectService projectService; private final DeadlineService deadlineService; private final FileService fileService; + private final UserService userService; public GrantService(GrantRepository grantRepository, FileService fileService, DeadlineService deadlineService, - ProjectService projectService) { + ProjectService projectService, + UserService userService) { this.grantRepository = grantRepository; - this.projectService = projectService; this.fileService = fileService; this.deadlineService = deadlineService; + this.projectService = projectService; + this.userService = userService; } public List findAll() { @@ -52,7 +58,7 @@ public class GrantService { } public GrantDto findOneDto(Integer id) { - return new GrantDto(grantRepository.findOne(id)); + return new GrantDto(grantRepository.getOne(id)); } @Transactional @@ -73,17 +79,24 @@ public class GrantService { if (grantDto.getApplicationFileName() != null) { grant.setApplication(fileService.createFileFromTmp(grantDto.getApplicationFileName())); } + grant.getAuthors().clear(); + if (grantDto.getAuthorIds() != null && !grantDto.getAuthorIds().isEmpty()) { + grantDto.getAuthorIds().forEach(authorIds -> grant.getAuthors().add(userService.findById(authorIds))); + } + if (grantDto.getLeaderId() != null) { + grant.setLeader(userService.findById(grantDto.getLeaderId())); + } return grant; } - public void createProject(GrantDto grantDto) { + public void createProject(GrantDto grantDto) throws IOException { grantDto.setProject( new ProjectDto(projectService.save(new ProjectDto(grantDto.getTitle())))); } @Transactional public Integer update(GrantDto grantDto) throws IOException { - Grant grant = grantRepository.findOne(grantDto.getId()); + Grant grant = grantRepository.getOne(grantDto.getId()); Grant.GrantStatus oldStatus = grant.getStatus(); if (grantDto.getApplicationFileName() != null && grant.getApplication() != null) { fileService.deleteFile(grant.getApplication()); @@ -94,11 +107,10 @@ public class GrantService { @Transactional public void delete(Integer grantId) throws IOException { - Grant grant = grantRepository.findOne(grantId); + Grant grant = grantRepository.getOne(grantId); if (grant.getApplication() != null) { fileService.deleteFile(grant.getApplication()); } - //возможно при удалении гранта будет удаляться и проект, к нему привязанный grantRepository.delete(grant); } @@ -107,13 +119,15 @@ public class GrantService { } @Transactional - public Grant create(String title, Project projectId, Date deadlineDate) { + public Grant create(String title, Project projectId, Date deadlineDate, User user) { Grant grant = new Grant(); grant.setTitle(title); grant.setComment("Комментарий к гранту 1"); grant.setProject(projectId); grant.setStatus(APPLICATION); grant.getDeadlines().add(new Deadline(deadlineDate, "первый дедлайн")); + grant.getAuthors().add(user); + grant.setLeader(user); grant = grantRepository.save(grant); return grant; } @@ -125,4 +139,22 @@ public class GrantService { update(grantDto); } } + + public List getGrantAuthors(GrantDto grantDto) { + List filteredUsers = userService.filterByAgeAndDegree(grantDto.isHasAge(), grantDto.isHasDegree()); + if (grantDto.isWasLeader()) { + filteredUsers = filteredUsers + .stream() + .filter(getCompletedGrantLeaders()::contains) + .collect(Collectors.toList()); + } + return filteredUsers; + } + + private List getCompletedGrantLeaders() { + return grantRepository.findByStatus(Grant.GrantStatus.COMPLETED) + .stream() + .map(Grant::getLeader) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/ru/ulstu/index/controller/IndexController.java b/src/main/java/ru/ulstu/index/controller/IndexController.java new file mode 100644 index 0000000..f6ab100 --- /dev/null +++ b/src/main/java/ru/ulstu/index/controller/IndexController.java @@ -0,0 +1,23 @@ +package ru.ulstu.index.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.ulstu.core.controller.AdviceController; +import ru.ulstu.user.service.UserService; +import springfox.documentation.annotations.ApiIgnore; + +@Controller() +@RequestMapping(value = "/index") +@ApiIgnore +public class IndexController extends AdviceController { + public IndexController(UserService userService) { + super(userService); + } + + @GetMapping + public void currentUser(ModelMap modelMap) { + //нужен здесь для добавления параметров на стартовой странице + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinField.java b/src/main/java/ru/ulstu/odin/model/OdinField.java index 5917a44..6beb7d9 100644 --- a/src/main/java/ru/ulstu/odin/model/OdinField.java +++ b/src/main/java/ru/ulstu/odin/model/OdinField.java @@ -1,13 +1,13 @@ package ru.ulstu.odin.model; import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotBlank; -import org.hibernate.validator.constraints.NotEmpty; import ru.ulstu.core.error.OdinException; import ru.ulstu.odin.model.annotation.OdinCaption; import ru.ulstu.odin.model.annotation.OdinReadOnly; import ru.ulstu.odin.model.annotation.OdinVisible; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -31,14 +31,13 @@ public abstract class OdinField implements Comparable { return this.name().toLowerCase(); } } - - private Field field; protected final OdinFieldType fieldType; protected final String fieldName; protected final String caption; protected final OdinVisible.OdinVisibleType visible; protected final boolean readOnly; protected final boolean notEmpty; + private Field field; public OdinField(Field field, OdinFieldType fieldType) { this.field = field; @@ -126,8 +125,12 @@ public abstract class OdinField implements Comparable { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } OdinField odinField = (OdinField) o; return Objects.equals(fieldName, odinField.fieldName); } diff --git a/src/main/java/ru/ulstu/odin/model/OdinStringField.java b/src/main/java/ru/ulstu/odin/model/OdinStringField.java index 5157d7e..498b375 100644 --- a/src/main/java/ru/ulstu/odin/model/OdinStringField.java +++ b/src/main/java/ru/ulstu/odin/model/OdinStringField.java @@ -1,9 +1,9 @@ package ru.ulstu.odin.model; -import org.hibernate.validator.constraints.Email; import ru.ulstu.odin.model.annotation.OdinString; import ru.ulstu.odin.model.annotation.OdinString.OdinStringType; +import javax.validation.constraints.Email; import javax.validation.constraints.Size; import java.lang.reflect.Field; diff --git a/src/main/java/ru/ulstu/paper/controller/PaperController.java b/src/main/java/ru/ulstu/paper/controller/PaperController.java index cb34a8a..e11e4d9 100644 --- a/src/main/java/ru/ulstu/paper/controller/PaperController.java +++ b/src/main/java/ru/ulstu/paper/controller/PaperController.java @@ -1,5 +1,8 @@ package ru.ulstu.paper.controller; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.Errors; @@ -9,30 +12,37 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import ru.ulstu.deadline.model.DeadlineDto; +import ru.ulstu.deadline.model.Deadline; import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.PaperDto; import ru.ulstu.paper.model.PaperFilterDto; +import ru.ulstu.paper.service.LatexService; import ru.ulstu.paper.service.PaperService; import ru.ulstu.user.model.User; +import springfox.documentation.annotations.ApiIgnore; import javax.validation.Valid; import java.io.IOException; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.stream.Collectors; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.springframework.util.StringUtils.isEmpty; @Controller() @RequestMapping(value = "/papers") +@ApiIgnore public class PaperController { private final PaperService paperService; + private final LatexService latexService; - public PaperController(PaperService paperService) { + public PaperController(PaperService paperService, LatexService latexService) { this.paperService = paperService; + this.latexService = latexService; } @GetMapping("/papers") @@ -49,7 +59,7 @@ public class PaperController { @GetMapping("/dashboard") public void getDashboard(ModelMap modelMap) { - modelMap.put("papers", paperService.findAllActive()); + modelMap.put("papers", paperService.findAllActiveDto()); } @GetMapping("/paper") @@ -80,7 +90,7 @@ public class PaperController { if (errors.hasErrors()) { return "/papers/paper"; } - paperDto.getDeadlines().add(new DeadlineDto()); + paperDto.getDeadlines().add(new Deadline()); return "/papers/paper"; } @@ -95,6 +105,11 @@ public class PaperController { return paperService.getPaperStatuses(); } + @ModelAttribute("allTypes") + public List getPaperTypes() { + return paperService.getPaperTypes(); + } + @ModelAttribute("allAuthors") public List getAllAuthors() { return paperService.getPaperAuthors(); @@ -109,6 +124,14 @@ public class PaperController { return years; } + @PostMapping("/generatePdf") + public ResponseEntity getPdfFile(PaperDto paper) throws IOException, InterruptedException { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Disposition", "attachment; filename='" + + URLEncoder.encode(paper.getTitle() + ".pdf", UTF_8.toString()) + "'"); + return new ResponseEntity<>(latexService.generatePdfFromLatexFile(paper), headers, HttpStatus.OK); + } + private void filterEmptyDeadlines(PaperDto paperDto) { paperDto.setDeadlines(paperDto.getDeadlines().stream() .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) diff --git a/src/main/java/ru/ulstu/paper/controller/PaperRestController.java b/src/main/java/ru/ulstu/paper/controller/PaperRestController.java index 5dd6b9f..2bd8384 100644 --- a/src/main/java/ru/ulstu/paper/controller/PaperRestController.java +++ b/src/main/java/ru/ulstu/paper/controller/PaperRestController.java @@ -61,4 +61,9 @@ public class PaperRestController { public Response> filter(@RequestBody @Valid PaperFilterDto paperFilterDto) throws IOException { return new Response<>(paperService.filter(paperFilterDto)); } + + @GetMapping("formatted-list") + public Response> getFormattedPaperList() { + return new Response<>(paperService.getFormattedPaperList()); + } } diff --git a/src/main/java/ru/ulstu/paper/model/Paper.java b/src/main/java/ru/ulstu/paper/model/Paper.java index c09fd28..4f77826 100644 --- a/src/main/java/ru/ulstu/paper/model/Paper.java +++ b/src/main/java/ru/ulstu/paper/model/Paper.java @@ -2,11 +2,11 @@ package ru.ulstu.paper.model; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; -import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.UserContainer; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.file.model.FileData; +import ru.ulstu.timeline.model.Event; import ru.ulstu.user.model.User; import javax.persistence.CascadeType; @@ -17,11 +17,11 @@ import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.validation.constraints.NotBlank; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; @@ -53,12 +53,32 @@ public class Paper extends BaseEntity implements UserContainer { } } + public enum PaperType { + OTHER("Прочая публикация"), + VAK("ВАК"), + SCOPUS("Scopus"), + WEB_OF_SCIENCE("Web Of Science"); + + private String typeName; + + PaperType(String name) { + this.typeName = name; + } + + public String getTypeName() { + return typeName; + } + } + @NotBlank private String title; @Enumerated(value = EnumType.STRING) private PaperStatus status = PaperStatus.DRAFT; + @Enumerated(value = EnumType.STRING) + private PaperType type = PaperType.OTHER; + @Column(name = "create_date") @Temporal(TemporalType.TIMESTAMP) private Date createDate = new Date(); @@ -75,15 +95,25 @@ public class Paper extends BaseEntity implements UserContainer { private String comment; + private String url; + private Boolean locked = false; - @ManyToOne - @JoinColumn(name = "file_id") - private FileData fileData; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "paper_id") + private List events = new ArrayList<>(); + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "paper_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + private List files = new ArrayList<>(); @ManyToMany(fetch = FetchType.EAGER) private Set authors = new HashSet<>(); + @Column(name = "latex_text") + private String latexText; + public PaperStatus getStatus() { return status; } @@ -92,6 +122,14 @@ public class Paper extends BaseEntity implements UserContainer { this.status = status; } + public PaperType getType() { + return type; + } + + public void setType(PaperType type) { + this.type = type; + } + public Date getCreateDate() { return createDate; } @@ -132,12 +170,12 @@ public class Paper extends BaseEntity implements UserContainer { this.locked = locked; } - public FileData getFileData() { - return fileData; + public List getFiles() { + return files; } - public void setFileData(FileData fileData) { - this.fileData = fileData; + public void setFiles(List files) { + this.files = files; } public String getTitle() { @@ -156,6 +194,30 @@ public class Paper extends BaseEntity implements UserContainer { this.authors = authors; } + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLatexText() { + return latexText; + } + + public void setLatexText(String latexText) { + this.latexText = latexText; + } + @Override public Set getUsers() { return getAuthors(); diff --git a/src/main/java/ru/ulstu/paper/model/PaperDto.java b/src/main/java/ru/ulstu/paper/model/PaperDto.java index 545b432..9ed60e7 100644 --- a/src/main/java/ru/ulstu/paper/model/PaperDto.java +++ b/src/main/java/ru/ulstu/paper/model/PaperDto.java @@ -3,10 +3,11 @@ package ru.ulstu.paper.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.StringUtils; -import org.hibernate.validator.constraints.NotEmpty; -import ru.ulstu.deadline.model.DeadlineDto; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.user.model.UserDto; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Size; import java.util.ArrayList; import java.util.Date; @@ -24,48 +25,51 @@ public class PaperDto { @Size(min = 3, max = 254) private String title; private Paper.PaperStatus status; + private Paper.PaperType type; private Date createDate; private Date updateDate; @NotEmpty - private List deadlines = new ArrayList<>(); + private List deadlines = new ArrayList<>(); private String comment; + private String url; private Boolean locked; - private String tmpFileName; - private Integer fileId; - private String fileName; - private Date fileCreateDate; + private List files = new ArrayList<>(); private Set authorIds; private Set authors; private Integer filterAuthorId; + private String latexText; public PaperDto() { - deadlines.add(new DeadlineDto()); + deadlines.add(new Deadline()); } @JsonCreator public PaperDto(@JsonProperty("id") Integer id, @JsonProperty("title") String title, @JsonProperty("status") Paper.PaperStatus status, + @JsonProperty("type") Paper.PaperType type, @JsonProperty("createDate") Date createDate, @JsonProperty("updateDate") Date updateDate, - @JsonProperty("deadlines") List deadlines, + @JsonProperty("deadlines") List deadlines, @JsonProperty("comment") String comment, + @JsonProperty("latex_text") String latexText, + @JsonProperty("url") String url, @JsonProperty("locked") Boolean locked, - @JsonProperty("tmpFileName") String tmpFileName, + @JsonProperty("files") List files, @JsonProperty("authorIds") Set authorIds, @JsonProperty("authors") Set authors) { this.id = id; this.title = title; this.status = status; + this.type = type; this.createDate = createDate; this.updateDate = updateDate; this.deadlines = deadlines; this.comment = comment; + this.url = url; + this.latexText = latexText; this.locked = locked; - this.tmpFileName = tmpFileName; - this.fileId = null; - this.fileName = null; - this.fileCreateDate = null; + this.files = files; this.authors = authors; } @@ -73,15 +77,15 @@ public class PaperDto { this.id = paper.getId(); this.title = paper.getTitle(); this.status = paper.getStatus(); + this.type = paper.getType(); this.createDate = paper.getCreateDate(); this.updateDate = paper.getUpdateDate(); - this.deadlines = convert(paper.getDeadlines(), DeadlineDto::new); + this.deadlines = paper.getDeadlines(); this.comment = paper.getComment(); + this.url = paper.getUrl(); + this.latexText = paper.getLatexText(); this.locked = paper.getLocked(); - this.tmpFileName = null; - this.fileId = paper.getFileData() == null ? null : paper.getFileData().getId(); - this.fileName = paper.getFileData() == null ? null : paper.getFileData().getName(); - this.fileCreateDate = paper.getFileData() == null ? null : paper.getFileData().getCreateDate(); + this.files = convert(paper.getFiles(), FileDataDto::new); this.authorIds = convert(paper.getAuthors(), user -> user.getId()); this.authors = convert(paper.getAuthors(), UserDto::new); } @@ -110,6 +114,14 @@ public class PaperDto { this.status = status; } + public Paper.PaperType getType() { + return type; + } + + public void setType(Paper.PaperType type) { + this.type = type; + } + public Date getCreateDate() { return createDate; } @@ -126,11 +138,11 @@ public class PaperDto { this.updateDate = updateDate; } - public List getDeadlines() { + public List getDeadlines() { return deadlines; } - public void setDeadlines(List deadlines) { + public void setDeadlines(List deadlines) { this.deadlines = deadlines; } @@ -150,36 +162,12 @@ public class PaperDto { this.locked = locked; } - public String getTmpFileName() { - return tmpFileName; - } - - public void setTmpFileName(String tmpFileName) { - this.tmpFileName = tmpFileName; - } - - public Integer getFileId() { - return fileId; - } - - public void setFileId(Integer fileId) { - this.fileId = fileId; - } - - public String getFileName() { - return fileName; + public List getFiles() { + return files; } - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public Date getFileCreateDate() { - return fileCreateDate; - } - - public void setFileCreateDate(Date fileCreateDate) { - this.fileCreateDate = fileCreateDate; + public void setFiles(List files) { + this.files = files; } public Set getAuthors() { @@ -198,6 +186,22 @@ public class PaperDto { this.authorIds = authorIds; } + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLatexText() { + return latexText; + } + + public void setLatexText(String latexText) { + this.latexText = latexText; + } + public String getAuthorsString() { return StringUtils.abbreviate(authors .stream() diff --git a/src/main/java/ru/ulstu/paper/repository/PaperRepository.java b/src/main/java/ru/ulstu/paper/repository/PaperRepository.java index 3951f53..343e9e2 100644 --- a/src/main/java/ru/ulstu/paper/repository/PaperRepository.java +++ b/src/main/java/ru/ulstu/paper/repository/PaperRepository.java @@ -12,4 +12,6 @@ public interface PaperRepository extends JpaRepository { @Query("SELECT p FROM Paper p WHERE (:author IS NULL OR :author MEMBER OF p.authors) AND (YEAR(p.createDate) = :year OR :year IS NULL)") List filter(@Param("author") User author, @Param("year") Integer year); + + List findByIdNotIn(List paperIds); } diff --git a/src/main/java/ru/ulstu/paper/service/LatexService.java b/src/main/java/ru/ulstu/paper/service/LatexService.java new file mode 100644 index 0000000..82dd1ba --- /dev/null +++ b/src/main/java/ru/ulstu/paper/service/LatexService.java @@ -0,0 +1,76 @@ +package ru.ulstu.paper.service; + +import org.springframework.stereotype.Service; +import ru.ulstu.file.service.FileService; +import ru.ulstu.paper.model.PaperDto; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; + +@Service +public class LatexService { + private final String pdfLatexError = "Errors occurred while executing pdfLaTeX."; + private final String bibtexError = "Errors occurred while executing bibtex."; + private String errorMessage; + private File pdfFile; + private FileService fileService; + + public LatexService(FileService fileService) { + this.fileService = fileService; + } + + public byte[] generatePdfFromLatexFile(PaperDto paper) throws IOException, InterruptedException { + fileService.createLatexAttachs(paper); + File tex = fileService.createLatexFile(paper); + + if (!generate(paper.getTitle(), tex.getParentFile())) { + throw new IOException(errorMessage); + } + + return Files.readAllBytes(pdfFile.toPath()); + } + + private int startProcess(String[] args, File dir, String message) throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(args); + processBuilder.redirectErrorStream(true); + processBuilder.directory(dir); + + Process process = processBuilder.start(); + InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream()); + + try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + while ((bufferedReader.readLine()) != null) { + // + } + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + errorMessage = message + " Exit value of the process: " + exitCode; + } + return exitCode; + } + + private boolean generate(String filename, File dir) throws IOException, InterruptedException { + startProcess(new String[]{"pdflatex", filename, "--interaction=nonstopmode"}, dir, pdfLatexError); + startProcess(new String[]{"bibtex", filename}, dir, bibtexError); + if (startProcess(new String[]{"pdflatex", filename, "--interaction=nonstopmode"}, dir, pdfLatexError) != 0) { + return false; + } + return checkPdf(filename, dir); + } + + private boolean checkPdf(String filename, File dir) { + pdfFile = new File(dir.getAbsolutePath() + File.separator + filename + ".pdf"); + + if (pdfFile.isFile()) { + return true; + } else { + errorMessage = "The pdf file could not be created."; + return false; + } + } +} diff --git a/src/main/java/ru/ulstu/paper/service/PaperService.java b/src/main/java/ru/ulstu/paper/service/PaperService.java index 9df8be2..33344a5 100644 --- a/src/main/java/ru/ulstu/paper/service/PaperService.java +++ b/src/main/java/ru/ulstu/paper/service/PaperService.java @@ -5,11 +5,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.service.FileService; import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.PaperDto; import ru.ulstu.paper.model.PaperFilterDto; import ru.ulstu.paper.repository.PaperRepository; +import ru.ulstu.timeline.service.EventService; import ru.ulstu.user.model.User; import ru.ulstu.user.service.UserService; @@ -21,6 +23,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; import static org.springframework.util.ObjectUtils.isEmpty; import static ru.ulstu.core.util.StreamApiUtils.convert; import static ru.ulstu.paper.model.Paper.PaperStatus.ATTENTION; @@ -28,27 +31,32 @@ import static ru.ulstu.paper.model.Paper.PaperStatus.COMPLETED; import static ru.ulstu.paper.model.Paper.PaperStatus.DRAFT; import static ru.ulstu.paper.model.Paper.PaperStatus.FAILED; import static ru.ulstu.paper.model.Paper.PaperStatus.ON_PREPARATION; +import static ru.ulstu.paper.model.Paper.PaperType.OTHER; @Service public class PaperService { private final static int MAX_DISPLAY_SIZE = 40; + private final static String PAPER_FORMATTED_TEMPLATE = "%s %s"; private final PaperNotificationService paperNotificationService; private final PaperRepository paperRepository; private final UserService userService; private final DeadlineService deadlineService; private final FileService fileService; + private final EventService eventService; public PaperService(PaperRepository paperRepository, FileService fileService, PaperNotificationService paperNotificationService, UserService userService, - DeadlineService deadlineService) { + DeadlineService deadlineService, + EventService eventService) { this.paperRepository = paperRepository; this.fileService = fileService; this.paperNotificationService = paperNotificationService; this.userService = userService; this.deadlineService = deadlineService; + this.eventService = eventService; } public List findAll() { @@ -61,15 +69,19 @@ public class PaperService { return papers; } - public List findAllActive() { - return findAllDto() + public List findAllActive() { + return findAll() .stream() .filter(paper -> paper.getStatus() != COMPLETED && paper.getStatus() != FAILED) - .collect(Collectors.toList()); + .collect(toList()); + } + + public List findAllActiveDto() { + return convert(findAllActive(), PaperDto::new); } public PaperDto findOneDto(Integer id) { - return new PaperDto(paperRepository.findOne(id)); + return new PaperDto(paperRepository.getOne(id)); } @Transactional @@ -77,20 +89,24 @@ public class PaperService { Paper newPaper = copyFromDto(new Paper(), paperDto); newPaper = paperRepository.save(newPaper); paperNotificationService.sendCreateNotification(newPaper); + eventService.createFromPaper(newPaper); return newPaper.getId(); } private Paper copyFromDto(Paper paper, PaperDto paperDto) throws IOException { paper.setComment(paperDto.getComment()); + paper.setUrl(paperDto.getUrl()); + paper.setLatexText(paperDto.getLatexText()); paper.setCreateDate(paper.getCreateDate() == null ? new Date() : paper.getCreateDate()); paper.setLocked(paperDto.getLocked()); paper.setStatus(paperDto.getStatus() == null ? DRAFT : paperDto.getStatus()); + paper.setType(paperDto.getType() == null ? OTHER : paperDto.getType()); paper.setTitle(paperDto.getTitle()); paper.setUpdateDate(new Date()); paper.setDeadlines(deadlineService.saveOrCreate(paperDto.getDeadlines())); - if (paperDto.getTmpFileName() != null) { - paper.setFileData(fileService.createFileFromTmp(paperDto.getTmpFileName())); - } + paper.setFiles(fileService.saveOrCreate(paperDto.getFiles().stream() + .filter(f -> !f.isDeleted()) + .collect(toList()))); paper.getAuthors().clear(); if (paperDto.getAuthorIds() != null && !paperDto.getAuthorIds().isEmpty()) { paperDto.getAuthorIds().forEach(authorIds -> paper.getAuthors().add(userService.findById(authorIds))); @@ -100,13 +116,17 @@ public class PaperService { @Transactional public Integer update(PaperDto paperDto) throws IOException { - Paper paper = paperRepository.findOne(paperDto.getId()); + Paper paper = paperRepository.getOne(paperDto.getId()); Paper.PaperStatus oldStatus = paper.getStatus(); Set oldAuthors = new HashSet<>(paper.getAuthors()); - if (paperDto.getTmpFileName() != null && paper.getFileData() != null) { - fileService.deleteFile(paper.getFileData()); + + for (FileDataDto file : paperDto.getFiles().stream() + .filter(f -> f.isDeleted() && f.getId() != null) + .collect(toList())) { + fileService.delete(file.getId()); } paperRepository.save(copyFromDto(paper, paperDto)); + eventService.updatePaperDeadlines(paper); paper.getAuthors().forEach(author -> { if (!oldAuthors.contains(author)) { @@ -123,10 +143,7 @@ public class PaperService { @Transactional public void delete(Integer paperId) throws IOException { - Paper paper = paperRepository.findOne(paperId); - if (paper.getFileData() != null) { - fileService.deleteFile(paper.getFileData()); - } + Paper paper = paperRepository.getOne(paperId); paperRepository.delete(paper); } @@ -134,6 +151,10 @@ public class PaperService { return Arrays.asList(Paper.PaperStatus.values()); } + public List getPaperTypes() { + return Arrays.asList(Paper.PaperType.values()); + } + @Transactional public Paper create(String title, User user, Date deadlineDate) { Paper paper = new Paper(); @@ -143,9 +164,11 @@ public class PaperService { paper.setCreateDate(new Date()); paper.setUpdateDate(new Date()); paper.setStatus(DRAFT); + paper.setType(OTHER); paper = paperRepository.save(paper); paperNotificationService.sendCreateNotification(paper); + eventService.createFromPaper(paper); return paper; } @@ -165,7 +188,7 @@ public class PaperService { return statusCompareResult; } return paper1.getTitle().compareTo(paper2.getTitle()); - }).collect(Collectors.toList()); + }).collect(toList()); } public PaperDto findPaper(int id) { @@ -179,7 +202,7 @@ public class PaperService { && (paper.getStatus() == ON_PREPARATION || paper.getStatus() == DRAFT || paper.getStatus() == ATTENTION)) - .collect(Collectors.toList()); + .collect(toList()); papers.forEach(paper -> { Paper.PaperStatus oldStatus = paper.getStatus(); paper.setStatus(Paper.PaperStatus.FAILED); @@ -197,10 +220,44 @@ public class PaperService { } public PaperDto findById(Integer paperId) { - return new PaperDto(paperRepository.findOne(paperId)); + return new PaperDto(paperRepository.getOne(paperId)); + } + + public Paper findEntityById(Integer paperId) { + return paperRepository.getOne(paperId); + } + + public List findAllNotSelect(List paperIds) { + if (!paperIds.isEmpty()) { + return sortPapers(paperRepository.findByIdNotIn(paperIds)); + } else { + return sortPapers(paperRepository.findAll()); + } + } public List getPaperAuthors() { return userService.findAll(); } + + public List getFormattedPaperList() { + return findAllCompleted() + .stream() + .map(paper -> String.format(PAPER_FORMATTED_TEMPLATE, paper.getTitle(), getAuthors(paper))) + .collect(toList()); + } + + private List findAllCompleted() { + return findAll() + .stream() + .filter(paper -> paper.getStatus() == COMPLETED) + .collect(toList()); + } + + private String getAuthors(Paper paper) { + return paper.getAuthors() + .stream() + .map(User::getUserAbbreviate) + .collect(Collectors.joining(", ")); + } } diff --git a/src/main/java/ru/ulstu/project/controller/ProjectController.java b/src/main/java/ru/ulstu/project/controller/ProjectController.java new file mode 100644 index 0000000..affdaec --- /dev/null +++ b/src/main/java/ru/ulstu/project/controller/ProjectController.java @@ -0,0 +1,86 @@ +package ru.ulstu.project.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.project.model.Project; +import ru.ulstu.project.model.ProjectDto; +import ru.ulstu.project.service.ProjectService; +import springfox.documentation.annotations.ApiIgnore; + +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; + +@Controller() +@RequestMapping(value = "/projects") +@ApiIgnore +public class ProjectController { + private final ProjectService projectService; + + public ProjectController(ProjectService projectService) { + this.projectService = projectService; + } + + @GetMapping("/dashboard") + public void getDashboard(ModelMap modelMap) { + modelMap.put("projects", projectService.findAllDto()); + } + + @GetMapping("/projects") + public void getProjects(ModelMap modelMap) { + modelMap.put("projects", projectService.findAllDto()); + } + + @GetMapping("/project") + public void getProject(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + modelMap.put("projectDto", projectService.findOneDto(id)); + } else { + modelMap.put("projectDto", new ProjectDto()); + } + } + + @ModelAttribute("allStatuses") + public List getProjectStatuses() { + return projectService.getProjectStatuses(); + } + + @PostMapping(value = "/project", params = "save") + public String save(@Valid ProjectDto projectDto, Errors errors) throws IOException { + filterEmptyDeadlines(projectDto); + if (projectDto.getDeadlines().isEmpty()) { + errors.rejectValue("deadlines", "errorCode", "Не может быть пустым"); + } + if (errors.hasErrors()) { + return "/projects/project"; + } + projectService.save(projectDto); + return String.format("redirect:%s", "/projects/projects"); + } + + @PostMapping(value = "/project", params = "addDeadline") + public String addDeadline(@Valid ProjectDto projectDto, Errors errors) { + filterEmptyDeadlines(projectDto); + if (errors.hasErrors()) { + return "/projects/project"; + } + projectDto.getDeadlines().add(new Deadline()); + return "/projects/project"; + } + + private void filterEmptyDeadlines(ProjectDto projectDto) { + projectDto.setDeadlines(projectDto.getDeadlines().stream() + .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/ru/ulstu/project/model/Project.java b/src/main/java/ru/ulstu/project/model/Project.java index 06722a1..5b9da69 100644 --- a/src/main/java/ru/ulstu/project/model/Project.java +++ b/src/main/java/ru/ulstu/project/model/Project.java @@ -1,26 +1,67 @@ package ru.ulstu.project.model; -import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.file.model.FileData; +import ru.ulstu.grant.model.Grant; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; @Entity public class Project extends BaseEntity { + public enum ProjectStatus { + APPLICATION("Заявка"), + ON_COMPETITION("Отправлен на конкурс"), + SUCCESSFUL_PASSAGE("Успешное прохождение"), + IN_WORK("В работе"), + COMPLETED("Завершен"), + FAILED("Провалены сроки"); + + private String statusName; + + ProjectStatus(String statusName) { + this.statusName = statusName; + } + + public String getStatusName() { + return statusName; + } + } @NotBlank private String title; + @Enumerated(value = EnumType.STRING) + private ProjectStatus status = ProjectStatus.APPLICATION; + + @NotNull + private String description; + @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "project_id") private List deadlines = new ArrayList<>(); + @ManyToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "grant_id") + private Grant grant; + + @NotNull + private String repository; + + @ManyToOne + @JoinColumn(name = "file_id") + private FileData application; + public String getTitle() { return title; } @@ -29,6 +70,38 @@ public class Project extends BaseEntity { this.title = title; } + public ProjectStatus getStatus() { + return status; + } + + public void setStatus(ProjectStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Grant getGrant() { + return grant; + } + + public void setGrant(Grant grant) { + this.grant = grant; + } + + public String getRepository() { + return repository; + } + + public void setRepository(String repository) { + this.repository = repository; + } + public List getDeadlines() { return deadlines; } @@ -36,4 +109,12 @@ public class Project extends BaseEntity { public void setDeadlines(List deadlines) { this.deadlines = deadlines; } + + public FileData getApplication() { + return application; + } + + public void setApplication(FileData application) { + this.application = application; + } } diff --git a/src/main/java/ru/ulstu/project/model/ProjectDto.java b/src/main/java/ru/ulstu/project/model/ProjectDto.java index fdce26b..d9e60ab 100644 --- a/src/main/java/ru/ulstu/project/model/ProjectDto.java +++ b/src/main/java/ru/ulstu/project/model/ProjectDto.java @@ -2,21 +2,24 @@ package ru.ulstu.project.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotEmpty; -import ru.ulstu.deadline.model.DeadlineDto; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.grant.model.GrantDto; +import javax.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; -import static ru.ulstu.core.util.StreamApiUtils.convert; - public class ProjectDto { private Integer id; @NotEmpty private String title; - - private List deadlines = new ArrayList<>(); + private Project.ProjectStatus status; + private String description; + private List deadlines = new ArrayList<>(); + private GrantDto grant; + private String repository; + private String applicationFileName; public ProjectDto() { } @@ -28,17 +31,31 @@ public class ProjectDto { @JsonCreator public ProjectDto(@JsonProperty("id") Integer id, @JsonProperty("title") String title, - @JsonProperty("deadlines") List deadlines) { + @JsonProperty("status") Project.ProjectStatus status, + @JsonProperty("description") String description, + @JsonProperty("grant") GrantDto grant, + @JsonProperty("repository") String repository, + @JsonProperty("deadlines") List deadlines) { this.id = id; this.title = title; + this.status = status; + this.description = description; + this.grant = grant; + this.repository = repository; this.deadlines = deadlines; + this.applicationFileName = null; } public ProjectDto(Project project) { this.id = project.getId(); this.title = project.getTitle(); - this.deadlines = convert(project.getDeadlines(), DeadlineDto::new); + this.status = project.getStatus(); + this.description = project.getDescription(); + this.applicationFileName = project.getApplication() == null ? null : project.getApplication().getName(); + this.grant = project.getGrant() == null ? null : new GrantDto(project.getGrant()); + this.repository = project.getRepository(); + this.deadlines = project.getDeadlines(); } public Integer getId() { @@ -57,11 +74,51 @@ public class ProjectDto { this.title = title; } - public List getDeadlines() { + public Project.ProjectStatus getStatus() { + return status; + } + + public void setStatus(Project.ProjectStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public GrantDto getGrant() { + return grant; + } + + public void setGrant(GrantDto grant) { + this.grant = grant; + } + + public String getRepository() { + return repository; + } + + public void setRepository(String repository) { + this.repository = repository; + } + + public List getDeadlines() { return deadlines; } - public void setDeadlines(List deadlines) { + public void setDeadlines(List deadlines) { this.deadlines = deadlines; } + + public String getApplicationFileName() { + return applicationFileName; + } + + public void setApplicationFileName(String applicationFileName) { + this.applicationFileName = applicationFileName; + } } diff --git a/src/main/java/ru/ulstu/project/service/ProjectService.java b/src/main/java/ru/ulstu/project/service/ProjectService.java index b54a60a..115f5ae 100644 --- a/src/main/java/ru/ulstu/project/service/ProjectService.java +++ b/src/main/java/ru/ulstu/project/service/ProjectService.java @@ -2,45 +2,82 @@ package ru.ulstu.project.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.util.StringUtils; import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.file.service.FileService; +import ru.ulstu.grant.repository.GrantRepository; import ru.ulstu.project.model.Project; import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.repository.ProjectRepository; +import java.io.IOException; +import java.util.Arrays; import java.util.List; import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; +import static ru.ulstu.project.model.Project.ProjectStatus.APPLICATION; @Service public class ProjectService { + private final static int MAX_DISPLAY_SIZE = 40; private final ProjectRepository projectRepository; private final DeadlineService deadlineService; + private final GrantRepository grantRepository; + private final FileService fileService; public ProjectService(ProjectRepository projectRepository, - DeadlineService deadlineService) { + DeadlineService deadlineService, + GrantRepository grantRepository, + FileService fileService) { this.projectRepository = projectRepository; this.deadlineService = deadlineService; + this.grantRepository = grantRepository; + this.fileService = fileService; } public List findAll() { return projectRepository.findAll(); } + public List findAllDto() { + List projects = convert(findAll(), ProjectDto::new); + projects.forEach(projectDto -> projectDto.setTitle(StringUtils.abbreviate(projectDto.getTitle(), MAX_DISPLAY_SIZE))); + return projects; + } + + public ProjectDto findOneDto(Integer id) { + return new ProjectDto(projectRepository.getOne(id)); + } + + public List getProjectStatuses() { + return Arrays.asList(Project.ProjectStatus.values()); + } + @Transactional - public Project create(ProjectDto projectDto) { + public Project create(ProjectDto projectDto) throws IOException { Project newProject = copyFromDto(new Project(), projectDto); newProject = projectRepository.save(newProject); return newProject; } - private Project copyFromDto(Project project, ProjectDto projectDto) { + private Project copyFromDto(Project project, ProjectDto projectDto) throws IOException { + project.setDescription(projectDto.getDescription()); + project.setStatus(projectDto.getStatus() == null ? APPLICATION : projectDto.getStatus()); project.setTitle(projectDto.getTitle()); + if (projectDto.getGrant() != null && projectDto.getGrant().getId() != null) { + project.setGrant(grantRepository.getOne(projectDto.getGrant().getId())); + } + project.setRepository(projectDto.getRepository()); project.setDeadlines(deadlineService.saveOrCreate(projectDto.getDeadlines())); + if (projectDto.getApplicationFileName() != null) { + project.setApplication(fileService.createFileFromTmp(projectDto.getApplicationFileName())); + } return project; } - public Project save(ProjectDto projectDto) { + public Project save(ProjectDto projectDto) throws IOException { if (isEmpty(projectDto.getId())) { return create(projectDto); } else { @@ -53,7 +90,7 @@ public class ProjectService { } public Project findById(Integer id) { - return projectRepository.findOne(id); + return projectRepository.getOne(id); } } diff --git a/src/main/java/ru/ulstu/students/controller/Navigation.java b/src/main/java/ru/ulstu/students/controller/Navigation.java new file mode 100644 index 0000000..3804a8b --- /dev/null +++ b/src/main/java/ru/ulstu/students/controller/Navigation.java @@ -0,0 +1,16 @@ +package ru.ulstu.students.controller; + +import org.springframework.validation.Errors; + +public class Navigation { + public static final String REDIRECT_TO = "redirect:%s"; + public static final String TASKS_PAGE = "/students/tasks"; + public static final String TASK_PAGE = "/students/task"; + + public static String hasErrors(Errors errors, String page) { + if (errors.hasErrors()) { + return page; + } + return null; + } +} diff --git a/src/main/java/ru/ulstu/students/controller/TaskController.java b/src/main/java/ru/ulstu/students/controller/TaskController.java new file mode 100644 index 0000000..50cb052 --- /dev/null +++ b/src/main/java/ru/ulstu/students/controller/TaskController.java @@ -0,0 +1,90 @@ +package ru.ulstu.students.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.students.model.Task; +import ru.ulstu.students.model.TaskDto; +import ru.ulstu.students.service.TaskService; +import springfox.documentation.annotations.ApiIgnore; + +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; +import static ru.ulstu.students.controller.Navigation.REDIRECT_TO; +import static ru.ulstu.students.controller.Navigation.TASKS_PAGE; +import static ru.ulstu.students.controller.Navigation.TASK_PAGE; + +@Controller() +@RequestMapping(value = "/students") +@ApiIgnore +public class TaskController { + + private final TaskService taskService; + + public TaskController(TaskService taskService) { + this.taskService = taskService; + } + + @GetMapping("/tasks") + public void getTasks(ModelMap modelMap) { + modelMap.put("tasks", taskService.findAllDto()); + } + + @GetMapping("/dashboard") + public void getDashboard(ModelMap modelMap) { + modelMap.put("tasks", taskService.findAllDto()); + } + + @GetMapping("/task") + public void getTask(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + modelMap.put("taskDto", taskService.findOneDto(id)); + } else { + modelMap.put("taskDto", new TaskDto()); + } + } + + @PostMapping(value = "/task", params = "save") + public String save(@Valid TaskDto taskDto, Errors errors) throws IOException { + filterEmptyDeadlines(taskDto); + if (taskDto.getDeadlines().isEmpty()) { + errors.rejectValue("deadlines", "errorCode", "Не может быть пустым"); + } + if (errors.hasErrors()) { + return TASK_PAGE; + } + taskService.save(taskDto); + return String.format(REDIRECT_TO, TASKS_PAGE); + } + + @PostMapping(value = "/task", params = "addDeadline") + public String addDeadline(@Valid TaskDto taskDto, Errors errors) { + filterEmptyDeadlines(taskDto); + if (errors.hasErrors()) { + return TASK_PAGE; + } + taskDto.getDeadlines().add(new Deadline()); + return TASK_PAGE; + } + + @ModelAttribute("allStatuses") + public List getTaskStatuses() { + return taskService.getTaskStatuses(); + } + + private void filterEmptyDeadlines(TaskDto taskDto) { + taskDto.setDeadlines(taskDto.getDeadlines().stream() + .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/ru/ulstu/students/model/Task.java b/src/main/java/ru/ulstu/students/model/Task.java new file mode 100644 index 0000000..5646ef3 --- /dev/null +++ b/src/main/java/ru/ulstu/students/model/Task.java @@ -0,0 +1,130 @@ +package ru.ulstu.students.model; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.tags.model.Tag; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Entity +public class Task extends BaseEntity { + + public enum TaskStatus { + IN_WORK("В работе"), + COMPLETED("Завершен"), + FAILED("Провалены сроки"), + LOADED_FROM_KIAS("Загружен автоматически"); + + private String statusName; + + TaskStatus(String name) { + this.statusName = name; + } + + public String getStatusName() { + return statusName; + } + } + + @NotBlank + private String title; + + private String description; + + @Enumerated(value = EnumType.STRING) + private ru.ulstu.students.model.Task.TaskStatus status = TaskStatus.IN_WORK; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "task_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + @OrderBy("date") + private List deadlines = new ArrayList<>(); + + @Column(name = "create_date") + @Temporal(TemporalType.TIMESTAMP) + private Date createDate = new Date(); + + @Column(name = "update_date") + @Temporal(TemporalType.TIMESTAMP) + private Date updateDate = new Date(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "task_tags", + joinColumns = {@JoinColumn(name = "task_id")}, + inverseJoinColumns = {@JoinColumn(name = "tag_id")}) + private List tags = new ArrayList<>(); + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public TaskStatus getStatus() { + return status; + } + + public void setStatus(TaskStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public Date getUpdateDate() { + return updateDate; + } + + public void setUpdateDate(Date updateDate) { + this.updateDate = updateDate; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } +} diff --git a/src/main/java/ru/ulstu/students/model/TaskDto.java b/src/main/java/ru/ulstu/students/model/TaskDto.java new file mode 100644 index 0000000..5071dab --- /dev/null +++ b/src/main/java/ru/ulstu/students/model/TaskDto.java @@ -0,0 +1,144 @@ +package ru.ulstu.students.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.tags.model.Tag; + +import javax.validation.constraints.NotEmpty; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class TaskDto { + + private final static int MAX_TAGS_LENGTH = 50; + + private Integer id; + @NotEmpty + private String title; + private String description; + private Task.TaskStatus status; + private List deadlines = new ArrayList<>(); + private Date createDate; + private Date updateDate; + private Set tagIds; + private List tags; + + public TaskDto() { + deadlines.add(new Deadline()); + } + + @JsonCreator + public TaskDto(@JsonProperty("id") Integer id, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("createDate") Date createDate, + @JsonProperty("updateDate") Date updateDate, + @JsonProperty("status") Task.TaskStatus status, + @JsonProperty("deadlines") List deadlines, + @JsonProperty("tagIds") Set tagIds, + @JsonProperty("tags") List tags) { + this.id = id; + this.title = title; + this.status = status; + this.deadlines = deadlines; + this.createDate = createDate; + this.updateDate = updateDate; + this.description = description; + this.tags = tags; + } + + public TaskDto(Task task) { + this.id = task.getId(); + this.title = task.getTitle(); + this.status = task.getStatus(); + this.deadlines = task.getDeadlines(); + this.createDate = task.getCreateDate(); + this.updateDate = task.getUpdateDate(); + this.description = task.getDescription(); + this.tags = task.getTags(); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Task.TaskStatus getStatus() { + return status; + } + + public void setStatus(Task.TaskStatus status) { + this.status = status; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public Date getUpdateDate() { + return updateDate; + } + + public void setUpdateDate(Date updateDate) { + this.updateDate = updateDate; + } + + public Set getTagIds() { + return tagIds; + } + + public void setTagIds(Set tagIds) { + this.tagIds = tagIds; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public String getTagsString() { + return StringUtils.abbreviate(tags + .stream() + .map(tag -> tag.getTagName()) + .collect(Collectors.joining(", ")), MAX_TAGS_LENGTH); + } +} diff --git a/src/main/java/ru/ulstu/students/repository/TaskRepository.java b/src/main/java/ru/ulstu/students/repository/TaskRepository.java new file mode 100644 index 0000000..6f48d1f --- /dev/null +++ b/src/main/java/ru/ulstu/students/repository/TaskRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.students.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.students.model.Task; + +public interface TaskRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/students/service/TaskService.java b/src/main/java/ru/ulstu/students/service/TaskService.java new file mode 100644 index 0000000..dac6ef5 --- /dev/null +++ b/src/main/java/ru/ulstu/students/service/TaskService.java @@ -0,0 +1,95 @@ +package ru.ulstu.students.service; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.students.model.Task; +import ru.ulstu.students.model.TaskDto; +import ru.ulstu.students.repository.TaskRepository; +import ru.ulstu.tags.service.TagService; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; +import static ru.ulstu.students.model.Task.TaskStatus.IN_WORK; + +@Service +public class TaskService { + + private final static int MAX_DISPLAY_SIZE = 40; + + private final TaskRepository taskRepository; + private final DeadlineService deadlineService; + private final TagService tagService; + + public TaskService(TaskRepository grantRepository, + DeadlineService deadlineService, TagService tagService) { + this.taskRepository = grantRepository; + this.deadlineService = deadlineService; + this.tagService = tagService; + } + + public List findAll() { + return taskRepository.findAll(); + } + + public List findAllDto() { + List tasks = convert(findAll(), TaskDto::new); + tasks.forEach(taskDto -> taskDto.setTitle(StringUtils.abbreviate(taskDto.getTitle(), MAX_DISPLAY_SIZE))); + return tasks; + } + + public TaskDto findOneDto(Integer id) { + return new TaskDto(taskRepository.getOne(id)); + } + + @Transactional + public Integer create(TaskDto taskDto) throws IOException { + Task newTask = copyFromDto(new Task(), taskDto); + newTask = taskRepository.save(newTask); + return newTask.getId(); + } + + private Task copyFromDto(Task task, TaskDto taskDto) throws IOException { + task.setTitle(taskDto.getTitle()); + task.setDescription(taskDto.getDescription()); + task.setStatus(taskDto.getStatus() == null ? IN_WORK : taskDto.getStatus()); + task.setDeadlines(deadlineService.saveOrCreate(taskDto.getDeadlines())); + task.setCreateDate(task.getCreateDate() == null ? new Date() : task.getCreateDate()); + task.setUpdateDate(new Date()); + task.getTags().clear(); + task.setTags(tagService.saveOrCreate(taskDto.getTags())); + return task; + } + + @Transactional + public Integer update(TaskDto taskDto) throws IOException { + Task task = taskRepository.getOne(taskDto.getId()); + taskRepository.save(copyFromDto(task, taskDto)); + return task.getId(); + } + + @Transactional + public void delete(Integer taskId) throws IOException { + Task task = taskRepository.getOne(taskId); + taskRepository.delete(task); + } + + public void save(TaskDto taskDto) throws IOException { + if (isEmpty(taskDto.getId())) { + create(taskDto); + } else { + update(taskDto); + } + } + + public List getTaskStatuses() { + return Arrays.asList(Task.TaskStatus.values()); + } + +} diff --git a/src/main/java/ru/ulstu/tags/model/Tag.java b/src/main/java/ru/ulstu/tags/model/Tag.java new file mode 100644 index 0000000..460e9b0 --- /dev/null +++ b/src/main/java/ru/ulstu/tags/model/Tag.java @@ -0,0 +1,44 @@ +package ru.ulstu.tags.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import ru.ulstu.core.model.BaseEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Size; + +@Entity +@Table(name = "tag") +public class Tag extends BaseEntity { + + @NotEmpty + @Size(max = 50) + @Column(name = "tag_name") + private String tagName; + + public Tag() { + + } + + @JsonCreator + public Tag(@JsonProperty("id") Integer id, + @JsonProperty("tag_name") String tagName) { + this.setId(id); + this.tagName = tagName; + } + + public Tag(String name) { + this.tagName = name; + } + + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } +} diff --git a/src/main/java/ru/ulstu/tags/repository/TagRepository.java b/src/main/java/ru/ulstu/tags/repository/TagRepository.java new file mode 100644 index 0000000..213abae --- /dev/null +++ b/src/main/java/ru/ulstu/tags/repository/TagRepository.java @@ -0,0 +1,12 @@ +package ru.ulstu.tags.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.ulstu.tags.model.Tag; + +public interface TagRepository extends JpaRepository { + + @Query("SELECT t FROM Tag t WHERE (t.tagName = :tagName)") + Tag findByName(@Param("tagName") String tagName); +} diff --git a/src/main/java/ru/ulstu/tags/service/TagService.java b/src/main/java/ru/ulstu/tags/service/TagService.java new file mode 100644 index 0000000..ad5d227 --- /dev/null +++ b/src/main/java/ru/ulstu/tags/service/TagService.java @@ -0,0 +1,53 @@ +package ru.ulstu.tags.service; + +import org.springframework.stereotype.Service; +import ru.ulstu.tags.model.Tag; +import ru.ulstu.tags.repository.TagRepository; + +import javax.transaction.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class TagService { + + private final TagRepository tagRepository; + + + public TagService(TagRepository tagRepository) { + + this.tagRepository = tagRepository; + } + + public List saveOrCreate(List tags) { + return tags + .stream() + .map(tag -> { + if (tag.getId() != null) { + return getExistById(tag); + } else { + Tag existTag = isExistByName(tag.getTagName()); + return existTag != null ? existTag : create(tag); + } + }).collect(Collectors.toList()); + } + + @Transactional + public Tag getExistById(Tag tag) { + return tagRepository.getOne(tag.getId()); + } + + @Transactional + public Tag isExistByName(String tagName) { + return tagRepository.findByName(tagName); + } + + @Transactional + public Tag create(Tag tag) { + Tag newTag = new Tag(); + newTag.setTagName(tag.getTagName()); + newTag = tagRepository.save(newTag); + return newTag; + } + +} diff --git a/src/main/java/ru/ulstu/timeline/model/Event.java b/src/main/java/ru/ulstu/timeline/model/Event.java index 290f136..f8fd179 100644 --- a/src/main/java/ru/ulstu/timeline/model/Event.java +++ b/src/main/java/ru/ulstu/timeline/model/Event.java @@ -1,7 +1,7 @@ package ru.ulstu.timeline.model; -import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.paper.model.Paper; import ru.ulstu.user.model.User; import javax.persistence.CascadeType; @@ -16,6 +16,7 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.List; @@ -71,6 +72,10 @@ public class Event extends BaseEntity { @JoinColumn(name = "child_id") private List parents; + @ManyToOne + @JoinColumn(name = "paper_id") + private Paper paper; + public String getTitle() { return title; } @@ -150,4 +155,12 @@ public class Event extends BaseEntity { public void setParents(List parents) { this.parents = parents; } + + public Paper getPaper() { + return paper; + } + + public void setPaper(Paper paper) { + this.paper = paper; + } } diff --git a/src/main/java/ru/ulstu/timeline/model/EventDto.java b/src/main/java/ru/ulstu/timeline/model/EventDto.java index 9e70a9d..17e4623 100644 --- a/src/main/java/ru/ulstu/timeline/model/EventDto.java +++ b/src/main/java/ru/ulstu/timeline/model/EventDto.java @@ -2,7 +2,8 @@ package ru.ulstu.timeline.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotBlank; +import javax.validation.constraints.NotBlank; +import ru.ulstu.paper.model.PaperDto; import ru.ulstu.user.model.UserDto; import javax.validation.constraints.NotNull; @@ -23,6 +24,7 @@ public class EventDto { private final Date updateDate; private final String description; private final List recipients; + private PaperDto paperDto; @JsonCreator public EventDto(@JsonProperty("id") Integer id, @@ -33,6 +35,7 @@ public class EventDto { @JsonProperty("createDate") Date createDate, @JsonProperty("updateDate") Date updateDate, @JsonProperty("description") String description, + @JsonProperty("paperDto") PaperDto paperDto, @JsonProperty("recipients") List recipients) { this.id = id; this.title = title; @@ -43,6 +46,7 @@ public class EventDto { this.updateDate = updateDate; this.description = description; this.recipients = recipients; + this.paperDto = paperDto; } public EventDto(Event event) { @@ -54,6 +58,7 @@ public class EventDto { this.createDate = event.getCreateDate(); this.updateDate = event.getUpdateDate(); this.description = event.getDescription(); + this.paperDto = new PaperDto(event.getPaper()); this.recipients = convert(event.getRecipients(), UserDto::new); } @@ -92,4 +97,12 @@ public class EventDto { public Date getExecuteDate() { return executeDate; } + + public PaperDto getPaperDto() { + return paperDto; + } + + public void setPaperDto(PaperDto paperDto) { + this.paperDto = paperDto; + } } diff --git a/src/main/java/ru/ulstu/timeline/model/Timeline.java b/src/main/java/ru/ulstu/timeline/model/Timeline.java index 8e588cb..06e04bc 100644 --- a/src/main/java/ru/ulstu/timeline/model/Timeline.java +++ b/src/main/java/ru/ulstu/timeline/model/Timeline.java @@ -6,6 +6,7 @@ import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; @Entity @@ -13,7 +14,7 @@ public class Timeline extends BaseEntity { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "timeline_id") - private List events; + private List events = new ArrayList<>(); public List getEvents() { return events; diff --git a/src/main/java/ru/ulstu/timeline/repository/EventRepository.java b/src/main/java/ru/ulstu/timeline/repository/EventRepository.java index 4848e12..eb5c08b 100644 --- a/src/main/java/ru/ulstu/timeline/repository/EventRepository.java +++ b/src/main/java/ru/ulstu/timeline/repository/EventRepository.java @@ -2,6 +2,7 @@ package ru.ulstu.timeline.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import ru.ulstu.paper.model.Paper; import ru.ulstu.timeline.model.Event; import java.util.List; @@ -12,4 +13,6 @@ public interface EventRepository extends JpaRepository { @Query("SELECT e FROM Event e WHERE e.executeDate > CURRENT_DATE ORDER BY e.executeDate") List findAllFuture(); + + List findAllByPaper(Paper paper); } diff --git a/src/main/java/ru/ulstu/timeline/service/EventScheduler.java b/src/main/java/ru/ulstu/timeline/service/EventScheduler.java index 6df7fcb..7899aca 100644 --- a/src/main/java/ru/ulstu/timeline/service/EventScheduler.java +++ b/src/main/java/ru/ulstu/timeline/service/EventScheduler.java @@ -34,6 +34,10 @@ public class EventScheduler { Map variables = ImmutableMap.of("description", event.getDescription()); event.getRecipients() .forEach(recipient -> mailService.sendEmailFromTemplate(variables, recipient, "eventNotification", event.getTitle())); + if (event.getPeriod() == null) { + event.setStatus(Event.EventStatus.COMPLETED); + eventService.save(event); + } }); } diff --git a/src/main/java/ru/ulstu/timeline/service/EventService.java b/src/main/java/ru/ulstu/timeline/service/EventService.java index ca95b9a..a1f54f5 100644 --- a/src/main/java/ru/ulstu/timeline/service/EventService.java +++ b/src/main/java/ru/ulstu/timeline/service/EventService.java @@ -1,15 +1,22 @@ package ru.ulstu.timeline.service; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; import ru.ulstu.timeline.model.Event; import ru.ulstu.timeline.model.EventDto; +import ru.ulstu.timeline.model.Timeline; import ru.ulstu.timeline.repository.EventRepository; import ru.ulstu.user.model.UserDto; import ru.ulstu.user.service.UserService; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static ru.ulstu.core.util.StreamApiUtils.convert; @@ -17,11 +24,14 @@ import static ru.ulstu.core.util.StreamApiUtils.convert; public class EventService { private final EventRepository eventRepository; + private final TimelineService timelineService; private final UserService userService; public EventService(EventRepository eventRepository, + @Lazy TimelineService timelineService, UserService userService) { this.eventRepository = eventRepository; + this.timelineService = timelineService; this.userService = userService; } @@ -52,19 +62,24 @@ public class EventService { @Transactional public Integer update(EventDto eventDto) { - Event event = eventRepository.findOne(eventDto.getId()); + Event event = eventRepository.getOne(eventDto.getId()); return eventRepository.save(copyFromDto(event, eventDto)).getId(); } + @Transactional + public Event save(Event event) { + return eventRepository.save(event); + } + @Transactional public void delete(Integer eventId) { - Event event = eventRepository.findOne(eventId); + Event event = eventRepository.getOne(eventId); event.setParents(null); eventRepository.delete(event); } public List findByIds(List ids) { - return eventRepository.findAll(ids); + return eventRepository.findAllById(ids); } public void createBasedOn(Event event, Date executeDate) { @@ -79,11 +94,41 @@ public class EventService { event = eventRepository.save(event); //set child to parent - Event parentEvent = eventRepository.findOne(parentEventId); + Event parentEvent = eventRepository.getOne(parentEventId); parentEvent.setChild(event); eventRepository.save(parentEvent); } + public void createFromPaper(Paper newPaper) { + List timelines = timelineService.findAll(); + Timeline timeline = timelines.isEmpty() ? new Timeline() : timelines.get(0); + + for (Deadline deadline : newPaper.getDeadlines() + .stream() + .filter(d -> d.getDate().after(new Date()) || DateUtils.isSameDay(d.getDate(), new Date())) + .collect(Collectors.toList())) { + Event newEvent = new Event(); + newEvent.setTitle("Дедлайн статьи"); + newEvent.setStatus(Event.EventStatus.NEW); + newEvent.setExecuteDate(deadline.getDate()); + newEvent.setCreateDate(new Date()); + newEvent.setUpdateDate(new Date()); + newEvent.setDescription("Дедлайн '" + deadline.getDescription() + "' cтатьи '" + newPaper.getTitle() + "'"); + newEvent.setRecipients(new ArrayList(newPaper.getAuthors())); + newEvent.setPaper(newPaper); + eventRepository.save(newEvent); + + timeline.getEvents().add(newEvent); + timelineService.save(timeline); + } + } + + public void updatePaperDeadlines(Paper paper) { + eventRepository.deleteAll(eventRepository.findAllByPaper(paper)); + + createFromPaper(paper); + } + public List findByCurrentDate() { return eventRepository.findByCurrentDate(); } diff --git a/src/main/java/ru/ulstu/timeline/service/TimelineService.java b/src/main/java/ru/ulstu/timeline/service/TimelineService.java index 94388ee..dd5c384 100644 --- a/src/main/java/ru/ulstu/timeline/service/TimelineService.java +++ b/src/main/java/ru/ulstu/timeline/service/TimelineService.java @@ -23,15 +23,24 @@ public class TimelineService { this.eventService = eventService; } - public List findAll() { + public List findAllDto() { return convert(timelineRepository.findAll(), TimelineDto::new); } + public List findAll() { + return timelineRepository.findAll(); + } + @Transactional public int create(TimelineDto timelineDto) { return timelineRepository.save(copyFromDto(new Timeline(), timelineDto)).getId(); } + @Transactional + public Timeline save(Timeline timeline) { + return timelineRepository.save(timeline); + } + private Timeline copyFromDto(Timeline timeline, TimelineDto timelineDto) { timeline.setEvents(eventService.findByIds(convert(timelineDto.getEvents(), EventDto::getId))); return timeline; @@ -39,13 +48,13 @@ public class TimelineService { @Transactional public Integer update(TimelineDto timelineDto) { - Timeline timeline = timelineRepository.findOne(timelineDto.getId()); + Timeline timeline = timelineRepository.getOne(timelineDto.getId()); return timelineRepository.save(copyFromDto(timeline, timelineDto)).getId(); } @Transactional public void delete(Integer timelineId) { - Timeline timeline = timelineRepository.findOne(timelineId); + Timeline timeline = timelineRepository.getOne(timelineId); timelineRepository.delete(timeline); } } diff --git a/src/main/java/ru/ulstu/user/model/User.java b/src/main/java/ru/ulstu/user/model/User.java index 16a873b..fcfd88e 100644 --- a/src/main/java/ru/ulstu/user/model/User.java +++ b/src/main/java/ru/ulstu/user/model/User.java @@ -1,18 +1,20 @@ package ru.ulstu.user.model; import org.hibernate.annotations.BatchSize; -import org.hibernate.validator.constraints.Email; import ru.ulstu.configuration.Constants; import ru.ulstu.core.model.BaseEntity; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; @@ -24,6 +26,8 @@ import java.util.Set; @Entity @Table(name = "users") public class User extends BaseEntity { + private final static String USER_ABBREVIATE_TEMPLATE = "%s %s%s"; + @NotNull @Pattern(regexp = Constants.LOGIN_REGEX) @Size(min = 1, max = 50) @@ -45,6 +49,10 @@ public class User extends BaseEntity { @Column(name = "last_name", length = 50, nullable = false) private String lastName; + @Size(max = 50) + @Column(name = "patronymic", length = 50) + private String patronymic; + @NotNull @Email @Size(min = 5, max = 100) @@ -79,6 +87,28 @@ public class User extends BaseEntity { @BatchSize(size = 20) private Set roles; + @Column(name = "birth_date") + @Temporal(TemporalType.TIMESTAMP) + private Date birthDate; + + public enum UserDegree { + CANDIDATE("Кандидат технических наук"), + DOCTOR("Доктор технических наук"); + + private String degreeName; + + UserDegree(String degreeName) { + this.degreeName = degreeName; + } + + public String getDegreeName() { + return degreeName; + } + } + + @Enumerated(value = EnumType.STRING) + private UserDegree degree; + public User() { roles = new HashSet<>(); activated = false; @@ -174,4 +204,35 @@ public class User extends BaseEntity { this.roles.clear(); this.roles.addAll(roles); } + + public String getPatronymic() { + return patronymic; + } + + public void setPatronymic(String patronymic) { + this.patronymic = patronymic; + } + + public Date getBirthDate() { + return birthDate; + } + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public UserDegree getDegree() { + return degree; + } + + public void setDegree(UserDegree degree) { + this.degree = degree; + } + + public String getUserAbbreviate() { + return String.format(USER_ABBREVIATE_TEMPLATE, + lastName == null ? "" : lastName, + firstName == null ? "" : firstName.substring(0, 1) + ".", + patronymic == null ? "" : patronymic.substring(0, 1) + "."); + } } diff --git a/src/main/java/ru/ulstu/user/model/UserDto.java b/src/main/java/ru/ulstu/user/model/UserDto.java index c98e0bb..c3f8502 100644 --- a/src/main/java/ru/ulstu/user/model/UserDto.java +++ b/src/main/java/ru/ulstu/user/model/UserDto.java @@ -1,8 +1,6 @@ package ru.ulstu.user.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.hibernate.validator.constraints.Email; -import org.hibernate.validator.constraints.NotBlank; import org.springframework.util.StringUtils; import ru.ulstu.configuration.Constants; import ru.ulstu.odin.model.OdinDto; @@ -12,9 +10,12 @@ import ru.ulstu.odin.model.annotation.OdinString; import ru.ulstu.odin.model.annotation.OdinVisible; import ru.ulstu.user.controller.UserController; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.util.Collection; +import java.util.Date; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; @@ -70,6 +71,10 @@ public class UserDto implements OdinDto { @Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50) private String passwordConfirm; + private Date birthDate; + + private User.UserDegree degree; + public UserDto() { activated = false; roles = new LinkedHashSet<>(); @@ -86,6 +91,8 @@ public class UserDto implements OdinDto { this.roles.addAll(user.getRoles().stream() .map(UserRoleDto::new) .collect(Collectors.toList())); + this.birthDate = user.getBirthDate(); + this.degree = user.getDegree(); } public Integer getId() { @@ -163,6 +170,22 @@ public class UserDto implements OdinDto { return passwordConfirm; } + public Date getBirthDate() { + return birthDate; + } + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public User.UserDegree getDegree() { + return degree; + } + + public void setDegree(User.UserDegree degree) { + this.degree = degree; + } + @JsonIgnore public boolean isPasswordsValid() { if (StringUtils.isEmpty(password) || StringUtils.isEmpty(passwordConfirm)) { @@ -188,6 +211,8 @@ public class UserDto implements OdinDto { ", roles=" + roles + ", password='" + password + '\'' + ", passwordConfirm='" + passwordConfirm + '\'' + + ", birthDate='" + birthDate + '\'' + + ", degree='" + degree + '\'' + '}'; } } diff --git a/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java b/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java index 33d84bc..6da2813 100644 --- a/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java +++ b/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java @@ -1,8 +1,8 @@ package ru.ulstu.user.model; -import org.hibernate.validator.constraints.NotEmpty; import ru.ulstu.configuration.Constants; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Size; import java.util.Objects; diff --git a/src/main/java/ru/ulstu/user/model/UserSession.java b/src/main/java/ru/ulstu/user/model/UserSession.java index 1eb761d..50fec65 100644 --- a/src/main/java/ru/ulstu/user/model/UserSession.java +++ b/src/main/java/ru/ulstu/user/model/UserSession.java @@ -2,7 +2,13 @@ package ru.ulstu.user.model; import ru.ulstu.core.model.BaseEntity; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import javax.validation.constraints.NotNull; import java.util.Date; diff --git a/src/main/java/ru/ulstu/user/repository/UserRepository.java b/src/main/java/ru/ulstu/user/repository/UserRepository.java index 2edc8aa..afcdd69 100644 --- a/src/main/java/ru/ulstu/user/repository/UserRepository.java +++ b/src/main/java/ru/ulstu/user/repository/UserRepository.java @@ -2,6 +2,8 @@ package ru.ulstu.user.repository; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ru.ulstu.user.model.User; import java.util.Date; @@ -25,4 +27,11 @@ public interface UserRepository extends JpaRepository { @EntityGraph(attributePaths = "roles") User findOneWithRolesByLogin(String login); + + @Query("SELECT u FROM User u " + + "WHERE (YEAR(CURRENT_DATE) - YEAR(u.birthDate) < 35 OR :hasAge = FALSE) " + + "AND (u.degree = 'CANDIDATE' OR :hasDegree = FALSE)" + + "ORDER BY u.lastName") + List filterByAgeAndDegree(@Param("hasAge") boolean hasAge, + @Param("hasDegree") boolean hasDegree); } diff --git a/src/main/java/ru/ulstu/user/service/MailService.java b/src/main/java/ru/ulstu/user/service/MailService.java index da1da6d..77be5e7 100644 --- a/src/main/java/ru/ulstu/user/service/MailService.java +++ b/src/main/java/ru/ulstu/user/service/MailService.java @@ -8,7 +8,7 @@ import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.thymeleaf.context.Context; -import org.thymeleaf.spring4.SpringTemplateEngine; +import org.thymeleaf.spring5.SpringTemplateEngine; import ru.ulstu.configuration.ApplicationProperties; import ru.ulstu.configuration.Constants; import ru.ulstu.user.model.User; @@ -19,11 +19,9 @@ import java.util.Map; @Service public class MailService { - private final Logger log = LoggerFactory.getLogger(MailService.class); - private static final String USER = "user"; private static final String BASE_URL = "baseUrl"; - + private final Logger log = LoggerFactory.getLogger(MailService.class); private final JavaMailSender javaMailSender; private final SpringTemplateEngine templateEngine; private final MailProperties mailProperties; diff --git a/src/main/java/ru/ulstu/user/service/UserMapper.java b/src/main/java/ru/ulstu/user/service/UserMapper.java index 7359bba..647754b 100644 --- a/src/main/java/ru/ulstu/user/service/UserMapper.java +++ b/src/main/java/ru/ulstu/user/service/UserMapper.java @@ -26,7 +26,7 @@ public class UserMapper { public Set rolesFromDto(Set strings) { return Optional.ofNullable(strings).orElse(Collections.emptySet()).stream() .filter(Objects::nonNull) - .map(role -> userRoleRepository.findOne(role.getId().toString())) + .map(role -> userRoleRepository.getOne(role.getId().toString())) .filter(Objects::nonNull) .collect(Collectors.toSet()); } diff --git a/src/main/java/ru/ulstu/user/service/UserService.java b/src/main/java/ru/ulstu/user/service/UserService.java index d8d29a5..f92d2e3 100644 --- a/src/main/java/ru/ulstu/user/service/UserService.java +++ b/src/main/java/ru/ulstu/user/service/UserService.java @@ -1,318 +1,332 @@ -package ru.ulstu.user.service; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Sort; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; -import ru.ulstu.configuration.ApplicationProperties; -import ru.ulstu.core.error.EntityIdIsNullException; -import ru.ulstu.core.jpa.OffsetablePageRequest; -import ru.ulstu.core.model.BaseEntity; -import ru.ulstu.core.model.response.PageableItems; -import ru.ulstu.user.error.UserActivationError; -import ru.ulstu.user.error.UserEmailExistsException; -import ru.ulstu.user.error.UserIdExistsException; -import ru.ulstu.user.error.UserIsUndeadException; -import ru.ulstu.user.error.UserLoginExistsException; -import ru.ulstu.user.error.UserNotActivatedException; -import ru.ulstu.user.error.UserNotFoundException; -import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; -import ru.ulstu.user.error.UserResetKeyError; -import ru.ulstu.user.model.User; -import ru.ulstu.user.model.UserDto; -import ru.ulstu.user.model.UserListDto; -import ru.ulstu.user.model.UserResetPasswordDto; -import ru.ulstu.user.model.UserRole; -import ru.ulstu.user.model.UserRoleConstants; -import ru.ulstu.user.model.UserRoleDto; -import ru.ulstu.user.repository.UserRepository; -import ru.ulstu.user.repository.UserRoleRepository; -import ru.ulstu.user.util.UserUtils; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@Service -@Transactional -public class UserService implements UserDetailsService { - private final Logger log = LoggerFactory.getLogger(UserService.class); - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final UserRoleRepository userRoleRepository; - private final UserMapper userMapper; - private final MailService mailService; - private final ApplicationProperties applicationProperties; - - public UserService(UserRepository userRepository, - PasswordEncoder passwordEncoder, - UserRoleRepository userRoleRepository, - UserMapper userMapper, - MailService mailService, - ApplicationProperties applicationProperties) { - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.userRoleRepository = userRoleRepository; - this.userMapper = userMapper; - this.mailService = mailService; - this.applicationProperties = applicationProperties; - } - - private User getUserByEmail(String email) { - return userRepository.findOneByEmailIgnoreCase(email); - } - - private User getUserByActivationKey(String activationKey) { - return userRepository.findOneByActivationKey(activationKey); - } - - public User getUserByLogin(String login) { - return userRepository.findOneByLoginIgnoreCase(login); - } - - @Transactional(readOnly = true) - public UserDto getUserWithRolesById(Integer userId) { - final User userEntity = userRepository.findOneWithRolesById(userId); - if (userEntity == null) { - throw new UserNotFoundException(userId.toString()); - } - return userMapper.userEntityToUserDto(userEntity); - } - - @Transactional(readOnly = true) - public PageableItems getAllUsers(int offset, int count) { - final Page page = userRepository.findAll(new OffsetablePageRequest(offset, count, new Sort("id"))); - return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent())); - } - - // TODO: read only active users - public List findAll() { - return userRepository.findAll(); - } - - @Transactional(readOnly = true) - public PageableItems getUserRoles() { - final List roles = userRoleRepository.findAll().stream() - .map(UserRoleDto::new) - .sorted(Comparator.comparing(UserRoleDto::getViewValue)) - .collect(Collectors.toList()); - return new PageableItems<>(roles.size(), roles); - } - - public UserDto createUser(UserDto userDto) { - if (userDto.getId() != null) { - throw new UserIdExistsException(); - } - if (getUserByLogin(userDto.getLogin()) != null) { - throw new UserLoginExistsException(userDto.getLogin()); - } - if (getUserByEmail(userDto.getEmail()) != null) { - throw new UserEmailExistsException(userDto.getEmail()); - } - if (!userDto.isPasswordsValid()) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - User user = userMapper.userDtoToUserEntity(userDto); - user.setActivated(false); - user.setActivationKey(UserUtils.generateActivationKey()); - user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); - user.setPassword(passwordEncoder.encode(userDto.getPassword())); - user = userRepository.save(user); - mailService.sendActivationEmail(user); - log.debug("Created Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public UserDto activateUser(String activationKey) { - final User user = getUserByActivationKey(activationKey); - if (user == null) { - throw new UserActivationError(activationKey); - } - user.setActivated(true); - user.setActivationKey(null); - user.setActivationDate(null); - log.debug("Activated user: {}", user.getLogin()); - return userMapper.userEntityToUserDto(userRepository.save(user)); - } - - public UserDto updateUser(UserDto userDto) { - if (userDto.getId() == null) { - throw new EntityIdIsNullException(); - } - if (!Objects.equals( - Optional.ofNullable(getUserByEmail(userDto.getEmail())) - .map(BaseEntity::getId).orElse(userDto.getId()), - userDto.getId())) { - throw new UserEmailExistsException(userDto.getEmail()); - } - if (!Objects.equals( - Optional.ofNullable(getUserByLogin(userDto.getLogin())) - .map(BaseEntity::getId).orElse(userDto.getId()), - userDto.getId())) { - throw new UserLoginExistsException(userDto.getLogin()); - } - User user = userRepository.findOne(userDto.getId()); - if (user == null) { - throw new UserNotFoundException(userDto.getId().toString()); - } - if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { - userDto.setLogin(applicationProperties.getUndeadUserLogin()); - userDto.setActivated(true); - userDto.setRoles(Collections.singletonList(new UserRoleDto(UserRoleConstants.ADMIN))); - } - user.setLogin(userDto.getLogin()); - user.setFirstName(userDto.getFirstName()); - user.setLastName(userDto.getLastName()); - user.setEmail(userDto.getEmail()); - if (userDto.isActivated() != user.getActivated()) { - if (userDto.isActivated()) { - user.setActivationKey(null); - user.setActivationDate(null); - } else { - user.setActivationKey(UserUtils.generateActivationKey()); - user.setActivationDate(new Date()); - } - } - user.setActivated(userDto.isActivated()); - final Set roles = userMapper.rolesFromDto(userDto.getRoles()); - user.setRoles(roles.isEmpty() - ? Collections.singleton(new UserRole(UserRoleConstants.USER)) - : roles); - if (!StringUtils.isEmpty(userDto.getOldPassword())) { - if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - user.setPassword(passwordEncoder.encode(userDto.getPassword())); - log.debug("Changed password for User: {}", user.getLogin()); - } - user = userRepository.save(user); - log.debug("Changed Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public UserDto updateUserInformation(UserDto userDto) { - if (userDto.getId() == null) { - throw new EntityIdIsNullException(); - } - if (!Objects.equals( - Optional.ofNullable(getUserByEmail(userDto.getEmail())) - .map(BaseEntity::getId).orElse(userDto.getId()), - userDto.getId())) { - throw new UserEmailExistsException(userDto.getEmail()); - } - User user = userRepository.findOne(userDto.getId()); - if (user == null) { - throw new UserNotFoundException(userDto.getId().toString()); - } - user.setFirstName(userDto.getFirstName()); - user.setLastName(userDto.getLastName()); - user.setEmail(userDto.getEmail()); - user = userRepository.save(user); - log.debug("Updated Information for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - public UserDto changeUserPassword(UserDto userDto) { - if (userDto.getId() == null) { - throw new EntityIdIsNullException(); - } - if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - final String login = UserUtils.getCurrentUserLogin(); - final User user = userRepository.findOneByLoginIgnoreCase(login); - if (user == null) { - throw new UserNotFoundException(login); - } - if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - user.setPassword(passwordEncoder.encode(userDto.getPassword())); - log.debug("Changed password for User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(userRepository.save(user)); - } - - public boolean requestUserPasswordReset(String email) { - User user = userRepository.findOneByEmailIgnoreCase(email); - if (user == null) { - throw new UserNotFoundException(email); - } - if (!user.getActivated()) { - throw new UserNotActivatedException(); - } - user.setResetKey(UserUtils.generateResetKey()); - user.setResetDate(new Date()); - user = userRepository.save(user); - mailService.sendPasswordResetMail(user); - log.debug("Created Reset Password Request for User: {}", user.getLogin()); - return true; - } - - public boolean completeUserPasswordReset(String key, UserResetPasswordDto userResetPasswordDto) { - if (!userResetPasswordDto.isPasswordsValid()) { - throw new UserPasswordsNotValidOrNotMatchException(); - } - User user = userRepository.findOneByResetKey(key); - if (user == null) { - throw new UserResetKeyError(key); - } - user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword())); - user.setResetKey(null); - user.setResetDate(null); - user = userRepository.save(user); - log.debug("Reset Password for User: {}", user.getLogin()); - return true; - } - - public UserDto deleteUser(Integer userId) { - final User user = userRepository.findOne(userId); - if (user == null) { - throw new UserNotFoundException(userId.toString()); - } - if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { - throw new UserIsUndeadException(user.getLogin()); - } - userRepository.delete(user); - log.debug("Deleted User: {}", user.getLogin()); - return userMapper.userEntityToUserDto(user); - } - - @Override - public UserDetails loadUserByUsername(String username) { - final User user = userRepository.findOneByLoginIgnoreCase(username); - if (user == null) { - throw new UserNotFoundException(username); - } - if (!user.getActivated()) { - throw new UserNotActivatedException(); - } - return new org.springframework.security.core.userdetails.User(user.getLogin(), - user.getPassword(), - Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream() - .map(role -> new SimpleGrantedAuthority(role.getName())) - .collect(Collectors.toList())); - } - - public List findByIds(List ids) { - return userRepository.findAll(ids); - } - - public User findById(Integer id) { - return userRepository.findOne(id); - } -} +package ru.ulstu.user.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import ru.ulstu.configuration.ApplicationProperties; +import ru.ulstu.core.error.EntityIdIsNullException; +import ru.ulstu.core.jpa.OffsetablePageRequest; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.core.model.response.PageableItems; +import ru.ulstu.user.error.UserActivationError; +import ru.ulstu.user.error.UserEmailExistsException; +import ru.ulstu.user.error.UserIdExistsException; +import ru.ulstu.user.error.UserIsUndeadException; +import ru.ulstu.user.error.UserLoginExistsException; +import ru.ulstu.user.error.UserNotActivatedException; +import ru.ulstu.user.error.UserNotFoundException; +import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; +import ru.ulstu.user.error.UserResetKeyError; +import ru.ulstu.user.model.User; +import ru.ulstu.user.model.UserDto; +import ru.ulstu.user.model.UserListDto; +import ru.ulstu.user.model.UserResetPasswordDto; +import ru.ulstu.user.model.UserRole; +import ru.ulstu.user.model.UserRoleConstants; +import ru.ulstu.user.model.UserRoleDto; +import ru.ulstu.user.repository.UserRepository; +import ru.ulstu.user.repository.UserRoleRepository; +import ru.ulstu.user.util.UserUtils; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@Transactional +public class UserService implements UserDetailsService { + private final Logger log = LoggerFactory.getLogger(UserService.class); + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final UserRoleRepository userRoleRepository; + private final UserMapper userMapper; + private final MailService mailService; + private final ApplicationProperties applicationProperties; + + public UserService(UserRepository userRepository, + PasswordEncoder passwordEncoder, + UserRoleRepository userRoleRepository, + UserMapper userMapper, + MailService mailService, + ApplicationProperties applicationProperties) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.userRoleRepository = userRoleRepository; + this.userMapper = userMapper; + this.mailService = mailService; + this.applicationProperties = applicationProperties; + } + + private User getUserByEmail(String email) { + return userRepository.findOneByEmailIgnoreCase(email); + } + + private User getUserByActivationKey(String activationKey) { + return userRepository.findOneByActivationKey(activationKey); + } + + public User getUserByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + + @Transactional(readOnly = true) + public UserDto getUserWithRolesById(Integer userId) { + final User userEntity = userRepository.findOneWithRolesById(userId); + if (userEntity == null) { + throw new UserNotFoundException(userId.toString()); + } + return userMapper.userEntityToUserDto(userEntity); + } + + @Transactional(readOnly = true) + public PageableItems getAllUsers(int offset, int count) { + final Page page = userRepository.findAll(new OffsetablePageRequest(offset, count, Sort.by("id"))); + return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent())); + } + + // TODO: read only active users + public List findAll() { + return userRepository.findAll(); + } + + @Transactional(readOnly = true) + public PageableItems getUserRoles() { + final List roles = userRoleRepository.findAll().stream() + .map(UserRoleDto::new) + .sorted(Comparator.comparing(UserRoleDto::getViewValue)) + .collect(Collectors.toList()); + return new PageableItems<>(roles.size(), roles); + } + + public UserDto createUser(UserDto userDto) { + if (userDto.getId() != null) { + throw new UserIdExistsException(); + } + if (getUserByLogin(userDto.getLogin()) != null) { + throw new UserLoginExistsException(userDto.getLogin()); + } + if (getUserByEmail(userDto.getEmail()) != null) { + throw new UserEmailExistsException(userDto.getEmail()); + } + if (!userDto.isPasswordsValid()) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + User user = userMapper.userDtoToUserEntity(userDto); + user.setActivated(false); + user.setActivationKey(UserUtils.generateActivationKey()); + user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER))); + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + user = userRepository.save(user); + mailService.sendActivationEmail(user); + log.debug("Created Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public UserDto activateUser(String activationKey) { + final User user = getUserByActivationKey(activationKey); + if (user == null) { + throw new UserActivationError(activationKey); + } + user.setActivated(true); + user.setActivationKey(null); + user.setActivationDate(null); + log.debug("Activated user: {}", user.getLogin()); + return userMapper.userEntityToUserDto(userRepository.save(user)); + } + + public UserDto updateUser(UserDto userDto) { + if (userDto.getId() == null) { + throw new EntityIdIsNullException(); + } + if (!Objects.equals( + Optional.ofNullable(getUserByEmail(userDto.getEmail())) + .map(BaseEntity::getId).orElse(userDto.getId()), + userDto.getId())) { + throw new UserEmailExistsException(userDto.getEmail()); + } + if (!Objects.equals( + Optional.ofNullable(getUserByLogin(userDto.getLogin())) + .map(BaseEntity::getId).orElse(userDto.getId()), + userDto.getId())) { + throw new UserLoginExistsException(userDto.getLogin()); + } + User user = userRepository.getOne(userDto.getId()); + if (user == null) { + throw new UserNotFoundException(userDto.getId().toString()); + } + if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { + userDto.setLogin(applicationProperties.getUndeadUserLogin()); + userDto.setActivated(true); + userDto.setRoles(Collections.singletonList(new UserRoleDto(UserRoleConstants.ADMIN))); + } + user.setLogin(userDto.getLogin()); + user.setFirstName(userDto.getFirstName()); + user.setLastName(userDto.getLastName()); + user.setEmail(userDto.getEmail()); + if (userDto.isActivated() != user.getActivated()) { + if (userDto.isActivated()) { + user.setActivationKey(null); + user.setActivationDate(null); + } else { + user.setActivationKey(UserUtils.generateActivationKey()); + user.setActivationDate(new Date()); + } + } + user.setActivated(userDto.isActivated()); + final Set roles = userMapper.rolesFromDto(userDto.getRoles()); + user.setRoles(roles.isEmpty() + ? Collections.singleton(new UserRole(UserRoleConstants.USER)) + : roles); + if (!StringUtils.isEmpty(userDto.getOldPassword())) { + if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + log.debug("Changed password for User: {}", user.getLogin()); + } + user = userRepository.save(user); + log.debug("Changed Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public UserDto updateUserInformation(UserDto userDto) { + if (userDto.getId() == null) { + throw new EntityIdIsNullException(); + } + if (!Objects.equals( + Optional.ofNullable(getUserByEmail(userDto.getEmail())) + .map(BaseEntity::getId).orElse(userDto.getId()), + userDto.getId())) { + throw new UserEmailExistsException(userDto.getEmail()); + } + User user = userRepository.getOne(userDto.getId()); + if (user == null) { + throw new UserNotFoundException(userDto.getId().toString()); + } + user.setFirstName(userDto.getFirstName()); + user.setLastName(userDto.getLastName()); + user.setEmail(userDto.getEmail()); + user = userRepository.save(user); + log.debug("Updated Information for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + public UserDto changeUserPassword(UserDto userDto) { + if (userDto.getId() == null) { + throw new EntityIdIsNullException(); + } + if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + final String login = UserUtils.getCurrentUserLogin(SecurityContextHolder.getContext()); + final User user = userRepository.findOneByLoginIgnoreCase(login); + if (user == null) { + throw new UserNotFoundException(login); + } + if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + user.setPassword(passwordEncoder.encode(userDto.getPassword())); + log.debug("Changed password for User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(userRepository.save(user)); + } + + public boolean requestUserPasswordReset(String email) { + User user = userRepository.findOneByEmailIgnoreCase(email); + if (user == null) { + throw new UserNotFoundException(email); + } + if (!user.getActivated()) { + throw new UserNotActivatedException(); + } + user.setResetKey(UserUtils.generateResetKey()); + user.setResetDate(new Date()); + user = userRepository.save(user); + mailService.sendPasswordResetMail(user); + log.debug("Created Reset Password Request for User: {}", user.getLogin()); + return true; + } + + public boolean completeUserPasswordReset(String key, UserResetPasswordDto userResetPasswordDto) { + if (!userResetPasswordDto.isPasswordsValid()) { + throw new UserPasswordsNotValidOrNotMatchException(); + } + User user = userRepository.findOneByResetKey(key); + if (user == null) { + throw new UserResetKeyError(key); + } + user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword())); + user.setResetKey(null); + user.setResetDate(null); + user = userRepository.save(user); + log.debug("Reset Password for User: {}", user.getLogin()); + return true; + } + + public UserDto deleteUser(Integer userId) { + final User user = userRepository.getOne(userId); + if (user == null) { + throw new UserNotFoundException(userId.toString()); + } + if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) { + throw new UserIsUndeadException(user.getLogin()); + } + userRepository.delete(user); + log.debug("Deleted User: {}", user.getLogin()); + return userMapper.userEntityToUserDto(user); + } + + @Override + public UserDetails loadUserByUsername(String username) { + final User user = userRepository.findOneByLoginIgnoreCase(username); + if (user == null) { + throw new UserNotFoundException(username); + } + if (!user.getActivated()) { + throw new UserNotActivatedException(); + } + return new org.springframework.security.core.userdetails.User(user.getLogin(), + user.getPassword(), + Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream() + .map(role -> new SimpleGrantedAuthority(role.getName())) + .collect(Collectors.toList())); + } + + public List findByIds(List ids) { + return userRepository.findAllById(ids); + } + + public User findById(Integer id) { + return userRepository.getOne(id); + } + + public User getCurrentUser() { + String login = UserUtils.getCurrentUserLogin(SecurityContextHolder.getContext()); + User user = userRepository.findOneByLoginIgnoreCase(login); + if (user == null) { + throw new UserNotFoundException(login); + } + return user; + } + + public List filterByAgeAndDegree(boolean hasDegree, boolean hasAge) { + return userRepository.filterByAgeAndDegree(hasDegree, hasAge); + } +} diff --git a/src/main/java/ru/ulstu/user/util/UserUtils.java b/src/main/java/ru/ulstu/user/util/UserUtils.java index de585a5..8524c74 100644 --- a/src/main/java/ru/ulstu/user/util/UserUtils.java +++ b/src/main/java/ru/ulstu/user/util/UserUtils.java @@ -17,12 +17,12 @@ public class UserUtils { return RandomStringUtils.randomNumeric(DEF_COUNT); } - public static String getCurrentUserLogin() { - final SecurityContext securityContext = SecurityContextHolder.getContext(); + public static String getCurrentUserLogin(SecurityContext securityContext) { if (securityContext == null) { return null; } final Authentication authentication = securityContext.getAuthentication(); + if (authentication.getPrincipal() instanceof UserDetails) { final UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); return springSecurityUser.getUsername(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fb1b116..75c8ef9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,12 +2,11 @@ spring.main.banner-mode=off server.port=8443 server.http.port=8080 -spring.http.multipart.maxFileSize=20MB -spring.http.multipart.maxRequestSize=20MB +multipart.maxFileSize=20MB +multipart.maxRequestSize=20MB # Thymeleaf Settings spring.thymeleaf.cache=false # SSL Settings -security.require-ssl=true server.ssl.key-store=classpath:sample.jks server.ssl.key-store-password=secret server.ssl.key-password=password @@ -25,14 +24,15 @@ spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFact spring.datasource.url=jdbc:postgresql://localhost:5432/ng-tracker spring.datasource.username=postgres spring.datasource.password=postgres -spring.datasource.driverclassName=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=validate +spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false # Liquibase Settings -liquibase.drop-first=false -liquibase.enabled=true -liquibase.change-log=classpath:db/changelog-master.xml +spring.liquibase.change-log=classpath:db/changelog-master.xml +spring.liquibase.drop-first=false +spring.liquibase.enabled=true # Application Settings ng-tracker.base-url=http://127.0.0.1:8080 ng-tracker.undead-user-login=admin ng-tracker.dev-mode=true -ng-tracker.use-https=false \ No newline at end of file +ng-tracker.use-https=false +ng-tracker.check-run=false \ No newline at end of file diff --git a/src/main/resources/commits.log b/src/main/resources/commits.log index cb97503..5d1bca7 100644 --- a/src/main/resources/commits.log +++ b/src/main/resources/commits.log @@ -1,489 +1,298 @@ -Anton Romanov;Thu Mar 15 11:10:34 2018 +0400;change to date time -romanov73;Thu Mar 15 11:03:19 2018 +0400;read commits in constructor -romanov73;Thu Mar 15 09:12:07 2018 +0400;add commits page -Romanov Anton;Wed Mar 14 20:26:54 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:16:53 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:08:09 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:00:58 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:50:22 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:34:17 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:20:42 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:59:18 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:47:17 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:35:37 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:33:54 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:29:27 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:21:18 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:19:27 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:16:06 2018 +0000;Update README.md -romanov73;Wed Mar 14 21:28:10 2018 +0400;fix using constant -Aleksey Filippov;Wed Mar 14 20:07:25 2018 +0400;some fixes after merge -Aleksey Filippov;Wed Mar 14 19:52:45 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into odin-ui -Aleksey Filippov;Wed Mar 14 19:48:45 2018 +0400;refactoring of odin paginator -romanov73;Wed Mar 14 18:43:16 2018 +0400;Merge branch 'develop' into 36-rest -romanov73;Wed Mar 14 18:17:02 2018 +0400;Merge branch 'develop' into 36-rest -Aleksey Filippov;Wed Mar 14 18:11:00 2018 +0400;fix null value check in formatter -Aleksey Filippov;Wed Mar 14 18:06:16 2018 +0400;fix odin paginator style, fix odin negative file -romanov73;Wed Mar 14 18:04:47 2018 +0400;rename entities -Aleksey Filippov;Wed Mar 14 17:58:59 2018 +0400;improve odin table look -romanov73;Wed Mar 14 17:47:45 2018 +0400;fix db changelogs -romanov73;Wed Mar 14 17:47:20 2018 +0400;fix db changelogs -Gleb;Wed Mar 14 08:50:23 2018 +0000;Merge branch '48-' into 'develop' -funny73;Wed Mar 14 12:28:55 2018 +0400;Поправил отображение подразделения при редактировании смены -Aleksey Filippov;Wed Mar 14 00:02:24 2018 +0400;move navbar to the left, move odin css to separate file -Aleksey Filippov;Tue Mar 13 23:26:33 2018 +0400;some style fixes -Aleksey Filippov;Tue Mar 13 23:22:37 2018 +0400;move version panel to navbar -funny73;Tue Mar 13 20:41:41 2018 +0400;Поправил валидацию сменности -romanov73;Tue Mar 13 17:26:54 2018 +0400;fix offsetable page request -romanov73;Tue Mar 13 17:26:02 2018 +0400;fix table constructor -romanov73;Tue Mar 13 17:24:56 2018 +0400;fix checking empty value -Romanov Anton;Tue Mar 13 13:09:41 2018 +0000;Merge branch 'odin-ui' into '36-rest' -Aleksey Filippov;Tue Mar 13 16:59:27 2018 +0400;notes updated -Aleksey Filippov;Tue Mar 13 16:56:00 2018 +0400;odin refactoring -Aleksey Filippov;Tue Mar 13 16:53:38 2018 +0400;odin refactoring -romanov73;Tue Mar 13 15:27:17 2018 +0400;fix tool load calc -romanov73;Tue Mar 13 14:46:46 2018 +0400;fix area load calc -Aleksey Filippov;Tue Mar 13 13:38:23 2018 +0400;some fixes -romanov73;Tue Mar 13 13:33:25 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop -romanov73;Tue Mar 13 13:33:07 2018 +0400;fix tools count calc -Aleksey Filippov;Tue Mar 13 13:12:09 2018 +0400;some odin refactoring -Aleksey Filippov;Tue Mar 13 13:11:41 2018 +0400;some offsetablepagerequest fix -Aleksey Filippov;Tue Mar 13 13:06:48 2018 +0400;add offsetablepagerequest -Aleksey Filippov;Tue Mar 13 07:27:12 2018 +0000;Merge branch '51-rest-points' into '36-rest' -romanov73;Tue Mar 13 11:22:02 2018 +0400;fix by comment: remove default version id -romanov73;Tue Mar 13 00:04:50 2018 +0400;fix path -romanov73;Mon Mar 12 23:55:05 2018 +0400;add tool square dictionary -romanov73;Mon Mar 12 23:36:49 2018 +0400;add dictionary pages -romanov73;Mon Mar 12 23:18:01 2018 +0400;sort second level menu items by name -romanov73;Mon Mar 12 23:11:22 2018 +0400;fix menu -romanov73;Mon Mar 12 22:52:15 2018 +0400;add stream api utils and converter for "map ... collect" -funny73;Mon Mar 12 20:01:47 2018 +0400;Добавил свойство базового изделия для изделия -romanov73;Mon Mar 12 19:46:52 2018 +0400;refactor units -romanov73;Mon Mar 12 19:36:57 2018 +0400;refactor tool types and work types -romanov73;Mon Mar 12 19:20:23 2018 +0400;refactor tools -romanov73;Mon Mar 12 19:07:09 2018 +0400;refactor stages -romanov73;Mon Mar 12 18:53:26 2018 +0400;refactor positions -romanov73;Mon Mar 12 18:27:36 2018 +0400;refactor employees -romanov73;Mon Mar 12 18:26:49 2018 +0400;refactor employees -romanov73;Mon Mar 12 18:09:38 2018 +0400;refactor categories -Aleksey Filippov;Mon Mar 12 17:32:06 2018 +0400;todo added -Aleksey Filippov;Mon Mar 12 17:29:51 2018 +0400;paginator added to odin -Aleksey Filippov;Mon Mar 12 17:29:06 2018 +0400;notes updated -funny73;Mon Mar 12 16:29:13 2018 +0400;Добавил сущность изделие без привязки к производственной программе -funny73;Mon Mar 12 15:16:35 2018 +0400;Переименовал сущность Product на ProductOnProgram Ещё немного переименования -funny73;Mon Mar 12 14:39:52 2018 +0400;Переименовал сущность Product на ProductOnProgram -Aleksey Filippov;Mon Mar 12 13:44:42 2018 +0400;add formatters and initial form support to odin -Aleksey Filippov;Mon Mar 12 13:44:16 2018 +0400;userlistdto refactoring -Aleksey Filippov;Mon Mar 12 13:43:59 2018 +0400;dateutils improvements -Aleksey Filippov;Mon Mar 12 13:43:43 2018 +0400;add support of localdatetime type to odin -Aleksey Filippov;Mon Mar 12 13:42:54 2018 +0400;odin backend example added -Aleksey Filippov;Mon Mar 12 13:42:12 2018 +0400;notes updated -funny73;Mon Mar 12 12:16:02 2018 +0400;Поправил всплывающие сообщения при работе с "Категориями" -Aleksey Filippov;Mon Mar 12 11:25:51 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 12 11:25:30 2018 +0400;odinid annotation added -Aleksey Filippov;Sun Mar 11 15:23:49 2018 +0400;notes updated -Aleksey Filippov;Sun Mar 11 15:18:45 2018 +0400;some fixes -Aleksey Filippov;Sun Mar 11 15:09:57 2018 +0400;simple table draw support added, some refactoring -Aleksey Filippov;Sun Mar 11 15:08:22 2018 +0400;add user id field to userlistdto -Aleksey Filippov;Sun Mar 11 14:32:50 2018 +0400;add jsonproperty annotation support -Aleksey Filippov;Sun Mar 11 13:38:45 2018 +0400;some template fixes -Aleksey Filippov;Sun Mar 11 13:33:09 2018 +0400;some balance page fixes -Aleksey Filippov;Sun Mar 11 13:25:58 2018 +0400;add jsonignore annotation support -Aleksey Filippov;Thu Mar 8 14:16:20 2018 +0400;some odin improvements -Aleksey Filippov;Wed Mar 7 16:03:04 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Wed Mar 7 16:01:21 2018 +0400;odin submodule for basic types added, some refactoring -romanov73;Tue Mar 6 22:52:02 2018 +0400;remove old packages -romanov73;Tue Mar 6 19:12:27 2018 +0400;add tool type crud -romanov73;Tue Mar 6 13:09:51 2018 +0400;add version crud -romanov73;Tue Mar 6 10:48:36 2018 +0400;add unit crud -romanov73;Mon Mar 5 16:58:37 2018 +0400;fix tree -romanov73;Mon Mar 5 16:38:37 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Mon Mar 5 16:38:21 2018 +0400;employee crud -Aleksey Filippov;Mon Mar 5 15:43:49 2018 +0400;some fixes -Aleksey Filippov;Mon Mar 5 15:39:53 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Mon Mar 5 15:39:33 2018 +0400;user reset password function added, some refactoring -romanov73;Mon Mar 5 15:29:16 2018 +0400;fix delete category -Aleksey Filippov;Mon Mar 5 15:08:27 2018 +0400;user change password function added, some refactoring -romanov73;Mon Mar 5 15:08:17 2018 +0400;add category crud -romanov73;Mon Mar 5 14:07:10 2018 +0400;add tree component -Aleksey Filippov;Mon Mar 5 11:30:22 2018 +0400;user delete function added, some refactoring -Aleksey Filippov;Mon Mar 5 11:18:23 2018 +0400;user update function added -Aleksey Filippov;Mon Mar 5 11:18:06 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 10:06:04 2018 +0400;add user activation function -Aleksey Filippov;Mon Mar 5 09:48:22 2018 +0400;add scheduler for users -Aleksey Filippov;Mon Mar 5 09:48:07 2018 +0400;add activateddate field to userentity -Aleksey Filippov;Mon Mar 5 09:47:25 2018 +0400;notes file updated -Aleksey Filippov;Mon Mar 5 09:47:11 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 09:21:49 2018 +0400;thymeleaf cache settings added -Aleksey Filippov;Mon Mar 5 09:21:11 2018 +0400;notes file added -Aleksey Filippov;Mon Mar 5 09:20:57 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 08:37:01 2018 +0400;some refactoring -romanov73;Mon Mar 5 00:08:02 2018 +0400;add callbacks on version getters -romanov73;Mon Mar 5 00:07:34 2018 +0400;fix npe -romanov73;Sun Mar 4 01:13:57 2018 +0400;fix column style -romanov73;Sun Mar 4 01:06:38 2018 +0400;fix table style -romanov73;Sun Mar 4 00:27:14 2018 +0400;add balance dto + draw employee balance -romanov73;Sat Mar 3 22:43:06 2018 +0400;add balance services -romanov73;Sat Mar 3 16:31:19 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Sat Mar 3 16:31:04 2018 +0400;add employee balance table -Aleksey Filippov;Sat Mar 3 15:54:23 2018 +0400;some user support improvements -Aleksey Filippov;Sat Mar 3 15:53:20 2018 +0400;edit migration files, need manual fix of databasechangelog table -Aleksey Filippov;Sat Mar 3 15:51:58 2018 +0400;add application properties handler -Aleksey Filippov;Sat Mar 3 15:51:19 2018 +0400;disable tests -Aleksey Filippov;Sat Mar 3 13:49:32 2018 +0400;some refactoring -romanov73;Fri Mar 2 18:10:23 2018 +0400;add balance page -Aleksey Filippov;Fri Mar 2 18:04:39 2018 +0400;some refactoring -Aleksey Filippov;Fri Mar 2 17:51:41 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 17:50:59 2018 +0400;advicecontroller improvements -Aleksey Filippov;Fri Mar 2 17:50:39 2018 +0400;some mvc improvements -romanov73;Fri Mar 2 15:15:46 2018 +0400;add dtos -romanov73;Fri Mar 2 15:02:38 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 14:54:33 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 14:53:39 2018 +0400;add migrations -Aleksey Filippov;Fri Mar 2 14:53:30 2018 +0400;some core improvements -Aleksey Filippov;Fri Mar 2 14:53:13 2018 +0400;some users improvements -romanov73;Fri Mar 2 12:42:54 2018 +0400;add category service -romanov73;Fri Mar 2 12:37:51 2018 +0400;fix unit service -romanov73;Fri Mar 2 11:24:11 2018 +0400;add unit controller -romanov73;Thu Mar 1 23:19:42 2018 +0400;fix versions panel -romanov73;Thu Mar 1 22:50:57 2018 +0400;Merge branch 'develop' into 36-rest -romanov73;Thu Mar 1 22:50:37 2018 +0400;Merge branch 'develop' into 36-rest -Romanov Anton;Thu Mar 1 18:31:03 2018 +0000;Merge branch '29-' into 'develop' -romanov73;Thu Mar 1 22:25:49 2018 +0400;change versions -romanov73;Thu Mar 1 20:14:22 2018 +0400;show version select -romanov73;Thu Mar 1 19:56:24 2018 +0400;add old models, add versions controller -romanov73;Thu Mar 1 19:55:59 2018 +0400;add old models, add versions controller -romanov73;Thu Mar 1 19:04:47 2018 +0400;save menu to session -romanov73;Thu Mar 1 18:50:00 2018 +0400;add favicon -funny73;Thu Mar 1 18:01:43 2018 +0400;1) Поправил имена колонок и таблицы для смен под постгрес 2) Исправил ошибку в имени переменной thidShift ->thirdShift -romanov73;Thu Mar 1 16:03:03 2018 +0400;add menu and restore changelogs -funny73;Thu Mar 1 15:33:36 2018 +0400;Добавил распорядок смен для каждого подразделения. Смена назначается на месяц. Есть возможность сохранить любой вариант комбинирования 1,2 и 3 смены. Например (1,3); (2); ()... Добавлен интерфейс для просмотра и редактирования распорядка смен по каждому подразделению. Добавлена валидация - для каждого месяца может быть только один распорядок смен. -Aleksey Filippov;Thu Mar 1 15:23:06 2018 +0400;migrate to webjars -Aleksey Filippov;Thu Mar 1 13:31:16 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Thu Mar 1 13:30:55 2018 +0400;create migrations for user models -Aleksey Filippov;Thu Mar 1 13:30:09 2018 +0400;old code refactoring -romanov73;Thu Mar 1 12:51:14 2018 +0400;add basic menu from rest -romanov73;Thu Mar 1 11:51:17 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Thu Mar 1 11:50:59 2018 +0400;try to fix ci: 3 remove tests -Aleksey Filippov;Thu Mar 1 11:50:25 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Thu Mar 1 11:49:59 2018 +0400;moved to new database -romanov73;Thu Mar 1 11:45:40 2018 +0400;try to fix ci: 2 change image -romanov73;Thu Mar 1 11:44:42 2018 +0400;try to fix ci: 1 -romanov73;Thu Mar 1 11:15:52 2018 +0400;fix csrf tokens -Aleksey Filippov;Thu Mar 1 00:25:43 2018 +0400;initial user management support added -romanov73;Wed Feb 28 22:26:00 2018 +0400;save current version in local storage -romanov73;Wed Feb 28 18:40:57 2018 +0400;add ajax datatable -romanov73;Wed Feb 28 18:01:23 2018 +0400;add static index page with menu -Romanov Anton;Tue Feb 27 16:53:04 2018 +0000;Merge branch '33-' into 'develop' -romanov73;Tue Feb 27 20:50:44 2018 +0400;calc area balance -romanov73;Tue Feb 27 17:36:31 2018 +0400;remove unused panel -Romanov Anton;Tue Feb 27 13:06:02 2018 +0000;Merge branch '32-' into 'develop' -romanov73;Tue Feb 27 17:02:35 2018 +0400;calc tool balance -romanov73;Tue Feb 27 15:45:39 2018 +0400;calc tool balance -romanov73;Tue Feb 27 12:16:41 2018 +0400;calc tool power -romanov73;Tue Feb 27 11:40:59 2018 +0400;use tool types in dto -romanov73;Tue Feb 27 11:29:05 2018 +0400;fix dtos -romanov73;Mon Feb 26 23:50:53 2018 +0400;inherit dtos -romanov73;Mon Feb 26 15:38:00 2018 +0400;fix services for balance calculation -Aleksey Filippov;Mon Feb 26 15:00:48 2018 +0400;move from maven to gradle, move from javaee to spring boot -romanov73;Thu Feb 22 18:03:35 2018 +0400;Add menu resource -Romanov Anton;Tue Feb 20 18:12:55 2018 +0000;Merge branch 'balance-different-dto' into 'develop' -romanov73;Tue Feb 20 22:09:33 2018 +0400;restore tests -romanov73;Tue Feb 20 21:46:45 2018 +0400;filter employees by stage -romanov73;Tue Feb 20 21:21:27 2018 +0400;filter employees by workload -romanov73;Tue Feb 20 20:58:23 2018 +0400;Merge branch 'develop' into balance-different-dto -funny73;Tue Feb 20 19:52:24 2018 +0400;Add shift unit model Add link to unit view -romanov73;Tue Feb 20 16:14:09 2018 +0400;Important fix using coefficient -romanov73;Mon Feb 19 19:00:07 2018 +0400;partially fix ajustment -romanov73;Mon Feb 19 18:24:10 2018 +0400;Modify dto for using with different periods and work types -Romanov Anton;Mon Feb 19 08:11:39 2018 +0000;Merge branch '30-' into 'develop' -romanov73;Mon Feb 19 12:09:05 2018 +0400;Add work type to position -Romanov Anton;Wed Feb 14 20:57:35 2018 +0000;Merge branch '34-' into 'develop' -romanov73;Thu Feb 15 00:51:24 2018 +0400;fixes by comments -romanov73;Thu Feb 15 00:47:21 2018 +0400;merge fields -romanov73;Wed Feb 14 23:42:35 2018 +0400;Merge branch 'develop' into 34-gleb -romanov73;Wed Feb 14 21:05:51 2018 +0400;add clock -Romanov Anton;Wed Feb 14 16:49:29 2018 +0000;Merge branch '45-balance-employee-recomendations' into 'develop' -romanov73;Wed Feb 14 20:46:59 2018 +0400;cleanup code -romanov73;Wed Feb 14 18:25:42 2018 +0400;reduce code -romanov73;Wed Feb 14 18:24:59 2018 +0400;adjust additional employees -romanov73;Wed Feb 14 17:22:10 2018 +0400;move employees from other units -romanov73;Tue Feb 13 23:59:09 2018 +0400;fix save product name -romanov73;Tue Feb 13 23:28:30 2018 +0400;add converter, fix UI and backing for add complex object -romanov73;Tue Feb 13 22:10:59 2018 +0400;Merge branch 'develop' into 34-gleb -funny73;Tue Feb 13 18:07:25 2018 +0400;Add stage to workload -funny73;Tue Feb 13 16:05:45 2018 +0400;Add stage to category -romanov73;Tue Feb 13 13:20:16 2018 +0400;add map of all units balances -romanov73;Tue Feb 13 11:24:50 2018 +0400;move method -romanov73;Tue Feb 13 00:35:30 2018 +0400;add dialog -romanov73;Mon Feb 12 22:59:49 2018 +0400;filter only workshops and manufactures -romanov73;Mon Feb 12 22:04:55 2018 +0400;rename employees -romanov73;Mon Feb 12 17:34:37 2018 +0400;fix font size -romanov73;Mon Feb 12 16:59:16 2018 +0400;fix versions panel width -Romanov Anton;Mon Feb 12 12:47:04 2018 +0000;Merge branch '41-' into 'develop' -romanov73;Mon Feb 12 16:43:06 2018 +0400;change to working hours -romanov73;Mon Feb 12 14:05:36 2018 +0400;remove constructor -romanov73;Mon Feb 12 14:05:13 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop -romanov73;Mon Feb 12 14:04:57 2018 +0400;fix selects -Romanov Anton;Mon Feb 12 09:20:05 2018 +0000;Merge branch '39-work-type-code' into 'develop' -Aleksey Filippov;Mon Feb 12 13:16:36 2018 +0400;add migration for work type code -Aleksey Filippov;Mon Feb 12 13:16:28 2018 +0400;add work type code support to xhtml -Aleksey Filippov;Mon Feb 12 13:16:12 2018 +0400;add work type code -Romanov Anton;Mon Feb 12 08:25:47 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 08:25:35 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 08:07:30 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 07:10:38 2018 +0000;Merge branch '40-' into 'develop' -Aleksey Filippov;Mon Feb 12 11:08:13 2018 +0400;add migration for position type -Aleksey Filippov;Mon Feb 12 11:07:45 2018 +0400;add position type support to xhtml -Aleksey Filippov;Mon Feb 12 11:07:23 2018 +0400;move position converter from boundary to view -Aleksey Filippov;Mon Feb 12 11:06:55 2018 +0400;add position type converter -Aleksey Filippov;Mon Feb 12 11:06:35 2018 +0400;add position type to backing -Aleksey Filippov;Mon Feb 12 11:06:00 2018 +0400;add position type support to service -Aleksey Filippov;Mon Feb 12 11:05:44 2018 +0400;add position type to model -Romanov Anton;Mon Feb 12 07:02:32 2018 +0000;Merge branch '33-' into 'develop' -romanov73;Mon Feb 12 14:58:51 2018 +0400;refactor -romanov73;Mon Feb 12 14:58:33 2018 +0400;add human hours coefficient -romanov73;Mon Feb 12 14:56:43 2018 +0400;fix api name -romanov73;Mon Feb 12 00:58:15 2018 +0400;add balance button -romanov73;Sun Feb 11 01:36:15 2018 +0400;add tools balance prototype -romanov73;Sat Feb 10 23:09:33 2018 +0400;move enum -romanov73;Sat Feb 10 23:02:36 2018 +0400;refactor -romanov73;Sat Feb 10 22:54:28 2018 +0400;add quarter -romanov73;Sat Feb 10 22:42:14 2018 +0400;refactor -romanov73;Sat Feb 10 20:52:46 2018 +0400;Merge branch 'develop' into 33-balance-area -romanov73;Sat Feb 10 20:52:32 2018 +0400;Merge branch 'develop' into 33-balance-area -Romanov Anton;Sat Feb 10 16:14:26 2018 +0000;Merge branch '31-map-2-dto' into 'develop' -romanov73;Sat Feb 10 19:35:17 2018 +0400;fixes after Almaz consultation -romanov73;Sat Feb 10 14:25:00 2018 +0400;fix npe -romanov73;Sat Feb 10 13:50:21 2018 +0400;add filter by work type -romanov73;Sat Feb 10 13:08:56 2018 +0400;rename field -romanov73;Sat Feb 10 13:08:38 2018 +0400;filter by all children -Aleksey Filippov;Sat Feb 10 11:26:17 2018 +0400;some fixes -Aleksey Filippov;Sat Feb 10 11:26:06 2018 +0400;add dto for total balance -Aleksey Filippov;Sat Feb 10 11:11:58 2018 +0400;add dto for employee load by unit -Aleksey Filippov;Sat Feb 10 10:30:11 2018 +0400;getEmployeesByUnit method refactoring -romanov73;Sat Feb 10 00:22:37 2018 +0400;add other type of balance area -romanov73;Fri Feb 9 22:03:39 2018 +0400;add tools balance -romanov73;Fri Feb 9 19:50:21 2018 +0400;divide balance page -Aleksey Filippov;Fri Feb 9 16:37:18 2018 +0400;adapt backing and view to areas and employee experience dtos -Aleksey Filippov;Fri Feb 9 16:36:18 2018 +0400;add dto for employee experience -Aleksey Filippov;Fri Feb 9 16:35:51 2018 +0400;add dto for areas -Aleksey Filippov;Fri Feb 9 14:57:01 2018 +0400;some ui fixes -Aleksey Filippov;Fri Feb 9 14:55:36 2018 +0400;move db methods from getters to init -romanov73;Thu Feb 8 01:20:12 2018 +0400;add areas panel -romanov73;Thu Feb 8 01:19:13 2018 +0400;add areas panel -romanov73;Thu Feb 8 01:02:39 2018 +0400;add global preloader -romanov73;Thu Feb 8 00:57:46 2018 +0400;select default unit -romanov73;Thu Feb 8 00:57:23 2018 +0400;add preloader -romanov73;Wed Feb 7 22:46:44 2018 +0400;change id -Romanov Anton;Wed Feb 7 13:53:35 2018 +0000;Merge branch '23-' into 'develop' -Romanov Anton;Wed Feb 7 13:49:27 2018 +0000;Merge branch 'deploy-fixes' into 'develop' -funny73;Wed Feb 7 17:34:41 2018 +0400;UDP modify AirplaneKitCounter to double -Aleksey Filippov;Wed Feb 7 17:33:23 2018 +0400;some wildfly deploy fixes -funny73;Wed Feb 7 17:19:22 2018 +0400;Modify AirplaneKitCounter to double -> Workload.java -funny73;Wed Feb 7 17:00:10 2018 +0400;Add AirplaneKitCounter to balance view -> BalanceEmployeeService.java Modify Worckload total Value (Value * AirplaneKitCounter) -> BalanceService.java -funny73;Wed Feb 7 14:13:35 2018 +0400;Add AirplaneKitCounter to view -funny73;Wed Feb 7 14:13:15 2018 +0400;Add AirplaneKitCounter to model Workload -funny73;Wed Feb 7 14:12:15 2018 +0400;Changelog for airplainetKitCounter -Romanov Anton;Wed Feb 7 06:54:55 2018 +0000;Merge branch '22-' into 'develop' -romanov73;Wed Feb 7 14:47:34 2018 +0400;add unit types -Romanov Anton;Tue Feb 6 22:43:40 2018 +0000;Merge branch '25-' into 'develop' -romanov73;Wed Feb 7 02:41:34 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 02:06:46 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 02:05:10 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 00:41:01 2018 +0400;add method get all unit children -romanov73;Tue Feb 6 23:26:18 2018 +0400;add filter by unit in balance results -romanov73;Tue Feb 6 21:26:45 2018 +0400;refactor work with tree of menu items -romanov73;Tue Feb 6 21:08:01 2018 +0400;refactor work with tree -romanov73;Tue Feb 6 21:07:22 2018 +0400;fix update after remove enum key -romanov73;Tue Feb 6 20:03:31 2018 +0400;rename method -Gleb;Tue Feb 6 15:07:04 2018 +0000;Merge branch '17-' into 'develop' -romanov73;Tue Feb 6 21:15:12 2018 +0400;fix inf. -funny73;Tue Feb 6 19:02:36 2018 +0400;Add changelog for units with unit_type == 'Корпус', unit_type='Цех' -funny73;Tue Feb 6 18:19:50 2018 +0400;Remove "Korpus" from "Tip podrazdeleniya" -Romanov Anton;Tue Feb 6 11:31:43 2018 +0000;Merge branch '24-' into 'develop' -romanov73;Tue Feb 6 19:01:21 2018 +0400;add user -Romanov Anton;Thu Feb 1 19:45:21 2018 +0000;Merge branch 'wildfly-deploy' into 'develop' -Aleksey Filippov;Thu Feb 1 23:24:34 2018 +0400;add wildfly-maven-plugin, remove maven-glassfish-plugin -romanov73;Thu Feb 1 19:51:24 2018 +0400;add todos -Romanov Anton;Wed Jan 31 15:03:39 2018 +0000;Merge branch 'master' into 'develop' -romanov73;Wed Jan 31 18:53:27 2018 +0400;fix round -romanov73;Wed Jan 31 18:43:33 2018 +0400;fix round -romanov73;Wed Jan 31 18:38:33 2018 +0400;add balance by employees -romanov73;Wed Jan 31 18:30:18 2018 +0400;add balance by employees -Romanov Anton;Wed Jan 31 13:02:52 2018 +0000;Merge branch 'develop' into 'master' -Romanov Anton;Wed Jan 31 12:58:00 2018 +0000;Merge branch '4-' into 'develop' -romanov73;Wed Jan 31 16:47:51 2018 +0400;add balance by employees -romanov73;Wed Jan 31 00:47:39 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:47:25 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:47:08 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:46:53 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:45:59 2018 +0400;add interface for edit additional fields -romanov73;Wed Jan 31 00:45:04 2018 +0400;add comparable for using in tree map -romanov73;Wed Jan 31 00:44:33 2018 +0400;add fields for production program -romanov73;Wed Jan 31 00:43:56 2018 +0400;move interface to base entity -romanov73;Wed Jan 31 00:43:21 2018 +0400;add database fields for production program -romanov73;Wed Jan 31 00:42:55 2018 +0400;add dynamic columns -romanov73;Tue Jan 30 15:11:12 2018 +0400;add work type for workload -romanov73;Tue Jan 30 14:55:56 2018 +0400;add comments -romanov73;Tue Jan 30 03:00:28 2018 +0400;show by production program -romanov73;Tue Jan 30 00:07:31 2018 +0400;add employee available capacity -romanov73;Mon Jan 29 23:42:06 2018 +0400;fix units hierarchy bypass -romanov73;Mon Jan 29 23:28:28 2018 +0400;divide logic -romanov73;Mon Jan 29 22:59:45 2018 +0400;add employee experience table -romanov73;Mon Jan 29 22:15:29 2018 +0400;fix calc areas -romanov73;Mon Jan 29 21:57:23 2018 +0400;fix calc employee experience -romanov73;Mon Jan 29 21:12:52 2018 +0400;calc employee experience by units -romanov73;Mon Jan 29 14:01:36 2018 +0400;add employee experience -Romanov Anton;Mon Jan 29 06:04:01 2018 +0000;Update README.md -Romanov Anton;Sun Jan 28 16:16:56 2018 +0000;Merge branch 'develop' into 'master' -Romanov Anton;Sun Jan 28 16:06:14 2018 +0000;Merge branch '3-' into 'develop' -romanov73;Sun Jan 28 19:53:21 2018 +0400;remove product work types edit -romanov73;Sun Jan 28 19:41:31 2018 +0400;fix months select -romanov73;Sun Jan 28 19:06:10 2018 +0400;fix year select -romanov73;Sun Jan 28 04:28:21 2018 +0400;fix program edit -romanov73;Sun Jan 28 04:27:55 2018 +0400;add product backend -romanov73;Sun Jan 28 04:26:53 2018 +0400;add table and fields -Romanov Anton;Sat Jan 27 08:11:15 2018 +0000;Merge branch '9-' into 'develop' -romanov73;Sat Jan 27 15:28:22 2018 +0400;sort menu items, change logo -Romanov Anton;Sat Jan 27 05:55:52 2018 +0000;Update README.md -romanov73;Sat Jan 20 00:12:44 2018 +0400;fix versions select -Romanov Anton;Fri Jan 19 04:27:48 2018 +0000;Merge branch 'tool-square-catalog' into 'master' -Aleksey Filippov;Fri Jan 19 01:11:50 2018 +0400;add tool square catalog migration -Aleksey Filippov;Thu Jan 18 02:20:48 2018 +0400;add tool square catalog -romanov73;Thu Jan 11 21:22:14 2018 +0400;fix rest path -romanov73;Tue Jan 9 03:44:10 2018 +0400;fixes -romanov73;Tue Jan 9 00:09:27 2018 +0400;fix unit name -romanov73;Tue Jan 9 00:07:47 2018 +0400;fix year -romanov73;Mon Jan 8 19:16:28 2018 +0400;add short view -romanov73;Mon Jan 8 11:55:30 2018 +0400;fix -romanov73;Mon Jan 8 01:24:37 2018 +0400;fill work type by tool name -romanov73;Mon Jan 8 00:23:00 2018 +0400;ui fixes -romanov73;Sun Jan 7 23:54:40 2018 +0400;fix units hierarchy, add unit type, calc areas balance -romanov73;Sat Jan 6 22:51:11 2018 +0400;remove versions -romanov73;Sat Jan 6 22:06:03 2018 +0400;fix edit program -romanov73;Sat Jan 6 21:40:11 2018 +0400;add title image -romanov73;Sat Jan 6 18:04:27 2018 +0400;add production program input -romanov73;Wed Jan 3 21:05:48 2018 +0400;change wizard to tabs -romanov73;Wed Jan 3 01:03:05 2018 +0400;add wizard -romanov73;Sat Dec 30 02:02:42 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:46:01 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:32:33 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:28:24 2017 +0400;fix tools loading -romanov73;Sat Dec 30 00:54:26 2017 +0400;fix employee loading -romanov73;Fri Dec 29 19:04:14 2017 +0400;fix unit select -romanov73;Fri Dec 29 17:19:10 2017 +0400;fix unit select -romanov73;Fri Dec 29 01:40:38 2017 +0400;add cache -romanov73;Fri Dec 29 01:27:18 2017 +0400;add cache -romanov73;Fri Dec 29 01:14:09 2017 +0400;add cache -romanov73;Fri Dec 29 01:12:17 2017 +0400;change color -romanov73;Fri Dec 29 00:50:57 2017 +0400;add ci -romanov73;Fri Dec 29 00:48:10 2017 +0400;add ci -romanov73;Fri Dec 29 00:34:36 2017 +0400;add ci -romanov73;Fri Dec 29 00:31:22 2017 +0400;add ci -romanov73;Fri Dec 29 00:18:51 2017 +0400;add ci -romanov73;Fri Dec 29 00:16:44 2017 +0400;add ci -romanov73;Fri Dec 29 00:13:33 2017 +0400;add ci -romanov73;Fri Dec 29 00:09:51 2017 +0400;fix menus -Romanov Anton;Thu Dec 28 19:47:02 2017 +0000;Merge branch 'balance-example' into 'master' -romanov73;Thu Dec 28 23:44:52 2017 +0400;add other dictionaries -romanov73;Sat Dec 23 10:40:41 2017 +0400;add tool and work types -romanov73;Sat Dec 23 09:12:38 2017 +0400;fix menu item -romanov73;Sat Dec 23 09:12:23 2017 +0400;fix employee load -romanov73;Sat Dec 23 08:51:18 2017 +0400;add employee category -romanov73;Sat Dec 23 08:22:10 2017 +0400;add categories dictionary -romanov73;Fri Dec 22 12:38:08 2017 +0400;add menu item -romanov73;Fri Dec 22 12:31:26 2017 +0400;add employee category -romanov73;Fri Dec 22 09:55:31 2017 +0400;fix calendar -romanov73;Fri Dec 22 09:33:56 2017 +0400;fix date -romanov73;Thu Dec 21 16:38:20 2017 +0400;fix for context path change -romanov73;Thu Dec 21 13:58:02 2017 +0400;fluid panel -romanov73;Wed Dec 20 20:50:17 2017 +0400;fix context path -romanov73;Wed Dec 20 17:12:19 2017 +0400;fix displaying position -romanov73;Wed Dec 20 17:10:14 2017 +0400;load employees from file -romanov73;Wed Dec 20 15:09:54 2017 +0400;fix tool loading -romanov73;Mon Dec 18 18:18:04 2017 +0400;add resource versions -romanov73;Mon Dec 18 17:36:50 2017 +0400;add resource -romanov73;Mon Dec 18 17:07:47 2017 +0400;add swagger -romanov73;Sat Dec 16 09:42:46 2017 +0400;add new version -romanov73;Fri Dec 15 19:50:46 2017 +0400;fix title -romanov73;Fri Dec 15 19:24:13 2017 +0400;fix title -romanov73;Fri Dec 15 11:57:18 2017 +0400;add menu items -romanov73;Fri Dec 15 11:52:01 2017 +0400;add positions dictionary -romanov73;Fri Dec 15 11:29:02 2017 +0400;fix select for employee -romanov73;Fri Dec 15 10:35:12 2017 +0400;add employee backing -romanov73;Fri Dec 15 09:36:33 2017 +0400;fix 500 error page -romanov73;Thu Dec 14 22:26:45 2017 +0400;fix mapping -romanov73;Thu Dec 14 22:49:44 2017 +0400;add employee -romanov73;Thu Dec 14 22:37:30 2017 +0400;add employee -romanov73;Thu Dec 14 14:12:03 2017 +0400;fix crud service -romanov73;Thu Dec 14 12:51:59 2017 +0400;fix table style -romanov73;Thu Dec 14 12:22:48 2017 +0400;remove border for grid -romanov73;Thu Dec 14 12:20:14 2017 +0400;add version_id for units -romanov73;Thu Dec 14 11:49:53 2017 +0400;fix font size -romanov73;Thu Dec 14 11:08:46 2017 +0400;add unit fields -romanov73;Thu Dec 14 10:23:01 2017 +0400;KISS units hierarchy -romanov73;Wed Dec 13 19:04:05 2017 +0400;refresh page -romanov73;Wed Dec 13 17:43:54 2017 +0400;add version -romanov73;Wed Dec 13 01:46:05 2017 +0400;body width -romanov73;Wed Dec 13 01:45:17 2017 +0400;add menu item -romanov73;Tue Dec 12 22:20:07 2017 +0400;fix tree -romanov73;Tue Dec 12 20:02:20 2017 +0400;fix login -romanov73;Tue Dec 12 19:29:03 2017 +0400;add unit tree -romanov73;Tue Dec 12 18:23:38 2017 +0400;add unit dictionary -romanov73;Tue Dec 12 16:18:03 2017 +0400;add filter -romanov73;Tue Dec 12 14:13:31 2017 +0400;fix menu session -romanov73;Tue Dec 12 00:10:47 2017 +0400;remove bootstrap -romanov73;Mon Dec 11 21:53:18 2017 +0400;add unit select -romanov73;Mon Dec 11 19:49:15 2017 +0400;add menu item and role -romanov73;Mon Dec 11 19:37:38 2017 +0400;fix permissions -romanov73;Mon Dec 11 17:08:39 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 17:08:04 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 17:07:27 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 14:12:24 2017 +0400;add tools dictionary page -romanov73;Sat Dec 9 09:54:45 2017 +0400;add global exception hadler -romanov73;Sat Nov 25 13:52:33 2017 +0400;add monitoring -romanov73;Sat Nov 25 13:21:18 2017 +0400;fix xls and xlsx -romanov73;Sat Nov 25 12:39:45 2017 +0400;upload tools -romanov73;Fri Nov 24 22:16:27 2017 +0400;save tools -romanov73;Fri Nov 24 13:06:21 2017 +0400;fix message -romanov73;Fri Nov 24 12:11:43 2017 +0400;add loading from xlsx -romanov73;Fri Nov 17 19:43:35 2017 +0400;add message -romanov73;Fri Nov 17 15:11:22 2017 +0400;fix button -romanov73;Fri Nov 17 10:15:42 2017 +0400;add registration service -romanov73;Thu Nov 16 21:35:04 2017 +0400;add push script -romanov73;Thu Nov 16 20:54:49 2017 +0400;fix named query execution -romanov73;Sun Nov 12 22:19:03 2017 +0400;sort menu items -romanov73;Sat Nov 11 15:09:35 2017 +0400;fix title -romanov73;Sat Nov 11 14:54:25 2017 +0400;fix table -romanov73;Sat Nov 11 14:35:38 2017 +0400;fix style attribute -romanov73;Sat Nov 11 14:08:56 2017 +0400;show user sessions -romanov73;Sat Nov 11 13:51:34 2017 +0400;rename service -romanov73;Fri Nov 10 22:03:25 2017 +0400;fix update tree -romanov73;Fri Nov 10 14:29:25 2017 +0400;edit menu items -romanov73;Fri Nov 10 12:17:24 2017 +0400;set bootstrap theme for primefaces -romanov73;Thu Nov 9 23:40:40 2017 +0400;fix saving entitites -romanov73;Thu Nov 9 23:27:42 2017 +0400;fix saving entitites -romanov73;Thu Nov 9 20:51:19 2017 +0400;fix titles -romanov73;Thu Nov 9 18:00:45 2017 +0400;fix login page title -romanov73;Thu Nov 9 13:36:21 2017 +0400;commit log -romanov73;Thu Nov 9 07:31:37 2017 +0400;reverse sort commits -romanov73;Thu Nov 9 00:27:48 2017 +0400;sort commits -romanov73;Wed Nov 8 23:16:07 2017 +0400;extend commits log -romanov73;Wed Nov 8 21:42:16 2017 +0400;fix styles -romanov73;Wed Nov 8 19:17:08 2017 +0400;add logout -romanov73;Wed Nov 8 19:13:17 2017 +0400;add logout -romanov73;Wed Nov 8 20:18:35 2017 +0400;Merge branch 'master' of gitlab.com:romanov73/balance -romanov73;Wed Nov 8 20:17:55 2017 +0400;add database diagramm -Romanov Anton;Wed Nov 8 04:13:39 2017 +0000;Update README.md -Romanov Anton;Wed Nov 8 04:11:33 2017 +0000;Update README.md -romanov73;Wed Nov 8 07:48:13 2017 +0400;Merge remote-tracking branch 'origin/master' -Romanov Anton;Tue Nov 7 18:05:04 2017 +0000;Update README.md -romanov73;Tue Nov 7 22:01:50 2017 +0400;add example of permissions validation -romanov73;Tue Nov 7 20:56:31 2017 +0400;rename project -romanov73;Wed Oct 11 21:37:49 2017 +0400;reverse sort commits -romanov73;Wed Oct 11 21:32:19 2017 +0400;fix read resource as file -romanov73;Wed Oct 11 20:23:10 2017 +0400;Merge remote-tracking branch 'origin/master' -romanov73;Wed Oct 11 20:22:52 2017 +0400;add commits log -Romanov Anton;Tue Oct 10 20:47:29 2017 +0000;Update README.md -romanov73;Fri Oct 6 01:55:57 2017 +0400;add menu to platform \ No newline at end of file +Anton Romanov;Thu Mar 28 14:59:35 2019 +0400;partially restored commits page +Anton Romanov;Wed Mar 27 13:27:49 2019 +0400;#33 small external link +Anton Romanov;Wed Mar 27 09:07:02 2019 +0000;Update README.md +Anton Romanov;Wed Mar 27 07:58:58 2019 +0000;Merge branch '33-paper-url' into 'dev' +Семенова Мария;Sat Mar 23 12:23:30 2019 +0400;#33 link in new tab +Семенова Мария;Sat Mar 23 12:04:51 2019 +0400;Merge branch 'dev' into 33-paper-url +Anton Romanov;Fri Mar 22 10:36:09 2019 +0000;Merge branch '13-paper-files' into 'dev' +Anton Romanov;Fri Mar 22 13:58:03 2019 +0400;#13 reduce of code +Anton Romanov;Fri Mar 22 13:52:06 2019 +0400;#13 fix condition +Семенова Мария;Fri Mar 22 13:21:32 2019 +0400;#13 streams refactoring +Семенова Мария;Fri Mar 22 09:32:21 2019 +0400;#13 rename 'deleted', move creating fileDto to service +Семенова Мария;Thu Mar 21 18:26:28 2019 +0400;#13 fileDataDto instead of Object[] +Семенова Мария;Tue Mar 19 17:59:19 2019 +0400;#13 some refactoring +Семенова Мария;Tue Mar 19 17:41:02 2019 +0400;Merge branch 'dev' into 13-paper-files +Семенова Мария;Mon Mar 18 23:31:33 2019 +0400;#33 url in paper +Anton Romanov;Mon Mar 18 22:35:01 2019 +0400;merge deploy environments +Anton Romanov;Mon Mar 18 22:25:28 2019 +0400;add environment +Семенова Мария;Mon Mar 18 21:02:26 2019 +0400;#13 download files +Семенова Мария;Mon Mar 18 19:17:28 2019 +0400;#13 add files to db +Семенова Мария;Mon Mar 18 18:39:15 2019 +0400;#13 addNewFile js function +Семенова Мария;Mon Mar 18 16:13:22 2019 +0400;Merge branch 'dev' into 13-paper-files +Семенова Мария;Mon Mar 18 16:02:55 2019 +0400;#13 file list on paper page +Anton Romanov;Mon Mar 18 11:08:15 2019 +0000;Merge branch '50-refactorForGrants' into 'dev' +T-Midnight;Mon Mar 18 14:27:54 2019 +0400;Delete DeadlineDTO and update usages +Семенова Мария;Mon Mar 18 13:00:34 2019 +0400;#13 add fileDataDto +Семенова Мария;Mon Mar 18 11:07:30 2019 +0400;#13 change model, schema +T-Midnight;Fri Mar 15 12:24:02 2019 +0400;Add new status for grant +T-Midnight;Fri Mar 15 12:19:25 2019 +0400;Rename title +T-Midnight;Fri Mar 15 12:19:01 2019 +0400;Create Navigation class to avoid "magic strings" and code duplication +Anton Romanov;Wed Mar 13 07:16:32 2019 +0000;Merge branch '72-link-to-timetable' into 'dev' +Anton Romanov;Tue Mar 12 14:44:11 2019 +0400;add patronymic +Anton Romanov;Tue Mar 12 13:40:48 2019 +0400;display user in filter +Anton Romanov;Tue Mar 12 12:51:59 2019 +0300;add service methods +Anton Romanov;Tue Mar 12 12:35:09 2019 +0300;add timetable link +Anton Romanov;Mon Mar 11 10:49:07 2019 +0000;Merge branch '54-view-conference' into 'dev' +Nightblade73;Mon Mar 11 14:43:32 2019 +0400;#54 deleted edit btn +Nightblade73;Mon Mar 11 12:58:40 2019 +0400;#54 add back link, css fixes +Nightblade73;Mon Mar 11 11:36:00 2019 +0400;#54 add custom paper-list +Nightblade73;Sun Mar 10 22:15:09 2019 +0400;#54 add custom deadline-list, add edit-button, add edit and delete icons +Nightblade73;Thu Mar 7 22:50:37 2019 +0400;#54 add member list +Nightblade73;Wed Mar 6 20:51:46 2019 +0400;#54 part of make-up +Nightblade73;Wed Mar 6 16:48:51 2019 +0400;#54 add transitions +Anton Romanov;Wed Mar 6 16:40:33 2019 +0400;fix login +Nightblade73;Wed Mar 6 16:27:25 2019 +0400;#50 creating html page, changing href +Anton Romanov;Wed Mar 6 12:25:34 2019 +0000;Merge branch '29-page-header' into 'dev' +Anton Romanov;Wed Mar 6 16:19:43 2019 +0400;fix css +Anton Romanov;Wed Mar 6 14:36:46 2019 +0300;fix props +Anton Romanov;Wed Mar 6 14:35:09 2019 +0300;fix props +user;Wed Mar 6 14:25:02 2019 +0300;header fix +Anton Romanov;Mon Mar 4 10:28:46 2019 +0400;fix code +Anton Romanov;Mon Mar 4 10:24:30 2019 +0400;fix code +Anton Romanov;Fri Mar 1 11:27:57 2019 +0400;fix branch +Anton Romanov;Fri Mar 1 11:22:25 2019 +0400;add links to papers +Anton Romanov;Fri Mar 1 11:21:25 2019 +0400;add links to papers +Anton Romanov;Sun Feb 3 14:54:02 2019 +0000;Update README.md +Anton Romanov;Thu Jan 17 01:01:26 2019 +0400;filter dashboard papers +Anton Romanov;Thu Jan 17 00:57:34 2019 +0400;fix failed conditions +Anton Romanov;Tue Jan 8 19:55:27 2019 +0000;Update README.md +Anton Romanov;Tue Jan 8 19:36:36 2019 +0000;Update README.md +Anton Romanov;Sat Jan 5 08:01:07 2019 +0400;fix notifications +Anton Romanov;Fri Jan 4 17:41:48 2019 +0400;fix notifications +Anton Romanov;Sun Dec 30 19:23:11 2018 +0400;some layout fix +Anton Romanov;Sun Dec 30 19:22:51 2018 +0400;sort deadlines +Anton Romanov;Sat Dec 29 09:58:37 2018 +0400;fix enum field name +Anton Romanov;Fri Dec 28 10:04:42 2018 +0000;Merge branch '35-' into 'master' +Anton Romanov;Fri Dec 28 14:00:09 2018 +0400;some refactor +Anton Romanov;Fri Dec 28 13:57:20 2018 +0400;some refactor +T-Midnight;Tue Dec 25 12:50:14 2018 +0400;Create model folder for grant +T-Midnight;Tue Dec 25 02:44:50 2018 +0400;Hide button "Add Project" when project already exists (not perfect) +T-Midnight;Tue Dec 25 00:14:38 2018 +0400;Made button "delete grant" visible +T-Midnight;Mon Dec 24 23:39:19 2018 +0400;Rename table grant to grants +T-Midnight;Mon Dec 24 15:20:57 2018 +0400;Update classes +T-Midnight;Mon Dec 24 15:18:52 2018 +0400;Add thymeleaf template +T-Midnight;Mon Dec 24 13:05:37 2018 +0400;Create service&repository for Project +T-Midnight;Mon Dec 24 11:07:02 2018 +0400;Create js for grants +T-Midnight;Mon Dec 24 11:03:03 2018 +0400;Create html fragments +T-Midnight;Sun Dec 23 02:22:41 2018 +0400;Create Controller&Service +T-Midnight;Sun Dec 23 02:22:31 2018 +0400;Update ProjectDto +T-Midnight;Sun Dec 23 02:22:24 2018 +0400;Add constructor +T-Midnight;Sun Dec 23 02:22:06 2018 +0400;Add function getNextDeadline() +T-Midnight;Sat Dec 22 18:03:18 2018 +0400;Create GrantRepository +T-Midnight;Sat Dec 22 03:31:43 2018 +0400;Create setters for GrantDto +Anton Romanov;Fri Dec 21 00:07:26 2018 +0400;fix display paper title +Anton Romanov;Thu Dec 20 23:30:25 2018 +0400;fix paper status template +Anton Romanov;Wed Dec 19 01:29:54 2018 +0400;try to fix template resolvers, step 4 +Anton Romanov;Wed Dec 19 01:00:08 2018 +0400;try to fix template resolvers, step 3 +Anton Romanov;Wed Dec 19 00:44:12 2018 +0400;try to fix template resolvers, step 2 +Anton Romanov;Wed Dec 19 00:22:36 2018 +0400;try to fix template resolvers +Anton Romanov;Wed Dec 19 00:11:39 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Dec 19 00:09:07 2018 +0400;try to fix gradlew +Anton Romanov;Tue Dec 18 23:02:48 2018 +0400;sort filtered papers +Anton Romanov;Tue Dec 18 18:45:43 2018 +0000;Merge branch '47-statuses' into 'master' +Anton Romanov;Tue Dec 18 22:42:34 2018 +0400;add statuses +Anton Romanov;Tue Dec 18 18:18:25 2018 +0000;Merge branch '46-mvc' into 'master' +Anton Romanov;Tue Dec 18 22:15:21 2018 +0400;fix filer +Anton Romanov;Tue Dec 18 19:40:23 2018 +0400;fix scripts +Anton Romanov;Tue Dec 18 19:23:15 2018 +0400;show authors +Anton Romanov;Tue Dec 18 18:47:15 2018 +0400;confirm delete paper +Anton Romanov;Tue Dec 18 18:05:30 2018 +0400;add papers navigation +Anton Romanov;Mon Dec 17 17:56:48 2018 +0400;fix submit form +Anton Romanov;Mon Dec 17 17:29:06 2018 +0400;fix email notification +Anton Romanov;Mon Dec 17 17:28:52 2018 +0400;fix route +Anton Romanov;Mon Dec 17 13:46:08 2018 +0400;edit deadlines +Anton Romanov;Fri Dec 14 16:00:46 2018 +0400;Merge branch 'master' into 46-mvc +Anton Romanov;Fri Dec 14 16:00:33 2018 +0400;Merge branch 'master' into 46-mvc +Anton Romanov;Tue Dec 11 11:14:21 2018 +0000;Merge branch '36-' into 'master' +Anton Romanov;Tue Dec 11 15:08:35 2018 +0400;move classes, add deadline to entities, fix db changelogs +T-Midnight;Sat Dec 8 23:53:38 2018 +0400;Create model for grant, deadline and project +Anton Romanov;Wed Dec 5 18:08:47 2018 +0400;save authors +Anton Romanov;Wed Dec 5 17:36:53 2018 +0400;fix npe +Anton Romanov;Wed Dec 5 17:06:56 2018 +0400;format dates +Anton Romanov;Wed Dec 5 16:51:26 2018 +0400;fix create paper +Anton Romanov;Wed Dec 5 06:00:33 2018 +0000;add rest controller +Anton Romanov;Tue Dec 4 14:49:23 2018 +0400;fix js +Anton Romanov;Tue Dec 4 14:48:39 2018 +0400;show status +Anton Romanov;Tue Dec 4 14:13:01 2018 +0400;add validation +Anton Romanov;Tue Dec 4 11:35:51 2018 +0400;pass values for paper +Anton Romanov;Mon Nov 26 23:07:14 2018 +0400;load paper +Anton Romanov;Fri Nov 23 16:40:15 2018 +0000;Merge branch '14-filter-frontend' into 'master' +Anton Romanov;Fri Nov 23 20:37:24 2018 +0400;fix failed commit +Anton Romanov;Fri Nov 23 16:24:26 2018 +0000;Merge branch '10-savePaper' into 'master' +Anton Romanov;Fri Nov 23 16:45:40 2018 +0400;show paper list in mvc +Alyona;Fri Nov 23 14:17:16 2018 +0400;filter +Alyona;Fri Nov 23 14:12:37 2018 +0400;Merge branch 'master' into 14-filter-frontend +Alyona;Fri Nov 23 14:10:49 2018 +0400;Merge remote-tracking branch 'origin/master' +Alyona;Fri Nov 23 14:10:36 2018 +0400;Merge branch 'master' of C:\Users\катя\IdeaProjects\ng-tracker with conflicts. +Elena;Fri Nov 23 14:05:23 2018 +0400;задача 10(new) +Elena;Fri Nov 23 13:53:20 2018 +0400;задача 10 +Elena;Fri Nov 23 12:39:36 2018 +0400;Merge branch 'master' into 10-savePaper +Anton Romanov;Thu Nov 22 21:48:36 2018 +0400;fix colors +Anton Romanov;Thu Nov 22 21:31:41 2018 +0400;fix colors +Anton Romanov;Thu Nov 22 21:06:58 2018 +0400;add paper status +Anton Romanov;Thu Nov 22 11:20:08 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Thu Nov 22 11:19:50 2018 +0400;add mertica +Anton Romanov;Wed Nov 21 15:47:26 2018 +0400;remove empty page +Anton Romanov;Wed Nov 21 15:47:14 2018 +0400;add toolbar +Anton Romanov;Wed Nov 21 15:42:20 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Nov 21 11:42:47 2018 +0000;Merge branch '31-' into 'master' +Anton Romanov;Wed Nov 21 15:40:20 2018 +0400;close failed papers +T-Midnight;Wed Nov 21 15:28:41 2018 +0400;Create grant page +Anton Romanov;Wed Nov 21 08:51:11 2018 +0000;Merge branch '30-main-grants-page' into 'master' +Anton Romanov;Wed Nov 21 12:46:42 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Nov 21 12:46:27 2018 +0400;notify if paper deadline in future +T-Midnight;Mon Nov 19 12:16:39 2018 +0400;Update dashboard for grants +Anton Romanov;Mon Nov 19 08:21:22 2018 +0400;fix for remove event +Anton Romanov;Mon Nov 19 07:46:02 2018 +0400;fix event create conditions +Anton Romanov;Sun Nov 18 14:29:41 2018 +0400;fix delete button +Anton Romanov;Sat Nov 17 12:31:22 2018 +0400;fix delete button +Anton Romanov;Sat Nov 17 11:56:52 2018 +0400;show events +Anton Romanov;Wed Nov 14 17:14:56 2018 +0400;fix build script for netbeans +Anton Romanov;Wed Nov 14 17:04:40 2018 +0400;show toolbar buttons +Anton Romanov;Wed Nov 14 17:04:29 2018 +0400;show toolbar buttons +Anton Romanov;Wed Nov 14 17:04:13 2018 +0400;show paper status +Anton Romanov;Wed Nov 14 17:03:22 2018 +0400;fix code +Anton Romanov;Wed Nov 14 15:18:22 2018 +0400;fix code +Anton Romanov;Wed Nov 14 15:15:23 2018 +0400;read dashboard +Alyona;Wed Nov 14 08:55:46 2018 +0400;filter +Anton Romanov;Tue Nov 13 11:46:25 2018 +0000;Merge branch '14-DB-filter' into 'master' +Anton Romanov;Tue Nov 13 15:41:18 2018 +0400;filter papers +Anton Romanov;Sun Nov 11 14:28:51 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Sun Nov 11 12:43:27 2018 +0400;move class +Anton Romanov;Sat Nov 10 21:44:36 2018 +0000;Merge branch '20-' into 'master' +Anton Romanov;Sun Nov 11 01:31:27 2018 +0400;fix scheduler +Anton Romanov;Sun Nov 11 01:29:06 2018 +0400;refactor and db changes +Anton Romanov;Sat Nov 10 23:15:35 2018 +0400;delete paper +T-Midnight;Sat Nov 10 21:55:09 2018 +0400;Create dashboard for grants +Anton Romanov;Sat Nov 10 21:43:52 2018 +0400;add route +Alyona;Sat Nov 10 20:28:17 2018 +0400;fix +Alyona;Fri Nov 9 15:43:42 2018 +0400;fix +Elena;Fri Nov 9 15:43:25 2018 +0400;Задача №10 +Anton Romanov;Fri Nov 9 15:37:58 2018 +0400;remove styles from email template +Alyona;Fri Nov 9 15:29:48 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Fri Nov 9 15:21:03 2018 +0400;move styles to body +Anton Romanov;Fri Nov 9 11:15:00 2018 +0000;Merge branch '21-Event-status' into 'master' +Anton Romanov;Fri Nov 9 15:08:10 2018 +0400;Merge branch 'master' into 21-Event-status +Anton Romanov;Fri Nov 9 15:05:39 2018 +0400;fix styles +Anton Romanov;Fri Nov 9 14:53:24 2018 +0400;add styles in email templates +Alyona;Fri Nov 9 14:51:19 2018 +0400;added period event +Alyona;Fri Nov 9 14:48:21 2018 +0400;added period event +Elena;Fri Nov 9 13:43:08 2018 +0400;Merge branch 'master' into 10-savePaper +Elena;Fri Nov 9 13:42:28 2018 +0400;Задача №10 +Alyona;Fri Nov 9 13:30:31 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Fri Nov 9 10:04:50 2018 +0400;add notification templates +Anton Romanov;Fri Nov 9 09:16:24 2018 +0400;move to notification service +Anton Romanov;Fri Nov 9 08:39:00 2018 +0400;fix scheduler code to use service +Anton Romanov;Thu Nov 8 21:58:10 2018 +0400;add toolbar +Anton Romanov;Thu Nov 8 20:34:09 2018 +0400;fix paths +Anton Romanov;Thu Nov 8 20:08:26 2018 +0400;add config parameter +Anton Romanov;Thu Nov 8 19:39:22 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Thu Nov 8 19:36:50 2018 +0400;add paper create strategy +Anton Romanov;Tue Nov 6 10:39:25 2018 +0000;Update README.md +Anton Romanov;Tue Nov 6 14:10:00 2018 +0400;fix ci +Anton Romanov;Thu Nov 1 12:53:19 2018 +0400;fix imports +Alyona;Wed Oct 31 20:06:27 2018 +0400;changelog +Alyona;Wed Oct 31 19:35:23 2018 +0400;Merge branch 'master' into 21-Event-status +Alyona;Wed Oct 31 19:33:21 2018 +0400;Merge branch 'master' of C:\Users\катя\IdeaProjects\ng-tracker with conflicts. +Alyona;Tue Oct 30 23:59:42 2018 +0400;added event status +Anton Romanov;Tue Oct 30 19:32:00 2018 +0000;Merge branch '4-show-time-line' into 'master' +Anton Romanov;Tue Oct 30 23:29:18 2018 +0400;Merge branch 'master' into 4-show-time-line +Anton Romanov;Tue Oct 30 23:26:55 2018 +0400;fix db +Anton Romanov;Tue Oct 30 23:07:01 2018 +0400;Revert "remove liquibase" +Anton Romanov;Tue Oct 30 23:04:49 2018 +0400;Merge remote-tracking branch 'origin/4-show-time-line' into 4-show-time-line +Anton Romanov;Tue Oct 30 23:04:21 2018 +0400;Revert "remove liquibase" +Anton Romanov;Sat Oct 27 21:00:43 2018 +0000;Merge branch '11-Message-about-deadlines' into 'master' +Anton Romanov;Sat Oct 27 21:00:01 2018 +0000;Merge branch '12-' into 'master' +Alyona;Sun Oct 28 00:33:00 2018 +0400;fix +Alyona;Sun Oct 28 00:26:09 2018 +0400;fix +Alyona;Sun Oct 28 00:17:16 2018 +0400;fix +Alyona;Sun Oct 28 00:15:42 2018 +0400;fix +Alyona;Sat Oct 27 23:50:12 2018 +0400;fix +Alyona;Sat Oct 27 23:46:49 2018 +0400;fix +Anton Romanov;Sat Oct 27 23:25:27 2018 +0400;- make scheduler as a service - reformat code - fix cyrillic letter +Anton Romanov;Sat Oct 27 23:16:14 2018 +0400;add database column +Alyona;Sat Oct 27 23:11:07 2018 +0400;class for send messages about update paper +Alyona;Sat Oct 27 22:32:51 2018 +0400;add deadlineSсheduler +Alyona;Sat Oct 27 22:24:10 2018 +0400;add deadlineDate to models +Anton Romanov;Thu Oct 25 10:27:57 2018 +0400;some fix +Anton Romanov;Thu Oct 25 04:59:51 2018 +0000;Merge branch '8-' into 'master' +Alyona;Thu Oct 25 08:53:48 2018 +0400;finish deadline +Alyona;Thu Oct 25 08:53:29 2018 +0400;finish deadline +Alyona;Thu Oct 25 08:44:25 2018 +0400;Merge branch 'master' into 8-Deadline +Alyona;Thu Oct 25 08:41:37 2018 +0400;partially add deadline +Anton Romanov;Thu Oct 18 07:25:58 2018 +0000;Merge branch '7-dates-Matveeva' into 'master' +Anton Romanov;Thu Oct 18 11:21:26 2018 +0400;use html5 datetime pickers +Elena;Thu Oct 18 10:38:04 2018 +0400;Задача №7 +Anton Romanov;Thu Oct 18 04:49:10 2018 +0000;Merge branch '6-authors-list-Matveeva' into 'master' +Anton Romanov;Thu Oct 11 19:49:16 2018 +0400;fix buttons size +Anton Romanov;Thu Oct 11 15:39:57 2018 +0400;fix styles +Anton Romanov;Thu Oct 11 10:03:44 2018 +0400;load paper statuses +Anton Romanov;Thu Oct 11 09:05:41 2018 +0400;fix progress background +Elena;Wed Oct 10 23:09:45 2018 +0400;Задача №6 +Anton Romanov;Wed Oct 10 13:18:24 2018 +0000;Merge branch '5-' into 'master' +Anton Romanov;Wed Oct 10 17:05:54 2018 +0400;fix paper view +Elena;Wed Oct 10 15:30:47 2018 +0400;Задача №5 new +Elena;Wed Oct 10 10:09:07 2018 +0400;Merge branch 'master' into 5-Matveeva-Page +Elena;Thu Oct 4 10:39:27 2018 +0400;Задача №5 +Anton Romanov;Tue Oct 2 07:46:40 2018 +0000;Merge branch '27-file-upload' into 'master' +Anton Romanov;Tue Oct 2 11:11:13 2018 +0400;Merge branch 'master' into 27-file-upload +Anton Romanov;Tue Oct 2 10:58:41 2018 +0400;add messages +Anton Romanov;Mon Oct 1 13:54:11 2018 +0400;add example for file uploading +Anton Romanov;Sat Sep 29 09:22:48 2018 +0400;minimize scripts +Anton Romanov;Thu Sep 27 21:32:16 2018 +0400;fix error pages +Anton Romanov;Thu Sep 27 16:01:02 2018 +0000;Merge branch 'refactor-ui' into 'master' +Anton Romanov;Thu Sep 27 12:11:25 2018 +0400;move pages +Anton Romanov;Thu Sep 27 10:35:22 2018 +0400;some refactor +Anton Romanov;Thu Sep 27 06:08:53 2018 +0000;Merge branch '3-ui' into 'master' +Anton Romanov;Thu Sep 27 10:03:00 2018 +0400;Merge branch 'master' into 3-ui +Anton Romanov;Thu Sep 27 10:02:49 2018 +0400;Merge branch 'master' into 3-ui +Alyona;Wed Sep 26 16:44:36 2018 +0400;Merge branch 'master' into 5-Page-of-paper +Alyona;Wed Sep 26 16:41:33 2018 +0400;Merge branch 'master' into 23-dashboard +Anton Romanov;Wed Sep 26 12:22:38 2018 +0000;Merge branch '23-dashboard' into 'master' +Alyona;Wed Sep 26 16:11:43 2018 +0400;Merge branch 'master' into 23-dashboard +Alyona;Wed Sep 26 16:11:19 2018 +0400;Merge branch 'master' into 23-dashboard +Alyona;Wed Sep 26 16:04:31 2018 +0400;add paper page +Anton Romanov;Wed Sep 26 11:55:20 2018 +0000;Merge branch '24-paper-page' into 'master' +Alyona;Tue Sep 25 17:13:01 2018 +0400;Alyona-Test +Elena;Tue Sep 25 16:45:01 2018 +0400;Тестовое задание +Anton Romanov;Sat Sep 22 06:56:33 2018 +0000;Update README.md +Anton Romanov;Sat Sep 8 10:19:21 2018 +0400;change ports +Anton Romanov;Sat Sep 8 10:07:21 2018 +0400;Merge remote-tracking branch 'origin/4-show-time-line' into 4-show-time-line +Anton Romanov;Sat Sep 8 09:54:14 2018 +0400;Merge branch 'master' into 4-show-time-line +Anton Romanov;Sat Sep 8 09:53:34 2018 +0400;path to timeline +Anton Romanov;Sat Sep 8 09:50:13 2018 +0400;fix papers font +Anton Romanov;Thu Sep 6 19:14:04 2018 +0000;Update README.md +Anton Romanov;Thu Sep 6 23:01:39 2018 +0400;add variable +Anton Romanov;Thu Sep 6 22:50:34 2018 +0400;deploy on vps +Anton Romanov;Wed Sep 5 21:58:04 2018 +0400;add timeline page +Anton Romanov;Wed Sep 5 14:50:49 2018 +0400;sort papers +Anton Romanov;Wed Sep 5 13:45:20 2018 +0400;fix paper colors +Anton Romanov;Wed Sep 5 13:31:51 2018 +0400;add method for create paper +Anton Romanov;Wed Sep 5 11:27:22 2018 +0400;get paper list +Anton Romanov;Sun Jun 10 13:43:40 2018 +0400;add timeline page +Anton Romanov;Sun Jun 10 01:53:32 2018 +0400;fix time +Anton Romanov;Sun Jun 10 01:49:38 2018 +0400;send notification +Anton Romanov;Sun Jun 10 01:12:28 2018 +0400;add event controller +Anton Romanov;Sat Jun 9 22:58:32 2018 +0400;remove liquibase +Anton Romanov;Sat Jun 9 22:41:00 2018 +0400;fix ports +Aleksey Filippov;Sun May 20 08:05:21 2018 +0200;delete orphaned files, migrate to webjars, some fixes and refactoring +Anton Romanov;Sat May 5 14:49:34 2018 +0400;add paper authors +Anton Romanov;Sat May 5 11:17:13 2018 +0400;add paper status +Anton Romanov;Sat May 5 11:14:37 2018 +0400;fix papers delete +Anton Romanov;Sat May 5 10:57:02 2018 +0400;fix papers crud +Anton Romanov;Sat May 5 00:39:18 2018 +0400;fix paper controller +Anton Romanov;Sat May 5 00:24:48 2018 +0400;add file uploading +Anton Romanov;Fri May 4 18:09:29 2018 +0400;add big logo +Anton Romanov;Fri May 4 18:04:09 2018 +0400;add paper controller +Anton Romanov;Fri May 4 17:54:46 2018 +0400;add ci +Anton Romanov;Fri May 4 17:23:22 2018 +0400;gitignore +Anton Romanov;Fri May 4 17:08:20 2018 +0400;add java code +Anton Romanov;Mon Apr 30 00:47:50 2018 +0400;add works +Anton Romanov;Sun Apr 29 20:48:05 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 20:46:54 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 20:44:47 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 18:24:42 2018 +0400;add papers page +Anton Romanov;Sun Apr 29 17:09:58 2018 +0400;fix menu and sources +Anton Romanov;Sun Apr 29 00:49:37 2018 +0400;fix title page +Anton Romanov;Sat Apr 28 23:30:38 2018 +0400;fix title page +Anton Romanov;Sat Apr 28 22:50:26 2018 +0400;copy from landing diff --git a/src/main/resources/db/changelog-20190318_000000-schema.xml b/src/main/resources/db/changelog-20190318_000000-schema.xml new file mode 100644 index 0000000..a4f7ec1 --- /dev/null +++ b/src/main/resources/db/changelog-20190318_000000-schema.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190318_000001-schema.xml b/src/main/resources/db/changelog-20190318_000001-schema.xml new file mode 100644 index 0000000..98ec657 --- /dev/null +++ b/src/main/resources/db/changelog-20190318_000001-schema.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/main/resources/db/changelog-20190323_000001-schema.xml b/src/main/resources/db/changelog-20190323_000001-schema.xml new file mode 100644 index 0000000..1ab3efb --- /dev/null +++ b/src/main/resources/db/changelog-20190323_000001-schema.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190327_000000-schema.xml b/src/main/resources/db/changelog-20190327_000000-schema.xml new file mode 100644 index 0000000..4913072 --- /dev/null +++ b/src/main/resources/db/changelog-20190327_000000-schema.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190331_000000-schema.xml b/src/main/resources/db/changelog-20190331_000000-schema.xml new file mode 100644 index 0000000..76a7daa --- /dev/null +++ b/src/main/resources/db/changelog-20190331_000000-schema.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190331_000010-schema.xml b/src/main/resources/db/changelog-20190331_000010-schema.xml new file mode 100644 index 0000000..04bda75 --- /dev/null +++ b/src/main/resources/db/changelog-20190331_000010-schema.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190402_000000-schema.xml b/src/main/resources/db/changelog-20190402_000000-schema.xml new file mode 100644 index 0000000..1bbb256 --- /dev/null +++ b/src/main/resources/db/changelog-20190402_000000-schema.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190404_000000-schema.xml b/src/main/resources/db/changelog-20190404_000000-schema.xml new file mode 100644 index 0000000..0f45e31 --- /dev/null +++ b/src/main/resources/db/changelog-20190404_000000-schema.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190410_000000-schema.xml b/src/main/resources/db/changelog-20190410_000000-schema.xml new file mode 100644 index 0000000..b8db7f9 --- /dev/null +++ b/src/main/resources/db/changelog-20190410_000000-schema.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog-20190417_000000-schema.xml b/src/main/resources/db/changelog-20190417_000000-schema.xml new file mode 100644 index 0000000..f0a0d64 --- /dev/null +++ b/src/main/resources/db/changelog-20190417_000000-schema.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog-20190418_000000-schema.xml b/src/main/resources/db/changelog-20190418_000000-schema.xml new file mode 100644 index 0000000..31cf21c --- /dev/null +++ b/src/main/resources/db/changelog-20190418_000000-schema.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190421_000000-schema.xml b/src/main/resources/db/changelog-20190421_000000-schema.xml new file mode 100644 index 0000000..b7fc374 --- /dev/null +++ b/src/main/resources/db/changelog-20190421_000000-schema.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190422_000000-schema.xml b/src/main/resources/db/changelog-20190422_000000-schema.xml new file mode 100644 index 0000000..f110910 --- /dev/null +++ b/src/main/resources/db/changelog-20190422_000000-schema.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-master.xml b/src/main/resources/db/changelog-master.xml index c07b1df..dfe3cfc 100644 --- a/src/main/resources/db/changelog-master.xml +++ b/src/main/resources/db/changelog-master.xml @@ -18,4 +18,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/common/changelog-20190312_130000-schema.xml b/src/main/resources/db/common/changelog-20190312_130000-schema.xml new file mode 100644 index 0000000..4078ccb --- /dev/null +++ b/src/main/resources/db/common/changelog-20190312_130000-schema.xml @@ -0,0 +1,16 @@ + + + + + + + + + + update users + set first_name = 'Антон', patronymic = 'Алексеевич', last_name = 'Романов' where id = 1; + + + \ No newline at end of file diff --git a/src/main/resources/db/common/changelog-20190423_100000-schema.xml b/src/main/resources/db/common/changelog-20190423_100000-schema.xml new file mode 100644 index 0000000..602014e --- /dev/null +++ b/src/main/resources/db/common/changelog-20190423_100000-schema.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/drivers/chromedriver b/src/main/resources/drivers/chromedriver new file mode 100644 index 0000000..02ff671 Binary files /dev/null and b/src/main/resources/drivers/chromedriver differ diff --git a/src/main/resources/drivers/chromedriver.exe b/src/main/resources/drivers/chromedriver.exe new file mode 100644 index 0000000..28a4067 Binary files /dev/null and b/src/main/resources/drivers/chromedriver.exe differ diff --git a/src/main/resources/drivers/geckodriver b/src/main/resources/drivers/geckodriver new file mode 100644 index 0000000..ba1da8c Binary files /dev/null and b/src/main/resources/drivers/geckodriver differ diff --git a/src/main/resources/drivers/geckodriver.exe b/src/main/resources/drivers/geckodriver.exe new file mode 100644 index 0000000..6208aa8 Binary files /dev/null and b/src/main/resources/drivers/geckodriver.exe differ diff --git a/src/main/resources/public/css/agency.css b/src/main/resources/public/css/agency.css index 5bfd18b..fb3ec24 100644 --- a/src/main/resources/public/css/agency.css +++ b/src/main/resources/public/css/agency.css @@ -1,50 +1,50 @@ body { - overflow-x: hidden; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + overflow-x: hidden; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } p { - line-height: 1.75; + line-height: 1.75; } a { - color: #000000; + color: #000000; } a:hover { - color: #fec503; + color: #fec503; } .text-draft { - color: rgba(0, 0, 0, 0.48) !important; + color: rgba(0, 0, 0, 0.48) !important; } .text-primary { - color: #228bba !important; + color: #228bba !important; } .text-warning { - color: #940000 !important; + color: #940000 !important; } .text-review { - color: #94028d !important; + color: #94028d !important; } .text-success { - color: #007741 !important; + color: #007741 !important; } .text-accepted { - color: #fec503 !important; + color: #fec503 !important; } .text-not-accepted { - color: #A38831 !important; + color: #A38831 !important; } .text-failed { - color: #A38831 !important; + color: #A38831 !important; } h1, @@ -53,751 +53,787 @@ h3, h4, h5, h6 { - font-weight: 700; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } section { - padding: 100px 0; + padding: 100px 0; } section h2.section-heading { - font-size: 3.5vw; - margin-top: 0; - margin-bottom: 15px; + font-size: 2.5vw; + margin-top: 0; + margin-bottom: 15px; } section h3.section-subheading { - font-size: 16px; - font-weight: 400; - font-style: italic; - margin-bottom: 75px; - text-transform: none; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 16px; + font-weight: 400; + font-style: italic; + margin-bottom: 75px; + text-transform: none; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; } @media (min-width: 768px) { - section { - padding: 100px 0; - } + section { + padding: 100px 0; + } } .btn { - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; } .btn-xl { - font-size: 18px; - padding: 20px 40px; + font-size: 18px; + padding: 20px 40px; } .btn-success { - background-color: #28a745; - border-color: #28a745; + background-color: #28a745; + border-color: #28a745; } .btn-primary { - background-color: #fed136; - border-color: #fed136; + background-color: #fed136; + border-color: #fed136; } .btn-default { - background-color: #ffffff; - border-color: #ced4da;; + background-color: #ffffff; + border-color: #ced4da;; } .btn-info { - background-color: #5bc0de; - border-color: #5bc0de; + background-color: #5bc0de; + border-color: #5bc0de; } .progress-bar { - display: flex; - -ms-flex-direction: column; - flex-direction: column; - -ms-flex-pack: center; - justify-content: center; - color: #fff; - text-align: center; - white-space: nowrap; - background-color: #fed136; - transition: width .6s ease; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #fed136; + transition: width .6s ease; } .btn-primary:active, .btn-primary:focus, .btn-primary:hover { - background-color: #fec810 !important; - border-color: #fec810 !important; - color: white; + background-color: #fec810 !important; + border-color: #fec810 !important; + color: white; } .btn-primary:active, .btn-primary:focus { - box-shadow: 0 0 0 0.2rem rgba(254, 209, 55, 0.5) !important; + box-shadow: 0 0 0 0.2rem rgba(254, 209, 55, 0.5) !important; } ::-moz-selection { - background: #fed136; - text-shadow: none; + background: #fed136; + text-shadow: none; } ::selection { - background: #fed136; - text-shadow: none; + background: #fed136; + text-shadow: none; } img::selection { - background: transparent; + background: transparent; } img::-moz-selection { - background: transparent; + background: transparent; } #mainNav { - background-color: #212529; + background-color: #212529; } #mainNav .navbar-toggler { - font-size: 12px; - right: 0; - padding: 13px; - text-transform: uppercase; - color: white; - border: 0; - background-color: #fed136; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 12px; + right: 0; + padding: 13px; + text-transform: uppercase; + color: white; + border: 0; + background-color: #fed136; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } #mainNav .navbar-brand { - color: #fed136; - font-family: 'Kaushan Script', 'Helvetica Neue', Helvetica, Arial, cursive; + color: #fed136; + font-family: 'Kaushan Script', 'Helvetica Neue', Helvetica, Arial, cursive; } #mainNav .navbar-brand.active, #mainNav .navbar-brand:active, #mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover { - color: #fec503; + color: #fec503; } #mainNav .navbar-nav .nav-item .nav-link { - font-size: 90%; - font-weight: 400; - padding: 0.75em 0; - letter-spacing: 1px; - color: white; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 90%; + font-weight: 400; + padding: 0.75em 0; + letter-spacing: 1px; + color: white; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } #mainNav .navbar-nav .nav-item .nav-link.active, #mainNav .navbar-nav .nav-item .nav-link:hover { - color: #fed136; + color: #fed136; } @media (min-width: 992px) { - #mainNav { - padding-top: 25px; - padding-bottom: 25px; - -webkit-transition: padding-top 0.3s, padding-bottom 0.3s; - -moz-transition: padding-top 0.3s, padding-bottom 0.3s; - transition: padding-top 0.3s, padding-bottom 0.3s; - border: none; - background-color: transparent; - } - #mainNav .navbar-brand { - font-size: 1.75em; - -webkit-transition: all 0.3s; - -moz-transition: all 0.3s; - transition: all 0.3s; - } - #mainNav .navbar-nav .nav-item .nav-link { - padding: 1.1em 1em !important; - } - #mainNav.navbar-shrink { - padding-top: 0; - padding-bottom: 0; - background-color: #212529; - } - #mainNav.navbar-shrink .navbar-brand { - font-size: 1.25em; - padding: 12px 0; - } + #mainNav { + padding-top: 25px; + padding-bottom: 25px; + -webkit-transition: padding-top 0.3s, padding-bottom 0.3s; + -moz-transition: padding-top 0.3s, padding-bottom 0.3s; + transition: padding-top 0.3s, padding-bottom 0.3s; + border: none; + background-color: transparent; + } + + #mainNav .navbar-brand { + font-size: 1.75em; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; + } + + #mainNav .navbar-nav .nav-item .nav-link { + padding: 1.1em 1em !important; + } + + #mainNav.navbar-shrink { + padding-top: 0; + padding-bottom: 0; + background-color: #212529; + } + + #mainNav.navbar-shrink .navbar-brand { + font-size: 1.25em; + padding: 12px 0; + } } header.masthead { - text-align: center; - color: white; - background-image: url("../img/header-bg.jpg"); - background-repeat: no-repeat; - background-attachment: scroll; - background-position: center center; - -webkit-background-size: cover; - -moz-background-size: cover; - -o-background-size: cover; - background-size: cover; + text-align: center; + color: white; + background-image: url("../img/header-bg.jpg"); + background-repeat: no-repeat; + background-attachment: scroll; + background-position: center center; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; } header.masthead .intro-text { - padding-top: 150px; - padding-bottom: 100px; + padding-top: 150px; + padding-bottom: 100px; } header.masthead .intro-text .intro-lead-in { - font-size: 22px; - font-style: italic; - line-height: 22px; - margin-bottom: 25px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 22px; + font-style: italic; + line-height: 22px; + margin-bottom: 25px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; } header.masthead .intro-text .intro-heading { - font-size: 50px; - font-weight: 700; - line-height: 50px; - margin-bottom: 25px; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 50px; + font-weight: 700; + line-height: 50px; + margin-bottom: 25px; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } @media (min-width: 768px) { - header.masthead .intro-text { - padding-top: 300px; - padding-bottom: 200px; - } - header.masthead .intro-text .intro-lead-in { - font-size: 40px; - font-style: italic; - line-height: 40px; - margin-bottom: 25px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; - } - header.masthead .intro-text .intro-heading { - font-size: 75px; - font-weight: 700; - line-height: 75px; - margin-bottom: 50px; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; - } + header.masthead .intro-text { + padding-top: 300px; + padding-bottom: 200px; + } + + header.masthead .intro-text .intro-lead-in { + font-size: 40px; + font-style: italic; + line-height: 40px; + margin-bottom: 25px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + } + + header.masthead .intro-text .intro-heading { + font-size: 75px; + font-weight: 700; + line-height: 75px; + margin-bottom: 50px; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + } } .service-heading { - margin: 15px 0; - text-transform: none; + margin: 15px 0; + text-transform: none; } #portfolio .portfolio-item { - right: 0; - margin: 0 0 15px; + right: 0; + margin: 0 0 15px; } #portfolio .portfolio-item .portfolio-link { - position: relative; - display: block; - max-width: 400px; - margin: 0 auto; - cursor: pointer; + position: relative; + display: block; + max-width: 400px; + margin: 0 auto; + cursor: pointer; } #portfolio .portfolio-item .portfolio-link .portfolio-hover { - position: absolute; - width: 100%; - height: 100%; - -webkit-transition: all ease 0.5s; - -moz-transition: all ease 0.5s; - transition: all ease 0.5s; - opacity: 0; - background: rgba(254, 209, 54, 0.9); + position: absolute; + width: 100%; + height: 100%; + -webkit-transition: all ease 0.5s; + -moz-transition: all ease 0.5s; + transition: all ease 0.5s; + opacity: 0; + background: rgba(254, 209, 54, 0.9); } #portfolio .portfolio-item .portfolio-link .portfolio-hover:hover { - opacity: 1; + opacity: 1; } #portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content { - font-size: 20px; - position: absolute; - top: 50%; - width: 100%; - height: 20px; - margin-top: -12px; - text-align: center; - color: white; + font-size: 20px; + position: absolute; + top: 50%; + width: 100%; + height: 20px; + margin-top: -12px; + text-align: center; + color: white; } #portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content i { - margin-top: -12px; + margin-top: -12px; } #portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h3, #portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h4 { - margin: 0; + margin: 0; } #portfolio .portfolio-item .portfolio-caption { - max-width: 400px; - margin: 0 auto; - padding: 25px; - text-align: center; - background-color: #fff; + max-width: 400px; + margin: 0 auto; + padding: 25px; + text-align: center; + background-color: #fff; } #portfolio .portfolio-item .portfolio-caption h4 { - margin: 0; - text-transform: none; + margin: 0; + text-transform: none; } #portfolio .portfolio-item .portfolio-caption p { - font-size: 16px; - font-style: italic; - margin: 0; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 16px; + font-style: italic; + margin: 0; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; } #portfolio * { - z-index: 2; + z-index: 2; } @media (min-width: 767px) { - #portfolio .portfolio-item { - margin: 0 0 30px; - } + #portfolio .portfolio-item { + margin: 0 0 30px; + } } .portfolio-modal { - padding-right: 0px !important; + padding-right: 0px !important; } .portfolio-modal .modal-dialog { - margin: 1rem; - max-width: 100vw; + margin: 1rem; + max-width: 100vw; } .portfolio-modal .modal-content { - padding: 100px 0; - text-align: center; + padding: 100px 0; + text-align: center; } .portfolio-modal .modal-content h2 { - font-size: 3em; - margin-bottom: 15px; + font-size: 3em; + margin-bottom: 15px; } .portfolio-modal .modal-content p { - margin-bottom: 30px; + margin-bottom: 30px; } .portfolio-modal .modal-content p.item-intro { - font-size: 16px; - font-style: italic; - margin: 20px 0 30px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 16px; + font-style: italic; + margin: 20px 0 30px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; } .portfolio-modal .modal-content ul.list-inline { - margin-top: 0; - margin-bottom: 30px; + margin-top: 0; + margin-bottom: 30px; } .portfolio-modal .modal-content img { - margin-bottom: 30px; + margin-bottom: 30px; } .portfolio-modal .modal-content button { - cursor: pointer; + cursor: pointer; } .portfolio-modal .close-modal { - position: absolute; - top: 25px; - right: 25px; - width: 75px; - height: 75px; - cursor: pointer; - background-color: transparent; + position: absolute; + top: 25px; + right: 25px; + width: 75px; + height: 75px; + cursor: pointer; + background-color: transparent; } .portfolio-modal .close-modal:hover { - opacity: 0.3; + opacity: 0.3; } .portfolio-modal .close-modal .lr { - /* Safari and Chrome */ - z-index: 1051; - width: 1px; - height: 75px; - margin-left: 35px; - /* IE 9 */ - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); - background-color: #212529; + /* Safari and Chrome */ + z-index: 1051; + width: 1px; + height: 75px; + margin-left: 35px; + /* IE 9 */ + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background-color: #212529; } .portfolio-modal .close-modal .lr .rl { - /* Safari and Chrome */ - z-index: 1052; - width: 1px; - height: 75px; - /* IE 9 */ - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - background-color: #212529; + /* Safari and Chrome */ + z-index: 1052; + width: 1px; + height: 75px; + /* IE 9 */ + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + background-color: #212529; } .timeline { - position: relative; - padding: 0; - list-style: none; + position: relative; + padding: 0; + list-style: none; } .timeline:before { - position: absolute; - top: 0; - bottom: 0; - left: 40px; - width: 2px; - margin-left: -1.5px; - content: ''; - background-color: #e9ecef; + position: absolute; + top: 0; + bottom: 0; + left: 40px; + width: 2px; + margin-left: -1.5px; + content: ''; + background-color: #e9ecef; } .timeline > li { - position: relative; - min-height: 50px; - margin-bottom: 50px; + position: relative; + min-height: 50px; + margin-bottom: 50px; } .timeline > li:after, .timeline > li:before { - display: table; - content: ' '; + display: table; + content: ' '; } .timeline > li:after { - clear: both; + clear: both; } .timeline > li .timeline-panel { - position: relative; - float: right; - width: 100%; - padding: 0 20px 0 100px; - text-align: left; + position: relative; + float: right; + width: 100%; + padding: 0 20px 0 100px; + text-align: left; } .timeline > li .timeline-panel:before { - right: auto; - left: -15px; - border-right-width: 15px; - border-left-width: 0; + right: auto; + left: -15px; + border-right-width: 15px; + border-left-width: 0; } .timeline > li .timeline-panel:after { - right: auto; - left: -14px; - border-right-width: 14px; - border-left-width: 0; + right: auto; + left: -14px; + border-right-width: 14px; + border-left-width: 0; } .timeline > li .timeline-image { - position: absolute; - z-index: 100; - left: 0; - width: 80px; - height: 80px; - margin-left: 0; - text-align: center; - color: white; - border: 7px solid #e9ecef; - border-radius: 100%; - background-color: #fed136; + position: absolute; + z-index: 100; + left: 0; + width: 80px; + height: 80px; + margin-left: 0; + text-align: center; + color: white; + border: 7px solid #e9ecef; + border-radius: 100%; + background-color: #fed136; } .timeline > li .timeline-image h4 { - font-size: 10px; - line-height: 14px; - margin-top: 12px; + font-size: 10px; + line-height: 14px; + margin-top: 12px; } .timeline > li.timeline-inverted > .timeline-panel { - float: right; - padding: 0 20px 0 100px; - text-align: left; + float: right; + padding: 0 20px 0 100px; + text-align: left; } .timeline > li.timeline-inverted > .timeline-panel:before { - right: auto; - left: -15px; - border-right-width: 15px; - border-left-width: 0; + right: auto; + left: -15px; + border-right-width: 15px; + border-left-width: 0; } .timeline > li.timeline-inverted > .timeline-panel:after { - right: auto; - left: -14px; - border-right-width: 14px; - border-left-width: 0; + right: auto; + left: -14px; + border-right-width: 14px; + border-left-width: 0; } .timeline > li:last-child { - margin-bottom: 0; + margin-bottom: 0; } .timeline .timeline-heading h4 { - margin-top: 0; - color: inherit; + margin-top: 0; + color: inherit; } .timeline .timeline-heading h4.subheading { - text-transform: none; + text-transform: none; } .timeline .timeline-body > ul, .timeline .timeline-body > p { - margin-bottom: 0; + margin-bottom: 0; } @media (min-width: 768px) { - .timeline:before { - left: 50%; - } - .timeline > li { - min-height: 100px; - margin-bottom: 100px; - } - .timeline > li .timeline-panel { - float: left; - width: 41%; - padding: 0 20px 20px 30px; - text-align: right; - } - .timeline > li .timeline-image { - left: 50%; - width: 100px; - height: 100px; - margin-left: -50px; - } - .timeline > li .timeline-image h4 { - font-size: 13px; - line-height: 18px; - margin-top: 16px; - } - .timeline > li.timeline-inverted > .timeline-panel { - float: right; - padding: 0 30px 20px 20px; - text-align: left; - } + .timeline:before { + left: 50%; + } + + .timeline > li { + min-height: 100px; + margin-bottom: 100px; + } + + .timeline > li .timeline-panel { + float: left; + width: 41%; + padding: 0 20px 20px 30px; + text-align: right; + } + + .timeline > li .timeline-image { + left: 50%; + width: 100px; + height: 100px; + margin-left: -50px; + } + + .timeline > li .timeline-image h4 { + font-size: 13px; + line-height: 18px; + margin-top: 16px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + float: right; + padding: 0 30px 20px 20px; + text-align: left; + } } @media (min-width: 992px) { - .timeline > li { - min-height: 150px; - } - .timeline > li .timeline-panel { - padding: 0 20px 20px; - } - .timeline > li .timeline-image { - width: 150px; - height: 150px; - margin-left: -75px; - } - .timeline > li .timeline-image h4 { - font-size: 18px; - line-height: 26px; - margin-top: 30px; - } - .timeline > li.timeline-inverted > .timeline-panel { - padding: 0 20px 20px; - } + .timeline > li { + min-height: 150px; + } + + .timeline > li .timeline-panel { + padding: 0 20px 20px; + } + + .timeline > li .timeline-image { + width: 150px; + height: 150px; + margin-left: -75px; + } + + .timeline > li .timeline-image h4 { + font-size: 18px; + line-height: 26px; + margin-top: 30px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + padding: 0 20px 20px; + } } @media (min-width: 1200px) { - .timeline > li { - min-height: 170px; - } - .timeline > li .timeline-panel { - padding: 0 20px 20px 100px; - } - .timeline > li .timeline-image { - width: 170px; - height: 170px; - margin-left: -85px; - } - .timeline > li .timeline-image h4 { - margin-top: 40px; - } - .timeline > li.timeline-inverted > .timeline-panel { - padding: 0 100px 20px 20px; - } + .timeline > li { + min-height: 170px; + } + + .timeline > li .timeline-panel { + padding: 0 20px 20px 100px; + } + + .timeline > li .timeline-image { + width: 170px; + height: 170px; + margin-left: -85px; + } + + .timeline > li .timeline-image h4 { + margin-top: 40px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + padding: 0 100px 20px 20px; + } } .team-member { - margin-bottom: 50px; - text-align: center; + margin-bottom: 50px; + text-align: center; } .team-member img { - width: 225px; - height: 225px; - border: 7px solid #fff; + width: 225px; + height: 225px; + border: 7px solid #fff; } .team-member h4 { - margin-top: 25px; - margin-bottom: 0; - text-transform: none; + margin-top: 25px; + margin-bottom: 0; + text-transform: none; } .team-member p { - margin-top: 0; + margin-top: 0; } section#contact { - background-color: #212529; - background-image: url("../img/map-image.png"); - background-repeat: no-repeat; - background-position: center; + background-color: #212529; + background-image: url("../img/map-image.png"); + background-repeat: no-repeat; + background-position: center; } section#contact .section-heading { - color: #fff; + color: #fff; } section#contact .form-group { - margin-bottom: 25px; + margin-bottom: 25px; } section#contact .form-group input, section#contact .form-group textarea { - padding: 20px; + padding: 20px; } section#contact .form-group input.form-control { - height: auto; + height: auto; } section#contact .form-group textarea.form-control { - height: 248px; + height: 248px; } section#contact .form-control:focus { - border-color: #fed136; - box-shadow: none; + border-color: #fed136; + box-shadow: none; } section#contact ::-webkit-input-placeholder { - font-weight: 700; - color: #ced4da; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + color: #ced4da; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } section#contact :-moz-placeholder { - font-weight: 700; - color: #ced4da; - /* Firefox 18- */ - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + color: #ced4da; + /* Firefox 18- */ + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } section#contact ::-moz-placeholder { - font-weight: 700; - color: #ced4da; - /* Firefox 19+ */ - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + color: #ced4da; + /* Firefox 19+ */ + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } section#contact :-ms-input-placeholder { - font-weight: 700; - color: #ced4da; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + color: #ced4da; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } footer { - padding: 25px 0; - text-align: center; + padding: 25px 0; + text-align: center; } footer span.copyright { - font-size: 90%; - line-height: 40px; - text-transform: none; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 90%; + line-height: 40px; + text-transform: none; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } footer ul.quicklinks { - font-size: 90%; - line-height: 40px; - margin-bottom: 0; - text-transform: none; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 90%; + line-height: 40px; + margin-bottom: 0; + text-transform: none; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; } ul.social-buttons { - margin-bottom: 0; + margin-bottom: 0; } ul.social-buttons li a { - font-size: 20px; - line-height: 40px; - display: block; - width: 40px; - height: 40px; - -webkit-transition: all 0.3s; - -moz-transition: all 0.3s; - transition: all 0.3s; - color: white; - border-radius: 100%; - outline: none; - background-color: #212529; + font-size: 20px; + line-height: 40px; + display: block; + width: 40px; + height: 40px; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; + color: white; + border-radius: 100%; + outline: none; + background-color: #212529; } ul.social-buttons li a:active, ul.social-buttons li a:focus, ul.social-buttons li a:hover { - background-color: #fed136; + background-color: #fed136; } .dashboard-card { - background: #ffffff none repeat scroll 0 0; - margin: 15px; - padding: 20px; - border: 0 solid #e7e7e7; - border-radius: 5px; - box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); + background: #ffffff none repeat scroll 0 0; + margin: 15px; + padding: 20px; + border: 0 solid #e7e7e7; + border-radius: 5px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); } .toolbar-button { - width: 100%; - margin: 5px; + width: 100%; + margin: 5px; } + /* --------------------------------------------------- FEEDBACK STYLE ----------------------------------------------------- */ .feedback-panel { - list-style-type: none; - padding: 5px; + list-style-type: none; + padding: 5px; } .feedback-panel .alert { - padding: 5px; - margin-bottom: 5px; - cursor: pointer; + padding: 5px; + margin-bottom: 5px; + cursor: pointer; } /* --------------------------------------------------- MEDIAQUERIES ----------------------------------------------------- */ @media (min-width: 768px) { - .feedback-panel { - position: fixed; - overflow-y: hidden; - max-height: calc(100vh - 60px); - width: 250px; - top: 50px; - right: 15px; - opacity: .8; - z-index: 10000; - } - - .feedback-panel .alert { - padding: 10px; - margin-bottom: 10px; - overflow: hidden; - } - - .feedback-panel .alert:hover { - border-color: #888; - } + section h2.section-heading { + font-size: 3.5vw; + margin-top: 0; + margin-bottom: 15px; + } +} + +@media (min-width: 1500px) { + section h2.section-heading { + font-size: 1.5vw; + margin-top: 0; + margin-bottom: 15px; + } +} + +@media (min-width: 768px) { + .feedback-panel { + position: fixed; + overflow-y: hidden; + max-height: calc(100vh - 60px); + width: 250px; + top: 50px; + right: 15px; + opacity: .8; + z-index: 10000; + } + + .feedback-panel .alert { + padding: 10px; + margin-bottom: 10px; + overflow: hidden; + } + + .feedback-panel .alert:hover { + border-color: #888; + } } \ No newline at end of file diff --git a/src/main/resources/public/css/conference.css b/src/main/resources/public/css/conference.css new file mode 100644 index 0000000..8cebd1d --- /dev/null +++ b/src/main/resources/public/css/conference.css @@ -0,0 +1,182 @@ +body { + min-width: 400px; +} + +.conference-row .col:hover { + background-color: #f3f3f3; + border-radius: .25rem; +} + +.filter-option-inner-inner { + color: white; +} + +.filter .dropdown { + margin-bottom: 10px; +} + +.conference-row .col .text-decoration { + text-decoration: none; +} + + +.form-group textarea { + min-height: 206px; + max-height: 463px; +} + +.deadline-list { + height: 200px; + padding: 0px; + overflow-y: scroll; +} + +.deadline { + margin: 0; + height: 40px; + min-height: 40px; +} + +.deadline-text { + flex: 1; +} + +.member-list { + height: 200px; + padding: 0px; + overflow-y: scroll; +} + +.member { + margin: 0; + height: 40px; + max-height: 40px; +} + +.member select { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + border: none; + outline: none; + padding: 0.5rem 1.75em 0.5rem 0.5em; + display: inline-block; + background: transparent url("https://cdn3.iconfinder.com/data/icons/faticons/32/arrow-down-01-16.png") no-repeat right 7px center; + +} + +.member select:nth-child(4) { + border-right: 1px solid #ced4da; +} + + +.member-name { + padding: .75rem 1.25rem; + cursor: default; + outline: none; + border: none; + border-right: 1px solid #ced4da; +} + +#take-part[disabled=disabled] { + background-color: #ced4da; + border-color: #c2c5c7; +} + +#take-part[disabled=disabled]:hover { + background-color: #737475 !important; + border-color: #5d5e5f !important; +} + + +.paper-list { + height: 200px; + padding: 0px; + overflow-y: scroll; +} + +.paper { + margin: 0; + min-height: 40px; + height: 40px; +} + +.paper-name { + flex: 1; +} + +.paper-name span { + margin: 6px 15px; + display: inline-block; +} + +.icon { + width: 38px; + height: 38px; + padding: 2px; + cursor: pointer; +} + +.icon-delete { + background-color: #f44; + background-image: url(/img/conference/delete.png); + background-repeat: round; + color: transparent !important; +} + +.icon-delete:hover { + background-color: #ff2929; + transition: background-color .15s ease-in-out; +} + +.icon-paper { + height: 26px; + width: 26px; + float: right; + margin: 5px; +} + +.grey-border { + color: #495057; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: .25rem; + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; +} + +#cancel-button { + position: relative; + font-size: 1rem; +} + +.paper-control button { + margin: 0 0 10px 10px; + float: right; +} + +@media (max-width: 1199px) and (min-width: 768px) { + .paper-control { + display: block!important; + } +} + +@media (max-width: 991px) { + + .dates-panel { + display: block!important; + } + + .date { + margin-bottom: 10px; + } +} + +@media (max-width: 768px) { + .dashboard-elements { + display: block!important; + } + + .dashboard-right { + margin-bottom: 10px; + } +} diff --git a/src/main/resources/public/css/grant.css b/src/main/resources/public/css/grant.css new file mode 100644 index 0000000..1073cdf --- /dev/null +++ b/src/main/resources/public/css/grant.css @@ -0,0 +1,12 @@ +.div-deadline-date { + padding-right: 0px; +} + +.form-deadline-date { + padding-right: 3px; +} + +.div-deadline-description { + padding-left: 5px; + padding-right: 5px; +} \ No newline at end of file diff --git a/src/main/resources/public/css/odin.css b/src/main/resources/public/css/odin.css new file mode 100644 index 0000000..2bfeb75 --- /dev/null +++ b/src/main/resources/public/css/odin.css @@ -0,0 +1,226 @@ +.odin-unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.odin-kill-padding { + padding: 0; +} + +.odin-input-group { + padding-left: 15px !important; + padding-right: 15px !important; +} + +.odin-closable .fa { + font-size: 1.5em; + margin-top: -1px; +} + +.odin-closable .fa:hover:before { + content: "\f057"; +} + +/* + Odin Toolbar +*/ +.odin-toolbar { + padding-left: 1px; + padding-bottom: 4px; +} + +.odin-toolbar .odin-btn { + float: none !important; +} + +.odin-btn { + min-width: 112px; + margin-right: 3px; +} + +@media (min-width: 768px) { + .odin-btn { + min-width: 150px; + } +} + +/* + Odin Paginator +*/ +.odin-paginator { + margin: 0; + margin-top: 5px; + text-align: center; +} + +.odin-paginator-content { + display: inline-block; +} + +.odin-paginator-content a { + cursor: pointer; + color: black; + float: left; + padding: 6px 16px; + text-decoration: none; + transition: background-color .3s; + border-radius: 4px; +} + +.odin-paginator-content i { + color: black; + float: left; + padding: 6px 16px; +} + +.odin-paginator-content a.active { + background-color: #4CAF50; + color: white; +} + +.odin-paginator-content a:hover:not(.active) { + background-color: #ddd; +} + +/* + Odin Formatters +*/ +.odin-negative { + color: red; + font-weight: bold; +} + +/* + Odin Table +*/ +.odin-table { + min-height: 324px; + border: 1px solid #ddd; + padding: 0; + margin: 0 0 0 1px; + background-color: #f8f8f8; +} + +.odin-table > table { + margin-bottom: 0; +} + +.odin-table > table > tbody > tr { + background-color: #fff; +} + +.odin-table-pointed-line { + cursor: pointer; +} + +.odin-table-selected-line { + background-color: #5bc0de !important; +} + +.odin-table-selected-line:hover { + background-color: #6bd0ee !important; +} + +/* + Odin Form + */ +.odin-form { + display: none; + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1500; + background: rgba(0, 0, 0, 0.5); +} + +.odin-form .panel { + position: relative; + max-height: 95%; + max-width: 95%; + overflow: auto; +} + +@media (min-width: 768px) { + .odin-form .panel { + max-width: 55%; + } +} + +.odin-form .panel-footer { + padding: 5px 7px; +} + +.odin-form .odin-btn { + float: right; +} + +.odin-checkbox { + width: 20px; + height: 20px; +} + +.odin-form .tab-pane { + padding-top: 5px; +} + +/* + Odin Confirm Box + */ +.odin-confirm-box { + display: none; + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 2000; + background: rgba(0, 0, 0, 0.5); +} + +.odin-confirm-box .panel { + position: relative; + max-width: 95%; +} + +.odin-confirm-box .panel-footer { + text-align: center; +} + +@media (min-width: 768px) { + .odin-confirm-box .panel { + max-width: 25%; + } +} + +.odin-confirm-box .panel-body { + text-align: center; +} + +/* + Odin Table Box + */ +.odin-table-box { + z-index: 2500; +} + +/* + Odin Simple Box + */ + +@media (min-width: 768px) { + .odin-simple-form .panel { + max-width: 35%; + } +} \ No newline at end of file diff --git a/src/main/resources/public/css/paper.css b/src/main/resources/public/css/paper.css new file mode 100644 index 0000000..824209f --- /dev/null +++ b/src/main/resources/public/css/paper.css @@ -0,0 +1,9 @@ +#files-list .row > div:nth-child(7) { + display: flex; + justify-content: center; + flex-direction: column; +} + +.nav-tabs { + margin-bottom: 20px; +} \ No newline at end of file diff --git a/src/main/resources/public/css/tasks.css b/src/main/resources/public/css/tasks.css new file mode 100644 index 0000000..14718de --- /dev/null +++ b/src/main/resources/public/css/tasks.css @@ -0,0 +1,65 @@ +.tags-container { + width: 100%; + padding: .375rem .75rem; + background-color: #fff; + border: 1px solid #ccc; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + display: inline-block; + color: #555; + vertical-align: middle; + border-radius: 4px; + max-width: 100%; + line-height: 22px; + cursor: text; +} + +.input-tag-name { + border: none; + box-shadow: none; + outline: none; + background-color: transparent; + padding: 0 6px; + margin: 0; + width: auto; + max-width: inherit; +} + +.tag { + display: inline-block; + padding: .2em .6em .3em; + background-color: orange; + border-radius: .25em; + margin-right: 4px; + margin-bottom: 4px; + + font-size: 75%; + font-weight: 700; + line-height: 1.5; + color: #fff; +} + +.tag-name span[data-role="remove"] { + margin-left: 8px; + cursor: pointer; +} + +.tag-name span[data-role="remove"]:after { + content: "x"; + padding: 0px 2px; +} + +.tag-name input[type="text"] { + background: transparent; + border: none; + display: inline-flex; + font-size: 100%; + font-weight: 700; + line-height: 1.5; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + outline: none; + cursor: default; + +} diff --git a/src/main/resources/public/img/conference/back.png b/src/main/resources/public/img/conference/back.png new file mode 100644 index 0000000..7667350 Binary files /dev/null and b/src/main/resources/public/img/conference/back.png differ diff --git a/src/main/resources/public/img/conference/delete.png b/src/main/resources/public/img/conference/delete.png new file mode 100644 index 0000000..595bf63 Binary files /dev/null and b/src/main/resources/public/img/conference/delete.png differ diff --git a/src/main/resources/public/img/conference/edit.png b/src/main/resources/public/img/conference/edit.png new file mode 100644 index 0000000..832bef3 Binary files /dev/null and b/src/main/resources/public/img/conference/edit.png differ diff --git a/src/main/resources/public/img/conference/paper.png b/src/main/resources/public/img/conference/paper.png new file mode 100644 index 0000000..78a0dfc Binary files /dev/null and b/src/main/resources/public/img/conference/paper.png differ diff --git a/src/main/resources/public/js/conference.js b/src/main/resources/public/js/conference.js new file mode 100644 index 0000000..8335f54 --- /dev/null +++ b/src/main/resources/public/js/conference.js @@ -0,0 +1,29 @@ +$(document).ready(function () { + + $('a[data-confirm]').click(function (ev) { + var href = $(this).attr('href'); + if (!$('#dataConfirmModal').length) { + $('#modalDelete').append(''); + } + $('#dataConfirmModal').find('#myModalLabel').text($(this).attr('data-confirm')); + $('#dataConfirmOK').attr('href', href); + $('#dataConfirmModal').modal({show: true}); + return false; + }); +}); diff --git a/src/main/resources/public/js/config.js b/src/main/resources/public/js/config.js new file mode 100644 index 0000000..50d1b40 --- /dev/null +++ b/src/main/resources/public/js/config.js @@ -0,0 +1,13 @@ +/* exported contextPath */ +var contextPath = ""; +var apiVersion = "/api/1.0"; + +var basePath = contextPath + apiVersion; + +/* exported uiLocale */ +var uiLocale = "ru"; + +/* exported urlVersions */ +var urlVersions = basePath + "/versions"; +/* exported urlCommits */ +var urlCommits = basePath + "/commits"; \ No newline at end of file diff --git a/src/main/resources/public/js/core.js b/src/main/resources/public/js/core.js index 5c9241c..58e68f4 100644 --- a/src/main/resources/public/js/core.js +++ b/src/main/resources/public/js/core.js @@ -1,7 +1,10 @@ // from config.js /* global urlVersions */ -var urlFileUpload = "/api/1.0/papers/uploadTmpFile"; +var urlFileUpload = "/api/1.0/files/uploadTmpFile"; +var urlFileDownload = "/api/1.0/files/download/"; +var urlPdfGenerating = "/papers/generatePdf"; +var urlFileDownloadTmp = "/api/1.0/files/download-tmp/"; /* exported MessageTypesEnum */ var MessageTypesEnum = { @@ -86,20 +89,18 @@ function getFromRest(url, callBack, errorCallBack) { } /* exported getFromRestWithVersion */ -function getFromRestWithVersion(url, callBack, errorCallBack) { - getFromRestWithVersionAndParams(url, "", callBack, errorCallBack); +function getFromRest(url, callBack, errorCallBack) { + getFromRestWithParams(url, "", callBack, errorCallBack); } /* exported getFromRestWithVersionAndParams */ -function getFromRestWithVersionAndParams(url, params, callBack, errorCallBack) { - getCurrentVersion(function (version) { - $.ajax({ - url: url + "?versionId=" + version + params, - cache: false, - success: function (response) { - errorHandler(response, callBack, errorCallBack); - } - }); +function getFromRestWithParams(url, params, callBack, errorCallBack) { + $.ajax({ + url: url + "?" + params, + cache: false, + success: function (response) { + errorHandler(response, callBack, errorCallBack); + } }); } @@ -189,32 +190,6 @@ function deleteFromRest(url, callBack, completeCallback, errorCallBack) { }); } -/* exported getCurrentVersion */ -function getCurrentVersion(callBack, errorCallBack) { - var version = localStorage.getItem("currentVersion"); - - if (($("#select-versions")[0].options.length > 0) - && ($("#select-versions option[value='" + version + "']").length === 0)) { - localStorage.removeItem("currentVersion"); - version = ""; - } - - if (isEmpty(version)) { - getFromRest(urlVersions, - function (versions) { - if (isEmpty(versions) || versions.count === 0) { - return; - } - var currentVersion = versions.items[0]; - localStorage.setItem("currentVersion", currentVersion.id); - callBack(currentVersion.id); - }, - errorCallBack); - } else { - callBack(version); - } -} - /* exported fillSelect */ function fillSelect(selectElement, values) { $(selectElement).html(""); diff --git a/src/main/resources/public/js/file-loader.js b/src/main/resources/public/js/file-loader.js index 9ee16a1..1ba467b 100644 --- a/src/main/resources/public/js/file-loader.js +++ b/src/main/resources/public/js/file-loader.js @@ -42,6 +42,7 @@ function FileLoader(args) { div.append(fileLabel); var fileInput = $("") .attr("type", "file") + .attr("multiple", '') .hide(); fileInput.change(function () { var files = $(this).prop("files"); @@ -75,20 +76,22 @@ function FileLoader(args) { showFeedbackMessage(ALERT_CHOOSE_FILE, MessageTypesEnum.DANGER); return; } - var currentFile = files[0]; - if (!isEmpty(fileExtensions) && fileExtensions.indexOf(getFileExt(currentFile)) === -1) { - showFeedbackMessage(ALERT_UNKNOWN_FILE_EXT, MessageTypesEnum.DANGER); - return; - } - if (currentFile.size === 0) { - showFeedbackMessage(ALERT_EMPTY_FILE, MessageTypesEnum.DANGER); - return; - } - if (currentFile.size / SIZE_TO_MB > MAX_FILE_SIZE_MB) { - showFeedbackMessage(ALERT_MAX_FILE + " " + MAX_FILE_SIZE_MB + "Mb", MessageTypesEnum.DANGER); - return; + for (var i = 0; i < files.length; i++) { + var currentFile = files[i]; + if (!isEmpty(fileExtensions) && fileExtensions.indexOf(getFileExt(currentFile)) === -1) { + showFeedbackMessage(ALERT_UNKNOWN_FILE_EXT, MessageTypesEnum.DANGER); + return; + } + if (currentFile.size === 0) { + showFeedbackMessage(ALERT_EMPTY_FILE, MessageTypesEnum.DANGER); + return; + } + if (MAX_FILE_SIZE_MB != -1 && currentFile.size / SIZE_TO_MB > MAX_FILE_SIZE_MB) { + showFeedbackMessage(ALERT_MAX_FILE + " " + MAX_FILE_SIZE_MB + "Mb", MessageTypesEnum.DANGER); + return; + } + upload(currentFile); } - upload(currentFile); }); buttonGroup.append(uploadButton); diff --git a/src/main/resources/public/js/odin.js b/src/main/resources/public/js/odin.js new file mode 100644 index 0000000..54d8832 --- /dev/null +++ b/src/main/resources/public/js/odin.js @@ -0,0 +1,1512 @@ +// form config.js +/* global uiLocale, urlVersions */ +// from core.js +/* global isEmpty, deleteFromRest, fillSelect, MessageTypesEnum, showFeedbackMessage, getCurrentVersionData */ +/* global getFromRestWithParams, getFromRest */ +/* global postToRestWithVersionAndParams, putToRestWithVersionAndParams */ + +// TODO: settings for table selection mode (single, multiple) + +var odinMetaList = "/meta/list"; +var odinMetaElement = "/meta/element"; +var odinCopy = "/copy-from-version"; + +/* exported OdinTableWithMeta */ +function OdinTableWithMeta(divId, dataUrl, disableCopy) { + return OdinTableWithMetaAndParams(divId, dataUrl, "", disableCopy, ""); +} + +/* exported OdinTableWithMetaAndGlobalCallback */ +function OdinTableWithMetaAndGlobalCallback(divId, dataUrl, disableCopy, globalSaveCallback, globalSelectCallback) { + return OdinTableWithMetaAndParams(divId, dataUrl, "", disableCopy, globalSaveCallback, globalSelectCallback); +} + +/* exported OdinTableWithMetaAndParams */ +function OdinTableWithMetaAndParams(divId, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback) { + return new OdinTable(divId, dataUrl + odinMetaList, dataUrl + odinMetaElement, dataUrl, + params, disableCopy, globalSaveCallback, globalSelectCallback); +} + +function OdinTable(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback) { + var CLICK_DELAY = 250; + var DATA_ATTR = "odin-data"; + var DATA_VIEW_ATTR = "odin-data-view"; + var TYPE_ATTR = "type"; + var ID_ATTR = "id"; + var DISABLED_ATTR = "disabled"; + var REQUIRED_ATTR = "required"; + var LABELS_SELECTOR = "label"; + var INPUTS_SELECTOR = ":input"; + var BUTTONS_SELECTOR = ":button"; + var CHECKED_ATTR = "checked"; + var NOT_CHECKED_ATTR = ""; + var PAGINATOR_ITEMS_PER_PAGE = 10; + var PAGINATOR_VISIBLE_BUTTONS_COUNT = 3; + var odinTable; + + var L10nEnum = { + VIEW: "Наименование", + COLLECTION: "Коллеция объектов", + GO_TO: "Перейти", + + BUTTON_ADD: "Добавить", + BUTTON_CREATE: "Создать", + BUTTON_EDIT: "Изменить", + BUTTON_DELETE: "Удалить", + + BUTTON_COPY: "Копировать", + + BUTTON_OK: "Продолжить", + BUTTON_CANCEL: "Отмена", + + BUTTON_SAVE: "Записать", + BUTTON_CLOSE: "Закрыть", + + CAPTION_CREATE_FORM: "Новый элемент", + CAPTION_EDIT_FORM: "(редактирование)", + CAPTION_CHOOSE_FORM: "Выбор объекта", + CAPTION_COPY_FORM: "Копирование данных", + CAPTION_DEFAULT_TAB: "Реквизиты", + CPATION_FORM_DEFAULT: "...", + + QUESTION_LOST_CHANGES: "Закрыть без сохранения?", + QUESTION_DELETE_ITEM: "Выполнить удаление?", + QUESTION_DEFAULT_CAPTION: "Продолжит?", + + ERROR_VERSIONS_MATCH: "Версии совпадают" + }; + Object.freeze(L10nEnum); + + var FieldTypesEnum = { + BOOLEAN: "boolean", + DATE: "date", + NUMERIC: "numeric", + STRING: "string", + COLLECTION: "collection", + OBJECT: "object" + }; + Object.freeze(FieldTypesEnum); + + var DateTypesEnum = { + DATETIME: "datetime", + DATE: "date", + TIME: "time" + }; + Object.freeze(DateTypesEnum); + + var StringTypesEnum = { + STRING: "string", + PASSWORD: "password", + TEXT: "text", + EMAIL: "email", + HREF: "href" + }; + Object.freeze(StringTypesEnum); + + var VisibleTypesEnum = { + ALL: "all", + ON_CREATE: "on_create", + ON_UPDATE: "on_update", + NONE: "none" + }; + Object.freeze(VisibleTypesEnum); + + var InputTypesEnum = { + CHECKBOX: "checkbox", + PASSWORD: "password", + TEXT: "text", + EMAIL: "email", + DATE: "date", + DATETIME: "datetime-local", + TIME: "time", + NUMBER: "number", + HIDDEN: "hidden", + SUBMIT: "submit" + }; + Object.freeze(InputTypesEnum); + + var FormTypesEnum = { + FORM: "form", + CONFIRM: "confirm", + SIMPLE: "simple" + }; + Object.freeze(FormTypesEnum); + + var NumberAttrEnum = { + MIN: "min", + MAX: "max", + STEP: "step" + }; + Object.freeze(NumberAttrEnum); + + var StringAttrEnum = { + MIN: "minlength", + MAX: "maxlength" + }; + Object.freeze(StringAttrEnum); + + $(document.body).append("
"); + $("#odinTemplates").load("/templates/odin.html", function () { + $("#" + divId).append("
" + + " " + + "
"); + odinTable = new OdinTableMain(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback); + }); + + this.getSelectedItems = function () { + return odinTable.getSelectedItems(); + } + + function format(str, args) { + if (isEmpty(str) || isEmpty(args)) { + return str; + } + if (!Array.isArray(args)) { + throw "Odin: Format args is not array"; + } + return str.replace(/\{(\d+)\}/g, + function (match, number) { + return typeof args[number] !== "undefined" ? + args[number] : match; + }); + } + + function centerDiv(component) { + if (isEmpty(component)) { + return; + } + component.offset({ + top: ($(window).height() - component.height()) / 2, + left: ($(window).width() - component.width()) / 2 + }); + } + + function tmpl(id) { + var html = $("#odinTemplates").find("#" + id).html(); + if (isEmpty(html)) { + throw "Odin: Template load error"; + } + var template = format(html.trim(), Array.prototype.slice.call(arguments, 1)); + return $(template); + } + + function find(object, selector) { + var result = object.find(selector); + if (isEmpty(result)) { + throw "Can't find elements by selector " + selector; + } + return result; + } + + function findFiltered(object, selector, notSelector) { + var result = object.find(selector).not(notSelector); + if (isEmpty(result)) { + throw "Can't find elements by selector " + selector; + } + return result; + } + + function OdinFormCommon(type) { + var BUTTON_CLOSE_SELECTOR = "i.fa"; + var CAPTION_SELECTOR = "#caption"; + var PANEL_SELECTOR = ".panel"; + var FORM_INPUTS_SELECTOR = ":input"; + var DATA_CHANGED_ATTR = "changed"; + + var form; + var header; + var content; + var footer; + + var createForm = function () { + switch (type) { + case FormTypesEnum.FORM: + form = tmpl("form"); + break; + case FormTypesEnum.CONFIRM: + form = tmpl("formConfirm"); + break; + default: + form = tmpl("formSimple"); + } + if (isEmpty(form)) { + throw "Odin: Unknown form type"; + } + header = createFormHeader(); + content = createFormContent(); + footer = createFormFooter(); + var formPanel = tmpl("formPanel"); + formPanel.append(header); + formPanel.append(content); + formPanel.append(footer); + form.append(formPanel); + setCaption(L10nEnum.CPATION_FORM_DEFAULT); + $(document.body).append(form); + }; + + var createFormHeader = function () { + var fHeader = tmpl("formHeader"); + fHeader.find(BUTTON_CLOSE_SELECTOR).click(function () { + hideForm(); + }); + return fHeader; + }; + + var createFormContent = function () { + return tmpl("formContent"); + }; + + var createFormFooter = function () { + return tmpl("formFooter"); + }; + + var setCaption = function (caption) { + if (isEmpty(caption)) { + throw "Odin: Caption is not set"; + } + find(header, CAPTION_SELECTOR).text(caption); + }; + + var addFormClass = function (clazz) { + if (isEmpty(clazz)) { + throw "Odin: Additional class is not set"; + } + form.addClass(clazz); + }; + + var isModified = function () { + return form.data(DATA_CHANGED_ATTR); + }; + + var setFormSubmit = function (callBack) { + if (isEmpty(callBack)) { + throw "Odin: Callback is not set"; + } + form.submit(function () { + callBack(); + return false; + }); + }; + + var showForm = function () { + form.show(0, function () { + centerDiv(find(form, PANEL_SELECTOR)); + }); + if (type === FormTypesEnum.FORM) { + find(form, FORM_INPUTS_SELECTOR).change(function () { + form.data(DATA_CHANGED_ATTR, true); + }); + } + }; + + var hideForm = function () { + var callBack = function () { + form.hide(); + form.remove(); + }; + callBack(); + }; + + this._getHeader = function () { + return header; + }; + + this._getContent = function () { + return content; + }; + + this._getFooter = function () { + return footer; + }; + + this._setCaption = function (caption) { + setCaption(caption); + }; + + this._setFormSubmit = function (callBack) { + setFormSubmit(callBack); + }; + + this._addFormClass = function (clazz) { + addFormClass(clazz); + }; + + this._isModified = function () { + return isModified(); + }; + + this._show = function () { + showForm(); + }; + + this._hide = function () { + hideForm(); + }; + + createForm(); + } + + function OdinToolbar(tableDiv, createCallBack, editCallBack, deleteCallBack) { + var SELECTOR_ODIN_TABLE = ".odin-table"; + + var toolbar; + + var createButton; + var editButton; + var deleteButton; + + var initialize = function () { + if (isEmpty(tableDiv)) { + throw "Odin-Toolbar: Target div is not set"; + } + var table = find(tableDiv, SELECTOR_ODIN_TABLE); + toolbar = tmpl("toolbar"); + toolbar.insertBefore(table, null); + if (!isEmpty(createCallBack)) { + createButton = tmpl("createButton", L10nEnum.BUTTON_CREATE); + createButton.click(function () { + createCallBack(); + }); + toolbar.append(createButton); + } + if (!isEmpty(editCallBack)) { + editButton = tmpl("editButton", L10nEnum.BUTTON_EDIT); + editButton.click(function () { + editCallBack(); + }); + setEditButtonState(false); + toolbar.append(editButton); + } + if (!isEmpty(deleteCallBack)) { + deleteButton = tmpl("deleteButton", L10nEnum.BUTTON_DELETE); + deleteButton.click(function () { + deleteCallBack(); + }); + setDeleteButtonState(false); + toolbar.append(deleteButton); + } + }; + + var setEditButtonState = function (state) { + if (isEmpty(editButton)) { + return; + } + editButton.attr("disabled", !state); + }; + + var setDeleteButtonState = function (state) { + if (isEmpty(deleteButton)) { + return; + } + deleteButton.attr("disabled", !state); + }; + + this.addButton = function (caption, callBack) { + if (isEmpty(caption) || isEmpty(callBack)) { + throw "Odin-Toolbar: Caption or callback is not set"; + } + var button = tmpl("basicButton", caption); + button.click(function () { + callBack(); + }); + toolbar.append(button); + }; + + this.setEditButtonState = function (state) { + setEditButtonState(state); + }; + + this.setDeleteButtonState = function (state) { + setDeleteButtonState(state); + }; + + initialize(); + } + + function OdinPaginator(tableDiv, callBack) { + var SELECTOR_PAGINATOR_CONTENT = ".odin-paginator-content"; + var SELECTOR_PAGINATOR_BUTTON = "a"; + var PAGINATOR_BUTTON_PREFIX = "odin-paginator-"; + var PAGINATOR_SELECTED_BUTTON_CLASS = "active"; + + var initialize = function () { + if (isEmpty(tableDiv)) { + throw "Odin-Paginator: Target div is not set"; + } + if (isEmpty(callBack)) { + throw "Odin-Paginator: Callback is not set"; + } + tableDiv.append(tmpl("paginator")); + }; + + var drawPaginator = function (data, offset) { + var paginatorContentDiv = find(tableDiv, SELECTOR_PAGINATOR_CONTENT); + paginatorContentDiv.empty(); + if (isEmpty(data)) { + return; + } + $.each(data, function (index, value) { + if (value.name) { + paginatorContentDiv.append(tmpl("paginatorButton", value.offset, value.name)); + } else { + paginatorContentDiv.append(tmpl("paginatorSeparator")); + } + }); + var buttons = find(paginatorContentDiv, SELECTOR_PAGINATOR_BUTTON); + buttons.removeClass("active"); + buttons.click(function () { + $('#preloader').show(); + var offset = $(this).attr(ID_ATTR).replace(PAGINATOR_BUTTON_PREFIX, ""); + callBack(offset); + }); + find(paginatorContentDiv, "#" + PAGINATOR_BUTTON_PREFIX + offset) + .addClass(PAGINATOR_SELECTED_BUTTON_CLASS); + }; + + this.drawPaginator = function (offset, count) { + var currentOffset = parseInt(offset ? offset : "0"); + var buttonCount = Math.floor( + count <= PAGINATOR_ITEMS_PER_PAGE ? 0 : count / PAGINATOR_ITEMS_PER_PAGE) + + (count < PAGINATOR_ITEMS_PER_PAGE || + count % PAGINATOR_ITEMS_PER_PAGE === 0 ? 0 : 1); + var data = []; + Array.apply(0, Array(buttonCount)).forEach(function (element, index) { + if (index > 0 && index < buttonCount - 1 && + buttonCount > PAGINATOR_VISIBLE_BUTTONS_COUNT * 2) { + if (index === currentOffset - PAGINATOR_VISIBLE_BUTTONS_COUNT || + index === currentOffset + PAGINATOR_VISIBLE_BUTTONS_COUNT + 1) { + data.push({ + "offset": null, + "name": null + }); + } + if (index <= currentOffset - PAGINATOR_VISIBLE_BUTTONS_COUNT || + index > currentOffset + PAGINATOR_VISIBLE_BUTTONS_COUNT) { + return true; + } + } + data.push({ + "offset": index, + "name": index + 1 + }); + }); + drawPaginator(data, currentOffset); + }; + + initialize(); + } + + function OdinTableFormatter() { + var BOOLEAN_TRUE_CLASS = "fa-check"; + var BOOLEAN_FALSE_CLASS = "fa-times"; + + var formatBooleanValue = function (value) { + return tmpl("tableBoolean", (value ? BOOLEAN_TRUE_CLASS : BOOLEAN_FALSE_CLASS)); + }; + + var formatDateValue = function (value, dateType) { + var date = new Date(value); + var formatterSettings = {}; + switch (dateType) { + case DateTypesEnum.DATETIME: + formatterSettings = { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric" + }; + break; + case DateTypesEnum.TIME: + formatterSettings = { + hour: "numeric", + minute: "numeric", + second: "numeric" + }; + break; + } + var dateFormatter = new Intl.DateTimeFormat(uiLocale, formatterSettings); + return dateFormatter.format(date); + }; + + var formatNumericValue = function (value, positiveOnly, scale) { + var numberFormatter = new Intl.NumberFormat(uiLocale, { + useGrouping: false, + minimumFractionDigits: scale + }); + var formattedValue = numberFormatter.format(value); + if (positiveOnly && value < 0) { + return tmpl("tableNumericNegative", formattedValue); + } + return formattedValue; + }; + + var formatStringValue = function (value, maxLength, stringType) { + switch (stringType) { + case StringTypesEnum.HREF: + return tmpl("anchor", value, L10nEnum.GO_TO); + default: + if (isEmpty(maxLength) || maxLength === 0) { + return value; + } + return value.substring(0, Math.min(value.length, maxLength)); + } + }; + + this.formatValue = function (value, fieldParams) { + if (isEmpty(value) || isEmpty(fieldParams)) { + return ""; + } + if (fieldParams.hidden) { + return value; + } + switch (fieldParams.fieldType) { + case FieldTypesEnum.BOOLEAN: + return formatBooleanValue(value); + case FieldTypesEnum.DATE: + return formatDateValue(value, fieldParams.type); + case FieldTypesEnum.NUMERIC: + return formatNumericValue(value, fieldParams.positiveOnly, fieldParams.scale); + case FieldTypesEnum.STRING: + return formatStringValue(value, fieldParams.maxLength, fieldParams.type); + case FieldTypesEnum.COLLECTION: + return L10nEnum.COLLECTION; + case FieldTypesEnum.OBJECT: + return value.view; + default: + return value; + } + }; + } + + function OdinTableView(odinDiv, editCallback, globalSelectCallback) { + var TABLEVIEW_SELECTED_LINE_CLASS = "odin-table-selected-line"; + var TABLEVIEW_LINE_SELECTOR = "tr"; + var TABLEVIEW_SELECTED_LINE_SELECTOR = TABLEVIEW_LINE_SELECTOR + + "." + TABLEVIEW_SELECTED_LINE_CLASS; + var TABLEVIEW_LINE_DATA_SELECTOR = TABLEVIEW_LINE_SELECTOR + + "[" + DATA_ATTR + "=\"{0}\"]"; + var TABLEVIEW_CHOOSABLE_CLASS = "table-hover odin-unselectable"; + var TABLEVIEW_LINE_POINTED_CLASS = "odin-table-pointed-line"; + var TABLEVIEW_HEAD_COLUMN_SELECTOR = "th:nth-child({0})"; + var TABLEVIEW_BODY_COLUMN_SELECTOR = "td:nth-child({0})"; + + var tTable; + var tHead; + var tBody; + var choosable; + var formatter; + var toolbar; + + var createLine = function (idValue) { + var prevent = false; + var timer = 0; + var newLine = tmpl("tableLine"); + if (!isEmpty(idValue) && choosable) { + newLine.attr(DATA_ATTR, idValue); + newLine.click(function () { + var btn = $(this); + timer = setTimeout(function () { + if (!prevent) { + btn.toggleClass(TABLEVIEW_SELECTED_LINE_CLASS); + } + prevent = false; + if (!isEmpty(globalSelectCallback)) { + globalSelectCallback(getLines()); + } + if (!isEmpty(toolbar)) { + toolbar.setEditButtonState(!isEmpty(getLines())); + toolbar.setDeleteButtonState(!isEmpty(getLines())); + } + }, CLICK_DELAY); + }); + if (!isEmpty(editCallback)) { + newLine.dblclick(function () { + clearTimeout(timer); + prevent = true; + editCallback(idValue); + }); + } + newLine.addClass(TABLEVIEW_LINE_POINTED_CLASS); + } + return newLine; + }; + + var addLineToHead = function (line) { + tHead.append(line); + }; + + var addLineToBody = function (line) { + tBody.append(line); + }; + + var clearBody = function () { + tBody.empty(); + }; + + var initialize = function () { + if (isEmpty(odinDiv)) { + throw "Odin: Odin div is not set"; + } + odinDiv.append(tmpl("table")); + tTable = find(odinDiv, "table"); + tHead = find(odinDiv, "thead"); + tBody = find(odinDiv, "tbody"); + formatter = new OdinTableFormatter(); + }; + + var getLines = function (isAll) { + var selector = isAll ? TABLEVIEW_LINE_SELECTOR : TABLEVIEW_SELECTED_LINE_SELECTOR; + var selectedItems = []; + try { + find(tBody, selector).each(function () { + selectedItems.push({ + id: $(this).attr(DATA_ATTR), + view: $(this).attr(DATA_VIEW_ATTR) + }); + }); + } catch (e) { + // Ignore + } + return selectedItems; + }; + + var disableToolbar = function () { + if (isEmpty(toolbar)) { + return; + } + toolbar.setEditButtonState(false); + toolbar.setDeleteButtonState(false); + }; + + this.createHead = function (metaData, isOdinDto) { + if (isEmpty(metaData)) { + throw "Odin: Metadata is not set"; + } + choosable = isOdinDto; + if (choosable) { + tTable.addClass(TABLEVIEW_CHOOSABLE_CLASS); + } + var tHeadLine = createLine(); + $.each(metaData, function (index, value) { + tHeadLine.append(tmpl("tableHeadColumn", value.caption)); + }); + addLineToHead(tHeadLine); + $.each(metaData, function (columnIndex, columnParams) { + if (columnParams.visible === VisibleTypesEnum.NONE) { + var index = columnIndex + 1; + find(tHead, format(TABLEVIEW_HEAD_COLUMN_SELECTOR, [index])).hide(); + } + }); + }; + + this.fillBody = function (metaData, data) { + clearBody(); + $.each(data, function (index, value) { + var idValue = choosable ? value.id : null; + var line = createLine(idValue); + line.attr(DATA_VIEW_ATTR, value.view); + $.each(metaData, function (columnIndex, columnParams) { + var column = tmpl("tableBodyColumn"); + column.append( + formatter.formatValue(value[columnParams.fieldName], + columnParams)); + line.append(column); + }); + addLineToBody(line); + }); + if (!isEmpty(data)) { + $.each(metaData, function (columnIndex, columnParams) { + if (columnParams.visible === VisibleTypesEnum.NONE) { + var index = columnIndex + 1; + find(tBody, format(TABLEVIEW_BODY_COLUMN_SELECTOR, [index])).hide(); + } + }); + } + }; + + this.getSelectedItems = function () { + return getLines(false); + }; + + this.getItems = function () { + return getLines(true); + }; + + // Delete form view only! + this.deleteById = function (id) { + if (isEmpty(id)) { + return; + } + try { + find(tBody, format(TABLEVIEW_LINE_DATA_SELECTOR, [id])).remove(); + disableToolbar(); + } catch (e) { + // Ignore + } + }; + + this.setToolbar = function (toolbarVar) { + toolbar = toolbarVar; + }; + + this.disableToolbar = function () { + disableToolbar(); + }; + + initialize(); + } + + function OdinTableMain(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, args, disableCopy, globalSaveCallback, globalSelectCallback) { + if (isEmpty(divId) || isEmpty(listMetaDataUrl) || isEmpty(dataUrl)) { + throw "Odin: Not enough parameters"; + } + + var OFFSET_ARG = "&offset="; + var COUNT_ARG = "&count="; + + var odinDiv; + var tableView; + var paginator; + + var currentOffset; + + var listMetaData; + var elementMetaData; + + var params = isEmpty(args) ? "" : args; + + var drawBody = function (offset) { + if (isEmpty(listMetaData)) { + throw "Odin: Meta data is not defined"; + } + currentOffset = isEmpty(offset) ? 0 : parseInt(offset); + getFromRestWithParams(dataUrl, + OFFSET_ARG + (currentOffset * PAGINATOR_ITEMS_PER_PAGE) + + COUNT_ARG + PAGINATOR_ITEMS_PER_PAGE + params, function (data) { + if (isEmpty(data)) { + throw "Odin: Response data is null"; + } + tableView.fillBody(listMetaData, data.items); + if (data.count > 0) { + paginator.drawPaginator(offset, data.count); + } + tableView.disableToolbar(); + $('#preloader').hide(); + }); + }; + + var initialize = function () { + odinDiv = find($(document.body), "#" + divId); + tableView = new OdinTableView(odinDiv, editItem, globalSelectCallback); + paginator = new OdinPaginator(odinDiv, drawBody); + getFromRest(listMetaDataUrl, function (data) { + if (isEmpty(data) || isEmpty(data.fields)) { + throw "Odin: Can't load list meta data"; + } + var isOdinDto = data.odinDto; + listMetaData = data.fields; + if (isOdinDto && !isEmpty(elementMetaDataUrl)) { + getFromRest(elementMetaDataUrl, function (data) { + if (isEmpty(data) || isEmpty(data.fields)) { + throw "Odin: Can't load element meta data"; + } + elementMetaData = data.fields; + var toolbar = new OdinToolbar(odinDiv, createItem, editItem, deleteItem, globalSaveCallback); + tableView.setToolbar(toolbar); + if (!disableCopy) { + toolbar.addButton(L10nEnum.BUTTON_COPY, function () { + new OdinCopyBox(dataUrl, drawBody); + }); + } + }); + } + tableView.createHead(listMetaData, isOdinDto); + drawBody(); + }); + }; + + var updateTable = function () { + // TODO: recalculate offset before redraw table + drawBody(currentOffset); + }; + + var createItem = function () { + new OdinForm(elementMetaData, dataUrl, updateTable, null, params); + }; + + var editItem = function (value) { + if (isEmpty(elementMetaDataUrl)) { + return; + } + var curValue = value; + if (isEmpty(value)) { + var selectedItems = tableView.getSelectedItems(); + if (!isEmpty(selectedItems)) { + curValue = selectedItems[0].id; + } + } + if (isEmpty(curValue)) { + return; + } + new OdinForm(elementMetaData, dataUrl, updateTable, curValue, params); + }; + + var deleteItem = function () { + var selectedItems = tableView.getSelectedItems(); + if (isEmpty(selectedItems)) { + return; + } + var callBack = function () { + $.each(selectedItems, function (index, value) { + deleteFromRest(dataUrl + "/" + value.id, null, function () { + if (index === selectedItems.length - 1) { + updateTable(); + } + }); + }); + }; + new OdinConfirmBox(callBack, L10nEnum.QUESTION_DELETE_ITEM); + }; + + this.getSelectedItems = function () { + return tableView.getSelectedItems(); + }; + + initialize(); + } + + function OdinFormControlGenerator() { + this.createInputGroup = function () { + return tmpl("formInputGroup"); + }; + + this.createLabel = function (id, caption) { + return tmpl("formLabel", id, caption); + }; + + var createInput = function (id, value, type) { + if (isEmpty(id)) { + throw "Odin: Input id is not set"; + } + if (isEmpty(type)) { + throw "Odin: Input type is not set"; + } + var curValue = isEmpty(value) ? "" : value; + var wrapper = tmpl("formInputWrapper"); + if (type === InputTypesEnum.CHECKBOX) { + wrapper.append(tmpl("formInputCheckbox", + id, (curValue ? CHECKED_ATTR : NOT_CHECKED_ATTR))); + } else { + wrapper.append(tmpl("formInput", + id, curValue, type)); + } + return wrapper; + }; + + this.createBooleanInput = function (id, value) { + return createInput(id, value, InputTypesEnum.CHECKBOX); + }; + + var fixDateUnit = function (dateUnitValue) { + return dateUnitValue < 10 ? "0" + dateUnitValue : dateUnitValue; + }; + + var dateToStr = function (value, type) { + if (isEmpty(value)) { + return ""; + } + var date = new Date(value); + var year = fixDateUnit(date.getFullYear()); + var month = fixDateUnit(date.getMonth() + 1); + var day = fixDateUnit(date.getDate()); + var hour = fixDateUnit(date.getHours()); + var minute = fixDateUnit(date.getMinutes()); + var formattedDate; + switch (type) { + case DateTypesEnum.DATETIME: + formattedDate = year + "-" + month + "-" + day + "T" + hour + ":" + minute; + break; + case DateTypesEnum.TIME: + formattedDate = hour + ":" + minute; + break; + default: + formattedDate = year + "-" + month + "-" + day; + } + return formattedDate; + }; + + this.createDateInput = function (id, value, type) { + var dateType; + switch (type) { + case DateTypesEnum.DATETIME: + dateType = InputTypesEnum.DATETIME; + break; + case DateTypesEnum.TIME: + dateType = InputTypesEnum.TIME; + break; + default: + dateType = InputTypesEnum.DATE; + } + return createInput(id, dateToStr(value, type), dateType); + }; + + this.createNumericInput = function (id, value) { + return createInput(id, value, InputTypesEnum.NUMBER); + }; + + this.createStringInput = function (id, value, type) { + var stringType; + switch (type) { + case StringTypesEnum.PASSWORD: + stringType = InputTypesEnum.PASSWORD; + break; + case StringTypesEnum.EMAIL: + stringType = InputTypesEnum.EMAIL; + break; + default: + stringType = InputTypesEnum.TEXT; + } + return createInput(id, value, stringType); + }; + + this.dateToStr = function (value, type) { + return dateToStr(value, type); + }; + } + + function OdinFormContentGenerator(createNavTabCallBack, isCreateOperation) { + var PRECISION_CHAR = "9"; + var SCALE_PREFIX = "0."; + var SCALE_CHAR = "0"; + var SCALE_POSTFIX = "1"; + var OBJECT_INPUT_CLASS = "input-group odin-input-group"; + var CREATE_BUTTON_TEXT_SELECTOR = ".btn-success > span"; + var CHOOSE_BTN_SELECTOR = ".odin-choose-btn"; + var CLEAR_BTN_SELECTOR = ".odin-clear-btn"; + var CLEARABLE_INPUT_SELECTOR = ".form-control"; + + var controlGenerator = new OdinFormControlGenerator(); + + var createBooleanControl = function (id, caption, value) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(controlGenerator.createBooleanInput(id, value)); + return group; + }; + + var createDateControl = function (id, caption, value, type) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(controlGenerator.createDateInput(id, value, type)); + return group; + }; + + var createNumericControl = function (id, caption, value, positiveOnly, precision, scale) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + var control = controlGenerator.createNumericInput(id, value); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + if (positiveOnly) { + currentControl.attr(NumberAttrEnum.MIN, 0); + } + if (precision) { + currentControl + .attr(NumberAttrEnum.MAX, Array(precision + 1).join(PRECISION_CHAR)); + } + if (scale) { + var step = SCALE_PREFIX + Array(scale).join(SCALE_CHAR) + SCALE_POSTFIX; + currentControl.attr(NumberAttrEnum.STEP, step); + } + }); + group.append(control); + return group; + }; + + var createStringControl = function (id, caption, value, minLength, maxLength, type) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + var control = controlGenerator.createStringInput(id, value, type); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + currentControl.attr(StringAttrEnum.MIN, minLength); + currentControl.attr(StringAttrEnum.MAX, maxLength); + }); + group.append(control); + return group; + }; + + var createCollectionControl = function (id, caption, values, path) { + // TODO: add required check if needed + var content = $(tmpl("emptyDiv")); + var tabView = new OdinTableView(content, null, null); + var createAction = function () { + new OdinChooseBox(path, function (selectedItems) { + if (isEmpty(selectedItems)) { + return; + } + var currentValues = tabView.getItems(); + $.merge(currentValues, selectedItems); + var existingIDs = []; + currentValues = $.grep(currentValues, function (v) { + if ($.inArray(v.id, existingIDs) !== -1) { + return false; + } else { + existingIDs.push(v.id); + return true; + } + }); + tabView.fillBody(metaData, currentValues); + }); + }; + var deleteAction = function () { + var callBack = function () { + $.each(tabView.getSelectedItems(), function (index, value) { + tabView.deleteById(value.id); + }); + }; + new OdinConfirmBox(callBack, L10nEnum.QUESTION_DELETE_ITEM); + }; + content.append(tabView); + var metaData = [ + { + "fieldType": "string", + "fieldName": "id", + "caption": "id", + "visible": "none" + }, + { + "fieldType": "string", + "fieldName": "view", + "caption": L10nEnum.VIEW, + "visible": "all" + } + ]; + tabView.createHead(metaData, true); + tabView.fillBody(metaData, values); + createNavTabCallBack(id, caption, content); + var toolbar = new OdinToolbar(content, createAction, null, deleteAction); + tabView.setToolbar(toolbar); + find(content, CREATE_BUTTON_TEXT_SELECTOR).text(L10nEnum.BUTTON_ADD); + return null; + }; + + var createObjectControl = function (id, caption, value, path) { + // TODO: add required check if needed + var chooseInput = controlGenerator.createStringInput( + id, isEmpty(value) ? "" : value.view, StringTypesEnum.TEXT); + chooseInput.addClass(OBJECT_INPUT_CLASS); + var buttonGroup = tmpl("buttonGroup"); + chooseInput.append(buttonGroup); + var chooseButton = tmpl("chooseButton"); + buttonGroup.append(chooseButton); + find(buttonGroup, CHOOSE_BTN_SELECTOR).click(function () { + new OdinChooseBox(path, function (selectedItems) { + if (isEmpty(selectedItems)) { + return; + } + find(chooseInput, CLEARABLE_INPUT_SELECTOR).each(function () { + $(this) + .val(selectedItems[0].view) + .attr(DATA_ATTR, selectedItems[0].id); + }); + }); + }); + var clearButton = tmpl("clearButton"); + buttonGroup.append(clearButton); + find(buttonGroup, CLEAR_BTN_SELECTOR).click(function () { + find(chooseInput, CLEARABLE_INPUT_SELECTOR).val(""); + }); + find(chooseInput, CLEARABLE_INPUT_SELECTOR).each(function () { + $(this) + .attr(DISABLED_ATTR, true) + .attr(DATA_ATTR, isEmpty(value) ? "" : value.id); + }); + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(chooseInput); + return group; + }; + + var configureControl = function (control, hidden, readonly, notempty) { + if (isEmpty(control)) { + return; + } + find(control, LABELS_SELECTOR).each(function () { + var currentControl = $(this); + if (hidden) { + currentControl.hide(); + } + }); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + if (hidden) { + currentControl.hide(); + currentControl.attr(TYPE_ATTR, InputTypesEnum.HIDDEN); + } + if (readonly) { + currentControl.attr(DISABLED_ATTR, true); + } + if (notempty) { + currentControl.attr(REQUIRED_ATTR, true); + } + }); + return control; + }; + + this.createFormControl = function (value, fieldParams) { + if (isEmpty(fieldParams)) { + return null; + } + var control; + switch (fieldParams.fieldType) { + case FieldTypesEnum.BOOLEAN: + control = createBooleanControl(fieldParams.fieldName, + fieldParams.caption, value); + break; + case FieldTypesEnum.DATE: + control = createDateControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.type); + break; + case FieldTypesEnum.NUMERIC: + control = createNumericControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.positiveOnly, + fieldParams.precision, fieldParams.scale); + break; + case FieldTypesEnum.STRING: + control = createStringControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.minLength, + fieldParams.maxLength, fieldParams.type); + break; + case FieldTypesEnum.COLLECTION: + control = createCollectionControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.path); + break; + case FieldTypesEnum.OBJECT: + control = createObjectControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.path); + break; + default: + return null; + } + var hidden; + switch (fieldParams.visible) { + case VisibleTypesEnum.NONE: + hidden = true; + break; + case VisibleTypesEnum.ON_CREATE: + case VisibleTypesEnum.ON_UPDATE: + hidden = isCreateOperation; + break; + default: + hidden = false; + } + return configureControl(control, hidden, fieldParams.readOnly, fieldParams.notEmpty); + }; + + this.getCurrentDateStr = function () { + return controlGenerator.dateToStr(new Date(), DateTypesEnum.DATE); + }; + } + + function OdinForm(metaData, dataUrl, tableCallBack, id, args) { + if (isEmpty(metaData) || isEmpty(dataUrl) || isEmpty(tableCallBack)) { + throw "Odin: Not enough parameters"; + } + + var DEFAULT_TAB_SELECTOR = "odin-values"; + var TABS_HEADER_SELECTOR = ".nav-tabs"; + var TABS_CONTENT_SELECTOR = ".tab-content"; + var TAB_SELECTOR = ".tab-pane"; + var COLLECTION_ITEM_SELECTOR = "tbody > tr"; + + OdinFormCommon.call(this, FormTypesEnum.FORM); + + var self = this; + + var generator; + + var params = isEmpty(args) ? "" : args; + + var isCreateForm = function () { + return isEmpty(id); + }; + + var formToJson = function () { + var formData = {}; + try { + findFiltered(self._getContent(), TAB_SELECTOR, "#" + DEFAULT_TAB_SELECTOR) + .each(function () { + var tabData = []; + try { + find($(this), COLLECTION_ITEM_SELECTOR) + .each(function () { + if ($(this).attr(DATA_ATTR)) { + tabData.push({ + id: $(this).attr(DATA_ATTR), + view: $(this).attr(DATA_VIEW_ATTR) + }); + return true; + } + }); + } catch (e) { + // Ignore + } + formData[$(this).attr(ID_ATTR)] = tabData; + }); + } catch (e) { + // Ignore + } + findFiltered(self._getContent(), INPUTS_SELECTOR, BUTTONS_SELECTOR) + .each(function () { + if (isEmpty($(this).val())) { + return true; + } + if ($(this).attr(DATA_ATTR)) { + formData[$(this).attr(ID_ATTR)] = { + id: $(this).attr(DATA_ATTR), + view: $(this).val() + }; + return true; + } + switch ($(this).attr(TYPE_ATTR)) { + case InputTypesEnum.CHECKBOX: + formData[$(this).attr(ID_ATTR)] = $(this).is(":" + CHECKED_ATTR); + break; + case InputTypesEnum.DATE: + case InputTypesEnum.DATETIME: + formData[$(this).attr(ID_ATTR)] = isEmpty($(this).val) ? + "" : Date.parse($(this).val()); + break; + case InputTypesEnum.TIME: + formData[$(this).attr(ID_ATTR)] = isEmpty($(this).val) ? + "" : Date.parse(generator.getCurrentDateStr() + + "T" + $(this).val()); + break; + case InputTypesEnum.NUMBER: + formData[$(this).attr(ID_ATTR)] = parseFloat($(this).val()); + break; + default: + formData[$(this).attr(ID_ATTR)] = $(this).val(); + } + }); + return JSON.stringify(formData); + }; + + var initialize = function () { + generator = new OdinFormContentGenerator(createNavTab, isCreateForm()); + self._getContent() + .append(tmpl("formTabs", DEFAULT_TAB_SELECTOR, L10nEnum.CAPTION_DEFAULT_TAB)); + var okBtn = tmpl("okButton", L10nEnum.BUTTON_SAVE); + okBtn.attr(TYPE_ATTR, InputTypesEnum.SUBMIT); + var closeBtn = tmpl("cancelButton", L10nEnum.BUTTON_CLOSE); + self._getFooter().append(closeBtn); + self._getFooter().append(okBtn); + if (isCreateForm()) { + configureAndShowForm(); + } else { + getFromRest(dataUrl + "/" + id, function (data) { + if (isEmpty(data)) { + throw format("Odin: Can't load data by id {0} and url {1}", [id, dataUrl]); + } + configureAndShowForm(data); + }); + } + closeBtn.click(function () { + closeForm(); + }); + }; + + var configureAndShowForm = function (data) { + var saveCallBack = function () { + saveData(formToJson()); + }; + self._setCaption(isEmpty(data) ? + L10nEnum.CAPTION_CREATE_FORM : + data.view + " " + L10nEnum.CAPTION_EDIT_FORM); + self._setFormSubmit(saveCallBack); + $.each(metaData, function (fieldIndex, fieldParams) { + var value = isEmpty(data) ? + null : data[fieldParams.fieldName]; + find(self._getContent(), "#" + DEFAULT_TAB_SELECTOR) + .append(generator.createFormControl(value, fieldParams)); + }); + self._show(); + }; + + var createNavTab = function (id, caption, content) { + find(self._getContent(), TABS_HEADER_SELECTOR).append(tmpl("formTab", id, caption)); + var tab = tmpl("formTabContent", id); + tab.append(content); + find(self._getContent(), TABS_CONTENT_SELECTOR).append(tab); + }; + + var saveData = function (data) { + var saveCallback = function (responseData) { + if (isEmpty(responseData)) { + throw "Odin: Error on data save"; + } + self._hide(); + tableCallBack(); + if (!isEmpty(globalSaveCallback)) { + globalSaveCallback(); + } + }; + if (isCreateForm()) { + postToRestWithParams(dataUrl, data, params, saveCallback); + } else { + putToRestWithParams(dataUrl, data, params, saveCallback); + } + }; + + var closeForm = function () { + if (self._isModified()) { + new OdinConfirmBox(self._hide, L10nEnum.QUESTION_LOST_CHANGES); + return; + } + self._hide(); + }; + + initialize(); + } + + function OdinConfirmBox(callBack, questionText) { + if (isEmpty(callBack) || isEmpty(questionText)) { + throw "Odin: Not enough parameters"; + } + + OdinFormCommon.call(this, FormTypesEnum.CONFIRM); + + var self = this; + + var initialize = function () { + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._setCaption(L10nEnum.QUESTION_DEFAULT_CAPTION); + self._getContent().text(questionText); + self._getFooter().append(yesBtn); + self._getFooter().append(cancelBtn); + self._show(); + + yesBtn.click(function () { + self._hide(); + callBack(); + if (!isEmpty(globalSaveCallback)) { + globalSaveCallback(); + } + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } + + function OdinChooseBox(path, callBack) { + if (isEmpty(path) || isEmpty(callBack)) { + throw "Odin: Not enough parameters"; + } + + var ADDITIONAL_FORM_CLASS = "odin-table-box"; + var ADDITIONAL_CONTENT_CLASS = "odin-kill-padding"; + var CONTENT_ID_VALUE = "table-block"; + + OdinFormCommon.call(this); + + var self = this; + + var initialize = function () { + self._addFormClass(ADDITIONAL_FORM_CLASS); + self._getContent().addClass(ADDITIONAL_CONTENT_CLASS); + self._getContent().attr(ID_ATTR, CONTENT_ID_VALUE); + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._getFooter().append(cancelBtn); + self._getFooter().append(yesBtn); + var table = new OdinTableMain("table-block", path + odinMetaList, null, path); + self._show(); + + yesBtn.click(function () { + self._hide(); + callBack(table.getSelectedItems()); + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } + + function OdinCopyBox(dataUrl, tableRedrawCallback) { + if (isEmpty(dataUrl) || isEmpty(tableRedrawCallback)) { + throw "Odin: Not enough parameters"; + } + + var ADDITIONAL_FORM_CLASS = "odin-simple-form"; + var FROM_VERSION_SELECTOR = "#fromVersion"; + var VERSION_SELECTOR = "#version"; + + OdinFormCommon.call(this); + + var self = this; + + var initialize = function () { + self._setCaption(L10nEnum.CAPTION_COPY_FORM); + self._addFormClass(ADDITIONAL_FORM_CLASS); + self._getContent().append(tmpl("copyForm")); + var from = find(self._getContent(), FROM_VERSION_SELECTOR); + var to = find(self._getContent(), VERSION_SELECTOR); + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._getFooter().append(cancelBtn); + self._getFooter().append(yesBtn); + + /*getFromRest(urlVersions, function (versions) { + if (isEmpty(versions) || versions.count === 0) { + return; + } + var versionsData = []; + versions.items.forEach(function (version) { + versionsData.push({ + id: version.id, + name: version.name + " от " + + new Date(version.date).toLocaleDateString(uiLocale) + }); + }); + fillSelect(from, versionsData.sort(function (a, b) { + return a.id - b.id; + })); + var currentVersion = getCurrentVersionData(); + if (!isEmpty(currentVersion)) { + to.attr(DATA_ATTR, currentVersion.id); + to.val(currentVersion.name); + } + self._show(); + });*/ + + yesBtn.click(function () { + if (isEmpty(from.val()) || isEmpty(to.val())) { + return; + } + if (from.val() === to.attr(DATA_ATTR)) { + showFeedbackMessage(L10nEnum.ERROR_VERSIONS_MATCH, MessageTypesEnum.DANGER); + return; + } + postToRestWithVersionAndParams(dataUrl + odinCopy, null, + "&fromVersionId=" + from.val(), function () { + self._hide(); + tableRedrawCallback(); + }); + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } +} diff --git a/src/main/resources/public/js/tasks.js b/src/main/resources/public/js/tasks.js new file mode 100644 index 0000000..6ee3537 --- /dev/null +++ b/src/main/resources/public/js/tasks.js @@ -0,0 +1,113 @@ +/*") + .attr("id", 'tags' + tagNumber) + .addClass("tag"); + // контейнер id + var idInput = $("") + .attr("type", "hidden") + .attr("id", "tags" + tagNumber + ".id") + .attr("name", "tags[" + tagNumber + "].id") + .attr("value", ''); + // контейнер текста + var conDiv = $("
") + .addClass("tag-name"); + // текст тега + var nameInput = $("") + .attr("type", "text") + .attr("id", "tags" + tagNumber + ".tagName") + .attr("name", "tags[" + tagNumber + "].tagName") + .attr("value", tagName) + .attr("readonly", "true") + .attr("size", tagName.length); + // кнопка удалить тег + var removeSpan = $("") + .attr("data-role", "remove") + .bind("click", removeTag); + + conDiv.append(nameInput); + conDiv.append(removeSpan); + newTagRow.append(idInput); + newTagRow.append(conDiv); + $(this).before(newTagRow); + } + + $(this).val(''); + } + }); + + $("span[data-role=remove]").click(removeTag); + $(".task-row").mouseenter(function (event) { + var taskRow = $(event.target).closest(".task-row"); + $(taskRow).css("background-color", "#f8f9fa"); + $(taskRow).find(".remove-task").removeClass("d-none"); + + }); + $(".task-row").mouseleave(function (event) { + var taskRow = $(event.target).closest(".task-row"); + $(taskRow).css("background-color", "white"); + $(taskRow).closest(".task-row").find(".remove-task").addClass("d-none"); + }); + + $('a[data-confirm]').click(function(ev) { + var href = $(this).attr('href'); + if (!$('#dataConfirmModal').length) { + $('#modalDelete').append(''); + } + $('#dataConfirmModal').find('#myModalLabel').text($(this).attr('data-confirm')); + $('#dataConfirmOK').attr('href', href); + $('#dataConfirmModal').modal({show:true}); + return false; + }); + +}); +/*]]>*/ \ No newline at end of file diff --git a/src/main/resources/public/templates/odin.html b/src/main/resources/public/templates/odin.html new file mode 100644 index 0000000..1f9f9d9 --- /dev/null +++ b/src/main/resources/public/templates/odin.html @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/activate.html b/src/main/resources/templates/activate.html index b20a284..d06cdc8 100644 --- a/src/main/resources/templates/activate.html +++ b/src/main/resources/templates/activate.html @@ -2,7 +2,7 @@ + layout:decorate="~{default}"> diff --git a/src/main/resources/templates/admin/commits.html b/src/main/resources/templates/admin/commits.html index a8b8e85..2f3f58f 100644 --- a/src/main/resources/templates/admin/commits.html +++ b/src/main/resources/templates/admin/commits.html @@ -2,14 +2,18 @@ + layout:decorate="~{default}">
-
+
+
+
-
+
+
+
+
+ + + diff --git a/src/main/resources/templates/conferences/dashboard.html b/src/main/resources/templates/conferences/dashboard.html new file mode 100644 index 0000000..cb4f2c0 --- /dev/null +++ b/src/main/resources/templates/conferences/dashboard.html @@ -0,0 +1,28 @@ + + + + + + +
+
+
+
+
+

Актуальные конференции

+
+
+
+
+
+ +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/conferences/fragments/confDashboardFragment.html b/src/main/resources/templates/conferences/fragments/confDashboardFragment.html new file mode 100644 index 0000000..17f4aa6 --- /dev/null +++ b/src/main/resources/templates/conferences/fragments/confDashboardFragment.html @@ -0,0 +1,27 @@ + + + + + + +
+
+
+ +
+
+

+

+ + title + +
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/conferences/fragments/confLineFragment.html b/src/main/resources/templates/conferences/fragments/confLineFragment.html new file mode 100644 index 0000000..7d948d4 --- /dev/null +++ b/src/main/resources/templates/conferences/fragments/confLineFragment.html @@ -0,0 +1,23 @@ + + + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/conferences/fragments/confNavigationFragment.html b/src/main/resources/templates/conferences/fragments/confNavigationFragment.html new file mode 100644 index 0000000..7e67365 --- /dev/null +++ b/src/main/resources/templates/conferences/fragments/confNavigationFragment.html @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html index 5bb5c14..00e2c0f 100644 --- a/src/main/resources/templates/default.html +++ b/src/main/resources/templates/default.html @@ -1,6 +1,6 @@ + xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"> @@ -8,7 +8,7 @@ - NG-Tacker + NG-Tracker @@ -23,6 +23,7 @@ + @@ -53,11 +54,14 @@ + @@ -91,6 +95,10 @@ + + + + + diff --git a/src/main/resources/templates/grants/grants.html b/src/main/resources/templates/grants/grants.html index 83083a0..bc31ccf 100644 --- a/src/main/resources/templates/grants/grants.html +++ b/src/main/resources/templates/grants/grants.html @@ -1,7 +1,7 @@ + layout:decorate="~{default}" xmlns:th=""> @@ -24,6 +24,7 @@
+
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 0f83f3f..c2adbe2 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,7 +1,7 @@ + layout:decorate="~{default}"> @@ -10,12 +10,6 @@
- - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/papers/dashboard.html b/src/main/resources/templates/papers/dashboard.html index b7849f7..d02f1be 100644 --- a/src/main/resources/templates/papers/dashboard.html +++ b/src/main/resources/templates/papers/dashboard.html @@ -1,7 +1,7 @@ + layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml"> @@ -18,6 +18,7 @@
+
diff --git a/src/main/resources/templates/papers/fragments/paperDashboardFragment.html b/src/main/resources/templates/papers/fragments/paperDashboardFragment.html index 122696e..d0ec0c5 100644 --- a/src/main/resources/templates/papers/fragments/paperDashboardFragment.html +++ b/src/main/resources/templates/papers/fragments/paperDashboardFragment.html @@ -10,7 +10,13 @@
- title +

+

+ + title +

authors

diff --git a/src/main/resources/templates/papers/fragments/paperFilesListFragment.html b/src/main/resources/templates/papers/fragments/paperFilesListFragment.html new file mode 100644 index 0000000..b4de456 --- /dev/null +++ b/src/main/resources/templates/papers/fragments/paperFilesListFragment.html @@ -0,0 +1,46 @@ + + + + + + + +
+ + + + +
+ + + + + +
+ + + +
+
+ + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/papers/paper.html b/src/main/resources/templates/papers/paper.html index a5ff6b5..d4683c1 100644 --- a/src/main/resources/templates/papers/paper.html +++ b/src/main/resources/templates/papers/paper.html @@ -1,9 +1,9 @@ + layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html"> - + @@ -20,34 +20,62 @@
-
- -
- - -

Incorrect title

-

-
+ + -
- - -
-
- -
+
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ + +
+
+
+ +
+
+
+ + + +
+ + diff --git a/src/main/resources/templates/projects/projects.html b/src/main/resources/templates/projects/projects.html new file mode 100644 index 0000000..fd19383 --- /dev/null +++ b/src/main/resources/templates/projects/projects.html @@ -0,0 +1,33 @@ + + + + + +
+
+ +
+
+
+
+

Проекты

+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/reset.html b/src/main/resources/templates/reset.html index 232e260..c2dd78f 100644 --- a/src/main/resources/templates/reset.html +++ b/src/main/resources/templates/reset.html @@ -2,7 +2,7 @@ + layout:decorate="~{default}"> diff --git a/src/main/resources/templates/resetRequest.html b/src/main/resources/templates/resetRequest.html index 2adab37..ef960f4 100644 --- a/src/main/resources/templates/resetRequest.html +++ b/src/main/resources/templates/resetRequest.html @@ -2,7 +2,7 @@ + layout:decorate="~{default}"> diff --git a/src/main/resources/templates/students/dashboard.html b/src/main/resources/templates/students/dashboard.html new file mode 100644 index 0000000..c0c2f0e --- /dev/null +++ b/src/main/resources/templates/students/dashboard.html @@ -0,0 +1,26 @@ + + + + + +
+
+
+
+

Работа со студентами

+
+
+
+
+
+ + + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/students/fragments/taskDashboardFragment.html b/src/main/resources/templates/students/fragments/taskDashboardFragment.html new file mode 100644 index 0000000..3779ef8 --- /dev/null +++ b/src/main/resources/templates/students/fragments/taskDashboardFragment.html @@ -0,0 +1,24 @@ + + + + + + +
+
+
+ + +
+
+ title +

status

+ + +
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/students/fragments/taskLineFragment.html b/src/main/resources/templates/students/fragments/taskLineFragment.html new file mode 100644 index 0000000..baad17e --- /dev/null +++ b/src/main/resources/templates/students/fragments/taskLineFragment.html @@ -0,0 +1,22 @@ + + + + + + +
+
+ + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/students/fragments/taskNavigationFragment.html b/src/main/resources/templates/students/fragments/taskNavigationFragment.html new file mode 100644 index 0000000..cf32140 --- /dev/null +++ b/src/main/resources/templates/students/fragments/taskNavigationFragment.html @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/students/fragments/taskStatusFragment.html b/src/main/resources/templates/students/fragments/taskStatusFragment.html new file mode 100644 index 0000000..77fd529 --- /dev/null +++ b/src/main/resources/templates/students/fragments/taskStatusFragment.html @@ -0,0 +1,28 @@ + + + + + + + + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/students/task.html b/src/main/resources/templates/students/task.html new file mode 100644 index 0000000..968c7a0 --- /dev/null +++ b/src/main/resources/templates/students/task.html @@ -0,0 +1,146 @@ + + + + + + + + +
+ +
+ +
+
+
+

Редактирование задачи

+
+
+
+
+
+
+
+
+
+ +
+ + +

Incorrect title

+

+
+ +
+ + +
+
+ + +
+ +
+ +
+
+ +
+ + +
+
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+

Incorrect title

+
+
+ +
+
+
+ + + Отмена + +
+
+
+
+
+
+
Дата создания:
+
+
+ + text + +
+
+
+
+
+
+
Дата изменения:
+
+
+ + text + +
+
+
+
+
+ +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/students/tasks.html b/src/main/resources/templates/students/tasks.html new file mode 100644 index 0000000..b1177d8 --- /dev/null +++ b/src/main/resources/templates/students/tasks.html @@ -0,0 +1,55 @@ + + + + + + + +
+
+ +
+
+
+
+

Работа со студентами

+
+
+
+
+
+
+ +
+ +
+
+
+
Фильтр:
+ + +
+
+
+
+
+ +
+ + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/timeline.html b/src/main/resources/templates/timeline.html index 5eda9f3..0fdaddd 100644 --- a/src/main/resources/templates/timeline.html +++ b/src/main/resources/templates/timeline.html @@ -1,7 +1,7 @@ + layout:decorate="~{default}"> diff --git a/src/test/java/IndexPageTest.java b/src/test/java/IndexPageTest.java new file mode 100644 index 0000000..1817240 --- /dev/null +++ b/src/test/java/IndexPageTest.java @@ -0,0 +1,52 @@ +import com.google.common.collect.ImmutableMap; +import core.PageObject; +import core.TestTemplate; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import paper.PaperPage; +import paper.PapersDashboardPage; +import paper.PapersPage; +import ru.ulstu.NgTrackerApplication; +import ru.ulstu.configuration.ApplicationProperties; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NgTrackerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class IndexPageTest extends TestTemplate { + private final Map> navigationHolder = ImmutableMap.of( + new PapersPage(), Arrays.asList("СТАТЬИ", "/papers/papers"), + new PaperPage(), Arrays.asList("РЕДАКТИРОВАНИЕ СТАТЬИ", "/papers/paper?id=0"), + new PapersDashboardPage(), Arrays.asList("СТАТЬИ", "/papers/dashboard") + ); + + @Autowired + private ApplicationProperties applicationProperties; + + @Test + public void testStartApplication() { + getContext().goTo(applicationProperties.getBaseUrl()); + Assertions + .assertThat(getContext().getTitle()) + .isEqualTo("NG-Tracker"); + } + + @Test + public void testModulesNavigation() { + navigationHolder.entrySet() + .stream() + .forEach(navigationEntry -> { + getContext().goTo(applicationProperties.getBaseUrl() + navigationEntry.getValue().get(1)); + PageObject pageObject = getContext().initPage(navigationEntry.getKey()); + Assertions + .assertThat(pageObject.getSubTitle()) + .isEqualToIgnoringCase(navigationEntry.getValue().get(0)); + }); + } +} diff --git a/src/test/java/context/ChromeContext.java b/src/test/java/context/ChromeContext.java new file mode 100644 index 0000000..899d51d --- /dev/null +++ b/src/test/java/context/ChromeContext.java @@ -0,0 +1,27 @@ +package context; + +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; + +public class ChromeContext extends Context { + private final static String WINDOWS_DRIVER = "chromedriver.exe"; + private final static String LINUX_DRIVER = "chromedriver"; + private final static String DRIVER_TYPE = "webdriver.chrome.driver"; + + @Override + protected void createDriver() { + final ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.addArguments("--headless"); + driver = new ChromeDriver(chromeOptions); + } + + @Override + protected String getDriverExecutable(boolean isWindows) { + return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER; + } + + @Override + protected String getDriverType() { + return DRIVER_TYPE; + } +} diff --git a/src/test/java/context/Context.java b/src/test/java/context/Context.java new file mode 100644 index 0000000..b32c08e --- /dev/null +++ b/src/test/java/context/Context.java @@ -0,0 +1,67 @@ +package context; + +import core.PageObject; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebDriver; + +import java.util.concurrent.TimeUnit; + +//import org.openqa.selenium.support.PageFactory; + +public abstract class Context { + private final static String DRIVER_LOCATION = "drivers/%s"; + + protected WebDriver driver; + + protected WebDriver getDriver() { + if (driver != null) { + return driver; + } + throw new IllegalStateException("WebDriver is not initialized"); + } + + public void start() { + System.setProperty(getDriverType(), getDriverExecutablePath()); + + createDriver(); + // это плохая инструкция для автотестов, т.к. лучше задавать для конкретного элемента или кейса + getDriver().manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); + } + + public void close() { + if (driver != null) { + driver.quit(); + } + } + + public void goTo(String url) { + getDriver().get(url); + } + + public String getTitle() { + return getDriver().getTitle(); + } + + public T initPage(T pageObject) { + return (T) pageObject.setDriver(getDriver()); + } + + protected abstract void createDriver(); + + protected abstract String getDriverType(); + + protected abstract String getDriverExecutable(boolean windows); + + private String getDriverExecutablePath() { + return Context.class.getClassLoader().getResource( + String.format(DRIVER_LOCATION, getDriverExecutable(isWindows()))).getFile(); + } + + private boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("windows"); + } + + public void setSize(Dimension dimension) { + driver.manage().window().setSize(dimension); + } +} diff --git a/src/test/java/context/FirefoxContext.java b/src/test/java/context/FirefoxContext.java new file mode 100644 index 0000000..1d5c163 --- /dev/null +++ b/src/test/java/context/FirefoxContext.java @@ -0,0 +1,24 @@ +package context; + +import org.openqa.selenium.firefox.FirefoxDriver; + +public class FirefoxContext extends Context { + private final static String WINDOWS_DRIVER = "geckodriver.exe"; + private final static String LINUX_DRIVER = "geckodriver"; + private final static String DRIVER_TYPE = "webdriver.gecko.driver"; + + @Override + protected void createDriver() { + driver = new FirefoxDriver(); + } + + @Override + protected String getDriverExecutable(boolean isWindows) { + return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER; + } + + @Override + protected String getDriverType() { + return DRIVER_TYPE; + } +} diff --git a/src/test/java/core/PageObject.java b/src/test/java/core/PageObject.java new file mode 100644 index 0000000..d1fae83 --- /dev/null +++ b/src/test/java/core/PageObject.java @@ -0,0 +1,14 @@ +package core; + +import org.openqa.selenium.WebDriver; + +public abstract class PageObject { + protected WebDriver driver; + + public abstract String getSubTitle(); + + public PageObject setDriver(WebDriver driver) { + this.driver = driver; + return this; + } +} diff --git a/src/test/java/core/TestTemplate.java b/src/test/java/core/TestTemplate.java new file mode 100644 index 0000000..a72a8c9 --- /dev/null +++ b/src/test/java/core/TestTemplate.java @@ -0,0 +1,29 @@ +package core; + +import context.ChromeContext; +import context.Context; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openqa.selenium.Dimension; + +public abstract class TestTemplate { + private static Context context; + + public static Context getContext() { + return context; + } + + @BeforeClass + public static void setup() { + context = new ChromeContext(); + context.start(); + context.setSize(new Dimension(1600, 900)); + } + + @AfterClass + public static void quit() { + if (context != null) { + context.close(); + } + } +} diff --git a/src/test/java/paper/PaperPage.java b/src/test/java/paper/PaperPage.java new file mode 100644 index 0000000..9c3c357 --- /dev/null +++ b/src/test/java/paper/PaperPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PaperPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +} diff --git a/src/test/java/paper/PapersDashboardPage.java b/src/test/java/paper/PapersDashboardPage.java new file mode 100644 index 0000000..51d7cb8 --- /dev/null +++ b/src/test/java/paper/PapersDashboardPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PapersDashboardPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +} diff --git a/src/test/java/paper/PapersPage.java b/src/test/java/paper/PapersPage.java new file mode 100644 index 0000000..f191d9b --- /dev/null +++ b/src/test/java/paper/PapersPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PapersPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +}