From 1174f03d42a6213bafdb5afdf55d0c3caccccc7f Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Mon, 1 Jul 2024 15:50:57 +0200 Subject: [PATCH 01/21] added paging functionality --- src/main/resources/static/contentUtility.js | 16 ++++--- src/main/resources/static/main.js | 50 ++++++++++++++------- src/main/resources/templates/index.html | 1 + 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index deb5658..7555315 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,22 +1,28 @@ -import {searchBarTimeout, searchSection} from "./main.js" +import {searchBarTimeout, searchSection, lastQuery} from "./main.js" import Dataset from "./dataset.js" -export function fetchQuery(fetchString) { +export function fetchQuery(fetchString, clearResults) { clearTimeout(searchBarTimeout); fetch(fetchString) .then(resp => resp.json()) .then((data) => { - parseContent(data.content); + parseContent(data.content, clearResults); + lastQuery.totalPages = data.totalPages; + if (clearResults) { + lastQuery.currentPage = 0; + } }); } -function parseContent(content) { +function parseContent(content, clearResults) { if (content.length === 0) { searchSection.querySelector("#nothing-found ").classList.remove("hidden"); } else { searchSection.querySelector("#nothing-found").classList.add("hidden"); const datasets = content.map(dataset => new Dataset(dataset)); - Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); + if (clearResults) { + Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); + } for (const dataset of datasets) { searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement()); } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 77af7e6..fb8700d 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -4,7 +4,7 @@ import Dataset from "./dataset.js"; const apiEndpoint = "/api/v1/datasets"; const baseURL = location.origin; const defaultPagingValue = 20; -const lastQuery = { +export const lastQuery = { url: "", totalPages: 0, currentPage: 0 @@ -35,12 +35,12 @@ addButton.addEventListener("click", () => { filterButton.addEventListener("change", () => { const filterString = filterButton.value; if (filterString !== filterButton.querySelector("#default-filter").value) { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); } }); searchButton.addEventListener("click", () => { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); }); @@ -48,18 +48,18 @@ searchBar.addEventListener("input", () => { updateSections(); clearTimeout(searchBarTimeout); searchBarTimeout = setTimeout(() => { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); }, searchDelay); }); searchBar.addEventListener('keypress', function (e) { if (e.key === 'Enter') { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); } }); sortButton.addEventListener("change", () => { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); }); resetButton.addEventListener("click", () => { @@ -79,7 +79,7 @@ for (const upvoteButton of upvoteButtons) { } // Consider moving this to datasets.js completely -const downvoteButtonClickListener = e => { +const downvoteButtonClickListener = e => { const entryID = e.target.parentElement.parentElement.dataset.id; vote(entryID, false); }; @@ -93,7 +93,7 @@ function navigateToAdd() { } function getFilterQuery() { - let filterString= filterButton.value.toUpperCase(); + let filterString = filterButton.value.toUpperCase(); if (filterString === "NONE") { return ["type", "%"] } else if (document.querySelector('#filter-btn option:checked').parentElement.label === "Standard categories") { @@ -141,13 +141,14 @@ export function vote(entryID, up) { method: "PUT", headers: { 'Content-Type': 'application/json', - }}) - .then(resp => resp.json()) - .then((data) => { - console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID? - let dataset = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']') - dataset.querySelector("span").innerText = data.upvotes; - }); + } + }) + .then(resp => resp.json()) + .then((data) => { + console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID? + let dataset = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']') + dataset.querySelector("span").innerText = data.upvotes; + }); } function incrementPageCount() { @@ -173,7 +174,7 @@ function updateSections() { // fetches the further categories used in the filter function function fetchCategories() { const fetchURL = new URL( - "api/v1/categories" , baseURL); + "api/v1/categories", baseURL); fetch(fetchURL) .then(resp => resp.json()) .then((data) => { @@ -217,6 +218,21 @@ window.onload = function () { fetchInitialEntries(); updateSections(); if (searchBar.value !== "") { - fetchQuery(createQuery()); + fetchQuery(createQuery(), true); + } + let observer = new IntersectionObserver(() => { + if (!searchSection.classList.contains("hidden")) { + fetchPagingResults(); + } + }, {root: null, rootMargin: "0px", threshold: .9}); + observer.observe(document.getElementById("observable")); +} + +function fetchPagingResults() { + lastQuery.currentPage++ + if (lastQuery.currentPage < lastQuery.totalPages) { + let pagingQuery = new URL(createQuery()); + pagingQuery.searchParams.append("page", lastQuery.currentPage.toString(10)); + fetchQuery(pagingQuery, false); } } diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 23ee989..34982f9 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -78,6 +78,7 @@ +
From 7554eefae4c85a251eae38a7cc9baf8a588f26a4 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Tue, 2 Jul 2024 10:41:52 +0200 Subject: [PATCH 02/21] feat: Add ability to add categories --- .../controler/CategoryController.java | 31 +++++++++-- .../PADAS/group3/DataDash/model/Category.java | 53 +++++++++++++++---- .../group3/DataDash/model/CategoryDto.java | 27 ++++++++++ .../DataDash/model/CategoryDtoMapper.java | 10 ++++ .../DataDash/model/CategoryRepository.java | 18 +++++++ .../DataDash/model/CategoryService.java | 32 +++++++++++ .../PADAS/group3/DataDash/model/Dataset.java | 4 +- .../group3/DataDash/model/DatasetService.java | 13 +++-- .../DataDash/model/LoadDummyDatabase.java | 8 ++- .../group3/DataDash/model/dataRepository.java | 17 ++++-- 10 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java create mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java create mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java create mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java index 06454ed..5f725a3 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java @@ -1,18 +1,43 @@ package de.uni_passau.fim.PADAS.group3.DataDash.controler; +import java.util.List; +import java.util.UUID; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import de.uni_passau.fim.PADAS.group3.DataDash.model.Category; +import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryDto; +import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + + @RestController @RequestMapping("/api/v1/categories") public class CategoryController { + @Autowired + private CategoryService categoryService; + @GetMapping - public Category[] getMethodName() { - return Category.values(); + public List getMethodName() { + return categoryService.getAllCategories() ; } + + @GetMapping("/id/{id}") + public CategoryDto getMethodName(@PathVariable("id") UUID id) { + return categoryService.getCategoryById(id); + } + + @PostMapping + public String postMethodName(@RequestBody CategoryDto dto) { + categoryService.addCategory(dto); + return null; + } + + } diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java index b7cc30e..4246baa 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java @@ -1,14 +1,45 @@ package de.uni_passau.fim.PADAS.group3.DataDash.model; -public enum Category { - HEALTH, - ENVIRONMENT, - ECONOMY, - POLITICS, - TECHNOLOGY, - SPORTS, - SCIENCE, - CULTURE, - EDUCATION, - OTHER +import java.util.List; +import java.util.UUID; + +import org.springframework.context.annotation.Lazy; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + +@Entity +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + private String name; + + @Lazy + @OneToMany(mappedBy = "categorie") + private List datasets; + + public Category(String name) { + this.name = name; + } + + public Category() { + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java new file mode 100644 index 0000000..a21d0cf --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java @@ -0,0 +1,27 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.model; + +import java.util.UUID; + +public class CategoryDto { + + private String name; + private UUID id; + + public CategoryDto() { + } + + CategoryDto(String name, UUID id) { + this.name = name; + this.id = id; + } + + + public String getName() { + return name; + } + + public UUID getId() { + return id; + } +} + diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java new file mode 100644 index 0000000..e7ecc10 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java @@ -0,0 +1,10 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.model; + +public class CategoryDtoMapper { + + public static CategoryDto toDto(Category category) { + CategoryDto dto = new CategoryDto(category.getName(), category.getId()); + return dto; + } + +} diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java new file mode 100644 index 0000000..4b179ad --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java @@ -0,0 +1,18 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.model; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface CategoryRepository extends JpaRepository{ + + Category getCategoryById(UUID id); + + List findAll(); + List findByName(String name); + Optional findById(UUID id); + +} diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java new file mode 100644 index 0000000..ba36dc0 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java @@ -0,0 +1,32 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.model; + +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.UUID; + + +@Service +public class CategoryService { + private CategoryRepository categoryRepository; + + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + public void addCategory(CategoryDto category) { + Category cat = new Category(category.getName()); + categoryRepository.save(cat); + } + + public List getAllCategories() { + List tmp = categoryRepository.findAll(); + List s = tmp.stream().map(CategoryDtoMapper::toDto).toList(); + return s; + } + + public CategoryDto getCategoryById(UUID id) { + return CategoryDtoMapper.toDto(categoryRepository.getCategoryById(id)); + } + + +} diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java index 7d694fb..91f71a2 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java @@ -11,6 +11,7 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; @Entity public class Dataset { @@ -39,7 +40,8 @@ public class Dataset { private int upvotes; private URL url; - @Enumerated(EnumType.STRING) + + @ManyToOne private Category categorie; public Dataset(String title, String abst, String description, String author, URL url, Category categories, Type type) { diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java index a3ac52d..6694907 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java @@ -13,9 +13,11 @@ import org.springframework.data.domain.Page; @Service public class DatasetService { private dataRepository datasetRepository; + private CategoryRepository categoryRepository; - public DatasetService(dataRepository datasetRepository) { + public DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) { this.datasetRepository = datasetRepository; + this.categoryRepository = categoryRepository; } public List getAllDatasets() { @@ -95,9 +97,14 @@ public class DatasetService { public Page searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) { //TODO: make it not Crash - Category category = categories.equals("%") ? null : Category.valueOf(categories); + //TODO: make it do useful stuff + Category category = categories.equals("%") ? null : categoryRepository.getCategoryById(UUID.fromString(categories)) ; Type t = type.equals("%") ? null : Type.valueOf(type); - return datasetRepository.searchByOptionalCriteria(Optional.ofNullable(search), Optional.ofNullable(category), Optional.ofNullable(t),pageable); + if(category == null){ + return datasetRepository.searchByOptionalCriteria(Optional.ofNullable(search), Optional.ofNullable(t),pageable); + } + return datasetRepository.searchByOptionalCriteriaWithCategory(Optional.ofNullable(search), category, Optional.ofNullable(t),pageable); } + } \ No newline at end of file diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java index dd6d8e0..18d9085 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java @@ -16,17 +16,21 @@ public class LoadDummyDatabase { private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoadDummyDatabase.class); @Bean - CommandLineRunner initDatabase(dataRepository repository) { + CommandLineRunner initDatabase(dataRepository repository, CategoryRepository categoryRepository) { return args -> { for (int i = 0; i < 100; i++) { - Dataset dataset = new Dataset("Title" + i, "Abst" + i, "Description" + i, "Author" + i,null, Category.EDUCATION, Type.API); + Category category = new Category("Category" + i); + log.info("Preloading" + categoryRepository.save(category)); + + Dataset dataset = new Dataset("Title" + i, "Abst" + i, "Description" + i, "Author" + i,null, category, Type.API); for (int j = 0; j < new Random().nextInt(50); j++) { dataset.upvote(); } log.info("Preloading" + repository.save(dataset)); + log.info("Preloading" + categoryRepository.save(category)); } List s = repository.findByTitleLike("%Title%"); log.info("Found Entry with ID: " + s.get(1).getId());}; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java index 17fde64..130415d 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java @@ -65,10 +65,19 @@ public interface dataRepository extends JpaRepository { "((LOWER(d.title) LIKE LOWER(:search)) OR " + "(LOWER(d.description) LIKE LOWER(:search)) OR " + "(LOWER(d.author) LIKE LOWER(:search))) AND" + - "(:categorie IS NULL OR d.categorie = :categorie) AND" + + "(d.categorie = :categorie) AND" + "(:type IS NULL OR d.type = :type)") - Page searchByOptionalCriteria(@Param("search") Optional search, - @Param("categorie") Optional categories, + Page searchByOptionalCriteriaWithCategory(@Param("search") Optional search, + @Param("categorie") Category categories, @Param("type") Optional type, Pageable pageable); -} \ No newline at end of file + + @Query("SELECT d FROM Dataset d WHERE " + + "((LOWER(d.title) LIKE LOWER(:search)) OR " + + "(LOWER(d.description) LIKE LOWER(:search)) OR " + + "(LOWER(d.author) LIKE LOWER(:search))) AND" + + "(:type IS NULL OR d.type = :type)") + Page searchByOptionalCriteria(@Param("search") Optional search, + @Param("type") Optional type, + Pageable pageable); +} From d48f9da2b77e4365acdbed384c98e5dd066d48e2 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Tue, 2 Jul 2024 10:45:11 +0200 Subject: [PATCH 03/21] chore: Suppress null warnings in CategoryRepository --- .../fim/PADAS/group3/DataDash/model/CategoryRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java index 4b179ad..a0b67e5 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java @@ -11,8 +11,10 @@ public interface CategoryRepository extends JpaRepository{ Category getCategoryById(UUID id); + @SuppressWarnings("null") List findAll(); List findByName(String name); + @SuppressWarnings("null") Optional findById(UUID id); } From 9434de5bfcbd0f87dea789dbd940a360c573f4aa Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Tue, 2 Jul 2024 14:39:08 +0200 Subject: [PATCH 04/21] chore: Update H2 database dependency and configuration --- pom.xml | 4 +-- .../DataDash/model/LoadDummyDatabase.java | 2 +- src/main/resources/application.properties | 13 ++++++++ src/main/resources/data.sql | 30 +++++++++++++++++++ src/main/resources/schema.sql | 7 +++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/schema.sql diff --git a/pom.xml b/pom.xml index 68a734d..5d76e0c 100644 --- a/pom.xml +++ b/pom.xml @@ -52,8 +52,8 @@ - org.hsqldb - hsqldb + com.h2database + h2 runtime diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java index 18d9085..ba3dd32 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java @@ -15,7 +15,7 @@ public class LoadDummyDatabase { private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoadDummyDatabase.class); - @Bean + //@Bean CommandLineRunner initDatabase(dataRepository repository, CategoryRepository categoryRepository) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 11b8d6e..defb9dd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,14 @@ spring.application.name=DataDash + +# Datasource configuration +spring.datasource.url=jdbc:h2:mem:studentcoursedb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.sql.init.mode=always +spring.jpa.hibernate.ddl-auto=none + +# Uncomment for web console +#spring.h2.console.enabled=true +#spring.h2.console.path=/h2-console +#spring.datasource.username=sa +#spring.datasource.password=pwd \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..68993c1 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,30 @@ +-- Insert sample data into category +INSERT INTO category (id, name) VALUES +('123e4567-e89b-12d3-a456-426614174003', 'Business'), +('123e4567-e89b-12d3-a456-426614174004', 'Education'), +('123e4567-e89b-12d3-a456-426614174005', 'Sports'), +('123e4567-e89b-12d3-a456-426614174006', 'Entertainment'), +('123e4567-e89b-12d3-a456-426614174007', 'Art'), +('123e4567-e89b-12d3-a456-426614174000', 'Science'), +('123e4567-e89b-12d3-a456-426614174001', 'Technology'), +('123e4567-e89b-12d3-a456-426614174002', 'Health'); + +-- Insert sample data into dataset +INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type) VALUES +('2023-01-01', 4.5, 100, 120, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174100', 'Abstract 1', 'Author 1', 'Description 1', 'Title 1', 'http://example.com/1', 'API'), +('2023-01-02', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174101', 'Abstract 2', 'Author 2', 'Description 2', 'Title 2', 'http://example.com/2', 'DATASET'), +('2023-01-03', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174102', 'Abstract 3', 'Author 3', 'Description 3', 'Title 3', 'http://example.com/3', 'API'), +('2023-01-04', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174103', 'Abstract 4', 'Author 4', 'Description 4', 'Title 4', 'http://example.com/4', 'DATASET'), +('2023-01-05', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174104', 'Abstract 5', 'Author 5', 'Description 5', 'Title 5', 'http://example.com/5', 'API'); +-- Insert 10 more sample data into dataset +INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type) VALUES +('2023-01-06', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174105', 'Abstract 6', 'Author 6', 'Description 6', 'Title 6', 'http://example.com/6', 'API'), +('2023-01-07', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174106', 'Abstract 7', 'Author 7', 'Description 7', 'Title 7', 'http://example.com/7', 'DATASET'), +('2023-01-08', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174007', '123e4567-e89b-12d3-a456-426614174107', 'Abstract 8', 'Author 8', 'Description 8', 'Title 8', 'http://example.com/8', 'API'), +('2023-01-09', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174108', 'Abstract 9', 'Author 9', 'Description 9', 'Title 9', 'http://example.com/9', 'DATASET'), +('2023-01-10', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174109', 'Abstract 10', 'Author 10', 'Description 10', 'Title 10', 'http://example.com/10', 'API'), +('2023-11-11', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174110', 'Abstract 11', 'Author 11', 'Description 11', 'Title 11', 'http://example.com/11', 'DATASET'), +('2023-09-12', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174111', 'Abstract 12', 'Author 12', 'Description 12', 'Title 12', 'http://example.com/12', 'API'), +('2023-03-13', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174112', 'Abstract 13', 'Author 13', 'Description 13', 'Title 13', 'http://example.com/13', 'DATASET'), +('2021-01-14', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174113', 'Abstract 14', 'Author 14', 'Description 14', 'Title 14', 'http://example.com/14', 'API'), +('2024-01-15', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174114', 'Abstract 15', 'Author 15', 'Description 15', 'Title 15', 'http://example.com/15', 'DATASET'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..bb7c8b1 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS dataset; +DROP TABLE IF EXISTS category; + + +create table category (id uuid not null, name varchar(255), primary key (id)); +create table dataset (date date, raiting float(24) not null, upvotes integer not null, votes integer not null, categorie_id uuid, id uuid not null, abst varchar(255), author varchar(255), description varchar(255), title varchar(255), url varchar(255), type enum ('API','DATASET'), primary key (id)); +alter table if exists dataset add constraint FKq6qwq6u473f89h71s7rf97ruy foreign key (categorie_id) references category; From 20eda5931a9be266f92252b3d16fbf2400fb7a5e Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Tue, 2 Jul 2024 17:41:48 +0200 Subject: [PATCH 05/21] started implementing local storage --- src/main/resources/static/contentUtility.js | 8 +++++-- src/main/resources/static/dataset.js | 13 ++++++++++- src/main/resources/static/main.css | 7 ++++++ src/main/resources/static/main.js | 25 +++++++++++++-------- src/main/resources/templates/index.html | 2 +- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index 7555315..fb4a75e 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,4 +1,4 @@ -import {searchBarTimeout, searchSection, lastQuery} from "./main.js" +import {searchBarTimeout, searchSection, lastQuery, votedIDs} from "./main.js" import Dataset from "./dataset.js" export function fetchQuery(fetchString, clearResults) { @@ -24,7 +24,11 @@ function parseContent(content, clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); } for (const dataset of datasets) { - searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement()); + if (votedIDs.has(dataset.getID())) { + searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement(false,)); + } else { + searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement()); + } } } diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index 27245e5..bef41a7 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -29,13 +29,20 @@ export default class Dataset { this.#votes = votes; } - createDatasetHTMLElement() { + createDatasetHTMLElement(votable, isUpVoted) { let template = document.querySelector("#dataset-template"); const clone = template.content.cloneNode(true); clone.querySelector(".dataset").dataset.id = this.#id; clone.querySelector("h3").innerText = this.#title; clone.querySelector("p").innerText = this.#description; clone.querySelector("span").innerText = this.#upvotes; + if (!votable) { + let votedButton = clone.querySelector(isUpVoted? ".upvote-btn":".downvote-btn"); + votedButton.classList.add("isVoted"); + votedButton.disabled = true; + let notVotedButton = clone.querySelector(isUpVoted? ".downvote-btn":".upvote-btn"); + notVotedButton.style.visibility = "hidden"; + } // Event Listeners clone.querySelector(".upvote-btn").addEventListener("click", () => { @@ -48,4 +55,8 @@ export default class Dataset { return clone; } + + getID() { + return this.#id; + } } diff --git a/src/main/resources/static/main.css b/src/main/resources/static/main.css index c62d21e..ab5e5bf 100644 --- a/src/main/resources/static/main.css +++ b/src/main/resources/static/main.css @@ -81,6 +81,7 @@ header { .hidden { display: none; } + #search-entry:focus-visible { outline: none; } @@ -117,6 +118,7 @@ header { align-items: center; gap: .5em; } + /* Buttons */ .upvote-btn, .downvote-btn, #search-btn, #filter-btn, #sort-btn, #reset-tools-btn { background: var(--icon-url) no-repeat; @@ -143,6 +145,7 @@ header { --icon-url: url(looking-glass.svg); --icon-size: 1rem; } + #filter-btn { --icon-url: url(filter.svg); --icon-size: 1rem; @@ -158,6 +161,10 @@ header { --icon-size: 1rem; } +.isVoted { + filter: brightness(1.75); +} + .divider { width: .05rem; height: 1rem; diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index fb8700d..5599b26 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -5,10 +5,10 @@ const apiEndpoint = "/api/v1/datasets"; const baseURL = location.origin; const defaultPagingValue = 20; export const lastQuery = { - url: "", totalPages: 0, currentPage: 0 }; +export const votedIDs = new Map; // definition of all buttons & sections const addButton = document.getElementById("add-btn"); @@ -146,13 +146,18 @@ export function vote(entryID, up) { .then(resp => resp.json()) .then((data) => { console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID? - let dataset = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']') - dataset.querySelector("span").innerText = data.upvotes; - }); -} + let dataSet = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']') + dataSet.querySelector("span").innerText = data.upvotes; + + let votedButton = dataSet.querySelector(up? ".upvote-btn":".downvote-btn"); + votedButton.classList.add("isVoted"); + votedButton.disabled = true; + let notVotedButton = dataSet.querySelector(up? ".downvote-btn":".upvote-btn"); + notVotedButton.style.visibility = "hidden"; + console.log(dataSet.getAttribute("data-id")); + votedIDs.set(dataSet.getAttribute("data-id"), up); + }); -function incrementPageCount() { - lastQuery.currentPage++; } // updates the page display. If no query is present, the initial page is shown, otherwise the search results. @@ -197,6 +202,7 @@ function fetchInitialEntries() { .then((data) => { const datasets = data.content.map(dataset => new Dataset(dataset)); for (const dataset of datasets) { + //dataSets.add(dataset); document.querySelector("#recents .datasets").appendChild(dataset.createDatasetHTMLElement()); } }); @@ -208,8 +214,9 @@ function fetchInitialEntries() { fetch(topVotedQueryURL) .then(resp => resp.json()) .then((data) => { - document.querySelector("#top .datasets") - .appendChild(new Dataset(data.content[0]).createDatasetHTMLElement()); + let dataset = new Dataset(data.content[0]); + //dataSets.add(dataset); + document.querySelector("#top .datasets").appendChild(dataset.createDatasetHTMLElement()); }); } diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 34982f9..5f8cbdd 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -5,7 +5,7 @@ DataDash - +
From 6d34b8c388afca071fe0ecd8368055e0f09289cf Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Wed, 3 Jul 2024 11:27:49 +0200 Subject: [PATCH 06/21] upvoting suppression by local storage now works, there is a bug however with the entries loaded o startup --- src/main/resources/static/contentUtility.js | 11 ++++------- src/main/resources/static/dataset.js | 12 +++++++----- src/main/resources/static/main.js | 11 +++++------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index fb4a75e..6030686 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,4 +1,4 @@ -import {searchBarTimeout, searchSection, lastQuery, votedIDs} from "./main.js" +import {searchBarTimeout, searchSection, lastQuery} from "./main.js" import Dataset from "./dataset.js" export function fetchQuery(fetchString, clearResults) { @@ -24,12 +24,9 @@ function parseContent(content, clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); } for (const dataset of datasets) { - if (votedIDs.has(dataset.getID())) { - searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement(false,)); - } else { - searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement()); - } + console.log(dataset) //TODO: remove + searchSection.querySelector(".datasets") + .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); } } - } diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index bef41a7..b13b7b6 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,4 +1,4 @@ -import { vote } from "./main.js"; +import { vote, votedIDs } from "./main.js"; export default class Dataset { #abstract; @@ -29,18 +29,20 @@ export default class Dataset { this.#votes = votes; } - createDatasetHTMLElement(votable, isUpVoted) { + createDatasetHTMLElement(entryID) { let template = document.querySelector("#dataset-template"); const clone = template.content.cloneNode(true); clone.querySelector(".dataset").dataset.id = this.#id; clone.querySelector("h3").innerText = this.#title; clone.querySelector("p").innerText = this.#description; clone.querySelector("span").innerText = this.#upvotes; - if (!votable) { - let votedButton = clone.querySelector(isUpVoted? ".upvote-btn":".downvote-btn"); + + // depending on whether the button has been up/downvoted, its according button get disabled and hidden + if (votedIDs.has(entryID)) { + let votedButton = clone.querySelector(votedIDs.get(entryID)? ".upvote-btn":".downvote-btn"); votedButton.classList.add("isVoted"); votedButton.disabled = true; - let notVotedButton = clone.querySelector(isUpVoted? ".downvote-btn":".upvote-btn"); + let notVotedButton = clone.querySelector(votedIDs.get(entryID)? ".downvote-btn":".upvote-btn"); notVotedButton.style.visibility = "hidden"; } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 5599b26..8edc846 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -89,7 +89,7 @@ for (const downvoteButton of downvoteButtons) { // functions of the main page function navigateToAdd() { - window.location.href = "/add"; + window.location.href = "/add"; //TODO: move to EventListner? } function getFilterQuery() { @@ -154,7 +154,6 @@ export function vote(entryID, up) { votedButton.disabled = true; let notVotedButton = dataSet.querySelector(up? ".downvote-btn":".upvote-btn"); notVotedButton.style.visibility = "hidden"; - console.log(dataSet.getAttribute("data-id")); votedIDs.set(dataSet.getAttribute("data-id"), up); }); @@ -202,8 +201,8 @@ function fetchInitialEntries() { .then((data) => { const datasets = data.content.map(dataset => new Dataset(dataset)); for (const dataset of datasets) { - //dataSets.add(dataset); - document.querySelector("#recents .datasets").appendChild(dataset.createDatasetHTMLElement()); + document.querySelector("#recents .datasets") + .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); } }); @@ -215,8 +214,8 @@ function fetchInitialEntries() { .then(resp => resp.json()) .then((data) => { let dataset = new Dataset(data.content[0]); - //dataSets.add(dataset); - document.querySelector("#top .datasets").appendChild(dataset.createDatasetHTMLElement()); + document.querySelector("#top .datasets") + .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); }); } From 02d2f90e85141893fc75c43d65e56ab6c97aa1ea Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Wed, 3 Jul 2024 11:52:29 +0200 Subject: [PATCH 07/21] fixed bug --- src/main/resources/static/contentUtility.js | 2 +- src/main/resources/static/main.js | 24 ++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index 6030686..d08c0be 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,6 +1,6 @@ import {searchBarTimeout, searchSection, lastQuery} from "./main.js" import Dataset from "./dataset.js" - +// TODO consider renaming this to "searchUtility.js" export function fetchQuery(fetchString, clearResults) { clearTimeout(searchBarTimeout); fetch(fetchString) diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 8edc846..78eaa29 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -69,7 +69,7 @@ resetButton.addEventListener("click", () => { updateSections(); }); -// Consider moving this to datasets.js completely +// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js const upvoteButtonClickListener = e => { const entryID = e.target.parentElement.parentElement.dataset.id; vote(entryID, true); @@ -78,7 +78,7 @@ for (const upvoteButton of upvoteButtons) { upvoteButton.addEventListener("click", upvoteButtonClickListener); } -// Consider moving this to datasets.js completely +// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js const downvoteButtonClickListener = e => { const entryID = e.target.parentElement.parentElement.dataset.id; vote(entryID, false); @@ -146,17 +146,17 @@ export function vote(entryID, up) { .then(resp => resp.json()) .then((data) => { console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID? - let dataSet = document.querySelector('[data-id= ' + CSS.escape(entryID) + ']') - dataSet.querySelector("span").innerText = data.upvotes; - - let votedButton = dataSet.querySelector(up? ".upvote-btn":".downvote-btn"); - votedButton.classList.add("isVoted"); - votedButton.disabled = true; - let notVotedButton = dataSet.querySelector(up? ".downvote-btn":".upvote-btn"); - notVotedButton.style.visibility = "hidden"; - votedIDs.set(dataSet.getAttribute("data-id"), up); + let dataSets = document.querySelectorAll('[data-id= ' + CSS.escape(entryID) + ']'); + for (const dataSetElement of dataSets) { + dataSetElement.querySelector("span").innerText = data.upvotes; + let votedButton = dataSetElement.querySelector(up? ".upvote-btn":".downvote-btn"); + votedButton.classList.add("isVoted"); + votedButton.disabled = true; + let notVotedButton = dataSetElement.querySelector(up? ".downvote-btn":".upvote-btn"); + notVotedButton.style.visibility = "hidden"; + votedIDs.set(dataSetElement.getAttribute("data-id"), up); + } }); - } // updates the page display. If no query is present, the initial page is shown, otherwise the search results. From 62b0d5c028a1f8140f0904b0f94229a6c095b5d1 Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Wed, 3 Jul 2024 12:08:59 +0200 Subject: [PATCH 08/21] local storage now properly implemented, (sessionstorage) --- src/main/resources/static/dataset.js | 6 +++--- src/main/resources/static/main.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index b13b7b6..947770f 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -38,11 +38,11 @@ export default class Dataset { clone.querySelector("span").innerText = this.#upvotes; // depending on whether the button has been up/downvoted, its according button get disabled and hidden - if (votedIDs.has(entryID)) { - let votedButton = clone.querySelector(votedIDs.get(entryID)? ".upvote-btn":".downvote-btn"); + if (votedIDs.getItem(entryID)) { + let votedButton = clone.querySelector(votedIDs.getItem(entryID)? ".upvote-btn":".downvote-btn"); votedButton.classList.add("isVoted"); votedButton.disabled = true; - let notVotedButton = clone.querySelector(votedIDs.get(entryID)? ".downvote-btn":".upvote-btn"); + let notVotedButton = clone.querySelector(votedIDs.getItem(entryID)? ".downvote-btn":".upvote-btn"); notVotedButton.style.visibility = "hidden"; } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 78eaa29..52ee670 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -8,7 +8,7 @@ export const lastQuery = { totalPages: 0, currentPage: 0 }; -export const votedIDs = new Map; +export const votedIDs = window.sessionStorage; // definition of all buttons & sections const addButton = document.getElementById("add-btn"); @@ -154,7 +154,7 @@ export function vote(entryID, up) { votedButton.disabled = true; let notVotedButton = dataSetElement.querySelector(up? ".downvote-btn":".upvote-btn"); notVotedButton.style.visibility = "hidden"; - votedIDs.set(dataSetElement.getAttribute("data-id"), up); + votedIDs.setItem(dataSetElement.getAttribute("data-id"), up); } }); } From 0ca9579949e8e1e897fa39986fcc38288af6b6fc Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Wed, 3 Jul 2024 15:03:14 +0200 Subject: [PATCH 09/21] feat: Add error handling for dataset endpoints & return dataset that is beeing returend for the abilty to get the id --- .../DataDash/controler/DatasetController.java | 72 +++++++++++-------- .../group3/DataDash/model/DatasetService.java | 4 +- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java index 8a69a50..6bdc396 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java @@ -9,8 +9,11 @@ import de.uni_passau.fim.PADAS.group3.DataDash.model.Category; import de.uni_passau.fim.PADAS.group3.DataDash.model.Dataset; import de.uni_passau.fim.PADAS.group3.DataDash.model.DatasetService; import de.uni_passau.fim.PADAS.group3.DataDash.model.Type; + import org.springframework.data.domain.Pageable; import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.data.domain.Sort; import java.util.UUID; @@ -23,59 +26,65 @@ import org.springframework.web.bind.annotation.RequestBody; public class DatasetController { @Autowired private DatasetService datasetService; - - // @GetMapping - // public List getAllDatasets() { - // return datasetService.getAllDatasets(); - // } - + @GetMapping("/id/{id}") - public Dataset getDatasetById(@PathVariable("id") UUID id) { - return datasetService.getDatasetById(id); + public ResponseEntity getDatasetById(@PathVariable("id") UUID id) { + Dataset d = datasetService.getDatasetById(id); + if (d == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(d , HttpStatus.OK); } + @ResponseStatus(HttpStatus.CREATED) @PostMapping public Dataset createDataset(@RequestBody Dataset dataset) { - datasetService.addDataset(dataset); - // TODO: figure out what the fuck i need to do here - return null; + return datasetService.addDataset(dataset); } - // @PutMapping("/{id}") - // public Dataset updateDataset(@PathVariable("id") Long id, @RequestBody - // Dataset dataset) { - // return datasetService.updateDataset(id, dataset); - // } - // - @DeleteMapping("/id/{id}") - public void deleteDataset(@PathVariable("id") UUID id) { + public ResponseEntity deleteDataset(@PathVariable("id") UUID id) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } datasetService.deleteDataset(id); + return new ResponseEntity<>(HttpStatus.OK); } @PutMapping("/id/{id}/upvote") - public Dataset upvote(@PathVariable("id") UUID id) { + public ResponseEntity upvote(@PathVariable("id") UUID id) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + datasetService.upvoteDataset(id); - return datasetService.getDatasetById(id); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); } @PutMapping("/id/{id}/downvote") - public Dataset downvote(@PathVariable("id") UUID id) { + public ResponseEntity downvote(@PathVariable("id") UUID id) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } datasetService.downvoteDataset(id); - return getDatasetById(id); // new ResponseEntity<>(null, HttpStatus.OK); + return new ResponseEntity<>(getDatasetById(id), HttpStatus.OK); } @PutMapping("/id/{id}/vote") - public Dataset postMethodName(@PathVariable("id") UUID id, + public ResponseEntity postMethodName(@PathVariable("id") UUID id, @RequestParam("stars") int stars) { - if (stars > 0 && stars < 6) { - datasetService.voteDataset(id, stars); + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - return datasetService.getDatasetById(id); + if (!(stars > 0 && stars < 6)) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + datasetService.voteDataset(id, stars); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); } @GetMapping - public Page getDatasetsByDateAfter(@RequestParam(value = "author", required = false) String author, + public Page getDatasetsByDateAfter(@RequestParam(value = "author", required = false) String author, @RequestParam(value = "title", required = false) String title, @RequestParam(value = "description", required = false) String description, @RequestParam(value = "abst", required = false) String abst, @@ -88,7 +97,8 @@ public class DatasetController { @RequestParam(value = "category", required = false) Category category) { Pageable pageable = PageRequest.of(page, size, Sort.by(direction.equals("asc") ? Sort.Direction.ASC : Sort.Direction.DESC, sort)); - return datasetService.getDatasetsByOptionalCriteria(title, description, author, abst, type, rating, category,pageable); + return datasetService.getDatasetsByOptionalCriteria(title, description, author, abst, type, rating, category, + pageable); } @GetMapping("/search") @@ -98,10 +108,10 @@ public class DatasetController { @RequestParam(value = "sort", required = false, defaultValue = "upvotes") String sort, @RequestParam(value = "direction", required = false, defaultValue = "desc") String direction, @RequestParam(value = "category", required = false, defaultValue = "%") String category, - @RequestParam(value = "type", required = false, defaultValue = "%") String type){ + @RequestParam(value = "type", required = false, defaultValue = "%") String type) { Pageable pageable = PageRequest.of(page, size, Sort.by(direction.equals("asc") ? Sort.Direction.ASC : Sort.Direction.DESC, sort)); return datasetService.searchByOptionalCriteria(search, category, type, pageable); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java index 6694907..990bd54 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java @@ -28,9 +28,9 @@ public class DatasetService { return datasetRepository.getDatasetById(id); } - public void addDataset(Dataset dataset) { + public Dataset addDataset(Dataset dataset) { dataset.setDate(LocalDate.now()); - datasetRepository.save(dataset); + return datasetRepository.save(dataset); } public void updateDatasetTitle(UUID id, String title) { From 31fc359d5592b816edb4cb1b9598ab0e64f7b239 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Thu, 4 Jul 2024 14:42:58 +0200 Subject: [PATCH 10/21] chore: Refactor CategoryController to improve code structure and error handling --- .../DataDash/controler/CategoryController.java | 16 +++++++++++----- .../group3/DataDash/model/CategoryService.java | 8 +++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java index 5f725a3..69a162e 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java @@ -3,12 +3,15 @@ import java.util.List; import java.util.UUID; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryDto; import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -29,14 +32,17 @@ public class CategoryController { } @GetMapping("/id/{id}") - public CategoryDto getMethodName(@PathVariable("id") UUID id) { - return categoryService.getCategoryById(id); + public ResponseEntity fetchCategoryById(@PathVariable("id") UUID id) { + CategoryDto category = categoryService.getCategoryById(id); + if(category == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(category, HttpStatus.OK); } - + @ResponseStatus(HttpStatus.CREATED) @PostMapping - public String postMethodName(@RequestBody CategoryDto dto) { + public void createCategory(@RequestBody CategoryDto dto) { categoryService.addCategory(dto); - return null; } diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java index ba36dc0..d975497 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java @@ -4,7 +4,6 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; - @Service public class CategoryService { private CategoryRepository categoryRepository; @@ -25,8 +24,11 @@ public class CategoryService { } public CategoryDto getCategoryById(UUID id) { - return CategoryDtoMapper.toDto(categoryRepository.getCategoryById(id)); + Category c = categoryRepository.getCategoryById(id); + if (c == null) { + return null; + } + return CategoryDtoMapper.toDto(c); } - } From 027528909dff35c2994b691f9160f58fd1e587aa Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Thu, 4 Jul 2024 14:46:10 +0200 Subject: [PATCH 11/21] chore: Refactor DatasetService to improve code structure and readability --- .../group3/DataDash/model/DatasetService.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java index 990bd54..ea1fe96 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java @@ -9,7 +9,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.data.domain.Page; - @Service public class DatasetService { private dataRepository datasetRepository; @@ -91,20 +90,22 @@ public class DatasetService { public Page getDatasetsByOptionalCriteria(String title, String description, String author, String abst, Type type, Float raiting, Category category, Pageable pageable) { return datasetRepository.findByOptionalCriteria(Optional.ofNullable(title), Optional.ofNullable(description), - Optional.ofNullable(author), Optional.ofNullable(abst), Optional.ofNullable(type), Optional.ofNullable(category), + Optional.ofNullable(author), Optional.ofNullable(abst), Optional.ofNullable(type), + Optional.ofNullable(category), Optional.ofNullable(raiting), pageable); } public Page searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) { - //TODO: make it not Crash - //TODO: make it do useful stuff - Category category = categories.equals("%") ? null : categoryRepository.getCategoryById(UUID.fromString(categories)) ; + Category category = categories.equals("%") ? null + : categoryRepository.getCategoryById(UUID.fromString(categories)); Type t = type.equals("%") ? null : Type.valueOf(type); - if(category == null){ - return datasetRepository.searchByOptionalCriteria(Optional.ofNullable(search), Optional.ofNullable(t),pageable); + if (category == null) { + return datasetRepository.searchByOptionalCriteria(Optional.ofNullable(search), Optional.ofNullable(t), + pageable); } - return datasetRepository.searchByOptionalCriteriaWithCategory(Optional.ofNullable(search), category, Optional.ofNullable(t),pageable); + return datasetRepository.searchByOptionalCriteriaWithCategory(Optional.ofNullable(search), category, + Optional.ofNullable(t), pageable); } } \ No newline at end of file From cdeb4fc2be8edd1a533cec42ddf7ca3579ec6e40 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Thu, 4 Jul 2024 15:32:45 +0200 Subject: [PATCH 12/21] refactor: fix internal server errror on bad request --- .../DataDash/controler/DatasetController.java | 35 ++++++++++++------- .../PADAS/group3/DataDash/model/Dataset.java | 8 +++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java index 6bdc396..a6f9a06 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java @@ -13,6 +13,7 @@ import de.uni_passau.fim.PADAS.group3.DataDash.model.Type; import org.springframework.data.domain.Pageable; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.data.domain.Sort; @@ -26,14 +27,14 @@ import org.springframework.web.bind.annotation.RequestBody; public class DatasetController { @Autowired private DatasetService datasetService; - + @GetMapping("/id/{id}") - public ResponseEntity getDatasetById(@PathVariable("id") UUID id) { + public ResponseEntity getDatasetById(@PathVariable("id") UUID id) { Dataset d = datasetService.getDatasetById(id); if (d == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - return new ResponseEntity<>(d , HttpStatus.OK); + return new ResponseEntity<>(d, HttpStatus.OK); } @ResponseStatus(HttpStatus.CREATED) @@ -52,7 +53,7 @@ public class DatasetController { } @PutMapping("/id/{id}/upvote") - public ResponseEntity upvote(@PathVariable("id") UUID id) { + public ResponseEntity upvote(@PathVariable("id") UUID id) { if (datasetService.getDatasetById(id) == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @@ -62,16 +63,16 @@ public class DatasetController { } @PutMapping("/id/{id}/downvote") - public ResponseEntity downvote(@PathVariable("id") UUID id) { + public ResponseEntity downvote(@PathVariable("id") UUID id) { if (datasetService.getDatasetById(id) == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } datasetService.downvoteDataset(id); - return new ResponseEntity<>(getDatasetById(id), HttpStatus.OK); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); } @PutMapping("/id/{id}/vote") - public ResponseEntity postMethodName(@PathVariable("id") UUID id, + public ResponseEntity postMethodName(@PathVariable("id") UUID id, @RequestParam("stars") int stars) { if (datasetService.getDatasetById(id) == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -96,22 +97,32 @@ public class DatasetController { @RequestParam(value = "direction", required = false, defaultValue = "desc") String direction, @RequestParam(value = "category", required = false) Category category) { Pageable pageable = PageRequest.of(page, size, - Sort.by(direction.equals("asc") ? Sort.Direction.ASC : Sort.Direction.DESC, sort)); + Sort.by(Sort.Direction.fromString(direction), sort)); return datasetService.getDatasetsByOptionalCriteria(title, description, author, abst, type, rating, category, pageable); } @GetMapping("/search") - public Page search(@RequestParam(value = "search", required = false, defaultValue = "%") String search, + public ResponseEntity> search( + @RequestParam(value = "search", required = false, defaultValue = "%") String search, @RequestParam(value = "page", required = false, defaultValue = "0") int page, @RequestParam(value = "size", required = false, defaultValue = "20") int size, @RequestParam(value = "sort", required = false, defaultValue = "upvotes") String sort, @RequestParam(value = "direction", required = false, defaultValue = "desc") String direction, @RequestParam(value = "category", required = false, defaultValue = "%") String category, @RequestParam(value = "type", required = false, defaultValue = "%") String type) { - Pageable pageable = PageRequest.of(page, size, - Sort.by(direction.equals("asc") ? Sort.Direction.ASC : Sort.Direction.DESC, sort)); - return datasetService.searchByOptionalCriteria(search, category, type, pageable); + Pageable pageable = null; + if (!Dataset.getSort().contains(sort)) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + try { + pageable = PageRequest.of(page, size, + Sort.by(Sort.Direction.fromString(direction), sort)); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + return new ResponseEntity<>(datasetService.searchByOptionalCriteria(search, category, type, pageable), + HttpStatus.OK); } } \ No newline at end of file diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java index 91f71a2..5f28d40 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java @@ -2,6 +2,8 @@ package de.uni_passau.fim.PADAS.group3.DataDash.model; import java.net.URL; import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; import java.util.UUID; import java.sql.Date; @@ -41,6 +43,8 @@ public class Dataset { private URL url; + private static final List sortable = Arrays.asList("author", "title", "upvotes", "date"); + @ManyToOne private Category categorie; @@ -111,6 +115,10 @@ public class Dataset { return url; } + public static List getSort() { + return sortable; + } + public void setAbst(String abst) { this.abst = abst.substring(0, Math.min(abst.length(), 100)); } From 9e5c83dce9ae6f27e9ccfdd2fcbde1cd67b99b0c Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Thu, 4 Jul 2024 15:33:55 +0200 Subject: [PATCH 13/21] chore: Remove unused import statement in DatasetController --- .../fim/PADAS/group3/DataDash/controler/DatasetController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java index a6f9a06..947b346 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java @@ -13,7 +13,6 @@ import de.uni_passau.fim.PADAS.group3.DataDash.model.Type; import org.springframework.data.domain.Pageable; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.data.domain.Sort; From bbb8c9259fa50a11e4faf528790624eb0cf4da8c Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Thu, 4 Jul 2024 18:28:44 +0200 Subject: [PATCH 14/21] accepted review comments --- src/main/resources/static/contentUtility.js | 3 +-- src/main/resources/static/dataset.js | 12 ++++++------ src/main/resources/static/main.js | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index d08c0be..ff48f52 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -24,9 +24,8 @@ function parseContent(content, clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); } for (const dataset of datasets) { - console.log(dataset) //TODO: remove searchSection.querySelector(".datasets") - .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); + .appendChild(dataset.createDatasetHTMLElement()); } } } diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index 947770f..29170a8 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,4 +1,4 @@ -import { vote, votedIDs } from "./main.js"; +import { vote } from "./main.js"; export default class Dataset { #abstract; @@ -29,20 +29,20 @@ export default class Dataset { this.#votes = votes; } - createDatasetHTMLElement(entryID) { + createDatasetHTMLElement() { let template = document.querySelector("#dataset-template"); const clone = template.content.cloneNode(true); clone.querySelector(".dataset").dataset.id = this.#id; clone.querySelector("h3").innerText = this.#title; clone.querySelector("p").innerText = this.#description; clone.querySelector("span").innerText = this.#upvotes; - + let votedIDs = window.localStorage; // depending on whether the button has been up/downvoted, its according button get disabled and hidden - if (votedIDs.getItem(entryID)) { - let votedButton = clone.querySelector(votedIDs.getItem(entryID)? ".upvote-btn":".downvote-btn"); + if (votedIDs.getItem(this.#id)) { + let votedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".upvote-btn":".downvote-btn"); votedButton.classList.add("isVoted"); votedButton.disabled = true; - let notVotedButton = clone.querySelector(votedIDs.getItem(entryID)? ".downvote-btn":".upvote-btn"); + let notVotedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".downvote-btn":".upvote-btn"); notVotedButton.style.visibility = "hidden"; } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 52ee670..32caf41 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -8,7 +8,7 @@ export const lastQuery = { totalPages: 0, currentPage: 0 }; -export const votedIDs = window.sessionStorage; +const votedIDs = window.localStorage; // definition of all buttons & sections const addButton = document.getElementById("add-btn"); @@ -202,7 +202,7 @@ function fetchInitialEntries() { const datasets = data.content.map(dataset => new Dataset(dataset)); for (const dataset of datasets) { document.querySelector("#recents .datasets") - .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); + .appendChild(dataset.createDatasetHTMLElement()); } }); @@ -215,7 +215,7 @@ function fetchInitialEntries() { .then((data) => { let dataset = new Dataset(data.content[0]); document.querySelector("#top .datasets") - .appendChild(dataset.createDatasetHTMLElement(dataset.getID())); + .appendChild(dataset.createDatasetHTMLElement()); }); } From fe4f0a8d6bf9ac0e13d09071d0db3dda15c28cdf Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Fri, 5 Jul 2024 11:31:22 +0200 Subject: [PATCH 15/21] refactor: Add licence field to Dataset model --- .../PADAS/group3/DataDash/model/Dataset.java | 10 ++++++ src/main/resources/data.sql | 34 +++++++++---------- src/main/resources/schema.sql | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java index 5f28d40..7f78848 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java @@ -43,6 +43,8 @@ public class Dataset { private URL url; + private String licence; + private static final List sortable = Arrays.asList("author", "title", "upvotes", "date"); @ManyToOne @@ -115,6 +117,10 @@ public class Dataset { return url; } + public String getLicence() { + return licence; + } + public static List getSort() { return sortable; } @@ -163,4 +169,8 @@ public class Dataset { upvotes--; } + public void setLicence(String licence) { + this.licence = licence; + } + } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 68993c1..51257a9 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -10,21 +10,21 @@ INSERT INTO category (id, name) VALUES ('123e4567-e89b-12d3-a456-426614174002', 'Health'); -- Insert sample data into dataset -INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type) VALUES -('2023-01-01', 4.5, 100, 120, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174100', 'Abstract 1', 'Author 1', 'Description 1', 'Title 1', 'http://example.com/1', 'API'), -('2023-01-02', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174101', 'Abstract 2', 'Author 2', 'Description 2', 'Title 2', 'http://example.com/2', 'DATASET'), -('2023-01-03', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174102', 'Abstract 3', 'Author 3', 'Description 3', 'Title 3', 'http://example.com/3', 'API'), -('2023-01-04', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174103', 'Abstract 4', 'Author 4', 'Description 4', 'Title 4', 'http://example.com/4', 'DATASET'), -('2023-01-05', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174104', 'Abstract 5', 'Author 5', 'Description 5', 'Title 5', 'http://example.com/5', 'API'); +INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence) VALUES +('2023-01-01', 4.5, 100, 120, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174100', 'Abstract 1', 'Author 1', 'Description 1', 'Title 1', 'http://example.com/1', 'API', 'MIT'), +('2023-01-02', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174101', 'Abstract 2', 'Author 2', 'Description 2', 'Title 2', 'http://example.com/2', 'DATASET', 'MIT'), +('2023-01-03', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174102', 'Abstract 3', 'Author 3', 'Description 3', 'Title 3', 'http://example.com/3', 'API', 'MIT'), +('2023-01-04', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174103', 'Abstract 4', 'Author 4', 'Description 4', 'Title 4', 'http://example.com/4', 'DATASET', 'MIT'), +('2023-01-05', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174104', 'Abstract 5', 'Author 5', 'Description 5', 'Title 5', 'http://example.com/5', 'API', 'MIT'); -- Insert 10 more sample data into dataset -INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type) VALUES -('2023-01-06', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174105', 'Abstract 6', 'Author 6', 'Description 6', 'Title 6', 'http://example.com/6', 'API'), -('2023-01-07', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174106', 'Abstract 7', 'Author 7', 'Description 7', 'Title 7', 'http://example.com/7', 'DATASET'), -('2023-01-08', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174007', '123e4567-e89b-12d3-a456-426614174107', 'Abstract 8', 'Author 8', 'Description 8', 'Title 8', 'http://example.com/8', 'API'), -('2023-01-09', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174108', 'Abstract 9', 'Author 9', 'Description 9', 'Title 9', 'http://example.com/9', 'DATASET'), -('2023-01-10', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174109', 'Abstract 10', 'Author 10', 'Description 10', 'Title 10', 'http://example.com/10', 'API'), -('2023-11-11', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174110', 'Abstract 11', 'Author 11', 'Description 11', 'Title 11', 'http://example.com/11', 'DATASET'), -('2023-09-12', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174111', 'Abstract 12', 'Author 12', 'Description 12', 'Title 12', 'http://example.com/12', 'API'), -('2023-03-13', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174112', 'Abstract 13', 'Author 13', 'Description 13', 'Title 13', 'http://example.com/13', 'DATASET'), -('2021-01-14', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174113', 'Abstract 14', 'Author 14', 'Description 14', 'Title 14', 'http://example.com/14', 'API'), -('2024-01-15', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174114', 'Abstract 15', 'Author 15', 'Description 15', 'Title 15', 'http://example.com/15', 'DATASET'); \ No newline at end of file +INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence) VALUES +('2023-01-06', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174105', 'Abstract 6', 'Author 6', 'Description 6', 'Title 6', 'http://example.com/6', 'API', 'MIT'), +('2023-01-07', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174106', 'Abstract 7', 'Author 7', 'Description 7', 'Title 7', 'http://example.com/7', 'DATASET', 'MIT'), +('2023-01-08', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174007', '123e4567-e89b-12d3-a456-426614174107', 'Abstract 8', 'Author 8', 'Description 8', 'Title 8', 'http://example.com/8', 'API', 'MIT'), +('2023-01-09', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174000', '123e4567-e89b-12d3-a456-426614174108', 'Abstract 9', 'Author 9', 'Description 9', 'Title 9', 'http://example.com/9', 'DATASET', 'MIT'), +('2023-01-10', 4.2, 80, 100, '123e4567-e89b-12d3-a456-426614174001', '123e4567-e89b-12d3-a456-426614174109', 'Abstract 10', 'Author 10', 'Description 10', 'Title 10', 'http://example.com/10', 'API', 'MIT'), +('2023-11-11', 4.6, 120, 140, '123e4567-e89b-12d3-a456-426614174002', '123e4567-e89b-12d3-a456-426614174110', 'Abstract 11', 'Author 11', 'Description 11', 'Title 11', 'http://example.com/11', 'DATASET', 'MIT'), +('2023-09-12', 4.8, 180, 200, '123e4567-e89b-12d3-a456-426614174003', '123e4567-e89b-12d3-a456-426614174111', 'Abstract 12', 'Author 12', 'Description 12', 'Title 12', 'http://example.com/12', 'API', 'MIT'), +('2023-03-13', 4.3, 90, 110, '123e4567-e89b-12d3-a456-426614174004', '123e4567-e89b-12d3-a456-426614174112', 'Abstract 13', 'Author 13', 'Description 13', 'Title 13', 'http://example.com/13', 'DATASET', 'MIT'), +('2021-01-14', 4.7, 150, 170, '123e4567-e89b-12d3-a456-426614174005', '123e4567-e89b-12d3-a456-426614174113', 'Abstract 14', 'Author 14', 'Description 14', 'Title 14', 'http://example.com/14', 'API', 'MIT'), +('2024-01-15', 4.9, 200, 220, '123e4567-e89b-12d3-a456-426614174006', '123e4567-e89b-12d3-a456-426614174114', 'Abstract 15', 'Author 15', 'Description 15', 'Title 15', 'http://example.com/15', 'DATASET', 'MIT'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index bb7c8b1..0385a13 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -3,5 +3,5 @@ DROP TABLE IF EXISTS category; create table category (id uuid not null, name varchar(255), primary key (id)); -create table dataset (date date, raiting float(24) not null, upvotes integer not null, votes integer not null, categorie_id uuid, id uuid not null, abst varchar(255), author varchar(255), description varchar(255), title varchar(255), url varchar(255), type enum ('API','DATASET'), primary key (id)); +create table dataset (date date, raiting float(24) not null, upvotes integer not null, votes integer not null, categorie_id uuid, id uuid not null, abst varchar(255), author varchar(255), description varchar(255), title varchar(255), url varchar(255), type enum ('API','DATASET'), licence varchar(255), primary key (id)); alter table if exists dataset add constraint FKq6qwq6u473f89h71s7rf97ruy foreign key (categorie_id) references category; From 93a52097de5abd61403b99e893502330fc1874ea Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Fri, 5 Jul 2024 12:04:32 +0200 Subject: [PATCH 16/21] - main.js: fixed bug in the filterButton EventListener (removed if-clause) added new EventListener for fetching categories updated fetchCategories() --- src/main/resources/static/dataset.js | 6 +----- src/main/resources/static/main.js | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index 29170a8..bd33176 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -37,7 +37,7 @@ export default class Dataset { clone.querySelector("p").innerText = this.#description; clone.querySelector("span").innerText = this.#upvotes; let votedIDs = window.localStorage; - // depending on whether the button has been up/downvoted, its according button get disabled and hidden + // depending on whether the button has been up/downvoted, its according button gets disabled and hidden if (votedIDs.getItem(this.#id)) { let votedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".upvote-btn":".downvote-btn"); votedButton.classList.add("isVoted"); @@ -57,8 +57,4 @@ export default class Dataset { return clone; } - - getID() { - return this.#id; - } } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 32caf41..cb558ac 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -9,6 +9,7 @@ export const lastQuery = { currentPage: 0 }; const votedIDs = window.localStorage; +const loadedCategories = new Set; // definition of all buttons & sections const addButton = document.getElementById("add-btn"); @@ -33,12 +34,13 @@ addButton.addEventListener("click", () => { }); filterButton.addEventListener("change", () => { - const filterString = filterButton.value; - if (filterString !== filterButton.querySelector("#default-filter").value) { - fetchQuery(createQuery(), true); - } + fetchQuery(createQuery(), true); }); +filterButton.addEventListener("click", () => { + fetchCategories(); +}) + searchButton.addEventListener("click", () => { fetchQuery(createQuery(), true); @@ -99,7 +101,7 @@ function getFilterQuery() { } else if (document.querySelector('#filter-btn option:checked').parentElement.label === "Standard categories") { return ["type", filterString]; } else { - return ["category", filterString]; + return ["category", filterButton.options[filterButton.selectedIndex].value] } } @@ -177,15 +179,18 @@ function updateSections() { // fetches the further categories used in the filter function function fetchCategories() { - const fetchURL = new URL( - "api/v1/categories", baseURL); + const fetchURL = new URL("api/v1/categories", baseURL); fetch(fetchURL) .then(resp => resp.json()) .then((data) => { for (let i = 0; i < data.length; i++) { - let category = data[i].toLowerCase(); - category = category.charAt(0).toUpperCase() + category.slice(1); - document.getElementById("other-categories").appendChild(new Option(category)); + let categoryName = data[i].name.toLowerCase(); + categoryName = categoryName.charAt(0).toUpperCase() + categoryName.slice(1); + if (!loadedCategories.has(categoryName)) { + let newCategory = new Option(categoryName, data[i].id); + document.getElementById("other-categories").appendChild(newCategory); + loadedCategories.add(categoryName); + } } }); } From 6ae3a73a7e745e3667588af7ede1658d258cca56 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Fri, 5 Jul 2024 12:19:47 +0200 Subject: [PATCH 17/21] refactor: Add licence field to Dataset constructor --- .../de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java | 3 ++- .../fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java index 7f78848..7877d53 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java @@ -50,7 +50,7 @@ public class Dataset { @ManyToOne private Category categorie; - public Dataset(String title, String abst, String description, String author, URL url, Category categories, Type type) { + public Dataset(String title, String abst, String description, String author, URL url, Category categories, Type type, String licence) { this.raiting = 0; this.votes = 0; @@ -63,6 +63,7 @@ public class Dataset { setCategorie(categories); setType(type); setUrl(url); + setLicence(licence); } public Dataset() { diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java index ba3dd32..6600bdb 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java @@ -25,7 +25,7 @@ public class LoadDummyDatabase { Category category = new Category("Category" + i); log.info("Preloading" + categoryRepository.save(category)); - Dataset dataset = new Dataset("Title" + i, "Abst" + i, "Description" + i, "Author" + i,null, category, Type.API); + Dataset dataset = new Dataset("Title" + i, "Abst" + i, "Description" + i, "Author" + i,null, category, Type.API, "MIT"); for (int j = 0; j < new Random().nextInt(50); j++) { dataset.upvote(); } From 15f743d8ab78ce3ba9fda78964828d6da52b8bea Mon Sep 17 00:00:00 2001 From: J-Klinke Date: Fri, 5 Jul 2024 12:30:40 +0200 Subject: [PATCH 18/21] fixed bugs: - suppressed display of search results if nothing was found but there were previous searches - improved display timing of search results/initial pages, which led to false displays --- src/main/resources/static/contentUtility.js | 2 ++ src/main/resources/static/main.css | 9 +++++---- src/main/resources/static/main.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index ff48f52..c8adbb8 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -17,8 +17,10 @@ export function fetchQuery(fetchString, clearResults) { function parseContent(content, clearResults) { if (content.length === 0) { searchSection.querySelector("#nothing-found ").classList.remove("hidden"); + searchSection.querySelector(".datasets").classList.add("hidden"); } else { searchSection.querySelector("#nothing-found").classList.add("hidden"); + searchSection.querySelector(".datasets").classList.remove("hidden"); const datasets = content.map(dataset => new Dataset(dataset)); if (clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); diff --git a/src/main/resources/static/main.css b/src/main/resources/static/main.css index ab5e5bf..8a0b141 100644 --- a/src/main/resources/static/main.css +++ b/src/main/resources/static/main.css @@ -78,10 +78,6 @@ header { text-align: center; } -.hidden { - display: none; -} - #search-entry:focus-visible { outline: none; } @@ -93,6 +89,11 @@ header { gap: 1rem; } +.hidden { + display: none; +} + + @container (width < 60ch) { .datasets { grid-template-columns: 1fr; diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index cb558ac..3501471 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -47,10 +47,10 @@ searchButton.addEventListener("click", () => { }); searchBar.addEventListener("input", () => { - updateSections(); clearTimeout(searchBarTimeout); searchBarTimeout = setTimeout(() => { fetchQuery(createQuery(), true); + updateSections(); }, searchDelay); }); From 68595e22a73cfafae9387b37dc63323e3eb820a1 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Fri, 5 Jul 2024 12:34:26 +0200 Subject: [PATCH 19/21] refactor: move static sites into static, Remove unused PageController and update main.js for page navigation --- .../group3/DataDash/controler/PageController.java | 12 ------------ src/main/resources/{templates => static}/add.html | 0 src/main/resources/{templates => static}/index.html | 0 src/main/resources/static/main.js | 3 +-- 4 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/PageController.java rename src/main/resources/{templates => static}/add.html (100%) rename src/main/resources/{templates => static}/index.html (100%) diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/PageController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/PageController.java deleted file mode 100644 index f1a0f6f..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/PageController.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.controler; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class PageController { - @GetMapping("/add") - public String getAddPage() { - return "add"; - } -} diff --git a/src/main/resources/templates/add.html b/src/main/resources/static/add.html similarity index 100% rename from src/main/resources/templates/add.html rename to src/main/resources/static/add.html diff --git a/src/main/resources/templates/index.html b/src/main/resources/static/index.html similarity index 100% rename from src/main/resources/templates/index.html rename to src/main/resources/static/index.html diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 65c62d6..5ddbced 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -81,8 +81,7 @@ for (const downvoteButton of downvoteButtons) { // functions of the main page function navigateToAdd() { - //TODO: url to add page not yet implemented, add here - window.location.href = "/add"; + window.location.href = "/add.html"; } function filter(filterString) { From 3d71bfee2b57d44416e9e717894b24dc1c39b350 Mon Sep 17 00:00:00 2001 From: Erik Foris Date: Fri, 5 Jul 2024 13:05:56 +0200 Subject: [PATCH 20/21] refactor: finilase project structure --- .../DataDash/{model => Dataset}/Dataset.java | 5 ++- .../DatasetController.java | 10 ++--- .../{model => Dataset}/DatasetService.java | 6 ++- .../PADAS/group3/DataDash/Dataset/Type.java | 6 +++ .../{model => Dataset}/dataRepository.java | 4 +- .../{model => category}/Category.java | 3 +- .../CategoryController.java | 6 +-- .../{model => category}/CategoryDto.java | 2 +- .../CategoryDtoMapper.java | 2 +- .../CategoryRepository.java | 2 +- .../{model => category}/CategoryService.java | 2 +- .../DataDash/model/LoadDummyDatabase.java | 40 ------------------- .../fim/PADAS/group3/DataDash/model/Type.java | 6 --- 13 files changed, 28 insertions(+), 66 deletions(-) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => Dataset}/Dataset.java (96%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{controler => Dataset}/DatasetController.java (94%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => Dataset}/DatasetService.java (95%) create mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Type.java rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => Dataset}/dataRepository.java (96%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => category}/Category.java (86%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{controler => category}/CategoryController.java (87%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => category}/CategoryDto.java (85%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => category}/CategoryDtoMapper.java (77%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => category}/CategoryRepository.java (88%) rename src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/{model => category}/CategoryService.java (94%) delete mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java delete mode 100644 src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Type.java diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java similarity index 96% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java index 7877d53..c8ed4e3 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Dataset.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java @@ -1,10 +1,13 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; import java.net.URL; import java.time.LocalDate; import java.util.Arrays; import java.util.List; import java.util.UUID; + +import de.uni_passau.fim.PADAS.group3.DataDash.category.Category; + import java.sql.Date; import jakarta.persistence.Entity; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java similarity index 94% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java index 947b346..3e54858 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java @@ -1,15 +1,9 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.controler; +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.web.bind.annotation.*; - -import de.uni_passau.fim.PADAS.group3.DataDash.model.Category; -import de.uni_passau.fim.PADAS.group3.DataDash.model.Dataset; -import de.uni_passau.fim.PADAS.group3.DataDash.model.DatasetService; -import de.uni_passau.fim.PADAS.group3.DataDash.model.Type; - import org.springframework.data.domain.Pageable; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.HttpStatus; @@ -20,6 +14,8 @@ import java.util.UUID; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import de.uni_passau.fim.PADAS.group3.DataDash.category.Category; + @RestController @RequestMapping("/api/v1/datasets") @EnableSpringDataWebSupport diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java similarity index 95% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java index ea1fe96..7762f31 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; import java.time.LocalDate; import java.util.List; @@ -7,6 +7,10 @@ import java.util.UUID; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; + +import de.uni_passau.fim.PADAS.group3.DataDash.category.Category; +import de.uni_passau.fim.PADAS.group3.DataDash.category.CategoryRepository; + import org.springframework.data.domain.Page; @Service diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Type.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Type.java new file mode 100644 index 0000000..9f2770d --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Type.java @@ -0,0 +1,6 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; + +public enum Type { + DATASET, + API +} \ No newline at end of file diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/dataRepository.java similarity index 96% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/dataRepository.java index 130415d..c896552 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/dataRepository.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/dataRepository.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; import java.util.List; import java.util.Optional; @@ -11,6 +11,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import de.uni_passau.fim.PADAS.group3.DataDash.category.Category; + public interface dataRepository extends JpaRepository { Dataset getDatasetById(UUID id); diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java similarity index 86% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java index 4246baa..a868283 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java @@ -1,10 +1,11 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.category; import java.util.List; import java.util.UUID; import org.springframework.context.annotation.Lazy; +import de.uni_passau.fim.PADAS.group3.DataDash.Dataset.Dataset; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java similarity index 87% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java index 69a162e..3ce13b0 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java @@ -1,14 +1,10 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.controler; +package de.uni_passau.fim.PADAS.group3.DataDash.category; import java.util.List; import java.util.UUID; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; - -import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryDto; -import de.uni_passau.fim.PADAS.group3.DataDash.model.CategoryService; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java similarity index 85% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java index a21d0cf..11644cc 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDto.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.category; import java.util.UUID; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDtoMapper.java similarity index 77% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDtoMapper.java index e7ecc10..683fdc8 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryDtoMapper.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDtoMapper.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.category; public class CategoryDtoMapper { diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryRepository.java similarity index 88% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryRepository.java index a0b67e5..acde965 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryRepository.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryRepository.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.category; import java.util.List; import java.util.Optional; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java similarity index 94% rename from src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java rename to src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java index d975497..3e2698f 100644 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/CategoryService.java +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java @@ -1,4 +1,4 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; +package de.uni_passau.fim.PADAS.group3.DataDash.category; import org.springframework.stereotype.Service; import java.util.List; diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java deleted file mode 100644 index 6600bdb..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; - -import java.util.List; -import java.util.Random; - -import org.slf4j.LoggerFactory; -import org.springframework.boot.CommandLineRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - - -@Configuration -public class LoadDummyDatabase { - - private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoadDummyDatabase.class); - - //@Bean - CommandLineRunner initDatabase(dataRepository repository, CategoryRepository categoryRepository) { - - - - return args -> { - for (int i = 0; i < 100; i++) { - Category category = new Category("Category" + i); - log.info("Preloading" + categoryRepository.save(category)); - - Dataset dataset = new Dataset("Title" + i, "Abst" + i, "Description" + i, "Author" + i,null, category, Type.API, "MIT"); - for (int j = 0; j < new Random().nextInt(50); j++) { - dataset.upvote(); - } - log.info("Preloading" + repository.save(dataset)); - log.info("Preloading" + categoryRepository.save(category)); - } - List s = repository.findByTitleLike("%Title%"); - log.info("Found Entry with ID: " + s.get(1).getId());}; - } - - -} \ No newline at end of file diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Type.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Type.java deleted file mode 100644 index 8f72c12..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Type.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; - -public enum Type { - DATASET, - API -} \ No newline at end of file From 66ac0bfc29ec5a4c51e2dd49049e1ee3396cf5e3 Mon Sep 17 00:00:00 2001 From: Elias Schriefer Date: Fri, 5 Jul 2024 16:57:06 +0200 Subject: [PATCH 21/21] Refactor Dataset class --- src/main/resources/static/contentUtility.js | 35 ++-- src/main/resources/static/dataset.js | 198 +++++++++++++++++--- src/main/resources/static/main.js | 98 +++------- src/main/resources/templates/index.html | 4 +- 4 files changed, 222 insertions(+), 113 deletions(-) diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index ff48f52..6c47123 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,28 +1,33 @@ -import {searchBarTimeout, searchSection, lastQuery} from "./main.js" -import Dataset from "./dataset.js" // TODO consider renaming this to "searchUtility.js" -export function fetchQuery(fetchString, clearResults) { + +import { searchBarTimeout, searchSection, lastQuery } from "./main.js" +import Dataset from "./dataset.js" + +export async function fetchQuery(fetchString, clearResults) { clearTimeout(searchBarTimeout); - fetch(fetchString) - .then(resp => resp.json()) - .then((data) => { - parseContent(data.content, clearResults); - lastQuery.totalPages = data.totalPages; - if (clearResults) { - lastQuery.currentPage = 0; - } - }); + const response = await fetch(fetchString); + const data = await response.json(); + + parseContent(data.content, clearResults); + lastQuery.totalPages = data.totalPages; + if (clearResults) { + lastQuery.currentPage = 0; + } } function parseContent(content, clearResults) { + const nothingFoundElement = searchSection.querySelector("#nothing-found"); + if (content.length === 0) { - searchSection.querySelector("#nothing-found ").classList.remove("hidden"); + nothingFoundElement.classList.remove("hidden"); } else { - searchSection.querySelector("#nothing-found").classList.add("hidden"); - const datasets = content.map(dataset => new Dataset(dataset)); + nothingFoundElement.classList.add("hidden"); + + const datasets = content.map(dataset => Dataset.get(dataset.id) ?? new Dataset(dataset)); if (clearResults) { Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); } + for (const dataset of datasets) { searchSection.querySelector(".datasets") .appendChild(dataset.createDatasetHTMLElement()); diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index 29170a8..1c5d40a 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,6 +1,8 @@ -import { vote } from "./main.js"; +import { DATASET_ENDPOINT, getBaseURL } from "./main.js"; export default class Dataset { + static #datasets = new Map(); + #abstract; #author; #categories; @@ -13,8 +15,13 @@ export default class Dataset { #upvotes; #url; #votes; + #elements = []; - constructor({abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes}) { + static get(id) { + return this.#datasets.get(id); + } + + constructor({ abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes }) { this.#abstract = shortDescription; this.#author = author; this.#categories = categories; @@ -27,38 +34,179 @@ export default class Dataset { this.#upvotes = upvotes; this.#url = url; this.#votes = votes; + + Dataset.#datasets.set(id, this); } + // Getters + get abstract() { + return this.#abstract; + } + + get author() { + return this.#author; + } + + get categories() { + return this.#categories; + } + + get date() { + return this.#date; + } + + get description() { + return this.#description; + } + + get id() { + return this.#id; + } + + get rating() { + return this.#rating; + } + + get title() { + return this.#title; + } + + get type() { + return this.#type; + } + + get upvotes() { + return this.#upvotes; + } + + get url() { + return this.#url; + } + + get votes() { + return this.#votes; + } + + get mainElement() { + return this.#elements[0]; + } + + get elements() { + return this.#elements; + } + + // Main methods + + // Only on main page createDatasetHTMLElement() { - let template = document.querySelector("#dataset-template"); - const clone = template.content.cloneNode(true); - clone.querySelector(".dataset").dataset.id = this.#id; - clone.querySelector("h3").innerText = this.#title; - clone.querySelector("p").innerText = this.#description; - clone.querySelector("span").innerText = this.#upvotes; - let votedIDs = window.localStorage; - // depending on whether the button has been up/downvoted, its according button get disabled and hidden - if (votedIDs.getItem(this.#id)) { - let votedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".upvote-btn":".downvote-btn"); - votedButton.classList.add("isVoted"); - votedButton.disabled = true; - let notVotedButton = clone.querySelector(votedIDs.getItem(this.#id)? ".downvote-btn":".upvote-btn"); - notVotedButton.style.visibility = "hidden"; + const clone = this.#createTemplateInstance("dataset-template"); + if (clone == null) { + return null; } - // Event Listeners - clone.querySelector(".upvote-btn").addEventListener("click", () => { - vote(this.#id, true); - }); + clone.querySelector(".dataset").dataset.id = this.#id; + clone.querySelector(".dataset-title").innerText = this.#title; + clone.querySelector(".dataset-description").innerText = this.#description; + clone.querySelector(".upvote-count").innerText = this.#upvotes; - clone.querySelector(".downvote-btn").addEventListener("click", () => { - vote(this.#id, false); - }) + this.#elements.push(clone.children[0]); + this.#createUpvoteButtons(clone); return clone; } - getID() { - return this.#id; + // Only on details page + createUpvoteComponent() { + let clone = this.#createTemplateInstance("voting-template") + + clone.querySelector(".upvote-count").innerText = this.#upvotes; + + this.#elements.push(clone.children[0]); + this.#createUpvoteButtons(clone); + + return clone; + } + + #createUpvoteButtons(templateInstance) { + if (this.storageGetKey("is-voted", false)) { + // The template instance (clone) has to be pushed before this can work + this.#disableVoteBtns(); + } + + // Event Listeners + templateInstance.querySelector(".upvote-btn").addEventListener("click", () => { + this.vote(); + }); + + templateInstance.querySelector(".downvote-btn").addEventListener("click", () => { + this.vote(false); + }) + } + + #createTemplateInstance(templateId) { + let template = document.getElementById(templateId); + if (template == null) { + return null; + } + + return template.content.cloneNode(true); + } + + isMainElement(element) { + return this.#elements.length > 0 && this.#elements[0].isEqualNode(element); + } + + async vote(up = true) { + const fetchURL = new URL( + `${DATASET_ENDPOINT}/id/${this.#id}/${up ? "up" : "down"}vote`, + getBaseURL(), + ); + + const response = await fetch(fetchURL, { + method: "PUT", + headers: { + 'Content-Type': 'application/json', + } + }); + const data = await response.json(); + + this.#upvotes = data.upvotes; + for (const element of this.#elements) { + element.querySelector(".upvote-count").innerText = this.#upvotes; + } + + this.storageSetKey("vote-type", up ? "upvote" : "downvote"); + this.storageSetKey("is-voted", true); + this.#disableVoteBtns(); + } + + #disableVoteBtns() { + if (this.#elements.length > 0) { + const voteType = this.storageGetKey("vote-type", null); + + this.#elements.flatMap(e => Array.from(e.querySelectorAll(".upvote .btn"))) + .forEach(btn => { + btn.disabled = true; + + if (btn.classList.contains(`${voteType}-btn`)) { + btn.classList.add("isVoted"); + } + }); + } + } + + storageGet() { + const json = localStorage.getItem(this.#id); + return json != null ? JSON.parse(json) : {}; + } + + storageSetKey(key, value) { + let currentStorage = this.storageGet(); + currentStorage[key] = value; + localStorage.setItem(this.#id, JSON.stringify(currentStorage)); + } + + storageGetKey(key, defaultValue) { + return this.storageGet()?.[key] ?? defaultValue; } } diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js index 32caf41..b359763 100644 --- a/src/main/resources/static/main.js +++ b/src/main/resources/static/main.js @@ -1,14 +1,14 @@ -import {fetchQuery} from "./contentUtility.js"; +import { fetchQuery } from "./contentUtility.js"; import Dataset from "./dataset.js"; -const apiEndpoint = "/api/v1/datasets"; -const baseURL = location.origin; +export const DATASET_ENDPOINT = "/api/v1/datasets"; +export const getBaseURL = () => location.origin; + const defaultPagingValue = 20; export const lastQuery = { totalPages: 0, - currentPage: 0 + currentPage: 0, }; -const votedIDs = window.localStorage; // definition of all buttons & sections const addButton = document.getElementById("add-btn"); @@ -69,24 +69,6 @@ resetButton.addEventListener("click", () => { updateSections(); }); -// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js -const upvoteButtonClickListener = e => { - const entryID = e.target.parentElement.parentElement.dataset.id; - vote(entryID, true); -}; -for (const upvoteButton of upvoteButtons) { - upvoteButton.addEventListener("click", upvoteButtonClickListener); -} - -// Consider moving this to datasets.js completely // TODO: we dont need them, do we? there in dataset.js -const downvoteButtonClickListener = e => { - const entryID = e.target.parentElement.parentElement.dataset.id; - vote(entryID, false); -}; -for (const downvoteButton of downvoteButtons) { - downvoteButton.addEventListener("click", downvoteButtonClickListener); -} - // functions of the main page function navigateToAdd() { window.location.href = "/add"; //TODO: move to EventListner? @@ -121,42 +103,18 @@ function getSortQuery() { // creates query for the whole toolbar, so that searching, sorting and filtering are always combined function createQuery() { updateSections(); - let queryURL = new URL(apiEndpoint + "/search", baseURL); + let queryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL()); queryURL.searchParams.append("search", getSearchQuery()); + let filterQuery = getFilterQuery(); queryURL.searchParams.append(filterQuery[0], filterQuery[1]); + let sortQuery = getSortQuery(); queryURL.searchParams.append("sort", sortQuery[0]); queryURL.searchParams.append("direction", sortQuery[1]); queryURL.searchParams.append("size", defaultPagingValue.toString(10)); - return queryURL; -} -export function vote(entryID, up) { - const fetchURL = new URL( - `${apiEndpoint}/id/${entryID}/${up ? "up" : "down"}vote`, - baseURL, - ); - fetch(fetchURL, { - method: "PUT", - headers: { - 'Content-Type': 'application/json', - } - }) - .then(resp => resp.json()) - .then((data) => { - console.log(data.upvotes); // TODO: remove, check einbauen: data.id === entryID? - let dataSets = document.querySelectorAll('[data-id= ' + CSS.escape(entryID) + ']'); - for (const dataSetElement of dataSets) { - dataSetElement.querySelector("span").innerText = data.upvotes; - let votedButton = dataSetElement.querySelector(up? ".upvote-btn":".downvote-btn"); - votedButton.classList.add("isVoted"); - votedButton.disabled = true; - let notVotedButton = dataSetElement.querySelector(up? ".downvote-btn":".upvote-btn"); - notVotedButton.style.visibility = "hidden"; - votedIDs.setItem(dataSetElement.getAttribute("data-id"), up); - } - }); + return queryURL; } // updates the page display. If no query is present, the initial page is shown, otherwise the search results. @@ -178,7 +136,7 @@ function updateSections() { // fetches the further categories used in the filter function function fetchCategories() { const fetchURL = new URL( - "api/v1/categories", baseURL); + "api/v1/categories", getBaseURL()); fetch(fetchURL) .then(resp => resp.json()) .then((data) => { @@ -191,32 +149,30 @@ function fetchCategories() { } // fetches entries for the initial page -function fetchInitialEntries() { - let recentsQueryURL = new URL(apiEndpoint + "/search", baseURL); +async function fetchInitialEntries() { + let recentsQueryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL()); recentsQueryURL.searchParams.append("sort", "date"); recentsQueryURL.searchParams.append("direction", "desc"); recentsQueryURL.searchParams.append("size", "6"); - fetch(recentsQueryURL) - .then(resp => resp.json()) - .then((data) => { - const datasets = data.content.map(dataset => new Dataset(dataset)); - for (const dataset of datasets) { - document.querySelector("#recents .datasets") - .appendChild(dataset.createDatasetHTMLElement()); - } - }); - let topVotedQueryURL = new URL(apiEndpoint + "/search", baseURL); + const recentsResponse = await fetch(recentsQueryURL); + const recentsData = await recentsResponse.json(); + + const recentsDatasets = recentsData.content.map(dataset => new Dataset(dataset)); + for (const recentDataset of recentsDatasets) { + document.querySelector("#recents .datasets").appendChild(recentDataset.createDatasetHTMLElement()); + } + + let topVotedQueryURL = new URL(DATASET_ENDPOINT + "/search", getBaseURL()); topVotedQueryURL.searchParams.append("sort", "upvotes"); topVotedQueryURL.searchParams.append("direction", "desc"); topVotedQueryURL.searchParams.append("size", "1"); - fetch(topVotedQueryURL) - .then(resp => resp.json()) - .then((data) => { - let dataset = new Dataset(data.content[0]); - document.querySelector("#top .datasets") - .appendChild(dataset.createDatasetHTMLElement()); - }); + + const topVotedResponse = await fetch(topVotedQueryURL); + const topVotedData = await topVotedResponse.json(); + const topVotedDataset = new Dataset(topVotedData.content[0]); + + document.querySelector("#top .datasets").appendChild(topVotedDataset.createDatasetHTMLElement()); } window.onload = function () { diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 5f8cbdd..c6e00e9 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -19,8 +19,8 @@
  • -

    title

    -

    Simply daily accountability phone call, powered by AI

    +

    title

    +

    Simply daily accountability phone call, powered by AI