Skip to content
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
44 changes: 44 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build Project

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history for all branches and tags

- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

- name: Build and test all projects
run: ./gradlew build --no-daemon

- name: Test Summary
uses: mikepenz/action-junit-report@v5
if: always() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
if: success()
with:
name: build-artifacts
path: |
*/build/libs/*.jar
*/build/reports/**
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,22 @@ class Authenticate(
private fun authenticateUser(
user: User,
validatedRequest: AuthenticateRequest,
): DomainResult<User> = either {
val passwordHashString = user.passwordHash.bind()
if (passwordProvider.matches(validatedRequest.password, passwordHashString)) {
user
} else {
raise(DomainError.InvalidCredentials)
): DomainResult<User> =
either {
val passwordHashString = user.passwordHash.bind()
if (passwordProvider.matches(validatedRequest.password, passwordHashString)) {
user
} else {
raise(DomainError.InvalidCredentials)
}
}
}

private fun createToken(authenticatedUser: User): DomainResult<AuthenticateResponse> = either {
val tokenInfo: TokenInfo = jwtProvider.generate(authenticatedUser).bind()
AuthenticateResponse(
token = tokenInfo.token,
expires = tokenInfo.expires,
)
}
private fun createToken(authenticatedUser: User): DomainResult<AuthenticateResponse> =
either {
val tokenInfo: TokenInfo = jwtProvider.generate(authenticatedUser).bind()
AuthenticateResponse(
token = tokenInfo.token,
expires = tokenInfo.expires,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package za.co.ee.learning.infrastructure.database

import arrow.core.Either
import arrow.core.Option
import arrow.core.right
import za.co.ee.learning.domain.DomainError
import za.co.ee.learning.domain.DomainResult
import za.co.ee.learning.domain.users.User
import za.co.ee.learning.domain.users.UserRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import za.co.ee.learning.domain.security.JWTProvider
import za.co.ee.learning.domain.security.TokenInfo
import za.co.ee.learning.domain.users.User
import java.time.Instant
import java.util.*
import java.util.Date
import java.util.UUID

class DefaultJWTProvider(
private val secret: String,
Expand All @@ -32,13 +33,14 @@ class DefaultJWTProvider(
val now = Instant.now()
val expiresAt = now.plusSeconds(expirationSeconds)

val token = JWT
.create()
.withIssuer(issuer)
.withSubject(user.id.toString())
.withIssuedAt(Date.from(now))
.withExpiresAt(Date.from(expiresAt))
.sign(algorithm)
val token =
JWT
.create()
.withIssuer(issuer)
.withSubject(user.id.toString())
.withIssuedAt(Date.from(now))
.withExpiresAt(Date.from(expiresAt))
.sign(algorithm)

TokenInfo(
token = token,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AuthenticateTest :
TokenInfo(
token = testToken,
expires = testExpires,
)
),
)

beforeTest {
Expand All @@ -62,10 +62,10 @@ class AuthenticateTest :

val value = result.shouldBeRight()
value shouldBe
AuthenticateResponse(
token = testToken,
expires = testExpires,
)
AuthenticateResponse(
token = testToken,
expires = testExpires,
)

verify { userRepository.findByEmail(validEmail) }
verify { passwordProvider.matches(validPassword, passwordHash) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class AuthenticateEndpointTest :
TokenInfo(
token = testToken,
expires = testExpires,
)
),
)
beforeTest {
clearAllMocks()
Expand Down
5 changes: 3 additions & 2 deletions quarkus-rest/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
kotlin("jvm")
kotlin("plugin.allopen") version "1.9.22"
kotlin("plugin.allopen") version "2.1.20"
id("org.jlleitschuh.gradle.ktlint") version "12.1.2"
id("io.quarkus")
}

Expand All @@ -16,7 +17,7 @@ val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project

dependencies {
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
implementation(enforcedPlatform("$quarkusPlatformGroupId:$quarkusPlatformArtifactId:$quarkusPlatformVersion"))
// Rest Support
implementation("io.quarkus:quarkus-rest-jackson")
// Rest Client Support
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package za.co.ee.learning.client

import jakarta.ws.rs.*
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient


data class UrlValidationRequest(val name: String, val url: String)

data class UrlValidationResponse(val name: String, val result: String)

@Path("/validate-url")
@RegisterRestClient(configKey="validator-service")
@RegisterRestClient(configKey = "validator-service")
interface UrlValidatorClient {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
fun validateUrl(urlValidationRequest: UrlValidationRequest): UrlValidationResponse
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ package za.co.ee.learning.model
*/
data class KotlinLearningPath(
var name: String,

var resources: MutableMap<String, String> = mutableMapOf()
var resources: MutableMap<String, String> = mutableMapOf(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ package za.co.ee.learning.resource

import io.smallrye.common.annotation.RunOnVirtualThread
import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.DELETE
import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
import jakarta.ws.rs.PUT
import jakarta.ws.rs.Path
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.Produces
import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import za.co.ee.learning.model.KotlinLearningPath
Expand All @@ -16,7 +24,6 @@ import za.co.ee.learning.service.LearningPathService
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED)
class LearningPathResource {

@Inject
lateinit var learningPathService: LearningPathService

Expand All @@ -40,7 +47,9 @@ class LearningPathResource {
@PUT
@Path("/name/{name}")
@RunOnVirtualThread
fun updateName(@PathParam("name") name: String): KotlinLearningPath {
fun updateName(
@PathParam("name") name: String,
): KotlinLearningPath {
return learningPathService.updateName(name)
}

Expand All @@ -54,7 +63,10 @@ class LearningPathResource {
@PUT
@Path("/resource")
@RunOnVirtualThread
fun addOrUpdateResource(@QueryParam("name") name: String?, @QueryParam("url") url: String?): Response {
fun addOrUpdateResource(
@QueryParam("name") name: String?,
@QueryParam("url") url: String?,
): Response {
if (name.isNullOrBlank() || url.isNullOrBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Both name and url parameters are required")
Expand All @@ -79,7 +91,9 @@ class LearningPathResource {
@DELETE
@Path("/resource/{name}")
@RunOnVirtualThread
fun removeResource(@PathParam("name") name: String): KotlinLearningPath {
fun removeResource(
@PathParam("name") name: String,
): KotlinLearningPath {
return learningPathService.removeResource(name)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package za.co.ee.learning.service

class InvalidLearningResourceUrl(message: String): Exception(message)
class InvalidLearningResourceUrl(message: String) : Exception(message)
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@ import za.co.ee.learning.model.KotlinLearningPath
*/
@ApplicationScoped
class LearningPathService {

@RestClient
private lateinit var urlValidationClient: UrlValidatorClient

private val defaultLearningPath = KotlinLearningPath(
name = "Kotlin Learning Resources",
resources = mutableMapOf(
"Kotlin Home" to "https://kotlinlang.org/",
"Quarkus Home" to "https://quarkus.io/",
"Http4k Home" to "https://www.http4k.org/",
"Ktor Home" to "https://ktor.io/"
private val defaultLearningPath =
KotlinLearningPath(
name = "Kotlin Learning Resources",
resources =
mutableMapOf(
"Kotlin Home" to "https://kotlinlang.org/",
"Quarkus Home" to "https://quarkus.io/",
"Http4k Home" to "https://www.http4k.org/",
"Ktor Home" to "https://ktor.io/",
),
)
)

private var learningPath: KotlinLearningPath = defaultLearningPath

Expand Down Expand Up @@ -55,17 +56,19 @@ class LearningPathService {
* @return The updated learning path
* @throws IllegalArgumentException if the URL is invalid
*/
fun addOrUpdateResource(name: String, url: String): KotlinLearningPath {
fun addOrUpdateResource(
name: String,
url: String,
): KotlinLearningPath {
try {
val urlValidationResponse = urlValidationClient.validateUrl(UrlValidationRequest(name, url))
if (urlValidationResponse.result != "Okay") {
throw InvalidLearningResourceUrl("Not a valid learning resource URL: $url")
}
learningPath.resources[name] = url
return learningPath
}
catch (r: RuntimeException) {
throw Exception("Error while validating the learning resource: cause - ${r} : $url")
} catch (r: RuntimeException) {
throw Exception("Error while validating the learning resource: cause - $r : $url")
}
}

Expand All @@ -86,15 +89,17 @@ class LearningPathService {
* @return The reset learning path
*/
fun resetToDefault(): KotlinLearningPath {
learningPath = KotlinLearningPath(
name = "Kotlin Learning Resources",
resources = mutableMapOf(
"Kotlin Home" to "https://kotlinlang.org/",
"Quarkus Home" to "https://quarkus.io/",
"Http4k Home" to "https://www.http4k.org/",
"Ktor Home" to "https://ktor.io/"
learningPath =
KotlinLearningPath(
name = "Kotlin Learning Resources",
resources =
mutableMapOf(
"Kotlin Home" to "https://kotlinlang.org/",
"Quarkus Home" to "https://quarkus.io/",
"Http4k Home" to "https://www.http4k.org/",
"Ktor Home" to "https://ktor.io/",
),
)
)
return learningPath
}
}
Loading