Skip to content

Commit

Permalink
Merge branch 'develop' into seeders
Browse files Browse the repository at this point in the history
  • Loading branch information
MRita443 authored Nov 26, 2024
2 parents 8489e23 + f5dd084 commit 3719b5e
Show file tree
Hide file tree
Showing 34 changed files with 706 additions and 64 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
docs/
static/

### STS ###
.apt_generated
Expand Down Expand Up @@ -38,3 +39,6 @@ out/

### VS Code ###
.vscode/

### Static Content
static/
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import org.springframework.security.access.AccessDeniedException
import org.springframework.security.core.AuthenticationException
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.multipart.MaxUploadSizeExceededException
import org.springframework.web.multipart.support.MissingServletRequestPartException
import org.springframework.web.servlet.NoHandlerFoundException
import pt.up.fe.ni.website.backend.config.Logging

data class SimpleError(
Expand All @@ -32,7 +32,7 @@ data class CustomError(val errors: List<SimpleError>)
@RestControllerAdvice
class ErrorController(private val objectMapper: ObjectMapper) : ErrorController, Logging {

@RequestMapping("/**")
@ExceptionHandler(NoHandlerFoundException::class)
@ResponseStatus(HttpStatus.NOT_FOUND)
fun endpointNotFound(): CustomError = wrapSimpleError("invalid endpoint")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,18 @@ class EventController(private val service: EventService) {
@PathVariable idEvent: Long,
@PathVariable idAccount: Long
) = service.removeTeamMemberById(idEvent, idAccount)

@PutMapping("/{idEvent}/gallery", consumes = ["multipart/form-data"])
fun addGalleryImage(
@PathVariable idEvent: Long,
@RequestParam
@ValidImage
image: MultipartFile
) = service.addGalleryImage(idEvent, image, EventService.IMAGE_FOLDER)

@DeleteMapping("/{idEvent}/gallery")
fun removeGalleryImage(
@PathVariable idEvent: Long,
@RequestPart imageUrl: String
) = service.removeGalleryImage(idEvent, imageUrl)
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,18 @@ class ProjectController(private val service: ProjectService) {
@PathVariable idProject: Long,
@PathVariable idAccount: Long
) = service.removeHallOfFameMemberById(idProject, idAccount)

@PutMapping("/{idProject}/gallery", consumes = ["multipart/form-data"])
fun addGalleryImage(
@PathVariable idProject: Long,
@RequestParam
@ValidImage
image: MultipartFile
) = service.addGalleryImage(idProject, image, ProjectService.IMAGE_FOLDER)

@DeleteMapping("/{idProject}/gallery")
fun removeGalleryImage(
@PathVariable idProject: Long,
@RequestPart imageUrl: String
) = service.removeGalleryImage(idProject, imageUrl)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract class ActivityDto<T : Activity>(
val title: String,
val description: String,
val teamMembersIds: List<Long>?,
val slug: String?,
val slug: String,
var image: String?,
@JsonIgnore
var imageFile: MultipartFile? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class EventDto(
title: String,
description: String,
teamMembersIds: List<Long>?,
slug: String?,
slug: String,
image: String?,

val registerUrl: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ class PostDto(
val title: String,
val body: String,
val thumbnailPath: String,
val slug: String?
val slug: String
) : EntityDto<Post>()
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class ProjectDto(
title: String,
description: String,
teamMembersIds: List<Long>?,
slug: String?,
slug: String,
image: String?,

val isArchived: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
Expand Down Expand Up @@ -38,11 +39,14 @@ abstract class Activity(

@Column(unique = true)
@field:Size(min = Constants.Slug.minSize, max = Constants.Slug.maxSize)
open val slug: String? = null,
open val slug: String,

@field:NotBlank
open var image: String,

@ElementCollection(fetch = FetchType.EAGER)
open val gallery: MutableList<String> = mutableListOf(),

@Id
@GeneratedValue
open val id: Long? = null
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class Event(
description: String,
teamMembers: MutableList<Account> = mutableListOf(),
associatedRoles: MutableList<PerActivityRole> = mutableListOf(),
slug: String? = null,
slug: String,
image: String,
gallery: MutableList<String> = mutableListOf(),

@field:NullOrNotBlank
@field:URL
Expand All @@ -33,4 +34,4 @@ class Event(
val category: String?,

id: Long? = null
) : Activity(title, description, teamMembers, associatedRoles, slug, image, id)
) : Activity(title, description, teamMembers, associatedRoles, slug, image, gallery, id)
2 changes: 1 addition & 1 deletion src/main/kotlin/pt/up/fe/ni/website/backend/model/Post.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ class Post(

@Column(unique = true)
@field:Size(min = Constants.Slug.minSize, max = Constants.Slug.maxSize)
val slug: String? = null
val slug: String
)
5 changes: 3 additions & 2 deletions src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ class Project(
description: String,
teamMembers: MutableList<Account> = mutableListOf(),
associatedRoles: MutableList<PerActivityRole> = mutableListOf(),
slug: String? = null,
slug: String,
image: String,
gallery: MutableList<String> = mutableListOf(),

var isArchived: Boolean = false,

Expand Down Expand Up @@ -48,4 +49,4 @@ class Project(
val timeline: List<@Valid TimelineEvent> = emptyList(),

id: Long? = null
) : Activity(title, description, teamMembers, associatedRoles, slug, image, id)
) : Activity(title, description, teamMembers, associatedRoles, slug, image, gallery, id)
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import pt.up.fe.ni.website.backend.model.Activity

@Repository
interface ActivityRepository<T : Activity> : CrudRepository<T, Long> {
fun findBySlug(slug: String?): T?
fun findBySlug(slug: String): T?
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import pt.up.fe.ni.website.backend.model.Post

@Repository
interface PostRepository : JpaRepository<Post, Long> {
fun findBySlug(slug: String?): Post?
fun findBySlug(slug: String): Post?
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ object ErrorMessages {

fun roleNotFound(id: Long): String = "role not found with id $id"

fun fileNotFound(fileName: String): String = "file not found with name $fileName"

fun userAlreadyHasRole(roleId: Long, userId: Long): String = "user $userId already has role $roleId"

fun userNotInRole(roleId: Long, userId: Long): String = "user $userId doesn't have role $roleId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pt.up.fe.ni.website.backend.service.activity

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import pt.up.fe.ni.website.backend.dto.entity.ActivityDto
import pt.up.fe.ni.website.backend.model.Activity
import pt.up.fe.ni.website.backend.repository.ActivityRepository
Expand All @@ -26,7 +27,7 @@ abstract class AbstractActivityService<T : Activity>(
}

dto.imageFile?.let {
val fileName = fileUploader.buildFileName(it, dto.title)
val fileName = fileUploader.buildFileName(it, dto.slug)
dto.image = fileUploader.uploadImage(imageFolder, fileName, it.bytes)
}

Expand All @@ -51,7 +52,7 @@ abstract class AbstractActivityService<T : Activity>(
if (imageFile == null) {
dto.image = activity.image
} else {
val fileName = fileUploader.buildFileName(imageFile, dto.title)
val fileName = fileUploader.buildFileName(imageFile, dto.slug)
dto.image = fileUploader.uploadImage(imageFolder, fileName, imageFile.bytes)
}

Expand All @@ -76,13 +77,33 @@ abstract class AbstractActivityService<T : Activity>(
fun removeTeamMemberById(idActivity: Long, idAccount: Long): T {
val activity = getActivityById(idActivity)
if (!accountService.doesAccountExist(idAccount)) {
throw NoSuchElementException(
ErrorMessages.accountNotFound(
idAccount
)
)
throw NoSuchElementException(ErrorMessages.accountNotFound(idAccount))
}
activity.teamMembers.removeIf { it.id == idAccount }
return repository.save(activity)
}

fun addGalleryImage(activityId: Long, image: MultipartFile, imageFolder: String): Activity {
val activity = getActivityById(activityId)

val fileName = fileUploader.buildFileName(image, activity.slug)
val imageName = fileUploader.uploadImage("$imageFolder/gallery", fileName, image.bytes)

activity.gallery.add(imageName)

return repository.save(activity)
}

fun removeGalleryImage(activityId: Long, imageName: String): Activity {
val activity = getActivityById(activityId)

if (activity.gallery.contains(imageName)) {
fileUploader.deleteImage(imageName)
activity.gallery.remove(imageName)
} else {
throw NoSuchElementException(ErrorMessages.fileNotFound(imageName))
}

return repository.save(activity)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ class CloudinaryFileUploader(private val basePath: String, private val cloudinar

return result["url"]?.toString() ?: ""
}

override fun deleteImage(fileName: String) {
// Here, fileName is the publicId in Cloudinary, which would be the path to the file
cloudinary.uploader().destroy(
fileName,
null
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import pt.up.fe.ni.website.backend.utils.extensions.filenameExtension

abstract class FileUploader {
abstract fun uploadImage(folder: String, fileName: String, image: ByteArray): String
abstract fun deleteImage(fileName: String)

fun buildFileName(photoFile: MultipartFile, prefix: String = ""): String {
val limitedPrefix = prefix.take(100) // File name length has a limit of 256 characters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ class StaticFileUploader(private val storePath: String, private val servePath: S

return "$servePath/$folder/$fileName"
}

override fun deleteImage(fileName: String) {
val storedFileName = fileName.replace(servePath, storePath)
val file = File(storedFileName)

if (file.exists()) file.delete()
}
}
11 changes: 8 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ server.error.whitelabel.enabled=false
# Jackson
spring.jackson.default-property-inclusion=non_null
spring.jackson.deserialization.fail-on-null-creator-properties=true
spring.jackson.date-format=dd-MM-yyyy
spring.jackson.date-format=dd-MM-yyyy HH:mm
spring.jackson.time-zone=Europe/Lisbon

# Auth Config
Expand All @@ -36,9 +36,14 @@ upload.provider=static
upload.cloudinary-base-path=website
upload.cloudinary-url=GET_YOURS_AT_CLOUDINARY_DASHBOARD
# Folder in which files will be stored
upload.static-path=classpath:static
upload.static-path=file:static
# URL that will serve static content
upload.static-serve=http://localhost:3000/static
upload.static-serve=http://localhost:8080/static

# Files are served in the following path
spring.mvc.static-path-pattern=/static/**
# Add NoHandlerFoundException as an exception
spring.mvc.throw-exception-if-no-handler-found=true

# Cors Origin
cors.allow-origin = http://localhost:3000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ class AuthControllerTest @Autowired constructor(
inner class CheckPermissions {
private val testPermissions = listOf(Permission.CREATE_ACCOUNT, Permission.CREATE_ACTIVITY)
private val testActivity = Project(
"Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image", false,
emptyList(), null, "test target audience"
"Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image",
mutableListOf(), false, emptyList(), null, "test target audience"
)
private val testRole = Role("MEMBER", Permissions(testPermissions), false)
private val testPerActivityRole = PerActivityRole(Permissions(listOf(Permission.EDIT_ACTIVITY)))
Expand Down
Loading

0 comments on commit 3719b5e

Please sign in to comment.