diff --git a/.gitignore b/.gitignore index a64f0357..50d27906 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,4 @@ out log lib - - - +storage \ No newline at end of file diff --git a/README.md b/README.md index a9eed911..3047ee38 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,200 @@ # Курс BaseJava (обновленный и переработанный) -## Разработка Web приложения "База данных резюме" - - используем: Java 8, IntelliJ IDEA, - GitHib/Git, Сервлеты, JSP, JSTL, Tomcat, JUnit, PostgreSQL, GSON, JAXB - - хранение резюме - - в памяти на основе массива, отсортированного массива, списка и ассоциированного массива (Map) - - в файловой системе (File API и Java 7 NIO File API) +## Разработка web-приложения "База данных резюме" + +В данном курсе вы создадите с нуля web-приложение, реализуя разные способы хранения резюме. Проект включает в себя следующее: + - **Технологии:** Java 8, GitHub/Git, JUnit, Logging, GSON, JAXB, SQL, PostgreSQL, Сервлеты, HTML, JSP, JSTL, Tomcat, Maven и многое другое + - **Различные способы реализации хранения резюме:** + - в сортированном и не сортированном массиве + - в коллекциях (List, Map) + - в файловой системе: + - с использованием File и Path API - в стандартной и кастомной сериализации Java - - в формате JSON (Google Gson) - - в формате XML (JAXB) - - в реляционной базе PostgreSQL - - деплой веб приложения - - в контейнер сервлетов Tomcat - - в облачный сервис Heroku - -> Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться. - -*— Илон Маск - -## Программа -### [Занятие 1](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson01.md) - - Презентация проекта - - Разработка ПО - - Обзор языка Java + - в формате JSON ([Google Gson](https://en.wikipedia.org/wiki/Gson)) + - в формате XML ([JAXB](https://ru.wikipedia.org/wiki/Java_Architecture_for_XML_Binding)) + - в реляционной базе [PostgreSQL](https://ru.wikipedia.org/wiki/PostgreSQL) + - **Установку (деплой) web-приложения:** + - в контейнер сервлетов [Tomcat](https://ru.wikipedia.org/wiki/Apache_Tomcat) + - на [собственный выделенный сервер](https://github.com/JavaOps/startup) + +> Любое знание стоит воспринимать как подобие семантического дерева: убедитесь в том, что понимаете фундаментальные принципы, то есть ствол и крупные ветки, прежде чем лезть в мелкие листья-детали. Иначе последним не на чем будет держаться + +— Илон Маск + +## Программа курса + +#### [Занятие 1](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson01.md) + - Обзор курса и методики обучения + - Подготовка и настройка рабочего окружения + - Подходы, применяемые при разработке ПО + - Обзор инструментов и технологий, используемых Java-разработчиками + - Введение в язык Java: история создания, JDK, JVM, JRE, JIT-компиляция - Системы управления версиями. Git - - ПЕРВОЕ ДОМАШНЕЕ ЗАДАНИЕ + - Домашнее задание -### [Занятие 2](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson02.md) +#### [Занятие 2](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson02.md) + - Типы данных + - Введение в объектно-ориентированное программирование - Принципы ООП - - Структура памяти: куча, стек, регистры, константы - - Типы данных. Пакеты - -### [Занятие 3](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson03.md) - - Объектная модель в Java + - Классы и объекты + - Классы-обертки + - Модификаторы доступа + - Конструктор + - Структура памяти java-программы: Heap (куча), Stack (стек) + - Пакеты + - Домашнее задание + +#### [Занятие 3](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson03.md) + - Разбор домашнего задания + - Обзор суперкласса Object + - Связь между equals() и hashCode() + - Статические методы и переменные + - Программирование с помощью интерфейсов + - Абстрактные классы - Сложность алгоритмов - - Паттерн проектирования Шаблонный метод + - Паттерн проектирования Template Method + - Домашнее задание -### [Занятие 4](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson04.md) - - Работа со строками - - Исключения - - Reflection. Аннотации. Модульное тестирование - -### [Занятие 5](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson05.md) - - Контейнеры/коллекции +#### [Занятие 4](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson04.md) + - Разбор домашнего задания + - Конструктор + - Работа со строками: String, StringBuilder, StringBuffer + - String literal pool + - Исключения (Exceptions) + - Ключевые слова: this, super + - Reflection + - Аннотации + - Введение в модульное тестирование. JUnit + - Домашнее задание + +#### [Занятие 5](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson05.md) + - Разбор домашнего задания + - Коллекций. Иерархия классов + - Списки (List) + - Множества (Set) + - Ассоциативные массивы (Map) + - Введение в Iterator + - Домашнее задание -### [Занятие 6](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson06.md) - - Iterator / Iterable. Вложенные, внутренние, локальные и анонимные классы - - Новое в Java 8 - -### [Занятие 7](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson07.md) - - Параметризация. Стирание типов - - Логирование - - Синглетон, Enum - -### [Занятие 8](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson08.md) - - Работа с датами и временем - - Работа с файлами и ресурсами - -### [Занятие 9](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson09.md) - - Ввод/вывод - - Сериализация - - NIO - - Основы Java 8 Stream API - -### [Занятие 10](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson10.md) - - Формат XML. Работа с XML в Java - - JSON - - DataInputStream / DataOutputStream - -### [Занятие 11](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson11.md) - - Многопоточность. Параллельное выполнение. - - Потоки. Синхронизация - - Ленивая инициализация, JMM +#### [Занятие 6](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson06.md) + - Разбор домашнего задания + - Паттерн проектирования Iterator + - Autoboxing и Unboxing + - Вложенные классы + - Внутренние классы + - Локальные классы + - Анонимные классы + - Введение в лямбда-выражения + - Функциональный интерфейс + - Домашнее задание + +#### [Занятие 7](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson07.md) + - Разбор домашнего задания + - Дженерики (Generic) + - Введение в логирование. Log4J, Java Logging API + - Паттерн проектирования Singleton + - Перечисления (Enum) + - Объектная модель + - Домашнее задание + +#### [Занятие 8](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson08.md) + - Разбор домашнего задания + - Классы работы с датами: Date, Calendar, TimeZone + - Дата и время в Java 8+ + - File API + - Освобождение ресурсов: try-with-resources + - Домашнее задание + +#### [Занятие 9](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson09.md) + - Разбор домашнего задания + - Обзор пакета java.io + - Классы чтения/записи потоков: InputStream и OutputStream + - Паттерн проектирования Decorator + - Классы чтения/записи символов: Reader и Writer + - Сериализация объектов + - Обзор пакета java.nio + - Введение в Java 8+ Stream API + - Домашнее задание + +#### [Занятие 10](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson10.md) + - Разбор домашнего задания + - Паттерн проектирования Strategy + - Работа с XML (JAXB) + - Работа с JSON (GSON) + - Классы чтения/записи примитивных типов: DataInputStream и DataOutputStream + - Домашнее задание + +#### [Занятие 11](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson11.md) + - Многопоточность + - Закон Мура и Амдала + - Потоки. Синхронизация доступа + - Обзор методов класса Object + - Ленивая инициализация + - Java Memory Model + - Deadlock + - Домашнее задание -### [Занятие 12](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson12.md) - - java.util.concurrent - -### [Занятие 13](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson13.md) - - Базы данных. Реляционные СУБД. PostgreSQL - - Конфигурирование данных в Java проекте - - Подключение DB в проект - -### [Занятие 14](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson14.md) - - JOIN +#### [Занятие 12](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson12.md) + - Разбор домашнего задания + - Обзор классов java.util.concurrent + - Синхронизаторы + - ThreadLocal-переменные + - Сравнение с обменом (Compare-and-swap) + - Домашнее задание + +#### [Занятие 13](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson13.md) + - Разбор домашнего задания + - Введение в реляционные базы данных + - Язык SQL + - Обзор NoSQL баз данных + - Установка и настройка СУБД PostgreSQL + - Работа с базами данных из IDEA + - Конфигурирование базы данных и каталога хранения + - Подключение базы данных к проекту + - Обзор JDBC-архитектуры + - Домашнее задание + +#### [Занятие 14](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson14.md) + - Разбор домашнего задания + - Операции соединения таблиц. JOIN - Транзакции - - Установка/запуск Tomcat - -### [Занятие 15](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson15.md) - - HTML, Tomcat + - Требования к транзакциям. ACID + - Уровни изоляции транзакций в SQL + - Установка и настройка контейнера сервлетов Tomcat + - Домашнее задание + +#### [Занятие 15](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson15.md) + - Разбор домашнего задания + - Введение в HTML + - Основы протокола HTTP + - Настройка web.xml + - Деплой web-приложения в Tomcat - Сервлеты + - Домашнее задание -### [Занятие 16](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson16.md) - - JSP - - JSTL +#### [Занятие 16](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson16.md) + - Разбор домашнего задания + - Жизненный цикл сервлета + - Создание динамических страниц. JSP + - Расширенные возможности JSP. JSTL + - Redirect и Forward + - CRUD-операции + - Домашнее задание -### [Занятие 17](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson17.md) - - Деплой в Heroku - - Classloader - - Обзор Java Enterprise +#### [Занятие 17](https://github.com/JavaWebinar/basejava/blob/lesson/lesson/lesson17.md) + - Разбор домашнего задания + - Деплой приложения на [собственный выделенный сервер](https://github.com/JavaOps/startup) + - Загрузка классов в Java. Classloader + - Домашнее задание ## Рекомендуемые книги -- YAKOV FAIN: Программирование на Java для начинающих -- Книги по Java: от новичка до профессионала -- Джошуа Блох: Java. Эффективное программирование, 2-е издание -- Гамма, Хелм, Джонсон: Приемы объектно-ориентированного проектирования. Паттерны проектирования -- Редмонд Э.: Семь баз данных за семь недель. Введение в современные базы данных и идеологию NoSQL. - -## Ресуры в сети -- Руководство по Java Core -- Java. Базовый курс -- intuit: Программирование на Java -- Основы программирования на Java: учебное пособие +- [Яков Файн, "Программирование на Java для начинающих"](http://myflex.org/books/java4kids/java4kids.htm) +- [Книги по Java: от новичка до профессионала](https://proglib.io/p/java-books-2019/) +- [Джошуа Блох, "Java. Эффективное программирование, 3-е издание"](https://www.ozon.ru/context/detail/id/148627191/) +- [Роберт Мартин, "Чистый код"](https://www.ozon.ru/context/detail/id/142429922/) +- [Серия Head First, "Паттерны проектирования"](https://www.ozon.ru/context/detail/id/144233005/) +- [Вайсфельд Мэтт, "Объектно-ориентированный подход"](https://www.ozon.ru/context/detail/id/166375103/?stat=YW5fMQ%3D%3D) + +## Ресурсы в сети +- [EduTools плагин от JetBrains для изучения Kotlin, Java, Python, Scala и других языков](http://javaops.ru/view/story/story21#edutools) +- [JetBrains Academy — интерактивный учебный курс по Java](https://www.jetbrains.com/ru-ru/academy/) diff --git a/rules.md b/rules.md new file mode 100644 index 00000000..924965da --- /dev/null +++ b/rules.md @@ -0,0 +1,33 @@ +## Правила на курсе BaseJava при проверке ДЗ наставником + +1. Проверка ДЗ проводится в Telegram +1. Все друг с другом общаются на Ты +1. Проверка ДЗ действует 4.5 месяца с момента нажатия вами в личном кабинете кнопки `Принять участие` +1. Взаимодействие с наставником проходит по следующей схеме: + - вы смотрите видеолекции (ссылки на них находятся рядом с иконками логотипа ютуба), изучаете дополнительный материал, выполняете ДЗ + - по мере готовности сообщаете наставнику, что его нужно проверить + - он проверяет и пишет, что требуется исправить + - вы исправляете, он снова проверяет. И так повторяется до тех пор, пока ваше решение не будет соответствовать условию ДЗ и представлению наставника о том, как его нужно выполнить + - если на вашем последнем сообщении наставник ставит 📌, то он его прочитал. Если стоит 🧐, то в данный момент ваше ДЗ на проверке. Если стоит ✔️- ДЗ проверено + - если все верно, то наставник напишет, чтобы вы переходили к следующему уроку +1. Ссылку на свой репозиторий наставнику достаточно скинуть один раз +1. Комментарии к вашему коду будут даваться наставником в краткой форме. Если вам требуется пояснение по какому-то пункту, то задавайте ему дополнительные вопросы для получения более развернутого ответа +1. Переходить к следующему уроку можно после того, как вам об этом сообщит наставник. Не делайте это самостоятельно +1. Для минимизации "простоев" допускается изучение материалов следующего урока, но без просмотра видео Григория, чтобы не раскрывались вопросы реализации ДЗ +1. Максимальное время проверки ДЗ до суток, но как правило раньше +1. Если наставник не проверил ДЗ за указанный срок, то напомните ему о себе (он вас мог случайно пропустить) +1. Доступ к материалам курса у вас остается навсегда +1. Проверка в выходные дни наставника не гарантируется, но возможна +1. Если у вас закончился срок проверок ДЗ, то его можно продлить в личном кабинете (ссылка на него есть в конце любого нашего письма) [на сайте](https://javaops.ru/) + +#### Общие рекомендации +- Учитесь грамотно формулировать свой вопрос: "у меня не работает" может иметь тысячи причин. Пишите больше подробностей, что вы сделали, что конкретно не работает, какие появляются ошибки. Присылайте в чат скрины с этими ошибками +- Учитесь гуглить с первых уроков - это важный навык, который нужно качать +- Перед тем как задать вопрос наставнику сначала погуглите; если в течении 2-3 часов вы не найдете ответ, то опишите подробно свою проблему наставнику + +#### Расписание проверок ДЗ + +[Максим Чимаев](https://t.me/ch1max) +- выходные дни вторник и четверг +- время проверки с 11:00 по 13:00 и с 17:30 по 19:30 (но не позднее 21:00 по мск. времени) +- проверка в выходные дни не гарантируется, но возможна diff --git a/src/ru/javawebinar/basejava/Config.java b/src/ru/javawebinar/basejava/Config.java index 5111b689..e007b2ab 100644 --- a/src/ru/javawebinar/basejava/Config.java +++ b/src/ru/javawebinar/basejava/Config.java @@ -6,7 +6,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.HashSet; import java.util.Properties; +import java.util.Set; public class Config { private static final String PROPS = "/resumes.properties"; @@ -14,6 +16,10 @@ public class Config { private final File storageDir; private final Storage storage; + private Set immutableUuids = new HashSet() {{ // for JDK 9+: Set.of("111", "222"); + add("11111111-1111-1111-1111-111111111111"); + add("22222222-2222-2222-2222-222222222222"); + }}; public static Config get() { return INSTANCE; @@ -37,4 +43,13 @@ public File getStorageDir() { public Storage getStorage() { return storage; } + + public boolean isImmutable(String uuids) { + return immutableUuids.contains(uuids); + } + + public void checkImmutable(String uuids) { + if (immutableUuids.contains(uuids)) + throw new RuntimeException("Зарезервированные резюме нельзя менять"); + } } \ No newline at end of file diff --git a/src/ru/javawebinar/basejava/model/ContactType.java b/src/ru/javawebinar/basejava/model/ContactType.java index 12651afe..989fa64e 100644 --- a/src/ru/javawebinar/basejava/model/ContactType.java +++ b/src/ru/javawebinar/basejava/model/ContactType.java @@ -13,7 +13,12 @@ public String toHtml0(String value) { MAIL("Почта") { @Override public String toHtml0(String value) { - return getTitle() + ": " + toLink("mailto:" + value, value); + return getTitle() + ": " + toLink(value); + } + + @Override + public String toLink(String value) { + return (value == null) ? "" : toLink("mailto:" + value, value); } }, LINKEDIN("Профиль LinkedIn") { @@ -64,6 +69,6 @@ public String toLink(String href) { } public static String toLink(String href, String title) { - return "" + title + ""; + return "" + title + ""; } } diff --git a/src/ru/javawebinar/basejava/web/ResumeServlet.java b/src/ru/javawebinar/basejava/web/ResumeServlet.java index 7587c78c..909b0b1c 100644 --- a/src/ru/javawebinar/basejava/web/ResumeServlet.java +++ b/src/ru/javawebinar/basejava/web/ResumeServlet.java @@ -13,19 +13,29 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class ResumeServlet extends HttpServlet { + private enum THEME { + dark, light, purple + } + private Storage storage; // = Config.get().getStorage(); + private final Set themes = new HashSet<>(); // https://stackoverflow.com/a/4936895/548473 @Override public void init(ServletConfig config) throws ServletException { super.init(config); storage = Config.get().getStorage(); + for (THEME t : THEME.values()) { + themes.add(t.name()); + } } - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); String uuid = request.getParameter("uuid"); String fullName = request.getParameter("fullName"); @@ -35,6 +45,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (isCreate) { r = new Resume(fullName); } else { + Config.get().checkImmutable(uuid); r = storage.get(uuid); r.setFullName(fullName); } @@ -93,12 +104,14 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } else { storage.update(r); } - response.sendRedirect("resume"); + response.sendRedirect("resume?theme=" + getTheme(request)); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { String uuid = request.getParameter("uuid"); String action = request.getParameter("action"); + request.setAttribute("theme", getTheme(request)); + if (action == null) { request.setAttribute("resumes", storage.getAllSorted()); request.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(request, response); @@ -107,6 +120,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t Resume r; switch (action) { case "delete": + Config.get().checkImmutable(uuid); storage.delete(uuid); response.sendRedirect("resume"); return; @@ -160,4 +174,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t ("view".equals(action) ? "/WEB-INF/jsp/view.jsp" : "/WEB-INF/jsp/edit.jsp") ).forward(request, response); } + + private String getTheme(HttpServletRequest request) { + String theme = request.getParameter("theme"); + return themes.contains(theme) ? theme : THEME.light.name(); + } } diff --git a/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java index 895afad1..9eefcc7f 100644 --- a/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java +++ b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java @@ -58,7 +58,7 @@ public void update() throws Exception { @Test(expected = NotExistStorageException.class) public void updateNotExist() throws Exception { - storage.get("dummy"); + storage.update(new Resume("dummy")); } @Test diff --git a/web/WEB-INF/jsp/edit.jsp b/web/WEB-INF/jsp/edit.jsp index 88a98e3f..9242aa01 100644 --- a/web/WEB-INF/jsp/edit.jsp +++ b/web/WEB-INF/jsp/edit.jsp @@ -3,96 +3,104 @@ <%@ page import="ru.javawebinar.basejava.model.OrganizationSection" %> <%@ page import="ru.javawebinar.basejava.model.SectionType" %> <%@ page import="ru.javawebinar.basejava.util.DateUtil" %> +<%@ page import="ru.javawebinar.basejava.Config" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - + + + Резюме ${resume.fullName} -
-
- -

