diff --git a/pom.xml b/pom.xml
index 36bb031..cdd484e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.3.0
+ 3.3.1
de.uni-passau.fim.PADAS.group3
@@ -22,11 +22,6 @@
org.springframework.boot
spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-thymeleaf
-
org.springframework.boot
@@ -57,13 +52,6 @@
runtime
-
- org.mockito
- mockito-core
- 4.5.1
- test
-
-
diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java
index 8a269c9..36796e3 100644
--- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java
+++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java
@@ -52,7 +52,7 @@ public class Dataset {
private String licence;
- private static final List sortable = Arrays.asList("author", "title", "upvotes", "date");
+ private static final List sortable = Arrays.asList("author", "title", "upvotes", "raiting", "date");
@ManyToOne
private Category categorie;
diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java
index a844374..d0a00ca 100644
--- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java
+++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java
@@ -71,7 +71,7 @@ public class DatasetController {
if (datasetService.getDatasetById(id) == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
- if (!(stars > 0 && stars < 6)) {
+ if (!(stars >= 0 && stars < 6)) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
datasetService.voteDataset(id, stars);
diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java
index 52e6ef6..757739e 100644
--- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java
+++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java
@@ -17,44 +17,44 @@ public class DatasetService {
private dataRepository datasetRepository;
private CategoryRepository categoryRepository;
- public DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) {
+ DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) {
this.datasetRepository = datasetRepository;
this.categoryRepository = categoryRepository;
}
- public Dataset getDatasetById(UUID id) {
+ Dataset getDatasetById(UUID id) {
return datasetRepository.getDatasetById(id);
}
- public Dataset addDataset(Dataset dataset) {
+ Dataset addDataset(Dataset dataset) {
dataset.setDate(LocalDate.now());
return datasetRepository.save(dataset);
}
- public void voteDataset(UUID id, int vote) {
+ void voteDataset(UUID id, int vote) {
Dataset dataset = datasetRepository.getDatasetById(id);
dataset.vote(vote);
datasetRepository.save(dataset);
}
- public void deleteDataset(UUID id) {
+ void deleteDataset(UUID id) {
Dataset dataset = datasetRepository.getDatasetById(id);
datasetRepository.delete(dataset);
}
- public void upvoteDataset(UUID id) {
+ void upvoteDataset(UUID id) {
Dataset dataset = datasetRepository.getDatasetById(id);
dataset.upvote();
datasetRepository.save(dataset);
}
- public void downvoteDataset(UUID id) {
+ void downvoteDataset(UUID id) {
Dataset dataset = datasetRepository.getDatasetById(id);
dataset.downvote();
datasetRepository.save(dataset);
}
- public Page searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) {
+ Page searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) {
Category category = categories.equals("%") ? null
: categoryRepository.getCategoryById(UUID.fromString(categories));
Type t = type.equals("%") ? null : Type.valueOf(type);
diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java
index 3ce13b0..f68be67 100644
--- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java
+++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java
@@ -37,8 +37,8 @@ public class CategoryController {
}
@ResponseStatus(HttpStatus.CREATED)
@PostMapping
- public void createCategory(@RequestBody CategoryDto dto) {
- categoryService.addCategory(dto);
+ public Category createCategory(@RequestBody CategoryDto dto) {
+ return categoryService.addCategory(dto);
}
diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java
index 3e2698f..ced75f3 100644
--- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java
+++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java
@@ -8,22 +8,22 @@ import java.util.UUID;
public class CategoryService {
private CategoryRepository categoryRepository;
- public CategoryService(CategoryRepository categoryRepository) {
+ CategoryService(CategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
}
- public void addCategory(CategoryDto category) {
+ Category addCategory(CategoryDto category) {
Category cat = new Category(category.getName());
- categoryRepository.save(cat);
+ return categoryRepository.save(cat);
}
- public List getAllCategories() {
+ List getAllCategories() {
List tmp = categoryRepository.findAll();
List s = tmp.stream().map(CategoryDtoMapper::toDto).toList();
return s;
}
- public CategoryDto getCategoryById(UUID id) {
+ CategoryDto getCategoryById(UUID id) {
Category c = categoryRepository.getCategoryById(id);
if (c == null) {
return null;
diff --git a/src/main/resources/static/add.css b/src/main/resources/static/add.css
index 5bbac6e..979d061 100644
--- a/src/main/resources/static/add.css
+++ b/src/main/resources/static/add.css
@@ -171,3 +171,30 @@ form :has(#url) {
.btn:disabled {
filter: var(--drop-shadow) grayscale(.5) brightness(.5);
}
+
+#category[value="new"] {
+ display: none;
+}
+
+label[for="category"] {
+ width: 0;
+ user-select: none;
+ overflow: hidden;
+}
+
+span:has(#category) {
+ gap: unset;
+}
+
+#new-category-group:not(.hidden) {
+ display: flex;
+ justify-content: stretch;
+ gap: var(--gap-small);
+ width: 100%;
+}
+
+#new-category-group button {
+ background-image: url("./sort.svg");
+ background-size: contain;
+ background-origin: content-box;
+}
diff --git a/src/main/resources/static/add.html b/src/main/resources/static/add.html
index 30888c3..3c9f9c8 100644
--- a/src/main/resources/static/add.html
+++ b/src/main/resources/static/add.html
@@ -27,7 +27,7 @@
-
+
@@ -48,6 +48,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/static/add.js b/src/main/resources/static/add.js
index 53292ad..819e55e 100644
--- a/src/main/resources/static/add.js
+++ b/src/main/resources/static/add.js
@@ -7,10 +7,16 @@ const {
["is-dataset"]: isDatasetSwitch,
["short-description"]: shortDescriptionEntry,
url: urlEntry,
+ ["terms-of-use"]: termsOfUseEntry,
+ license: licenseEntry,
+ category: categorySpinner,
+ ["new-category"]: newCategoryEntry,
+ ["change-category-btn"]: changeCategoryBtn,
["full-description"]: fullDescriptionEntry,
["btn-add"]: addBtn,
["btn-cancel"]: cancelBtn,
} = form.elements;
+const newCategoryGroup = document.getElementById("new-category-group");
const validationListener = () => {
addBtn.disabled = !form.checkValidity();
@@ -22,44 +28,127 @@ const validationListener = () => {
authorEntry,
shortDescriptionEntry,
urlEntry,
+ termsOfUseEntry,
+ licenseEntry,
+ newCategoryEntry,
fullDescriptionEntry,
].forEach(input => input.addEventListener("input", validationListener));
+// Category spinner
+const categorySpinnerSet = (...args) => {
+ if (args.length > 0) {
+ categorySpinner.value = args[0];
+ }
+
+ categorySpinner.setAttribute("value", categorySpinner.value);
+
+ if (categorySpinner.value == "new") {
+ newCategoryGroup.classList.remove("hidden");
+ newCategoryEntry.disabled = false;
+ newCategoryEntry.focus();
+ } else {
+ newCategoryGroup.classList.add("hidden");
+ newCategoryEntry.disabled = true;
+ }
+};
+
+const getCategory = () => {
+ return categorySpinner.value == "new"
+ ? newCategoryEntry.value
+ : categorySpinner.value;
+}
+
+categorySpinner.addEventListener("input", e => {
+ categorySpinnerSet();
+ validationListener();
+});
+
+changeCategoryBtn.addEventListener("click", e => {
+ e.preventDefault();
+ categorySpinnerSet("");
+ validationListener();
+});
+
+let categoriesResponse = await fetch(`${location.origin}/api/v1/categories`);
+let categories = [];
+if (!categoriesResponse.ok) {
+ console.warn("Could not load categories!");
+} else {
+ categories = await categoriesResponse.json();
+ for (const category of categories) {
+ let option = document.createElement("option");
+ option.value = category.id;
+ option.text = category.name;
+ categorySpinner.add(option);
+ }
+}
+
+// Form listeners
cancelBtn.addEventListener("click", () => {
window.location.href = location.origin;
})
form.addEventListener("submit", async e => {
e.preventDefault();
- if (!form.reportValidity()) return;
+ if (!form.reportValidity()) {
+ addBtn.disabled = true;
+ return;
+ }
+
+ let categoryID = categorySpinner.value;
+
+ if (categoryID == "new") {
+ const newCategoryName = newCategoryEntry.value.trim();
+
+ if (!categories.map(c => c.name).includes(newCategoryName)) {
+ // Try to add the new category
+ const newCategoryResponse = await fetch(`/api/v1/categories`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json;charset=utf-8" },
+ body: JSON.stringify({ name: newCategoryName }),
+ });
+
+ if (!newCategoryResponse.ok) {
+ newCategoryEntry.setCustomValidity(
+ `Could not create new category: ${newCategoryResponse.statusText}`
+ );
+ form.reportValidity();
+ return;
+ }
+
+ const newCategory = await newCategoryResponse.json();
+ categoryID = newCategory.id;
+ }
+ }
// Create the request body
const newContent = {
title: titleEntry.value,
author: authorEntry.value,
+ type: isDatasetSwitch.checked ? "API" : "DATASET",
abst: shortDescriptionEntry.value,
url: urlEntry.value,
+ termsOfUse: termsOfUseEntry.value,
+ licence: licenseEntry.value,
+ categorie: {
+ id: categoryID,
+ },
description: fullDescriptionEntry.value,
- type: isDatasetSwitch.checked ? "API" : "DATASET",
- categories: [],
};
- console.debug(newContent);
-
// Don't allow several requests to be sent at the same time
addBtn.disabled = true;
let response = await fetch("/api/v1/datasets", {
method: "POST",
+ headers: { "Content-Type": "application/json;charset=utf-8" },
body: JSON.stringify(newContent),
- headers: {
- "Content-Type": "application/json;charset=utf-8"
- }
-
});
let data = await response.json();
+
let dataset = new Dataset(data);
dataset.storageSetKey("created-locally", true);
+
if (response.ok) {
location.assign("/");
} else {
diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js
index e7f0934..87984cd 100644
--- a/src/main/resources/static/dataset.js
+++ b/src/main/resources/static/dataset.js
@@ -23,7 +23,7 @@ export default class Dataset {
return this.#datasets.get(id);
}
- constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, license, termsOfUse }) {
+ constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, licence: license, termsOfUse }) {
this.#shortDescription = shortDescription;
this.#author = author;
this.#category = categorie;
diff --git a/src/main/resources/static/details.css b/src/main/resources/static/details.css
index 86bb412..92d1372 100644
--- a/src/main/resources/static/details.css
+++ b/src/main/resources/static/details.css
@@ -79,6 +79,17 @@ h1 {
grid-column: 1 / 3;
}
+#rating-input {
+ mask-image: url("stars.svg");
+ -webkit-mask-image: url("stars.svg");
+ mask-size: contain;
+ mask-mode: alpha;
+ width: 5lh;
+ height: 1lh;
+ margin-inline: .5ch;
+ background: linear-gradient(to right, yellow 33%, black 33%);
+}
+
#rating {
color: color-mix(in oklab, var(--text-color) 80%, black);
color: transparent;
@@ -208,6 +219,51 @@ a {
}
}
+#details-btns {
+ grid-column: 1 / 4;
+ justify-content: end;
+ gap: 1rem;
+ display: flex;
+}
+
+#delete-btn {
+ background: #861c1c;
+}
+
+/* button styling to be revisited */
+.btn {
+ padding: .5lh 1lh;
+ border: none;
+ border-radius: .5lh;
+ --btn-color: var(--fg-color);
+ background-color: var(--btn-color);
+ color: var(--text-color);
+ font-weight: bold;
+ font-size: 1rem;
+ transition: background-color 100ms, filter 200ms;
+ transition-timing-function: ease-out;
+ --drop-shadow-opacity: .5;
+ --drop-shadow-offset-y: 0;
+ --drop-shadow-blur: .25rem;
+ --drop-shadow: drop-shadow(
+ rgba(0, 0, 0, var(--drop-shadow-opacity))
+ 0 var(--drop-shadow-offset-y) var(--drop-shadow-blur)
+ );
+ filter: var(--drop-shadow);
+}
+
+.btn:focus-visible, #is-dataset:focus-visible + #is-dataset-toggle {
+ outline-offset: 2px;
+}
+
+.btn:not(:disabled):hover {
+ background-color: color-mix(in oklab, var(--btn-color) 80%, var(--bg-color));
+ --drop-shadow-opacity: .8;
+ --drop-shadow-offset-y: .25rem;
+ --drop-shadow-blur: .4rem;
+}
+
+
#nothing-found-bg {
background-position-x: calc(50% + 3cqh);
}
diff --git a/src/main/resources/static/details.html b/src/main/resources/static/details.html
index e91405e..ff27926 100644
--- a/src/main/resources/static/details.html
+++ b/src/main/resources/static/details.html
@@ -24,6 +24,7 @@
Title
4
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis recusandae laborum odio corrupti voluptas quisquam dicta, quibusdam ipsum qui exercitationem.
https://example.com/dataset
@@ -58,6 +59,10 @@
ipsam nobis quis.
+
+
+
+
diff --git a/src/main/resources/static/details.js b/src/main/resources/static/details.js
index e770a5c..3f80529 100644
--- a/src/main/resources/static/details.js
+++ b/src/main/resources/static/details.js
@@ -13,23 +13,35 @@ const category = document.getElementById("category");
const license = document.getElementById("license");
const termsOfUse = document.getElementById("terms-of-use");
const fullDescription = document.getElementById("full-description");
+const backButton = document.getElementById("back-btn");
+const deleteButton = document.getElementById("delete-btn");
+
+let dataset = null;
+let currentRating = 0;
+let isRated = false;
const currentLocation = new URL(location.href);
if (currentLocation.searchParams.has("id")) {
const id = currentLocation.searchParams.get("id");
const response = await fetch(`${currentLocation.origin}/api/v1/datasets/id/${id}`);
- console.dir(response);
if (response.ok) {
const data = await response.json();
- const dataset = new Dataset(data);
- console.dir(data, dataset);
+ dataset = new Dataset(data);
const upvoteComponent = dataset.createUpvoteComponent();
+ console.log(dataset.storageGet());
+ debugger
+ if (dataset.storageGetKey("created-locally", false)) {
+ deleteButton.classList.remove("hidden");
+ }
+ isRated = dataset.storageGetKey("is-rated", false)
+
+
title.innerText = dataset.title;
title.dataset.type = dataset.type.toLowerCase();
rating.value = dataset.rating;
- ratingText.innerText = dataset.rating;
+ ratingText.innerText = parseFloat(dataset.rating).toFixed(1);
shortDescription.innerText = dataset.shortDescription;
url.href = dataset.url;
url.innerText = dataset.url;
@@ -57,3 +69,53 @@ if (currentLocation.searchParams.has("id")) {
mainPage.classList.add("hidden");
notFoundPage.classList.remove("hidden");
}
+
+backButton.addEventListener("click", () => {
+ window.location.href = location.origin;
+})
+
+deleteButton.addEventListener("click", () => {
+ if (dataset != null) {
+ fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id, {
+ method: 'DELETE'
+ }).then(resp => {
+ if (resp.ok) {
+ window.location.href = location.origin;
+ }
+ });
+ }
+});
+
+rating.addEventListener("mousemove", (event) => {
+ if (!isRated) {
+ let bounds = rating.getBoundingClientRect();
+ currentRating = Math.round(((event.clientX - bounds.left) / bounds.width) * 5);
+ console.log(currentRating);
+ rating.value = currentRating;
+ }
+
+});
+
+rating.addEventListener("mouseleave", () => {
+ rating.value = dataset.rating;
+});
+
+rating.addEventListener("click", () => {
+ if (!isRated) {
+ fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id + "/stars?stars=" + currentRating, {
+ method: 'PUT'
+ }).then(resp => {
+ if (resp.ok) {
+ dataset.storageSetKey("is-rated", true);
+ isRated = true;
+ fetch(`${currentLocation.origin}/api/v1/datasets/id/` + dataset.id)
+ .then(resp => resp.json())
+ .then((data) => {
+ dataset = new Dataset(data);
+ ratingText.innerText = parseFloat(dataset.rating).toFixed(1);
+ rating.value = dataset.rating;
+ });
+ }
+ });
+ }
+})
diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html
index 46da537..88979c4 100644
--- a/src/main/resources/static/index.html
+++ b/src/main/resources/static/index.html
@@ -45,8 +45,8 @@
-
-
+
+
diff --git a/src/main/resources/static/main.js b/src/main/resources/static/main.js
index ad98c3b..d58c423 100644
--- a/src/main/resources/static/main.js
+++ b/src/main/resources/static/main.js
@@ -1,4 +1,4 @@
-import { DATASET_ENDPOINT, getBaseURL } from "./constants.js";
+import { DATASET_ENDPOINT, getBaseURL } from "./constants.js"
import { fetchQuery } from "./contentUtility.js";
import Dataset from "./dataset.js";
diff --git a/src/test/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetControllerTests.java b/src/test/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetControllerTests.java
index 8a3c7d9..87bb850 100644
--- a/src/test/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetControllerTests.java
+++ b/src/test/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetControllerTests.java
@@ -185,17 +185,6 @@ public class DatasetControllerTests {
.andExpect(status().isBadRequest());
}
- @Test
- void postMethodName_whenInvalidStars2() throws Exception {
- UUID id = UUID.randomUUID();
- Dataset dataset = new Dataset("Title", "abst", "desc", "auth", null, null, Type.API, "MIT");
-
- given(datasetService.getDatasetById(id)).willReturn(dataset);
-
- mockMvc.perform(put("/api/v1/datasets/id/" + id + "/stars?stars=0"))
- .andExpect(status().isBadRequest());
- }
-
@Test
void postMethodName_whenInvalidStars3() throws Exception {
UUID id = UUID.randomUUID();