Skip to content

update master #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ or from the registry: **YOU MIGHT PROBABLY WANT TO START WITH THIS**

You must install:

- Node v16
- Node v20
- Yarn v1.22.17
- optional: IDEA

Expand Down Expand Up @@ -101,6 +101,24 @@ Once created, edit the `CMSchApplication` Run Configuration's Spring Boot Active
- `local,test` if you want test data in the database also
- `local` if you don't

## Set up push notifications

1. Enable the push notification component on the backend.
2. Create a Firebase project and make sure Firebase Cloud Messaging is enabled by navigating to `Run` > `Messaging`.

### Backend setup
1. Navigate to the Firebase Console of your project and open `Project Settings` > `Service accounts`
2. Click on `Generate new private key` and download the .json file
3. If you are working locally set the value of `hu.bme.sch.cmsch.google.service-account-key` property to the contents of the JSON file
4. If you are setting up the application inside docker set `FIREBASE_SERVICE_ACCOUNT_KEY` to the contents of the JSON file

### Frontend setup
1. Navigate to the Firebase Console of your project and open `Project Settings` > `General`
2. Scroll down and create a __Web App__ if there is no app already by clicking `Add app`
3. Find the values of `apiKey, projectId, appId, messagingSenderId` and set the `FIREBASE_*` properties in .env
4. Navigate to `Project Settings` > `Cloud Messaging` and scroll down to `Web Push certificates`
5. If there is no key, click on `Generate key pair`. Copy the value from `Key pair` column and set `VITE_FIREBASE_WEB_PUSH_PUBLIC_KEY` to it.

## Sponsors

<a href="https://vercel.com?utm_source=kir-dev&utm_campaign=oss"><img src="client/public/img/powered-by-vercel.svg" height="46" /></a>
23 changes: 13 additions & 10 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("org.springframework.boot") version "3.2.5"
id("io.spring.dependency-management") version "1.1.4"
id("org.owasp.dependencycheck") version "9.1.0"
kotlin("jvm") version "1.9.23"
kotlin("plugin.spring") version "1.9.23"
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.5"
id("org.owasp.dependencycheck") version "9.2.0"
kotlin("jvm") version "2.0.0"
kotlin("plugin.spring") version "2.0.0"
}

group = "hu.bme.sch"
Expand All @@ -23,6 +24,7 @@ repositories {
}