Имя:

-
- -
-

Контакты:

- -
-
${type.title}
-
-
-
-
- - - -

${type.title}

- - - - - - - - - - - - -
-
Название учереждения:
-
-
-
-
Сайт учереждения:
-
- -
-
-
+ + + +
+
+
ФИО
+ + +
Контакты
+ + + + + +
+ +
Секции
+ + + + +
${type.title}
+ + + + + + + + + + + + + +
+
+
+ +<%-- --%> + + + + +<%-- --%> + -
-
Начальная дата:
-
- -
-
-
-
Конечная дата:
-
- -
-
-
Должность:
-
-
-
-
Описание:
-
-
+ +
+ + +
+ + + +
-
- - - - - - - -
+ + + + + +
+ +
+ + + + +
+ + + + diff --git a/web/WEB-INF/jsp/fragments/footer.jsp b/web/WEB-INF/jsp/fragments/footer.jsp index ba7048e4..bcfbdb4e 100644 --- a/web/WEB-INF/jsp/fragments/footer.jsp +++ b/web/WEB-INF/jsp/fragments/footer.jsp @@ -1,4 +1,6 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> - + diff --git a/web/WEB-INF/jsp/fragments/header.jsp b/web/WEB-INF/jsp/fragments/header.jsp index 93803363..22ba67be 100644 --- a/web/WEB-INF/jsp/fragments/header.jsp +++ b/web/WEB-INF/jsp/fragments/header.jsp @@ -1,3 +1,11 @@ <%@page contentType="text/html" pageEncoding="UTF-8" %> -
Управление резюме
-
+ diff --git a/web/WEB-INF/jsp/list.jsp b/web/WEB-INF/jsp/list.jsp index 1cf7b524..aa8f620b 100644 --- a/web/WEB-INF/jsp/list.jsp +++ b/web/WEB-INF/jsp/list.jsp @@ -1,36 +1,76 @@ <%@ page import="ru.javawebinar.basejava.model.ContactType" %> +<%@ page import="ru.javawebinar.basejava.Config" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - + + + Список всех резюме +
+
Тема
+
+
+ +
+
+
-
- -
- - - - - - - - - - - - - - - - -
ИмяEmail
${resume.fullName}<%=ContactType.MAIL.toHtml(resume.getContact(ContactType.MAIL))%>
-
+
+
+ +
+ + + + + + + + + + + + + + + + +
ИмяКонтактыРедактироватьУдалить
+ ${resume.fullName} + + <%=ContactType.MAIL.toLink(resume.getContact(ContactType.MAIL))%> + + + + + + + + + + +
+
+
+
diff --git a/web/WEB-INF/jsp/view.jsp b/web/WEB-INF/jsp/view.jsp index 62ef44f3..19c051d7 100644 --- a/web/WEB-INF/jsp/view.jsp +++ b/web/WEB-INF/jsp/view.jsp @@ -7,88 +7,93 @@ - + + + Резюме ${resume.fullName} -
-

