From 52bf9b1898980643e0c3dcbc03adce2a4f473c53 Mon Sep 17 00:00:00 2001 From: neviyn Date: Fri, 30 Apr 2021 16:25:29 +0100 Subject: [PATCH] Uploading and tag searching now working --- .gitignore | 2 + .../kotlin/uk/co/neviyn/booru/Controller.kt | 60 ++++++++++++------- src/main/kotlin/uk/co/neviyn/booru/Entity.kt | 10 ++-- .../uk/co/neviyn/booru/FileSystemStorage.kt | 27 +++++++++ .../kotlin/uk/co/neviyn/booru/Repository.kt | 14 +++-- src/main/resources/templates/gallery.html | 8 +++ src/main/resources/templates/landing.html | 13 ++-- src/main/resources/templates/noresults.html | 17 ++++++ src/main/resources/templates/upload.html | 5 +- 9 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 src/main/kotlin/uk/co/neviyn/booru/FileSystemStorage.kt create mode 100644 src/main/resources/templates/noresults.html diff --git a/.gitignore b/.gitignore index 549e00a..d0366d9 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ build/ ### VS Code ### .vscode/ + +images/ \ No newline at end of file diff --git a/src/main/kotlin/uk/co/neviyn/booru/Controller.kt b/src/main/kotlin/uk/co/neviyn/booru/Controller.kt index 94f2114..ee81cc5 100644 --- a/src/main/kotlin/uk/co/neviyn/booru/Controller.kt +++ b/src/main/kotlin/uk/co/neviyn/booru/Controller.kt @@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.security.core.annotation.AuthenticationPrincipal -import org.springframework.security.crypto.codec.Hex import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @@ -12,14 +11,15 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.multipart.MultipartFile -import java.io.File import java.security.MessageDigest +import java.util.* +import javax.validation.constraints.NotEmpty @Controller class BaseController @Autowired constructor( val imageRepository: ImageRepository -){ +) { @GetMapping("/") fun landingPage(model: Model): String { @@ -46,24 +46,33 @@ class ImageController @GetMapping fun getGalleryPage( - @RequestParam(defaultValue = "1") pageNumber: Int, + @RequestParam(name = "page", defaultValue = "1") pageNumber: Int, @RequestParam tags: String?, model: Model ): String { val page = PageRequest.of(pageNumber - 1, 20) - val images : Page? = when (tags) { + val images: Page = when (tags) { null -> imageRepository.findAll(page) // No tags were given to search for + "" -> imageRepository.findAll(page) else -> { - val tagData = tags.split(" ") // Tags arrive separated by spaces, tags themselves cannot contain spaces - .distinct() // Eliminate duplicates - .mapNotNull { tagRepository.findByTagIs(it) } // Try to get actual tag objects + val distinctTags = tags.split(" ").distinct() + val tagData = distinctTags.mapNotNull { tagRepository.findByTagIs(it) } // Try to get actual tag objects when { tagData.isEmpty() -> null // No tags existed with the specified search terms - else -> imageRepository.findByTags(tagData, page) + tagData.size != distinctTags.size -> null // Error if an invalid tag was supplied + tagData.size == 1 -> { + // Simpler query for single tag searches + val result = imageRepository.findAllByTagsContaining(tagData[0], page) + if (result.isEmpty) null else result + } + else -> { + val result = imageRepository.findByTags(tagData, tagData.size.toLong(), page) + if (result.isEmpty) null else result + } } } - } - model.addAttribute("images", images) + } ?: return "noresults" + model.addAttribute("imagePage", images) return "gallery" } @@ -76,11 +85,10 @@ class UploadController val imageRepository: ImageRepository, val tagRepository: TagRepository, val userRepository: UserRepository, - val imageConfigurationProperties: ImageConfigurationProperties + val imageConfigurationProperties: ImageConfigurationProperties, + val storage: FileSystemStorage ) { - val digest: MessageDigest = MessageDigest.getInstance("SHA-512/256") - @GetMapping fun showUploadPage(): String = "upload" @@ -88,17 +96,27 @@ class UploadController fun uploadFile( @AuthenticationPrincipal userDetails: CustomUserDetails, @RequestParam file: MultipartFile, - @RequestParam tags: List + @RequestParam @NotEmpty tags: String ): String { if (file.isEmpty) return "upload" // TODO: Show error on page, can't upload nothing - val extension = File(file.originalFilename!!).extension + val extension = java.io.File(file.originalFilename!!).extension if (!imageConfigurationProperties.types.contains(extension)) return "upload" // TODO: Show error on page, unrecognised file type val user = userRepository.findByName(userDetails.username)!! - val hash: String = Hex.encode(digest.digest(file.bytes)).toString() - val tagData = tags.map { tagRepository.findByTagIs(it) ?: tagRepository.save(Tag(it)) }.toMutableSet() - val outputFile = File(imageConfigurationProperties.directory, "$hash.$extension") - outputFile.writeBytes(file.bytes) + val hash: String = generateFileHash(file) + val tagData = tags.split(" ").map { tagRepository.findByTagIs(it) ?: tagRepository.save(Tag(it)) }.toMutableSet() + storage.addImageFile(hash, extension, file.bytes) imageRepository.save(Image("$hash.$extension", user, tagData)) return "upload" // TODO: Show success on page } -} \ No newline at end of file +} + +val digest: MessageDigest = MessageDigest.getInstance("SHA-512/256") + +/** + * Generate a URL safe string representing the hash of a file + */ +fun generateFileHash(file: MultipartFile): String = Base64 + .getUrlEncoder() + .encodeToString( + digest.digest(file.bytes) + ) \ No newline at end of file diff --git a/src/main/kotlin/uk/co/neviyn/booru/Entity.kt b/src/main/kotlin/uk/co/neviyn/booru/Entity.kt index 08d5b73..39a69e9 100644 --- a/src/main/kotlin/uk/co/neviyn/booru/Entity.kt +++ b/src/main/kotlin/uk/co/neviyn/booru/Entity.kt @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore import javax.persistence.Column import javax.persistence.Entity import javax.persistence.FetchType +import javax.persistence.GeneratedValue +import javax.persistence.GenerationType import javax.persistence.Id import javax.persistence.JoinColumn import javax.persistence.JoinTable @@ -28,14 +30,14 @@ open class User( inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] ) open var roles: MutableSet = mutableSetOf(), - @Id open var id: Long = -1 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long = -1 ) @Entity open class Role( @Column(unique = true) open var name: String = "", - @Id open var id: Long = -1 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long = -1 ) @Entity @@ -52,7 +54,7 @@ open class Image( inverseJoinColumns = [JoinColumn(name = "tag_id", referencedColumnName = "id")] ) open var tags: MutableSet = mutableSetOf(), - @Id open var id: Long = -1 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long = -1 ) @Entity @@ -60,5 +62,5 @@ open class Tag( @Column(unique = true) @Pattern(regexp = "[a-zA-Z0-9_]*") // Only allow alphanumeric and underscores open var tag: String = "", - @Id open var id: Long = -1 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long = -1 ) \ No newline at end of file diff --git a/src/main/kotlin/uk/co/neviyn/booru/FileSystemStorage.kt b/src/main/kotlin/uk/co/neviyn/booru/FileSystemStorage.kt new file mode 100644 index 0000000..f937ccd --- /dev/null +++ b/src/main/kotlin/uk/co/neviyn/booru/FileSystemStorage.kt @@ -0,0 +1,27 @@ +package uk.co.neviyn.booru + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import java.io.File + +interface FileSystemStorage { + + /** + * Add a file to the filesystem with [name] and file [extension] + */ + fun addImageFile(name: String, extension: String, data: ByteArray) + +} + +@Service +class FileSystemStorageService +@Autowired constructor( + val imageConfigurationProperties: ImageConfigurationProperties +) : FileSystemStorage { + + override fun addImageFile(name: String, extension: String, data: ByteArray) { + val outputFile = File(imageConfigurationProperties.directory, "$name.$extension") + outputFile.writeBytes(data) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/uk/co/neviyn/booru/Repository.kt b/src/main/kotlin/uk/co/neviyn/booru/Repository.kt index e9cedcb..7197b5a 100644 --- a/src/main/kotlin/uk/co/neviyn/booru/Repository.kt +++ b/src/main/kotlin/uk/co/neviyn/booru/Repository.kt @@ -7,16 +7,22 @@ import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.CrudRepository interface UserRepository : CrudRepository { - fun findByName(name: String) : User? + fun findByName(name: String): User? } interface RoleRepository : CrudRepository interface ImageRepository : JpaRepository { - @Query("select i from Image i where ?1 in (i.tags)") - fun findByTags(tags: List, pageable: Pageable) : Page + //@Query("select i from Image i join i.tags t where t in ?1 group by i.id having count(i.id) = ?2") + @Query( + value = "select * from booru.image i where i.id in (select image_id from booru.tag_image ti where ti.tag_id in ?1 group by ti.image_id having count(ti.image_id) = ?2)", + nativeQuery = true + ) + fun findByTags(tagIDs: List, tagCount: Long, pageable: Pageable): Page + + fun findAllByTagsContaining(tag: Tag, pageable: Pageable): Page } interface TagRepository : CrudRepository { - fun findByTagIs(tag: String) : Tag? + fun findByTagIs(tag: String): Tag? } \ No newline at end of file diff --git a/src/main/resources/templates/gallery.html b/src/main/resources/templates/gallery.html index 560d1dd..46dad45 100644 --- a/src/main/resources/templates/gallery.html +++ b/src/main/resources/templates/gallery.html @@ -5,6 +5,14 @@ Gallery +
+
+
+ + +
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/landing.html b/src/main/resources/templates/landing.html index 1404de3..907e9c3 100644 --- a/src/main/resources/templates/landing.html +++ b/src/main/resources/templates/landing.html @@ -13,24 +13,21 @@
-

- Image board. +

+ Browse all

- -
- -
+
-

- Serving images. +

+ Serving images.

diff --git a/src/main/resources/templates/noresults.html b/src/main/resources/templates/noresults.html new file mode 100644 index 0000000..1f41476 --- /dev/null +++ b/src/main/resources/templates/noresults.html @@ -0,0 +1,17 @@ + + + + + No Images Found + + +
+
+
+

Couldn't find any images with those tags.

+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/upload.html b/src/main/resources/templates/upload.html index ecc89f2..aa74c3e 100644 --- a/src/main/resources/templates/upload.html +++ b/src/main/resources/templates/upload.html @@ -12,11 +12,12 @@
-
+ + - +