diff --git a/build.gradle b/build.gradle index 0f2b055d1..db021837f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '2.2.1.RELEASE' + id 'org.springframework.boot' version '2.4.4' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } @@ -22,6 +22,9 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'mysql:mysql-connector-java' + implementation 'org.springframework.boot:spring-boot-starter-validation' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f21..a4b442974 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/org/launchcode/codingevents/controllers/EventCategoryController.java b/src/main/java/org/launchcode/codingevents/controllers/EventCategoryController.java new file mode 100644 index 000000000..9c521f433 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/controllers/EventCategoryController.java @@ -0,0 +1,54 @@ +package org.launchcode.codingevents.controllers; + +import org.launchcode.codingevents.data.EventCategoryRepository; +import org.launchcode.codingevents.models.EventCategory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +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 javax.validation.Valid; + +/** + * Created by Chris Bay + */ +@Controller +@RequestMapping("eventCategories") +public class EventCategoryController { + + @Autowired + private EventCategoryRepository eventCategoryRepository; + + @GetMapping + public String displayAllCategories(Model model) { + model.addAttribute("title", "All Categories"); + model.addAttribute("categories", eventCategoryRepository.findAll()); + return "eventCategories/index"; + } + + @GetMapping("create") + public String renderCreateEventCategoryForm(Model model) { + model.addAttribute("title", "Create Category"); + model.addAttribute(new EventCategory()); + return "eventCategories/create"; + } + + @PostMapping("create") + public String processCreateEventCategoryForm(@Valid @ModelAttribute EventCategory eventCategory, + Errors errors, Model model) { + + if (errors.hasErrors()) { + model.addAttribute("title", "Create Category"); + model.addAttribute(new EventCategory()); + return "eventCategories/create"; + } + + eventCategoryRepository.save(eventCategory); + return "redirect:"; + } + +} diff --git a/src/main/java/org/launchcode/codingevents/controllers/EventController.java b/src/main/java/org/launchcode/codingevents/controllers/EventController.java index 2e837d2b0..126b7dc44 100644 --- a/src/main/java/org/launchcode/codingevents/controllers/EventController.java +++ b/src/main/java/org/launchcode/codingevents/controllers/EventController.java @@ -1,14 +1,20 @@ package org.launchcode.codingevents.controllers; +import org.launchcode.codingevents.data.EventCategoryRepository; +import org.launchcode.codingevents.data.EventRepository; +import org.launchcode.codingevents.data.TagRepository; +import org.launchcode.codingevents.models.Event; +import org.launchcode.codingevents.models.EventCategory; +import org.launchcode.codingevents.models.Tag; +import org.launchcode.codingevents.models.dto.EventTagDTO; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.List; +import javax.validation.Valid; +import java.util.Optional; /** * Created by Chris Bay @@ -17,25 +23,127 @@ @RequestMapping("events") public class EventController { - private static List events = new ArrayList<>(); + @Autowired + private EventRepository eventRepository; + + @Autowired + private EventCategoryRepository eventCategoryRepository; + + @Autowired + private TagRepository tagRepository; @GetMapping - public String displayAllEvents(Model model) { - model.addAttribute("title", "All Events"); - model.addAttribute("events", events); + public String displayEvents(@RequestParam(required = false) Integer categoryId, Model model, @RequestParam(required = false) Integer tagId) { + + if (categoryId == null & tagId == null) { + model.addAttribute("title", "All Events"); + model.addAttribute("events", eventRepository.findAll()); + } else { + Optional output = tagRepository.findById(tagId); + if (output.isPresent()){ + Tag tag = output.get(); + model.addAttribute("title", "Events by Tag: " + tag.getName()); + model.addAttribute("events", tag.getEvents()); + } else { + Optional result = eventCategoryRepository.findById(categoryId); + if (result.isEmpty()) { + model.addAttribute("title", "Invalid Category ID: " + categoryId); + } else { + EventCategory category = result.get(); + model.addAttribute("title", "Events in category: " + category.getName()); + model.addAttribute("events", category.getEvents()); + } + } + } + return "events/index"; } @GetMapping("create") public String displayCreateEventForm(Model model) { model.addAttribute("title", "Create Event"); + model.addAttribute(new Event()); + model.addAttribute("categories", eventCategoryRepository.findAll()); return "events/create"; } @PostMapping("create") - public String processCreateEventForm(@RequestParam String eventName) { - events.add(eventName); + public String processCreateEventForm(@ModelAttribute @Valid Event newEvent, + Errors errors, Model model) { + if(errors.hasErrors()) { + model.addAttribute("title", "Create Event"); + return "events/create"; + } + + eventRepository.save(newEvent); + return "redirect:"; + } + + @GetMapping("delete") + public String displayDeleteEventForm(Model model) { + model.addAttribute("title", "Delete Events"); + model.addAttribute("events", eventRepository.findAll()); + return "events/delete"; + } + + @PostMapping("delete") + public String processDeleteEventsForm(@RequestParam(required = false) int[] eventIds) { + + if (eventIds != null) { + for (int id : eventIds) { + eventRepository.deleteById(id); + } + } + return "redirect:"; } + @GetMapping("detail") + public String displayEventDetails(@RequestParam Integer eventId, Model model) { + + Optional result = eventRepository.findById(eventId); + + if (result.isEmpty()) { + model.addAttribute("title", "Invalid Event ID: " + eventId); + } else { + Event event = result.get(); + model.addAttribute("title", event.getName() + " Details"); + model.addAttribute("event", event); + model.addAttribute("tags", event.getTags()); + } + + return "events/detail"; + } + + // responds to /events/add-tag?eventId=13 + @GetMapping("add-tag") + public String displayAddTagForm(@RequestParam Integer eventId, Model model){ + Optional result = eventRepository.findById(eventId); + Event event = result.get(); + model.addAttribute("title", "Add Tag to: " + event.getName()); + model.addAttribute("tags", tagRepository.findAll()); + EventTagDTO eventTag = new EventTagDTO(); + eventTag.setEvent(event); + model.addAttribute("eventTag", eventTag); + return "events/add-tag.html"; + } + + @PostMapping("add-tag") + public String processAddTagForm(@ModelAttribute @Valid EventTagDTO eventTag, + Errors errors, + Model model){ + + if (!errors.hasErrors()) { + Event event = eventTag.getEvent(); + Tag tag = eventTag.getTag(); + if (!event.getTags().contains(tag)){ + event.addTag(tag); + eventRepository.save(event); + } + return "redirect:detail?eventId=" + event.getId(); + } + + return "redirect:add-tag"; + } + } diff --git a/src/main/java/org/launchcode/codingevents/controllers/TagController.java b/src/main/java/org/launchcode/codingevents/controllers/TagController.java new file mode 100644 index 000000000..37e569f60 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/controllers/TagController.java @@ -0,0 +1,53 @@ +package org.launchcode.codingevents.controllers; + +import org.launchcode.codingevents.data.TagRepository; +import org.launchcode.codingevents.models.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +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 javax.validation.Valid; + +/** + * Created by Chris Bay + */ +@Controller +@RequestMapping("tags") +public class TagController { + + @Autowired + TagRepository tagRepository; + + @GetMapping + public String displayTags(Model model) { + model.addAttribute("title", "All Tags"); + model.addAttribute("tags", tagRepository.findAll()); + return "tags/index"; + } + + @GetMapping("create") + public String displayCreateTagForm(Model model) { + model.addAttribute("title", "Create Tag"); + model.addAttribute(new Tag()); + return "tags/create"; + } + + @PostMapping("create") + public String processCreateTagForm(@ModelAttribute @Valid Tag tag, + Errors errors, Model model) { + + if (errors.hasErrors()) { + model.addAttribute("title", "Create Tag"); + model.addAttribute(tag); + return "tags/create"; + } + + tagRepository.save(tag); + return "redirect:"; + } +} diff --git a/src/main/java/org/launchcode/codingevents/data/EventCategoryRepository.java b/src/main/java/org/launchcode/codingevents/data/EventCategoryRepository.java new file mode 100644 index 000000000..b819c1a5b --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/data/EventCategoryRepository.java @@ -0,0 +1,13 @@ +package org.launchcode.codingevents.data; + +import org.launchcode.codingevents.models.EventCategory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by Chris Bay + */ +@Repository +public interface EventCategoryRepository extends CrudRepository { + +} diff --git a/src/main/java/org/launchcode/codingevents/data/EventRepository.java b/src/main/java/org/launchcode/codingevents/data/EventRepository.java new file mode 100644 index 000000000..6072b8fbf --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/data/EventRepository.java @@ -0,0 +1,12 @@ +package org.launchcode.codingevents.data; + +import org.launchcode.codingevents.models.Event; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by Chris Bay + */ +@Repository +public interface EventRepository extends CrudRepository { +} diff --git a/src/main/java/org/launchcode/codingevents/data/TagRepository.java b/src/main/java/org/launchcode/codingevents/data/TagRepository.java new file mode 100644 index 000000000..588d558ae --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/data/TagRepository.java @@ -0,0 +1,12 @@ +package org.launchcode.codingevents.data; + +import org.launchcode.codingevents.models.Tag; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * Created by Chris Bay + */ +@Repository +public interface TagRepository extends CrudRepository { +} diff --git a/src/main/java/org/launchcode/codingevents/models/AbstractEntity.java b/src/main/java/org/launchcode/codingevents/models/AbstractEntity.java new file mode 100644 index 000000000..5fcdfb34f --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/AbstractEntity.java @@ -0,0 +1,35 @@ +package org.launchcode.codingevents.models; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.util.Objects; + +/** + * Created by Chris Bay + */ +@MappedSuperclass +public abstract class AbstractEntity { + + @Id + @GeneratedValue + private int id; + + public int getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractEntity entity = (AbstractEntity) o; + return id == entity.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + +} diff --git a/src/main/java/org/launchcode/codingevents/models/Event.java b/src/main/java/org/launchcode/codingevents/models/Event.java new file mode 100644 index 000000000..8f5e959d3 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/Event.java @@ -0,0 +1,85 @@ +package org.launchcode.codingevents.models; + +import javax.persistence.*; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Chris Bay + */ +@Entity +public class Event extends AbstractEntity { + + @NotBlank(message = "Name is required") + @Size(min = 3, max = 50, message = "Name must be between 3 and 50 characters") + private String name; + + @OneToOne(cascade = CascadeType.ALL) + @Valid + @NotNull + private EventDetails eventDetails; + + @ManyToOne + @NotNull(message = "Category is required") + private EventCategory eventCategory; + + @ManyToMany + private final List tags = new ArrayList<>(); + + public Event(String name, EventCategory eventCategory) { + this.name = name; + this.eventCategory = eventCategory; + } + + public Event() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public EventCategory getEventCategory() { + return eventCategory; + } + + public void setEventCategory(EventCategory eventCategory) { + this.eventCategory = eventCategory; + } + + public EventDetails getEventDetails() { + return eventDetails; + } + + public void setEventDetails(EventDetails eventDetails) { + this.eventDetails = eventDetails; + } + + public List getTags() { + return tags; + } + + public void addTag(Tag tag) { + this.tags.add(tag); + } + +// public List getTags() { +// return tags; +// } + +// public void addTag(Tag tag){ +// this.tags.add(tag); +// } + + @Override + public String toString() { + return name; + } + +} diff --git a/src/main/java/org/launchcode/codingevents/models/EventCategory.java b/src/main/java/org/launchcode/codingevents/models/EventCategory.java new file mode 100644 index 000000000..da508ec36 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/EventCategory.java @@ -0,0 +1,44 @@ +package org.launchcode.codingevents.models; + +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Chris Bay + */ +@Entity +public class EventCategory extends AbstractEntity { + + @Size(min=3, message="Name must be at least 3 characters long") + private String name; + + @OneToMany(mappedBy = "eventCategory") + private final List events = new ArrayList<>(); + + public EventCategory(@Size(min = 3, message = "Name must be at least 3 characters long") String name) { + this.name = name; + } + + public EventCategory() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getEvents() { + return events; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/src/main/java/org/launchcode/codingevents/models/EventDetails.java b/src/main/java/org/launchcode/codingevents/models/EventDetails.java new file mode 100644 index 000000000..6bb1bf876 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/EventDetails.java @@ -0,0 +1,44 @@ +package org.launchcode.codingevents.models; + +import javax.persistence.Entity; +import javax.persistence.OneToOne; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +/** + * Created by Chris Bay + */ +@Entity +public class EventDetails extends AbstractEntity { + + @Size(max = 500, message = "Description too long!") + private String description; + + @NotBlank(message = "Email is required") + @Email(message = "Invalid email. Try again.") + private String contactEmail; + + public EventDetails(@Size(max = 500, message = "Description too long!") String description, @NotBlank(message = "Email is required") @Email(message = "Invalid email. Try again.") String contactEmail) { + this.description = description; + this.contactEmail = contactEmail; + } + + public EventDetails() {} + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getContactEmail() { + return contactEmail; + } + + public void setContactEmail(String contactEmail) { + this.contactEmail = contactEmail; + } +} diff --git a/src/main/java/org/launchcode/codingevents/models/Tag.java b/src/main/java/org/launchcode/codingevents/models/Tag.java new file mode 100644 index 000000000..04a8531f8 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/Tag.java @@ -0,0 +1,44 @@ +package org.launchcode.codingevents.models; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Chris Bay + */ +@Entity +public class Tag extends AbstractEntity { + + @Size(min = 1, max = 25) + @NotBlank + private String name; + + @ManyToMany(mappedBy = "tags") + private final List events = new ArrayList<>(); + + public Tag(String name) { + this.name = name; + } + + public Tag() {} + + public String getName() { + return name; + } + + public String getDisplayName() { + return "#" + name + " "; + } + + public void setName(String name) { + this.name = name; + } + + public List getEvents() { + return events; + } +} diff --git a/src/main/java/org/launchcode/codingevents/models/dto/EventTagDTO.java b/src/main/java/org/launchcode/codingevents/models/dto/EventTagDTO.java new file mode 100644 index 000000000..a2ad17c15 --- /dev/null +++ b/src/main/java/org/launchcode/codingevents/models/dto/EventTagDTO.java @@ -0,0 +1,36 @@ +package org.launchcode.codingevents.models.dto; + +import org.launchcode.codingevents.models.Event; +import org.launchcode.codingevents.models.Tag; + +import javax.validation.constraints.NotNull; + +/** + * Created by Chris Bay + */ +public class EventTagDTO { + + @NotNull + private Event event; + + @NotNull + private Tag tag; + + public EventTagDTO() {} + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + public Tag getTag() { + return tag; + } + + public void setTag(Tag tag) { + this.tag = tag; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b1378917..38084f5f5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,17 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/coding_events_demo +spring.datasource.username=coding_events_demo +spring.datasource.password=Learn2code! + +# Specify the DBMS +spring.jpa.database = MYSQL + +# Show or not log for each sql query +spring.jpa.show-sql = true + +# Hibernate ddl auto (create, create-drop, update) +spring.jpa.hibernate.ddl-auto = update + +# Use spring.jpa.properties.* for Hibernate native properties (the prefix is +# stripped before adding them to the entity manager) +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css index 654f3c43a..5369ff798 100644 --- a/src/main/resources/static/styles.css +++ b/src/main/resources/static/styles.css @@ -1,3 +1,6 @@ body { - font-size: 18px; + font-size: 14px; +} +.error { + color: red; } \ No newline at end of file diff --git a/src/main/resources/templates/eventCategories/create.html b/src/main/resources/templates/eventCategories/create.html new file mode 100644 index 000000000..20a307c64 --- /dev/null +++ b/src/main/resources/templates/eventCategories/create.html @@ -0,0 +1,19 @@ + + + + + +
+ +
+
+ + +
+ +
+ + + diff --git a/src/main/resources/templates/eventCategories/index.html b/src/main/resources/templates/eventCategories/index.html new file mode 100644 index 000000000..f52d412d1 --- /dev/null +++ b/src/main/resources/templates/eventCategories/index.html @@ -0,0 +1,21 @@ + + + + + +
+ + + + + + + + + + + +
Category Name
+ + + diff --git a/src/main/resources/templates/events/add-tag.html b/src/main/resources/templates/events/add-tag.html new file mode 100644 index 000000000..985b8ebfb --- /dev/null +++ b/src/main/resources/templates/events/add-tag.html @@ -0,0 +1,22 @@ + + + + + +
+ +
+
+ + +
+ +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/events/create.html b/src/main/resources/templates/events/create.html index bac9ddef0..5eb85493b 100644 --- a/src/main/resources/templates/events/create.html +++ b/src/main/resources/templates/events/create.html @@ -1,13 +1,43 @@ - +
- - +
+ +

+
+
+ +

+
+
+ +

+
+
+ +
+
+ +
diff --git a/src/main/resources/templates/events/delete.html b/src/main/resources/templates/events/delete.html new file mode 100644 index 000000000..97a1d7f46 --- /dev/null +++ b/src/main/resources/templates/events/delete.html @@ -0,0 +1,23 @@ + + + + + +
+ +
+ + +
+ +
+
+ + +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/events/detail.html b/src/main/resources/templates/events/detail.html new file mode 100644 index 000000000..3b1053ffc --- /dev/null +++ b/src/main/resources/templates/events/detail.html @@ -0,0 +1,40 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
ID
Description
Contact Email
Tags + +
+Add Tag + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/events/index.html b/src/main/resources/templates/events/index.html index 561ce87e2..4682ed304 100644 --- a/src/main/resources/templates/events/index.html +++ b/src/main/resources/templates/events/index.html @@ -1,15 +1,20 @@ - +
-
    - -
  • -
    -
+ + + + + + + + + +
Event Name
- \ No newline at end of file + diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 66b59a0f6..606c47d77 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -3,18 +3,27 @@ Coding Events + + + + - + - + - \ No newline at end of file + diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index b8aaaaaeb..8315be5dc 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,7 +1,7 @@ - +

Coding Events

diff --git a/src/main/resources/templates/tags/create.html b/src/main/resources/templates/tags/create.html new file mode 100644 index 000000000..cf78f777e --- /dev/null +++ b/src/main/resources/templates/tags/create.html @@ -0,0 +1,19 @@ + + + + + +
+ +
+
+ + +
+ +
+ + + diff --git a/src/main/resources/templates/tags/index.html b/src/main/resources/templates/tags/index.html new file mode 100644 index 000000000..bfacdee6e --- /dev/null +++ b/src/main/resources/templates/tags/index.html @@ -0,0 +1,21 @@ + + + + + +
+ + + + + + + + + + + +
Tag Name
+ + +