${resume.fullName} 

-

- - - <%=contactEntry.getKey().toHtml(contactEntry.getValue())%>
-
-

-


- + +
+
+
${resume.fullName} + + + +
+
+ + + +
<%=contactEntry.getKey().toHtml(contactEntry.getValue())%> +
+
+
+ +
+ -
- - +
${type.title}
- - - +
<%=((TextSection) section).getContent()%> +
- - - +
<%=((TextSection) section).getContent()%> +
- - - +
    + +
  • ${item}
  • +
    +
- - - - - - - - - - + + -

${type.title}

-

<%=((TextSection) section).getContent()%>

-
- <%=((TextSection) section).getContent()%> -
-
    - -
  • ${item}
  • -
    -
-
+
+ + +
${org.homePage.name}
+
+ + + +
+ + +
+
<%=HtmlUtil.formatDates(position)%> +
+
${position.title}
+
- -

${org.homePage.name}

+ -

${org.homePage.name}

+
${position.description}
-
<%=HtmlUtil.formatDates(position)%> - ${position.title}
${position.description}
-
- -
+ + + + diff --git a/web/css/edit-resume-styles.css b/web/css/edit-resume-styles.css new file mode 100644 index 00000000..cd4f817a --- /dev/null +++ b/web/css/edit-resume-styles.css @@ -0,0 +1,173 @@ +input, textarea { + height: 60px; + width: 100%; + + background: var(--input-field-background); + border: 0; + border-radius: 10px; + box-shadow: 0 0 50px 0 var(--input-field-shadow) inset; + padding: 0 25px 0 25px; + margin: 20px 0 0 0; +} + +input:focus, input:focus-visible, +textarea:focus, textarea:focus-visible { + outline: none !important; + border-color: var(--input-field-background); + box-shadow: 0 0 10px var(--input-field-shadow); +} + +input::placeholder, textarea::placeholder { + font-weight: normal; + font-size: 14px; + line-height: 19px; + + color: var(--text-color); + opacity: 0.5; +} + +textarea { + height: 40px; + padding-top: 20px; +} + +button { + font-family: Open Sans, serif; + font-style: normal; + font-weight: 600; + font-size: 14px; + line-height: 19px; + + align-items: center; + + height: 60px; + margin: 20px 0 10px 0; + + box-sizing: border-box; + border-radius: 10px; +} + +.form-wrapper { + font-family: Open Sans, serif; + font-style: normal; + font-weight: 600; + color: var(--text-color); + + height: 100%; + min-width: 610px; + width: 50%; + margin: 0 0 0 25%; + display: block; +} + +.section { + font-size: 32px; + line-height: 44px; + + margin: 50px 0 0 0; +} + +.field { + min-height: 60px; + color: var(--text-color); +} + +.field-label { + font-size: 24px; + line-height: 33px; + + margin: 40px 0 0 0; +} + +.spacer { + width: 100%; + min-width: 100%; + height: 2px; + margin: 40px 0 10px 0; + + background: var(--spacer-background); + border-radius: 10px; +} + +.date-section { + display: block; +} + +.date { + width: 250px; +} + +.date-margin { + margin-left: 10px; +} + +.green-button { + width: 295px; + margin: 20px 0 10px 0; + + color: var(--submit-button-color); + + border: 2px solid var(--submit-button-color); + background: var(--submit-button-background); +} + +.green-button:hover { + opacity: 0.8; + box-shadow: 0 0 10px var(--submit-button-color); +} + +.small-green-button { + height: 50px; + width: 200px; + margin: 20px 0 0 0; + + color: var(--submit-button-color); + + border: 2px solid var(--submit-button-color); + background: var(--submit-button-background); +} + +.green-button:hover, .small-green-button:hover { + opacity: 0.8; + box-shadow: 0 0 10px var(--submit-button-color); +} + +.button-section { + width: 100%; + margin: 40px 0 90px 0; + display: inline-flex; +} + +.red-cancel-button { + width: 100%; + margin-right: 10px; + + color: var(--button-text-color); + + border: 2px solid var(--cancel-button-color); + background: var(--cancel-button-color); +} + +.red-cancel-button:hover { + color: var(--cancel-button-color); + opacity: 0.8; + box-shadow: 0 0 10px var(--cancel-button-color); + background: var(--button-text-color); +} + +.green-submit-button { + width: 100%; + margin-left: 10px; + + color: var(--button-text-color); + + border: 2px solid var(--save-button-background); + background: var(--save-button-background); +} + +.green-submit-button:hover { + color: var(--save-button-background); + opacity: 0.8; + box-shadow: 0 0 10px var(--save-button-background); + background: var(--button-text-color); +} \ No newline at end of file diff --git a/web/css/resume-list-styles.css b/web/css/resume-list-styles.css new file mode 100644 index 00000000..87ec92ad --- /dev/null +++ b/web/css/resume-list-styles.css @@ -0,0 +1,108 @@ +table { + width: 100%; + border-spacing: 0; + text-align: center; + box-shadow: 0 0 50px var(--table-shadow-color); +} + +.table-wrapper { + height: 100%; + min-width: 610px; + width: 50%; + margin: 0 0 0 25%; + display: block; +} + +.add-resume { + height: 100px; + width: 100%; + display: inline-flex; + align-items: center; + justify-content: end; +} + +.add-resume-title { + font-family: Roboto, serif; + font-style: normal; + font-weight: bold; + font-size: 15px; + + width: 70px; + margin-left: 13px; + display: block; + + color: var(--add-resume-text-color); +} + +.resumes-list { + width: 100%; +} + +.resumes-list img { + height: 20px; +} + +.t-header, tr { + font-family: Roboto, serif; + font-style: normal; + font-weight: bold; + font-size: 16px; + line-height: 19px; + + height: 70px; + + color: var(--table-header-text-color); + background: var(--table-header-background); +} + +.t-body, tr { + font-family: Open Sans, serif; + font-style: normal; + font-weight: normal; + font-size: 14px; + line-height: 19px; + + color: var(--table-text-color); +} + +.t-body, tr:nth-child(odd) { + height: 70px; + background: var(--table-odd-row-background); +} + +.t-body, tr:nth-child(even) { + height: 90px; + background: var(--table-even-row-background); +} + +.resumes-list th:first-of-type { + border-top-left-radius: 10px; +} + +.resumes-list th:last-of-type { + border-top-right-radius: 10px; +} + +.resumes-list tr:last-of-type td:first-of-type { + border-bottom-left-radius: 10px; +} + +.resumes-list tr:last-of-type td:last-of-type { + border-bottom-right-radius: 10px; +} + +.name-column { + text-align: start; + padding: 0 0 0 38px; +} + +.info-column { + text-align: start; + width: 230px !important; + min-width: 180px !important; +} + +.img-column { + width: 110px !important; + min-width: 110px !important; +} \ No newline at end of file diff --git a/web/css/style.css b/web/css/style.css deleted file mode 100644 index 90b58df8..00000000 --- a/web/css/style.css +++ /dev/null @@ -1,71 +0,0 @@ -section { - width: 900px; - margin: auto; -} - -header { - background: none repeat scroll 0 0 #A6C9E2; - color: #2E6E9E; - font-size: 20px; - padding: 5px 20px; - text-align: center; -} - -footer { - background: none repeat scroll 0 0 #A6C9E2; - color: #2E6E9E; - font-size: 20px; - padding: 5px 20px; - margin: 20px 0; - text-align: center; -} - -dl { - background: none repeat scroll 0 0 #FAFAFA; - margin: 8px 0; - padding: 0; -} - -dt { - display: inline-block; - width: 170px; -} - -dd { - display: inline-block; - margin-left: 8px; - vertical-align: top; -} - -h2, h3 { - margin: 15px 0 5px; -} - -li { - margin: 15px 0; -} - -a[href^="skype:"] { - padding-left: 20px !important; - background: url(../img/skype.png) no-repeat center left; -} - -a[href^="mailto:"] { - padding-left: 20px !important; - background: url(../img/email.png) no-repeat center left; -} - -a[href^="https://stackoverflow.com"] { - padding-left: 20px !important; - background: url(../img/so.png) no-repeat center left; -} - -a[href^="https://www.linkedin.com"] { - padding-left: 20px !important; - background: url(../img/lin.png) no-repeat center left; -} - -a[href*="github."] { - padding-left: 20px !important; - background: url(../img/gh.png) no-repeat center left; -} \ No newline at end of file diff --git a/web/css/styles.css b/web/css/styles.css new file mode 100644 index 00000000..fc9b712d --- /dev/null +++ b/web/css/styles.css @@ -0,0 +1,148 @@ +body { + margin: 0; + padding: 0; + overflow: hidden; + background: var(--body-background); +} + +.header { + height: 100px; + width: 100%; + + background: var(--header-background); +} + +.header .arrow:hover { + color: transparent; + text-decoration: none; +} + +.header .resumes-control-title { + margin-left: 16px; + + font-family: Open Sans, serif; + font-style: normal; + font-weight: 600; + font-size: 20px; + + color: var(--header-text-color); +} + +.header .arrow-dot { + height: 30px; + width: 30px; + border-radius: 50%; + margin: 35px 0 0 25%; + background: var(--header-arrow-background); + display: inline-block; + text-align: center; +} + +.header .arrow-dot img { + width: 12px; + margin-top: 9px; + margin-left: -2px; +} + +.footer { + height: 100px; + width: 100%; + + position:absolute; + bottom:0; + + font-family: Open Sans, serif; + font-style: normal; + font-weight: normal; + font-size: 14px; + line-height: 19px; + + display: table; + + color: var(--footer-text-color); + background: var(--header-background); +} + +.footer a { + display:table-cell; + vertical-align:middle; +} + +.footer div { + width: 100%; + text-align: center; +} + +.scrollable-panel { + height: calc(100vh - 200px); + width: 100%; + margin: 0; + overflow: auto; +} + +.text-anchor { + text-decoration: none; +} + +.text-anchor:hover { + opacity: 0.8; + color: var(--anchor-color); + text-decoration: underline; +} + +.no-underline-anchor { + text-decoration: none; +} + +.no-underline-anchor:hover { + opacity: 0.8; +} + +.footer-text-anchor { + color: var(--footer-anchor-color); + text-decoration: none; +} + +.footer-text-anchor:hover { + opacity: 0.8; + text-decoration: underline; +} + +.themes { + font-family: Roboto, serif; + font-style: normal; + font-weight: bold; + font-size: 16px; + line-height: 19px; + + float: right; + display: flex; + margin: 40px 24px 0 0; + + color: var(--theme-text-color); +} + +.theme-title { + width: 45px; +} + +.theme-selector option { + color: var(--table-header-background); + background: var(--table-header-text-color); +} + +.theme-selector select { + color: var(--table-header-text-color); + background: var(--table-header-background); + border: none; +} + +.contact-link { + color: var(--contact-link-color); + text-decoration: none; +} + +.contact-link:hover { + text-decoration: underline; + opacity: 0.8; +} diff --git a/web/css/theme/dark.css b/web/css/theme/dark.css new file mode 100644 index 00000000..9c11d973 --- /dev/null +++ b/web/css/theme/dark.css @@ -0,0 +1,27 @@ +:root { + --header-background: #2A343D; + --header-text-color: #F8F9FA; + --header-arrow-background: #FFFFFF; + --footer-text-color: #F8F9FA; + --footer-anchor-color: #F8F9FA; + --anchor-color: #F8F9FA; + --text-color: #ECEEF2; + --add-resume-text-color: #3F9752; + --body-background: #1A2026; + --table-header-text-color: #F8F9FA; + --table-header-background: #0A0C0F; + --table-text-color: #F8F9FA; + --table-odd-row-background: #12161A; + --table-even-row-background: #1A2026; + --table-shadow-color: rgba(0, 0, 0, 0.5); + --input-field-background: #2A343D; + --input-field-shadow: rgba(0, 0, 0, 0.3); + --spacer-background: #E0E4EA; + --submit-button-color: #3F9752; + --submit-button-background: #1A2026; + --save-button-background: #3F9752; + --cancel-button-color: #F38064; + --button-text-color: #F8F9FA; + --theme-text-color: #ECEEF2; + --contact-link-color: #169FE1; +} \ No newline at end of file diff --git a/web/css/theme/light.css b/web/css/theme/light.css new file mode 100644 index 00000000..33660a3d --- /dev/null +++ b/web/css/theme/light.css @@ -0,0 +1,27 @@ +:root { + --header-background: #EDEFF2; + --header-text-color: #4A586E; + --header-arrow-background: #FFFFFF; + --footer-text-color: #4A586E; + --footer-anchor-color: #4A586E; + --anchor-color: #4A586E; + --text-color: #4A586E; + --add-resume-text-color: #4A586E; + --body-background: #FFFFFF; + --table-header-text-color: #4A586E; + --table-header-background: #E0E4EA; + --table-text-color: #F8F9FA; + --table-odd-row-background: #ECEEF2; + --table-even-row-background: #F8F9FA; + --table-shadow-color: none; + --input-field-background: #F8F9FA; + --input-field-shadow: #E0E4EA; + --spacer-background: #E0E4EA; + --submit-button-color: #319848; + --submit-button-background: #FFFFFF; + --save-button-background: #319848; + --cancel-button-color: #B7616A; + --button-text-color: #F8F9FA; + --theme-text-color: #4A586E; + --contact-link-color: #169FE1; +} \ No newline at end of file diff --git a/web/css/theme/purple.css b/web/css/theme/purple.css new file mode 100644 index 00000000..b3af212c --- /dev/null +++ b/web/css/theme/purple.css @@ -0,0 +1,27 @@ +:root { + --header-background: #7339D4; + --header-text-color: #ECEEF2; + --header-arrow-background: #FFFFFF; + --footer-text-color: #F8F9FA; + --footer-anchor-color: #F8F9FA; + --anchor-color: #F8F9FA; + --text-color: #ECEEF2; + --add-resume-text-color: #F8F9FA; + --body-background: #6129BD; + --table-header-text-color: #F8F9FA; + --table-header-background: #5A10D3; + --table-text-color: #F8F9FA; + --table-odd-row-background: #6212E5; + --table-even-row-background: #6C1DEE; + --table-shadow-color: 0 0 50px rgba(67, 12, 156, 0.5); + --input-field-background: #8A59DB; + --input-field-shadow: #8A59DB; + --spacer-background: #E0E4EA; + --submit-button-color: #ECEEF2; + --submit-button-background: #6129BD; + --save-button-background: #319848; + --cancel-button-color: #AE515A; + --button-text-color: #F8F9FA; + --theme-text-color: #ECEEF2; + --contact-link-color: #169FE1; +} \ No newline at end of file diff --git a/web/css/view-resume-styles.css b/web/css/view-resume-styles.css new file mode 100644 index 00000000..d82beeb1 --- /dev/null +++ b/web/css/view-resume-styles.css @@ -0,0 +1,100 @@ +.form-wrapper { + font-family: Open Sans, serif; + font-style: normal; + font-weight: 600; + color: var(--text-color); + + height: 100%; + min-width: 610px; + width: 50%; + margin: 0 0 0 25%; + display: block; +} + +.full-name { + font-size: 32px; + line-height: 44px; + margin-top: 40px; +} + +.contacts { + font-weight: 300; + font-size: 18px; + line-height: 30px; + margin-top: 21px; +} + +.section-wrapper { + margin-bottom: 83px; +} + +.spacer { + width: 100%; + min-width: calc(100% + 50px); + height: 2px; + margin: 43px 0 77px 0; + + background: var(--spacer-background); + border-radius: 10px; +} + +.section { + font-weight: 600; + font-size: 24px; + line-height: 33px; + margin: 40px 0 24px 0; +} + +.position { + font-weight: 600; + font-size: 18px; + line-height: 25px; +} + +.qualities { + font-weight: 300; + font-size: 18px; + line-height: 25px; +} + +.list { + font-weight: 300; + font-size: 18px; + line-height: 25px; +} + +.job-name { + font-weight: bold; + font-size: 22px; + line-height: 30px; + margin-left: 207px; +} + +.period-position { + display: flex; + margin-top: 10px; +} + +.period { + font-weight: 300; + font-size: 18px; + line-height: 25px; + min-width: 207px; +} + +.position { + font-weight: bold; + font-size: 18px; + line-height: 25px; +} + +.description { + font-weight: 300; + font-size: 18px; + line-height: 25px; + margin: 35px 0 0 207px; +} + +.footer-spacer { + height: 10px; +} \ No newline at end of file diff --git a/web/img/add.png b/web/img/add.png deleted file mode 100644 index 0ea124a7..00000000 Binary files a/web/img/add.png and /dev/null differ diff --git a/web/img/dark/add-person.svg b/web/img/dark/add-person.svg new file mode 100644 index 00000000..1f780704 --- /dev/null +++ b/web/img/dark/add-person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/img/dark/edit.svg b/web/img/dark/edit.svg new file mode 100644 index 00000000..45f5d190 --- /dev/null +++ b/web/img/dark/edit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/img/dark/remove.svg b/web/img/dark/remove.svg new file mode 100644 index 00000000..a504d5ca --- /dev/null +++ b/web/img/dark/remove.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/img/delete.png b/web/img/delete.png deleted file mode 100644 index ace289ed..00000000 Binary files a/web/img/delete.png and /dev/null differ diff --git a/web/img/email.png b/web/img/email.png deleted file mode 100644 index 83278739..00000000 Binary files a/web/img/email.png and /dev/null differ diff --git a/web/img/gh.png b/web/img/gh.png deleted file mode 100644 index f1c40a4a..00000000 Binary files a/web/img/gh.png and /dev/null differ diff --git a/web/img/left_arrow.svg b/web/img/left_arrow.svg new file mode 100644 index 00000000..c7325c1c --- /dev/null +++ b/web/img/left_arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/img/light/add-person.svg b/web/img/light/add-person.svg new file mode 100644 index 00000000..fed04df9 --- /dev/null +++ b/web/img/light/add-person.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/img/light/edit.svg b/web/img/light/edit.svg new file mode 100644 index 00000000..bcec11ae --- /dev/null +++ b/web/img/light/edit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/img/light/remove.svg b/web/img/light/remove.svg new file mode 100644 index 00000000..0e96f987 --- /dev/null +++ b/web/img/light/remove.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/web/img/lin.png b/web/img/lin.png deleted file mode 100644 index 48678662..00000000 Binary files a/web/img/lin.png and /dev/null differ diff --git a/web/img/pencil.png b/web/img/pencil.png deleted file mode 100644 index d5ba3d59..00000000 Binary files a/web/img/pencil.png and /dev/null differ diff --git a/web/img/purple/add-person.svg b/web/img/purple/add-person.svg new file mode 100644 index 00000000..8ee48c28 --- /dev/null +++ b/web/img/purple/add-person.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/img/purple/edit.svg b/web/img/purple/edit.svg new file mode 100644 index 00000000..b6fe15cc --- /dev/null +++ b/web/img/purple/edit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/img/purple/remove.svg b/web/img/purple/remove.svg new file mode 100644 index 00000000..d11f6c57 --- /dev/null +++ b/web/img/purple/remove.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/img/s.gif b/web/img/s.gif deleted file mode 100644 index 1d11fa9a..00000000 Binary files a/web/img/s.gif and /dev/null differ diff --git a/web/img/skype.png b/web/img/skype.png deleted file mode 100644 index 4708c21c..00000000 Binary files a/web/img/skype.png and /dev/null differ diff --git a/web/img/so.png b/web/img/so.png deleted file mode 100644 index dae5738a..00000000 Binary files a/web/img/so.png and /dev/null differ