dependencies {
implementation("com.google.firebase:firebase-admin:9.3.0")
implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2")
api("org.springframework.boot:spring-boot-configuration-processor")
api("org.springframework.boot:spring-boot-starter-data-jpa")
Expand All @@ -35,7 +37,7 @@ dependencies {
api("org.springframework.retry:spring-retry")
api("org.springframework.boot:spring-boot-starter-aop")
api("com.fasterxml.jackson.module:jackson-module-kotlin")
api("com.itextpdf:itext-core:8.0.3")
api("com.itextpdf:itext-core:8.0.4")
api("org.jetbrains.kotlin:kotlin-reflect")
api("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
api("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
Expand All @@ -51,7 +53,7 @@ dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
runtimeOnly("com.h2database:h2")
implementation("org.postgresql:postgresql")
implementation(platform("io.micrometer:micrometer-bom:1.12.5"))
implementation(platform("io.micrometer:micrometer-bom:1.13.0"))
implementation("io.micrometer:micrometer-core")
implementation("io.micrometer:micrometer-registry-prometheus")
implementation("io.micrometer:micrometer-observation")
Expand All @@ -64,9 +66,10 @@ dependencyCheck {
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "21"
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
jvmTarget.set(JvmTarget.JVM_21)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import hu.bme.sch.cmsch.component.task.resolveTaskStatus
import hu.bme.sch.cmsch.model.UserEntity
import hu.bme.sch.cmsch.repository.UserRepository
import hu.bme.sch.cmsch.service.TimeService
import org.postgresql.util.PSQLException
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional
import java.sql.SQLException
import java.util.*

const val AVATAR_TAG = "avatar"
Expand All @@ -46,7 +46,7 @@ open class NovaIntegrationService(

private val log = LoggerFactory.getLogger(javaClass)

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun updateSubmissions(emails: List<String>): Int {
val form = formRepository.findAll().firstOrNull { it.selected }
Expand Down Expand Up @@ -74,7 +74,7 @@ open class NovaIntegrationService(
return successful
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
open fun fetchSubmissions(): List<FilledOutFormDto> {
val form = formRepository.findAll().firstOrNull { it.selected }
Expand Down Expand Up @@ -151,7 +151,7 @@ open class NovaIntegrationService(
}
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun setPaymentStatus(email: String, status: Boolean, rejectionMessage: String?) {
val form = formRepository.findAll().firstOrNull { it.selected }
Expand All @@ -173,7 +173,7 @@ open class NovaIntegrationService(
}
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun setDetailsStatus(email: String, status: Boolean, rejectionMessage: String?) {
val form = formRepository.findAll().firstOrNull { it.selected }
Expand All @@ -195,7 +195,7 @@ open class NovaIntegrationService(
}
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun setAvatarStatus(email: String, status: Boolean, rejectionMessage: String?) {
val user = userRepository.findByEmail(email).orElse(null) ?: return
Expand All @@ -218,7 +218,7 @@ open class NovaIntegrationService(
}
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun setCvStatus(email: String, status: Boolean, rejectionMessage: String?) {
val user = userRepository.findByEmail(email).orElse(null) ?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ abstract class DashboardPage(
private var ignoreFromMenu: Boolean = false
) {

abstract fun getComponents(user: CmschUser): List<DashboardComponent>
abstract fun getComponents(user: CmschUser, requestParams: Map<String, String>): List<DashboardComponent>

@PostConstruct
fun init() {
Expand All @@ -54,7 +54,7 @@ abstract class DashboardPage(
}

@GetMapping("")
fun view(model: Model, auth: Authentication, @RequestParam(defaultValue = "-1") card: Int, @RequestParam(defaultValue = "") message: String): String {
fun view(model: Model, auth: Authentication, @RequestParam requestParams: Map<String, String>): String {
val user = auth.getUser()
adminMenuService.addPartsForMenu(user, model)
if (showPermission.validate(user).not()) {
Expand All @@ -68,25 +68,30 @@ abstract class DashboardPage(
model.addAttribute("description", description)
model.addAttribute("view", view)
model.addAttribute("wide", wide)
model.addAttribute("components", getComponents(user))
model.addAttribute("components", getComponents(user, requestParams))
model.addAttribute("user", user)
model.addAttribute("card", card)
model.addAttribute("message", message)
model.addAttribute("card", requestParams.getOrDefault("card", "-1"))
model.addAttribute("message", requestParams.getOrDefault("message", ""))

return "dashboard"
}

@ResponseBody
@GetMapping("/export/{id}", produces = [ MediaType.APPLICATION_OCTET_STREAM_VALUE ])
fun export(auth: Authentication, response: HttpServletResponse, @PathVariable id: Int): ByteArray {
fun export(
auth: Authentication,
response: HttpServletResponse,
@PathVariable id: Int,
@RequestParam requestParams: Map<String, String>
): ByteArray {
val user = auth.getUser()
if (!showPermission.validate(user)) {
throw IllegalStateException("Insufficient permissions")
}

val outputStream = ByteArrayOutputStream()
val components = getComponents(user)
val exportable = components.firstOrNull() { it.id == id }
val components = getComponents(user, requestParams)
val exportable = components.firstOrNull { it.id == id }
if (exportable == null || exportable !is DashboardTableCard || !exportable.exportable)
return outputStream.toByteArray()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package hu.bme.sch.cmsch.component

import org.postgresql.util.PSQLException
import org.springframework.context.ApplicationEventPublisher
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional
import java.sql.SQLException

@Service
open class ComponentSettingService(
Expand All @@ -26,7 +26,7 @@ open class ComponentSettingService(
settings.forEach(this::refreshCachedSetting)
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun persistSetting(setting: SettingProxy) {
if (!setting.persist)
Expand All @@ -46,13 +46,13 @@ open class ComponentSettingService(
})
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun persistSettings(settings: List<SettingProxy>) {
settings.forEach(this::persistSetting)
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun loadDefaultSetting(setting: SettingProxy) {
if (!setting.persist)
Expand All @@ -71,7 +71,7 @@ open class ComponentSettingService(
})
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun loadDefaultSettings(settings: List<SettingProxy>) {
settings.forEach(this::loadDefaultSetting)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatusCode
import org.springframework.http.ResponseEntity
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
Expand All @@ -20,7 +19,6 @@ import java.util.*

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = ["\${cmsch.frontend.production-url}"], allowedHeaders = ["*"])
class ApplicationApiController(
private val menuService: MenuService,
private val applicationComponent: ApplicationComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package hu.bme.sch.cmsch.component.app

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
Expand All @@ -10,7 +9,6 @@ const val IMAGE_PNG = "image/png"

@RestController
@RequestMapping("/manifest")
@CrossOrigin(origins = ["\${cmsch.frontend.production-url}"], allowedHeaders = ["*"])
@ConditionalOnBean(ApplicationComponent::class)
class ManifestApiController(
private val manifestComponent: ManifestComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional
import java.util.*
import jakarta.annotation.PostConstruct
import org.postgresql.util.PSQLException
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import org.springframework.transaction.PlatformTransactionManager
import java.sql.SQLException

@Service
@ConditionalOnBean(ApplicationComponent::class)
Expand Down Expand Up @@ -131,7 +131,7 @@ open class MenuService(
}
}

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
open fun persistSettings(menus: List<MenuSettingItem>, role: RoleType) {
menuRepository.deleteAllByRole(role)
Expand Down Expand Up @@ -189,7 +189,7 @@ open class MenuService(
var external: Boolean = false,
)

@Retryable(value = [ PSQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Retryable(value = [ SQLException::class ], maxAttempts = 5, backoff = Backoff(delay = 500L, multiplier = 1.5))
@Transactional(readOnly = false, isolation = Isolation.SERIALIZABLE)
fun importMenu(entries: List<MenuImportEntry>, rolesToInclude: List<RoleType>): Pair<Int, Int> {
var imported = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/admin/api/settings")
@CrossOrigin(origins = ["\${cmsch.frontend.production-url}"], allowedHeaders = ["*"])
class SettingsApiController(
private val adminMenuService: AdminMenuService
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class StylingComponent(
minRole,

lightGroup,
lightFooterTransparent,
lightNavbarTransparent,
lightBackgroundColor,
lightContainerColor,
lightTextColor,
Expand All @@ -36,6 +38,8 @@ class StylingComponent(
darkModeEnabled,
deviceTheme,
forceDarkMode,
darkFooterTransparent,
darkNavbarTransparent,
darkBackgroundColor,
darkContainerColor,
darkTextColor,
Expand Down Expand Up @@ -68,6 +72,16 @@ class StylingComponent(
description = "Az oldal világos stílusának színei, háttérképek"
)

val lightFooterTransparent = SettingProxy(componentSettingService, component,
"lightFooterTransparent", "false", type = SettingType.BOOLEAN,
fieldName = "Footer áttetsző", description = "Ha be van kapcsolva, a footer áttetsző"
)

val lightNavbarTransparent = SettingProxy(componentSettingService, component,
"lightNavbarTransparent", "false", type = SettingType.BOOLEAN,
fieldName = "Navbar áttetsző", description = "Ha be van kapcsolva, a navbar áttetsző"
)

val lightBackgroundColor = SettingProxy(componentSettingService, component,
"lightBackgroundColor", "#FFFFFF", type = SettingType.COLOR,
fieldName = "Háttérszín", description = "Az oldal hátterének a színe, ha nincs kép megadva, akkor ez látszik"
Expand Down Expand Up @@ -127,6 +141,16 @@ class StylingComponent(
fieldName = "Csak a sötét téma érhető el", description = "Ha be van kapcsolva, akkor csak a sötét téma használható"
)

val darkFooterTransparent = SettingProxy(componentSettingService, component,
"darkFooterTransparent", "false", type = SettingType.BOOLEAN,
fieldName = "Footer áttetsző", description = "Ha be van kapcsolva, a footer áttetsző"
)

val darkNavbarTransparent = SettingProxy(componentSettingService, component,
"darkNavbarTransparent", "false", type = SettingType.BOOLEAN,
fieldName = "Navbar áttetsző", description = "Ha be van kapcsolva, a navbar áttetsző"
)

val darkBackgroundColor = SettingProxy(componentSettingService, component,
"darkBackgroundColor", "#FFFFFF", type = SettingType.COLOR,
fieldName = "Háttérszín", description = "Az oldal hátterének a színe, ha nincs kép megadva, akkor ez látszik"
Expand Down
Loading
Loading