diff --git a/.gitignore b/.gitignore index 6ca05c4a5184..a687d4b70ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ out target *.iml log +patch diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/README.md b/README.md new file mode 100644 index 000000000000..4183a09d949b --- /dev/null +++ b/README.md @@ -0,0 +1,198 @@ +Java Enterprise Online Project +=============================== +Разработка полнофункционального Spring/JPA Enterprise приложения c авторизацией и правами доступа на основе ролей с использованием наиболее популярных инструментов и технологий Java: Maven, Spring MVC, Security, JPA(Hibernate), REST(Jackson), Bootstrap (css,js), datatables, jQuery + plugins, Java 8 Stream and Time API и хранением в базах данных Postgresql и HSQLDB. + + + + Когда вы слышите что-то, вы забываете это. + Когда вы видите что-то, вы запоминаете это. + Но только когда вы начинаете делать это, + вы начинаете понимать это + + Старинная китайская поговорка + +## Описание и план проекта +### Демо разрабатываемого приложения +### [Изменения проекта (Release Notes)](ReleaseNotes.md) +### Требования к участникам, Wiki +### Составление резюме, подготовка к интервью, поиск работы + +Вводное занятие (обязательно смотреть все видео) +=============== +##  1. Осваиваем Java Enterprise. Трудоустройство. Ответы на вопросы. +- Слайды презентации +- Java Tools and Technologies Landscape Report 2016 +- [Java in 2017 Survey](http://www.baeldung.com/java-in-2017) +- Из юниоров в разработчики: получаем первую работу + +#### Spring Pet-Clinic +- Spring PetClinic Sample Application +- Presentation + +##  2. Системы управления версиями. Git. +- **Wiki по ведению проекта в Git** +- Система управления версиями. VCS/DVSC. +- Ресурсы: + - Интерактивная Git обучалка + - Еще одна интерактивная обучалка, по-русски + - Книга Git + - Working with remote repositories + - Видео по обучению Git + - Git Overview + - [Основы Git за 20 минут](https://www.youtube.com/watch?v=TMeZGvtQnT8) + - [Git - для новичков](https://www.youtube.com/watch?list=PLY4rE9dstrJyTdVJpv7FibSaXB4BHPInb&v=PEKN8NtBDQ0) + +##  3. Работа с проектом (выполнять инструкции) +**ВНИМАНИЕ: выбирайте для проекта простой пусть без пробелов и русских букв, например (Windows) `c:\projects\topjava\`. Иначе впоследствии будут проблемы** +> Проект постоянно улучшается, поэтому видео иногда отличается от кода проекта. Изменения указываю после видео: в `UserMeals/UserMealWithExceed` поля изменились на `private` +- **Prepare_ to_ HW0.patch (скачать и положить в каталог вашего проекта)** + +## Инструкция по шагам (из видео): +- Установить ПО (git, JDK8, IntelliJ IDEA, Maven) +- Создать аккаунт на GitHub +- Сделать Fork **ЭТОГО** проекта (https://github.com/JavaOPs/topjava) +- Сделать локальный репозиторий проекта: +
git clone https://github.com/[Ваш аккаунт]/topjava.git+- Открыть и настроить проект в IDEA + - Выставить кодировку UTF-8 в консоли + - Поставить кодировку UTF-8 + - Поменять фонт по умолчанию (DejaVu) +- По ходу видео сделать Apply Patch... скаченного патча Prepare_ to_ HW0.patch +- Закоммитить и запушить изменения (commit + push) +- Сделать ветку домашнего задания +- Выполнить задание и залить на GitHub (commit + push) +- Переключиться в основную ветку проекта master. + +##  Домашнее задание HW0 +``` +Реализовать метод UserMealsUtil.getFilteredWithExceeded: +- должны возвращаться только записи между startTime и endTime +- поле UserMealWithExceed.exceed должно показывать, + превышает ли сумма калорий за весь день параметра метода caloriesPerDay + +Т.е UserMealWithExceed - это запись одной еды, но поле exceeded будет одинаково для всех записей за этот день. + +- Проверьте результат выполнения ДЗ (можно проверить логику в http://topjava.herokuapp.com , список еды) +- Оцените Time complexity вашего алгоритма, если он O(N*N)- попробуйте сделать O(N). +``` +- Java 8 Date and Time API +- Алгоритмы и структуры данных для начинающих: сложность алгоритмов +- Time complexity +- Временная сложность алгоритма +- Вычислительная сложность + +#### Optional (Java 8 Stream API) +``` +Сделать реализацию через Java 8 Stream API. +``` +- Видео: Доступно о Java 8 Lambda +- Java 8: Lambda выражения +- Java 8: Потоки +- Pуководство по Java 8 Stream +- [7 способов использовать groupingBy в Stream API](https://habrahabr.ru/post/348536) +- Лямбда-выражения в Java 8 +- A Guide to Java 8 +- Шпаргалка Java Stream API +- Алексея Владыкин: Элементы функционального программирования в Java +- Yakov Fain о новом в Java 8 +- stream.map vs forEach +- Дополнительно + - [Сергей Куксенко — Stream API, часть 1](https://www.youtube.com/watch?v=O8oN4KSZEXE) + - [Сергей Куксенко — Stream API, часть 2](https://www.youtube.com/watch?v=i0Jr2l3jrDA) + +#### Optional 2 (+5 бонусов) +``` +Сделать реализацию со сложностью O(N): +- циклом за 1 проход по List
+ * Handling Hibernate lazy-loading
+ *
+ * @link https://github.com/FasterXML/jackson
+ * @link https://github.com/FasterXML/jackson-datatype-hibernate
+ * @link https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers
+ */
+public class JacksonObjectMapper extends ObjectMapper {
+
+ private static final ObjectMapper MAPPER = new JacksonObjectMapper();
+
+ private JacksonObjectMapper() {
+ registerModule(new Hibernate5Module());
+
+ registerModule(new JavaTimeModule());
+ configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+
+ setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
+ setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+ setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ }
+
+ public static ObjectMapper getMapper() {
+ return MAPPER;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java
new file mode 100644
index 000000000000..f230bf1d9a87
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/json/JsonUtil.java
@@ -0,0 +1,37 @@
+package ru.javawebinar.topjava.web.json;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectReader;
+
+import java.io.IOException;
+import java.util.List;
+
+import static ru.javawebinar.topjava.web.json.JacksonObjectMapper.getMapper;
+
+public class JsonUtil {
+
+ public static
+
+
+ Стек технологий: Spring Security,
+ Spring MVC,
+ Spring Data JPA,
+ Spring Security
+ Test,
+ Hibernate ORM,
+ Hibernate Validator,
+ SLF4J,
+ Json Jackson,
+ JSP,
+ JSTL,
+ Apache Tomcat,
+ WebJars,
+ DataTables plugin,
+ EHCACHE,
+ PostgreSQL,
+ JUnit,
+ Hamcrest,
+ jQuery,
+ jQuery notification,
+ Bootstrap.Filter separately
+ *
+ */
+ public List
");
+ result.getFieldErrors().forEach(
+ fe -> {
+ String msg = fe.getDefaultMessage();
+ if (msg != null) {
+ if (!msg.startsWith(fe.getField())) {
+ msg = fe.getField() + ' ' + msg;
+ }
+ joiner.add(msg);
+ }
+ });
+ return new ResponseEntity<>(joiner.toString(), HttpStatus.UNPROCESSABLE_ENTITY);
+ }
+ if (userTo.isNew()) {
+ super.create(UserUtil.createNewFromTo(userTo));
+ } else {
+ super.update(userTo, userTo.getId());
+ }
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+
+ @Override
+ @PostMapping("/{id}")
+ @ResponseStatus(value = HttpStatus.NO_CONTENT)
+ public void enable(@PathVariable("id") int id, @RequestParam("enabled") boolean enabled) {
+ super.enable(id, enabled);
+ }
+}
diff --git a/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java
new file mode 100644
index 000000000000..a0624008c358
--- /dev/null
+++ b/src/main/java/ru/javawebinar/topjava/web/user/AdminRestController.java
@@ -0,0 +1,63 @@
+package ru.javawebinar.topjava.web.user;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+import ru.javawebinar.topjava.model.User;
+
+import java.net.URI;
+import java.util.List;
+
+@RestController
+@RequestMapping(AdminRestController.REST_URL)
+public class AdminRestController extends AbstractUserController {
+ static final String REST_URL = "/rest/admin/users";
+
+ @Override
+ @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ public List
+
+
+
+
+ <%--https://getbootstrap.com/docs/4.0/components/card/--%>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
" + jqXHR.responseJSON : ""),
+ type: "error",
+ layout: "bottomRight"
+ }).show();
+}
+
+function renderEditBtn(data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+}
+
+function renderDeleteBtn(data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/src/main/webapp/resources/js/topjava.meals.js b/src/main/webapp/resources/js/topjava.meals.js
new file mode 100644
index 000000000000..e015f4571f02
--- /dev/null
+++ b/src/main/webapp/resources/js/topjava.meals.js
@@ -0,0 +1,48 @@
+function updateFilteredTable() {
+ $.ajax({
+ type: "GET",
+ url: "ajax/profile/meals/filter",
+ data: $("#filter").serialize()
+ }).done(updateTableByData);
+}
+
+function clearFilter() {
+ $("#filter")[0].reset();
+ $.get("ajax/profile/meals/", updateTableByData);
+}
+
+$(function () {
+ makeEditable({
+ ajaxUrl: "ajax/profile/meals/",
+ datatableApi: $("#datatable").DataTable({
+ "paging": false,
+ "info": true,
+ "columns": [
+ {
+ "data": "dateTime"
+ },
+ {
+ "data": "description"
+ },
+ {
+ "data": "calories"
+ },
+ {
+ "defaultContent": "Edit",
+ "orderable": false
+ },
+ {
+ "defaultContent": "Delete",
+ "orderable": false
+ }
+ ],
+ "order": [
+ [
+ 0,
+ "desc"
+ ]
+ ]
+ }),
+ updateTable: updateFilteredTable
+ });
+});
\ No newline at end of file
diff --git a/src/main/webapp/resources/js/topjava.users.js b/src/main/webapp/resources/js/topjava.users.js
new file mode 100644
index 000000000000..6601846b62d7
--- /dev/null
+++ b/src/main/webapp/resources/js/topjava.users.js
@@ -0,0 +1,91 @@
+const userAjaxUrl = "ajax/admin/users/";
+
+function enable(chkbox, id) {
+ const enabled = chkbox.is(":checked");
+// https://stackoverflow.com/a/22213543/548473
+ $.ajax({
+ url: userAjaxUrl + id,
+ type: "POST",
+ data: "enabled=" + enabled
+ }).done(function () {
+ chkbox.closest("tr").attr("data-userEnabled", enabled);
+ successNoty(enabled ? "common.enabled" : "common.disabled");
+ }).fail(function () {
+ $(chkbox).prop("checked", !enabled);
+ });
+}
+
+// $(document).ready(function () {
+$(function () {
+ makeEditable({
+ ajaxUrl: userAjaxUrl,
+ datatableApi: $("#datatable").DataTable({
+ "ajax": {
+ "url": userAjaxUrl,
+ "dataSrc": ""
+ },
+ "paging": false,
+ "info": true,
+ "columns": [
+ {
+ "data": "name"
+ },
+ {
+ "data": "email",
+ "render": function (data, type, row) {
+ if (type === "display") {
+ return "" + data + "";
+ }
+ return data;
+ }
+ },
+ {
+ "data": "roles"
+ },
+ {
+ "data": "enabled",
+ "render": function (data, type, row) {
+ if (type === "display") {
+ return "";
+ }
+ return data;
+ }
+ },
+ {
+ "data": "registered",
+ "render": function (date, type, row) {
+ if (type === "display") {
+ return date.substring(0, 10);
+ }
+ return date;
+ }
+ },
+ {
+ "orderable": false,
+ "defaultContent": "",
+ "render": renderEditBtn
+ },
+ {
+ "orderable": false,
+ "defaultContent": "",
+ "render": renderDeleteBtn
+ }
+ ],
+ "order": [
+ [
+ 0,
+ "asc"
+ ]
+ ],
+ "createdRow": function (row, data, dataIndex) {
+ if (!data.enabled) {
+ $(row).attr("data-userEnabled", false);
+ }
+ }
+ }),
+ updateTable: function () {
+ $.get(userAjaxUrl, updateTableByData);
+ }
+ }
+ );
+});
\ No newline at end of file
diff --git a/src/main/webapp/test.html b/src/main/webapp/test.html
new file mode 100644
index 000000000000..711e4e2d8190
--- /dev/null
+++ b/src/main/webapp/test.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java
new file mode 100644
index 000000000000..ca734aa80361
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/ActiveDbProfileResolver.java
@@ -0,0 +1,12 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.context.ActiveProfilesResolver;
+
+//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver
+public class ActiveDbProfileResolver implements ActiveProfilesResolver {
+
+ @Override
+ public String[] resolve(Class> aClass) {
+ return new String[]{Profiles.getActiveDbProfile()};
+ }
+ }
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java
new file mode 100644
index 000000000000..4d1d1e9e3789
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/AllActiveProfileResolver.java
@@ -0,0 +1,12 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.context.ActiveProfilesResolver;
+
+//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver
+public class AllActiveProfileResolver implements ActiveProfilesResolver {
+
+ @Override
+ public String[] resolve(Class> aClass) {
+ return new String[]{Profiles.REPOSITORY_IMPLEMENTATION, Profiles.getActiveDbProfile()};
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javawebinar/topjava/MealTestData.java b/src/test/java/ru/javawebinar/topjava/MealTestData.java
new file mode 100644
index 000000000000..e5ba377765ef
--- /dev/null
+++ b/src/test/java/ru/javawebinar/topjava/MealTestData.java
@@ -0,0 +1,58 @@
+package ru.javawebinar.topjava;
+
+import org.springframework.test.web.servlet.ResultMatcher;
+import ru.javawebinar.topjava.model.Meal;
+import ru.javawebinar.topjava.to.MealTo;
+
+import java.time.Month;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.time.LocalDateTime.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static ru.javawebinar.topjava.TestUtil.readListFromJsonMvcResult;
+import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ;
+
+public class MealTestData {
+ public static final int MEAL1_ID = START_SEQ + 2;
+ public static final int ADMIN_MEAL_ID = START_SEQ + 8;
+
+ public static final Meal MEAL1 = new Meal(MEAL1_ID, of(2015, Month.MAY, 30, 10, 0), "Завтрак", 500);
+ public static final Meal MEAL2 = new Meal(MEAL1_ID + 1, of(2015, Month.MAY, 30, 13, 0), "Обед", 1000);
+ public static final Meal MEAL3 = new Meal(MEAL1_ID + 2, of(2015, Month.MAY, 30, 20, 0), "Ужин", 500);
+ public static final Meal MEAL4 = new Meal(MEAL1_ID + 3, of(2015, Month.MAY, 31, 10, 0), "Завтрак", 500);
+ public static final Meal MEAL5 = new Meal(MEAL1_ID + 4, of(2015, Month.MAY, 31, 13, 0), "Обед", 1000);
+ public static final Meal MEAL6 = new Meal(MEAL1_ID + 5, of(2015, Month.MAY, 31, 20, 0), "Ужин", 510);
+ public static final Meal ADMIN_MEAL1 = new Meal(ADMIN_MEAL_ID, of(2015, Month.JUNE, 1, 14, 0), "Админ ланч", 510);
+ public static final Meal ADMIN_MEAL2 = new Meal(ADMIN_MEAL_ID + 1, of(2015, Month.JUNE, 1, 21, 0), "Админ ужин", 1500);
+
+ public static final List