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/Dataset.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/Dataset.java similarity index 75% 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 7d694fb..8a269c9 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,16 +1,23 @@ -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.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; 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,10 +46,18 @@ public class Dataset { private int upvotes; private URL url; - @Enumerated(EnumType.STRING) + + @Column(name = "terms_of_use") + private URL termsOfUse; + + private String licence; + + private static final List sortable = Arrays.asList("author", "title", "upvotes", "date"); + + @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; @@ -55,6 +70,7 @@ public class Dataset { setCategorie(categories); setType(type); setUrl(url); + setLicence(licence); } public Dataset() { @@ -109,6 +125,18 @@ public class Dataset { return url; } + public URL getTermsOfUse() { + return termsOfUse; + } + + public String getLicence() { + return licence; + } + + public static List getSort() { + return sortable; + } + public void setAbst(String abst) { this.abst = abst.substring(0, Math.min(abst.length(), 100)); } @@ -132,6 +160,10 @@ public class Dataset { public void setUrl(URL url) { this.url = url; } + + public void setTermsOfUse(URL termsOfUse) { + this.termsOfUse = termsOfUse; + } public void setTitle(String title) { this.title = title.substring(0, Math.min(title.length(), 50)); @@ -153,4 +185,8 @@ public class Dataset { upvotes--; } + public void setLicence(String licence) { + this.licence = licence; + } + } 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 new file mode 100644 index 0000000..1d1ef41 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetController.java @@ -0,0 +1,103 @@ +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 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; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@RestController +@RequestMapping("/api/v1/datasets") +@EnableSpringDataWebSupport +public class DatasetController { + @Autowired + private DatasetService datasetService; + + @GetMapping("/id/{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) { + return datasetService.addDataset(dataset); + } + + @DeleteMapping("/id/{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 ResponseEntity upvote(@PathVariable("id") UUID id) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + datasetService.upvoteDataset(id); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); + } + + @PutMapping("/id/{id}/downvote") + public ResponseEntity downvote(@PathVariable("id") UUID id) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + datasetService.downvoteDataset(id); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); + } + + @PutMapping("/id/{id}/stars") + public ResponseEntity postMethodName(@PathVariable("id") UUID id, + @RequestParam("stars") int stars) { + if (datasetService.getDatasetById(id) == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + if (!(stars > 0 && stars < 6)) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + datasetService.voteDataset(id, stars); + return new ResponseEntity<>(datasetService.getDatasetById(id), HttpStatus.OK); + } + + @GetMapping("/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 = 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/Dataset/DatasetService.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java new file mode 100644 index 0000000..93d1388 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/Dataset/DatasetService.java @@ -0,0 +1,71 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.Dataset; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +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 +public class DatasetService { + private dataRepository datasetRepository; + private CategoryRepository categoryRepository; + + public DatasetService(dataRepository datasetRepository, CategoryRepository categoryRepository) { + this.datasetRepository = datasetRepository; + this.categoryRepository = categoryRepository; + } + + public Dataset getDatasetById(UUID id) { + return datasetRepository.getDatasetById(id); + } + + public Dataset addDataset(Dataset dataset) { + dataset.setDate(LocalDate.now()); + return datasetRepository.save(dataset); + } + + public void voteDataset(UUID id, int vote) { + Dataset dataset = datasetRepository.getDatasetById(id); + dataset.vote(vote); + datasetRepository.save(dataset); + } + + public void deleteDataset(UUID id) { + Dataset dataset = datasetRepository.getDatasetById(id); + datasetRepository.delete(dataset); + } + + public void upvoteDataset(UUID id) { + Dataset dataset = datasetRepository.getDatasetById(id); + dataset.upvote(); + datasetRepository.save(dataset); + } + + public 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) { + 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); + } + 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/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 79% 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 17fde64..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); @@ -65,10 +67,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); +} diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java new file mode 100644 index 0000000..a868283 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/Category.java @@ -0,0 +1,46 @@ +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; +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/category/CategoryController.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java new file mode 100644 index 0000000..3ce13b0 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryController.java @@ -0,0 +1,45 @@ +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 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; +import org.springframework.web.bind.annotation.RequestBody; + + + + +@RestController +@RequestMapping("/api/v1/categories") +public class CategoryController { + @Autowired + private CategoryService categoryService; + + @GetMapping + public List getMethodName() { + return categoryService.getAllCategories() ; + } + + @GetMapping("/id/{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 void createCategory(@RequestBody CategoryDto dto) { + categoryService.addCategory(dto); + } + + +} diff --git a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java new file mode 100644 index 0000000..11644cc --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDto.java @@ -0,0 +1,27 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.category; + +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/category/CategoryDtoMapper.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDtoMapper.java new file mode 100644 index 0000000..683fdc8 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryDtoMapper.java @@ -0,0 +1,10 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.category; + +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/category/CategoryRepository.java b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryRepository.java new file mode 100644 index 0000000..acde965 --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryRepository.java @@ -0,0 +1,20 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.category; + +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); + + @SuppressWarnings("null") + List findAll(); + List findByName(String name); + @SuppressWarnings("null") + Optional findById(UUID id); + +} 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 new file mode 100644 index 0000000..3e2698f --- /dev/null +++ b/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/category/CategoryService.java @@ -0,0 +1,34 @@ +package de.uni_passau.fim.PADAS.group3.DataDash.category; + +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) { + Category c = categoryRepository.getCategoryById(id); + if (c == null) { + return null; + } + return CategoryDtoMapper.toDto(c); + } + +} 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 deleted file mode 100644 index 06454ed..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/CategoryController.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.controler; - -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 org.springframework.web.bind.annotation.GetMapping; - - -@RestController -@RequestMapping("/api/v1/categories") -public class CategoryController { - @GetMapping - public Category[] getMethodName() { - return Category.values(); - } -} 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 deleted file mode 100644 index 00c1756..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/controler/DatasetController.java +++ /dev/null @@ -1,108 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.controler; - -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.data.web.config.EnableSpringDataWebSupport.PageSerializationMode; -import org.springframework.data.domain.Sort; - -import java.util.UUID; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO) -@RestController -@RequestMapping("/api/v1/datasets") -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); - } - - @PostMapping - public Dataset createDataset(@RequestBody Dataset dataset) { - datasetService.addDataset(dataset); - // TODO: figure out what the fuck i need to do here - return null; - } - - // @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) { - datasetService.deleteDataset(id); - } - - @PutMapping("/id/{id}/upvote") - public Dataset upvote(@PathVariable("id") UUID id) { - datasetService.upvoteDataset(id); - return datasetService.getDatasetById(id); - } - - @PutMapping("/id/{id}/downvote") - public Dataset downvote(@PathVariable("id") UUID id) { - datasetService.downvoteDataset(id); - return getDatasetById(id); // new ResponseEntity<>(null, HttpStatus.OK); - } - - @PutMapping("/id/{id}/stars") - public Dataset postMethodName(@PathVariable("id") UUID id, - @RequestParam("stars") int stars) { - if (stars > 0 && stars < 6) { - datasetService.voteDataset(id, stars); - } - return datasetService.getDatasetById(id); - } - - @GetMapping - 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, - @RequestParam(value = "type", required = false) Type type, - @RequestParam(value = "min-rating", required = false) Float rating, - @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) 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); - } - - @GetMapping("/search") - public Page 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); - } - -} \ No newline at end of file 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/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 deleted file mode 100644 index b7cc30e..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/Category.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; - -public enum Category { - HEALTH, - ENVIRONMENT, - ECONOMY, - POLITICS, - TECHNOLOGY, - SPORTS, - SCIENCE, - CULTURE, - EDUCATION, - OTHER -} 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 deleted file mode 100644 index a3ac52d..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/DatasetService.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.uni_passau.fim.PADAS.group3.DataDash.model; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.data.domain.Page; - - -@Service -public class DatasetService { - private dataRepository datasetRepository; - - public DatasetService(dataRepository datasetRepository) { - this.datasetRepository = datasetRepository; - } - - public List getAllDatasets() { - return datasetRepository.findAll(); - } - - public Dataset getDatasetById(UUID id) { - return datasetRepository.getDatasetById(id); - } - - public void addDataset(Dataset dataset) { - dataset.setDate(LocalDate.now()); - datasetRepository.save(dataset); - } - - public void updateDatasetTitle(UUID id, String title) { - datasetRepository.getDatasetById(id).setTitle(title); - } - - public void voteDataset(UUID id, int vote) { - Dataset dataset = datasetRepository.getDatasetById(id); - dataset.vote(vote); - datasetRepository.save(dataset); - } - - public void deleteDataset(UUID id) { - Dataset dataset = datasetRepository.getDatasetById(id); - datasetRepository.delete(dataset); - } - - public List getDatasetsByTitle(String title) { - return datasetRepository.findByTitle(title); - } - - public List getDatasetsByTitleLike(String title) { - return datasetRepository.findByTitleLike(title); - } - - public List findByDescriptionLike(String description) { - return datasetRepository.findByDescriptionLike(description); - } - - public List getDatasetsByAuthorLike(String author) { - return datasetRepository.findByAuthorLike(author); - } - - public List getDatasetsByType(Type type) { - return datasetRepository.findByType(type); - } - - public List getDatasetsByAbstLike(String abst) { - return datasetRepository.findByAbstLike(abst); - } - - public List getDatasetsByRaitingGreaterThan(float raiting) { - return datasetRepository.findByRaitingGreaterThan(raiting); - } - - public void upvoteDataset(UUID id) { - Dataset dataset = datasetRepository.getDatasetById(id); - dataset.upvote(); - datasetRepository.save(dataset); - } - - public void downvoteDataset(UUID id) { - Dataset dataset = datasetRepository.getDatasetById(id); - dataset.downvote(); - datasetRepository.save(dataset); - } - - 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(raiting), pageable); - } - - public Page searchByOptionalCriteria(String search, String categories, String type, Pageable pageable) { - //TODO: make it not Crash - Category category = categories.equals("%") ? null : Category.valueOf(categories); - Type t = type.equals("%") ? null : Type.valueOf(type); - - return datasetRepository.searchByOptionalCriteria(Optional.ofNullable(search), Optional.ofNullable(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 deleted file mode 100644 index dd6d8e0..0000000 --- a/src/main/java/de/uni_passau/fim/PADAS/group3/DataDash/model/LoadDummyDatabase.java +++ /dev/null @@ -1,36 +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) { - - - - 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); - for (int j = 0; j < new Random().nextInt(50); j++) { - dataset.upvote(); - } - log.info("Preloading" + repository.save(dataset)); - } - 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 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..5f7c01b --- /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, licence, terms_of_use) 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', 'http://url.de'), +('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', 'http://url.de'), +('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', 'http://url.de'), +('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', 'http://url.de'), +('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', 'http://url.de'); +-- Insert 10 more sample data into dataset +INSERT INTO dataset (date, raiting, upvotes, votes, categorie_id, id, abst, author, description, title, url, type, licence, terms_of_use) 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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'), +('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', 'http://zip.com'); \ 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..d61125c --- /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(200000), title varchar(255), url varchar(2048), terms_of_use varchar(2048), type enum ('API','DATASET'), licence varchar(255), primary key (id)); +alter table if exists dataset add constraint FKq6qwq6u473f89h71s7rf97ruy foreign key (categorie_id) references category; diff --git a/src/main/resources/static/add.css b/src/main/resources/static/add.css index 6d62a6f..e4cc24d 100644 --- a/src/main/resources/static/add.css +++ b/src/main/resources/static/add.css @@ -1,9 +1,5 @@ @import url("main.css"); -:root { - --accent-color: oklch(65.33% 0.158 247.76); -} - form { display: grid; grid-template-columns: 1fr 1fr auto; 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/static/add.js b/src/main/resources/static/add.js index 280eb67..53292ad 100644 --- a/src/main/resources/static/add.js +++ b/src/main/resources/static/add.js @@ -1,3 +1,5 @@ +import Dataset from "./dataset.js"; + const form = document.forms[0]; const { title: titleEntry, @@ -23,7 +25,11 @@ const validationListener = () => { fullDescriptionEntry, ].forEach(input => input.addEventListener("input", validationListener)); -form.addEventListener("submit", e => { +cancelBtn.addEventListener("click", () => { + window.location.href = location.origin; +}) + +form.addEventListener("submit", async e => { e.preventDefault(); if (!form.reportValidity()) return; @@ -43,17 +49,20 @@ form.addEventListener("submit", e => { // Don't allow several requests to be sent at the same time addBtn.disabled = true; - fetch("/api/v1/datasets", { + let response = await fetch("/api/v1/datasets", { method: "POST", body: JSON.stringify(newContent), headers: { "Content-Type": "application/json;charset=utf-8" } - }).then(response => { - if (response.status == 200) { - location.assign("/"); - } else { - addBtn.disabled = !form.checkValidity(); - } + }); + let data = await response.json(); + let dataset = new Dataset(data); + dataset.storageSetKey("created-locally", true); + if (response.ok) { + location.assign("/"); + } else { + addBtn.disabled = !form.checkValidity(); + } }); diff --git a/src/main/resources/static/constants.js b/src/main/resources/static/constants.js new file mode 100644 index 0000000..ed33038 --- /dev/null +++ b/src/main/resources/static/constants.js @@ -0,0 +1,2 @@ +export const DATASET_ENDPOINT = "/api/v1/datasets"; +export const getBaseURL = () => location.origin; diff --git a/src/main/resources/static/contentUtility.js b/src/main/resources/static/contentUtility.js index deb5658..6854b66 100644 --- a/src/main/resources/static/contentUtility.js +++ b/src/main/resources/static/contentUtility.js @@ -1,25 +1,38 @@ -import {searchBarTimeout, searchSection} from "./main.js" +// TODO consider renaming this to "searchUtility.js" + +import { searchBarTimeout, searchSection, lastQuery } from "./main.js" import Dataset from "./dataset.js" -export function fetchQuery(fetchString) { +export async function fetchQuery(fetchString, clearResults) { clearTimeout(searchBarTimeout); - fetch(fetchString) - .then(resp => resp.json()) - .then((data) => { - parseContent(data.content); - }); + 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) { +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"); + searchSection.querySelector(".datasets").classList.add("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()); + nothingFoundElement.classList.add("hidden"); + + const datasets = content.map(dataset => Dataset.get(dataset.id) ?? new Dataset(dataset)); + searchSection.querySelector(".datasets").classList.remove("hidden"); + if (clearResults) { + Array.from(searchSection.querySelectorAll(".datasets .dataset")).forEach(e => e.remove()); + } + for (const dataset of datasets) { - searchSection.querySelector(".datasets").appendChild(dataset.createDatasetHTMLElement()); + searchSection.querySelector(".datasets") + .appendChild(dataset.createDatasetHTMLElement()); } } - } diff --git a/src/main/resources/static/dataset.js b/src/main/resources/static/dataset.js index 27245e5..e7f0934 100644 --- a/src/main/resources/static/dataset.js +++ b/src/main/resources/static/dataset.js @@ -1,11 +1,13 @@ -import { vote } from "./main.js"; +import { DATASET_ENDPOINT, getBaseURL } from "./constants.js"; export default class Dataset { - #abstract; + static #datasets = new Map(); + + #shortDescription; #author; - #categories; + #category; #date; - #description; + #fullDescription; #id; #rating; #title; @@ -13,39 +15,222 @@ export default class Dataset { #upvotes; #url; #votes; + #license; + #termsOfUse; + #elements = []; - constructor({abst: shortDescription, author, categories, date, description, id, rating, title, type, upvotes, url, votes}) { - this.#abstract = shortDescription; + static get(id) { + return this.#datasets.get(id); + } + + constructor({ abst: shortDescription, author, categorie, date, description, id, raiting, title, type, upvotes, url, votes, license, termsOfUse }) { + this.#shortDescription = shortDescription; this.#author = author; - this.#categories = categories; + this.#category = categorie; this.#date = date; - this.#description = description; + this.#fullDescription = description; this.#id = id; - this.#rating = rating; + this.#rating = raiting; this.#title = title; this.#type = type; this.#upvotes = upvotes; this.#url = url; this.#votes = votes; + this.#license = license; + this.#termsOfUse = termsOfUse; + + Dataset.#datasets.set(id, this); } + // Getters + get shortDescription() { + return this.#shortDescription; + } + + get author() { + return this.#author; + } + + get category() { + return this.#category; + } + + get date() { + return this.#date; + } + + get fullDescription() { + return this.#fullDescription; + } + + 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 license() { + return this.#license; + } + + get termsOfUse() { + return this.#termsOfUse; + } + + get mainElement() { + return this.#elements[0]; + } + + get elements() { + return this.#elements; + } + + parseDate() { + return new Date(this.#date); + } + + // 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; + 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(".downvote-btn").addEventListener("click", () => { - vote(this.#id, false); + let datasetContainer = clone.querySelector(".dataset"); + datasetContainer.dataset.id = this.#id; + datasetContainer.addEventListener("click", event => { + if (!event.target.classList.contains("btn")) { + let detailsPage = new URL("/details.html", location.origin); + detailsPage.searchParams.append("id", this.#id); + window.location.href = detailsPage.toString(); + } }) + clone.querySelector(".dataset-title").innerText = this.#title; + clone.querySelector(".dataset-description").innerText = this.#shortDescription; + clone.querySelector(".upvote-count").innerText = this.#upvotes; + + this.#elements.push(clone.children[0]); + this.#createUpvoteButtons(clone); return clone; } + + // 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/details.css b/src/main/resources/static/details.css new file mode 100644 index 0000000..86bb412 --- /dev/null +++ b/src/main/resources/static/details.css @@ -0,0 +1,213 @@ +@import url("./main.css"); +@import url('https://fonts.googleapis.com/css2?family=Flow+Circular&display=swap'); + +:root { + --min-card-size: min(60ch, calc(100vw - var(--pad-main))); + --rating-color: gold; +} + +main { + & > :is(header, section, footer) { + background-color: var(--fg-color); + padding: var(--pad-datasets-block) var(--pad-datasets-inline); + position: relative; + } + + & > :first-child { + border-top-left-radius: var(--corner-radius); + border-top-right-radius: var(--corner-radius); + margin-top: var(--pad-main); + } + + & > :last-child { + border-bottom-left-radius: var(--corner-radius); + border-bottom-right-radius: var(--corner-radius); + margin-bottom: var(--pad-main); + } + + & > :not(:last-child):is(header, section)::after { + content: ''; + position: absolute; + inset-inline: calc(var(--pad-datasets-inline) - var(--gap-small)); + bottom: 0; + border-bottom: 3px solid var(--bg-color); + opacity: .25; + transform: translateY(50%); + z-index: 1; + } +} + +header { + margin-inline: 0; + display: grid; + grid-template-columns: 1fr 1fr max-content; + align-items: center; + grid-gap: var(--gap-medium); +} + +h1 { + margin-block: var(--gap-medium) 0; + text-align: center; +} + +#title { + grid-column: 1 / 3; + + &::after { + content: attr(data-type); + background-color: var(--accent-color); + font-size: .5em; + font-weight: initial; + position: relative; + bottom: .25lh; + margin-inline: var(--gap-small); + padding: 2pt 4pt; + border-radius: 4pt; + } + + &[data-type="dataset"]::after { + content: "Dataset"; + } + + &[data-type="api"]::after { + content: "API"; + } +} + +#details summary, #url { + text-align: start; + grid-column: 1 / 3; +} + +#rating { + color: color-mix(in oklab, var(--text-color) 80%, black); + color: transparent; + width: 5lh; + height: 1lh; + margin-inline: .5ch; + mask-image: url("stars.svg"); + -webkit-mask-image: url("stars.svg"); + mask-size: contain; + mask-mode: alpha; + --rating-percent: calc((var(--rating, 0) / 5) * 100%); + background: var(--bg-color); + vertical-align: bottom; +} + +#rating::-webkit-meter-bar { + background: var(--bg-color); + border: none; + border-radius: 0; + grid-row: 1 / -1; +} + +#rating::-webkit-meter-optimum-value, +#rating::-webkit-meter-suboptimum-value, +#rating::-webkit-meter-even-less-good-value { + background-color: var(--rating-color); +} + +#rating::-moz-meter-bar { + background: var(--rating-color); +} + +a { + --text-color: var(--accent-color); + + /* + Why doesn't it do this automatically? It is inherited from body, + so I should be able to just change --text-color, right? + */ + color: var(--text-color); +} + +.upvote { + margin: var(--gap-medium) 0; + align-self: self-start; + grid-column: 3; + grid-row: 1 / 4; +} + +#metadata { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + gap: var(--gap-large); +} + +#full-description { + text-wrap: balance; + text-wrap: pretty; + margin-top: 0; +} + +:is(#full-description, #not-found) br { + margin-bottom: .5lh; +} + +.skeleton { + font-family: "Flow Circular"; + font-weight: 400; + font-style: normal; + user-select: none; + --rating-color: var(--text-color); + + a, .btn { + pointer-events: none; + } + + & > * { + cursor: progress; + } + + @media screen and not (prefers-reduced-motion) { + :is(header, section) > :is(p, span, h1, a) { + animation: infinite 2s linear skeleton-loading; + background: radial-gradient( + circle farthest-side, + var(--highlight-color, white) 20%, + var(--text-color) 40% + ) no-repeat, var(--text-color); + background-clip: text; + -webkit-background-clip: text; + color: transparent; + + &:is(a) { + --highlight-color: color-mix(in oklab, white, var(--text-color)); + } + } + } + + #title::after { + color: color-mix(in oklab, var(--accent-color) 50%, currentcolor); + } +} + +@keyframes skeleton-loading { + from { + background-position-x: calc(-1.4 * var(--min-card-size)), 0; + } + + to { + background-position-x: calc(1.4 * var(--min-card-size)), 0; + } +} + +#not-found:not(.hidden) { + min-height: calc(100vh - 2 * var(--pad-main)); + display: flex; + flex-direction: column; + gap: var(--gap-large); + justify-content: space-evenly; + container: nothing-found / inline-size; + + p, h1 { + text-align: center; + text-wrap: balance; + text-wrap: pretty; + } +} + +#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 new file mode 100644 index 0000000..e91405e --- /dev/null +++ b/src/main/resources/static/details.html @@ -0,0 +1,76 @@ + + + + + + Dataset details + + + + + + + + + +
+
+

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 + +
+ +
+ Added on: + Category: Something + License: MIT + Terms of Use +
+ +
+

Full description
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Beatae + nihil saepe et numquam quo id voluptatum recusandae assumenda + doloremque pariatur consequatur molestias delectus dolore + corrupti, odio modi vitae repellat tempora sed eos voluptates + temporibus veritatis repudiandae. Cum eveniet molestias, in + beatae non reiciendis quia voluptatem id, facere architecto + vitae harum ipsum earum deleniti dolor atque recusandae odit + corporis error dolorum blanditiis vel maxime pariatur quibusdam! +
Saepe debitis ab possimus, dolorem neque ad voluptatibus ex + quisquam itaque. Nihil et non consequuntur error ipsa. + Necessitatibus voluptatibus aliquid itaque id ipsum, pariatur + odio explicabo, dolores ex, incidunt tenetur dolore. Assumenda + ipsam nobis quis. +

+
+
+ +
+

This is not the page you're looking for.

+
+

+ The dataset or API you were sent to is not in our database. + Either it has been deleted by its author or we didn't know of it + to begin with. +
+ You can try browsing for other datasets and APIs on our + homepage. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/static/details.js b/src/main/resources/static/details.js new file mode 100644 index 0000000..e770a5c --- /dev/null +++ b/src/main/resources/static/details.js @@ -0,0 +1,59 @@ +import Dataset from "./dataset.js"; + +const mainPage = document.getElementById("details"); +const notFoundPage = document.getElementById("not-found"); + +const title = document.getElementById("title"); +const rating = document.getElementById("rating"); +const ratingText = document.getElementById("rating-text"); +const shortDescription = document.getElementById("short-description"); +const url = document.getElementById("url"); +const date = document.getElementById("date"); +const category = document.getElementById("category"); +const license = document.getElementById("license"); +const termsOfUse = document.getElementById("terms-of-use"); +const fullDescription = document.getElementById("full-description"); + +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); + const upvoteComponent = dataset.createUpvoteComponent(); + + title.innerText = dataset.title; + title.dataset.type = dataset.type.toLowerCase(); + rating.value = dataset.rating; + ratingText.innerText = dataset.rating; + shortDescription.innerText = dataset.shortDescription; + url.href = dataset.url; + url.innerText = dataset.url; + mainPage.querySelector(".upvote").replaceWith(upvoteComponent); + + date.datetime = dataset.date; + date.innerText = dataset.parseDate().toLocaleDateString(undefined, { + day: "numeric", + month: "long", + year: "numeric", + }); + category.innerText = dataset.category.name; + category.dataset.id = dataset.category.id; + license.innerText = dataset.license; + termsOfUse.href = dataset.termsOfUse; + + fullDescription.innerText = dataset.fullDescription; + + mainPage.classList.remove("skeleton"); + } else { + mainPage.classList.add("hidden"); + notFoundPage.classList.remove("hidden"); + } +} else { + mainPage.classList.add("hidden"); + notFoundPage.classList.remove("hidden"); +} diff --git a/src/main/resources/templates/index.html b/src/main/resources/static/index.html similarity index 82% rename from src/main/resources/templates/index.html rename to src/main/resources/static/index.html index 23ee989..46da537 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/static/index.html @@ -4,8 +4,13 @@ DataDash + + + - + + +
@@ -19,8 +24,8 @@
  • -

    title

    -

    Simply daily accountability phone call, powered by AI

    +

    title

    +

    Simply daily accountability phone call, powered by AI