diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index d015175..ecc51c2 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -43,10 +43,10 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: recursive
- - name: Set up JDK 11
+ - name: Set up JDK 17
uses: actions/setup-java@v3
with:
- java-version: '11'
+ java-version: '17'
distribution: 'temurin'
- name: Setup NPM
uses: actions/setup-node@v3
@@ -77,8 +77,12 @@ jobs:
REDIS_PORT: "6379"
REDIS_PASSWORD: ""
CLAUDE_ACCESSTOKEN: ""
+ OBJECTSTORAGE_ENDPOINT: ""
+ OBJECTSTORAGE_BUCKET: ""
+ OBJECTSTORAGE_ACCESSKEY: ""
+ OBJECTSTORAGE_SECRETKEY: ""
- name: Upload test results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
if: failure() || always()
with:
name: test-results
diff --git a/Dockerfile b/Dockerfile
index e766e6c..ce267c2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM gradle:7-jdk11 AS build
+FROM gradle:7-jdk17 AS build
RUN apt-get install -y curl \
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
@@ -19,7 +19,7 @@ RUN npm run build
WORKDIR /home/gradle/src
RUN gradle shadowJar
-FROM openjdk:11
+FROM openjdk:17
COPY --from=build /home/gradle/src/build/libs/*.jar /app/report-system.jar
WORKDIR /app
RUN mkdir data
diff --git a/build.gradle.kts b/build.gradle.kts
index e4882f7..9d415a8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,18 +3,23 @@ val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project
val exposed_version: String by project
-
+val coroutines_version = "1.9.0" // Added explicit coroutines version
plugins {
application
- kotlin("jvm") version "1.9.22"
- kotlin("plugin.serialization") version "1.7.10"
+ kotlin("jvm") version "2.0.0"
+ kotlin("plugin.serialization") version "2.0.0"
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "eu.gaelicgames"
version = "1.0-ALPHA"
+
+kotlin {
+ jvmToolchain(17)
+}
+
application {
mainClass.set("eu.gaelicgames.referee.ApplicationKt")
@@ -22,16 +27,27 @@ application {
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
+
repositories {
mavenCentral()
+ maven { url = uri("https://www.jitpack.io") }
+
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/ktor/eap") }
}
+
+
+
java.sourceSets["main"].java {
srcDir("gaa-referee-report-common/src/main/kotlin")
}
dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
+
+
+ implementation("com.github.Tyde:gaa-teamsheet-pdf-parser:0.3")
+
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth:$ktor_version")
@@ -58,9 +74,10 @@ dependencies {
implementation("org.jetbrains.exposed", "exposed-dao", exposed_version)
implementation("org.jetbrains.exposed", "exposed-jdbc", exposed_version)
implementation("org.jetbrains.exposed", "exposed-java-time", exposed_version)
+ implementation("org.jetbrains.exposed", "exposed-json", exposed_version)
implementation("com.zaxxer:HikariCP:5.1.0")
- implementation("com.nimbusds:nimbus-jose-jwt:9.30.1")
+ implementation("com.nimbusds:nimbus-jose-jwt:9.37.2")
implementation("com.mailjet", "mailjet-client", "5.2.1")
@@ -72,8 +89,13 @@ dependencies {
implementation("at.favre.lib:bcrypt:0.9.0")
implementation("org.apache.commons","commons-csv","1.9.0")
+ implementation("aws.sdk.kotlin:s3:1.+")
+
testImplementation(kotlin("test"))
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
+
+ implementation("io.arrow-kt:arrow-core:1.2.4")
+ implementation("io.arrow-kt:arrow-fx-coroutines:1.2.4")
}
tasks.test {
useJUnitPlatform()
diff --git a/frontend-vite/package.json b/frontend-vite/package.json
index ed08162..c2a9676 100644
--- a/frontend-vite/package.json
+++ b/frontend-vite/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@heroicons/vue": "^2.1.1",
"@vueuse/core": "^10.9.0",
+ "caniuse-lite": "^1.0.30001687",
"debounce": "^1.2.1",
"feather-icons": "^4.29.0",
"luxon": "^3.3.0",
diff --git a/frontend-vite/src/TeamsheetDashboard.vue b/frontend-vite/src/TeamsheetDashboard.vue
new file mode 100644
index 0000000..9b8d5a3
--- /dev/null
+++ b/frontend-vite/src/TeamsheetDashboard.vue
@@ -0,0 +1,35 @@
+
+
+
+
+ {{msg.message}}
+
+
+
GGE Teamsheet Dashboard
+
+
+
+
+
+
diff --git a/frontend-vite/src/components/SubmitReport.vue b/frontend-vite/src/components/SubmitReport.vue
index 7ee939d..3ef94db 100644
--- a/frontend-vite/src/components/SubmitReport.vue
+++ b/frontend-vite/src/components/SubmitReport.vue
@@ -75,16 +75,16 @@ async function uploadAllData() {
let updateDiAndInjuries = store.gameReports.map((gameReport) => {
if (gameReport.id) {
let tAdiP = gameReport.teamAReport.disciplinaryActions.map((da) => {
- store.sendDisciplinaryAction(da, gameReport,true)
+ return store.sendDisciplinaryAction(da, gameReport,true)
})
let tBdiP = gameReport.teamBReport.disciplinaryActions.map((da) => {
- store.sendDisciplinaryAction(da, gameReport,true)
+ return store.sendDisciplinaryAction(da, gameReport,true)
})
let tAinP = gameReport.teamAReport.injuries.map((injury) => {
- store.sendInjury(injury, gameReport, true)
+ return store.sendInjury(injury, gameReport, true)
})
let tBinP = gameReport.teamBReport.injuries.map((injury) => {
- store.sendInjury(injury, gameReport, true)
+ return store.sendInjury(injury, gameReport, true)
})
//concat all four arrays
let allPromises = tAdiP.concat(tBdiP).concat(tAinP).concat(tBinP)
diff --git a/frontend-vite/src/components/teamsheet/AugmentTeamsheetData.vue b/frontend-vite/src/components/teamsheet/AugmentTeamsheetData.vue
new file mode 100644
index 0000000..bcfc037
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/AugmentTeamsheetData.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
diff --git a/frontend-vite/src/components/teamsheet/CommonEditTeamsheetData.vue b/frontend-vite/src/components/teamsheet/CommonEditTeamsheetData.vue
new file mode 100644
index 0000000..6e55120
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/CommonEditTeamsheetData.vue
@@ -0,0 +1,156 @@
+
+
+
+ Please complete your Teamsheet data
+ Tournament:
+
+
+
+ {{ option.date.toISODate() }} - {{ option.name }} - {{ option.location }}
+
+
+
+
+ {{ void(tournament = findTournamentById(value)) }}
+ {{ tournament?.date.toISODate() }} - {{ tournament?.name }} - {{ tournament?.location }}
+
+ {{ placeholder }}
+
+
+ Code:
+
+
+ Your Club
+
+
+ Your Name
+
+ Your Email
+
+
+ Players: {{ model.players.length }}
+
+
+ Player Number
+ Name
+ Jersey Number
+
+
+ {{ player.playerNumber }}
+ {{ player.name }}
+
+
+
+
+
+
+ Note: You can enter the jersey numbers at a later point before the tournament or on the day of the tournament
+ itself.
+
+
+
+
+
+
+ Submit
+
+
+
diff --git a/frontend-vite/src/components/teamsheet/TeamsheetComplete.vue b/frontend-vite/src/components/teamsheet/TeamsheetComplete.vue
new file mode 100644
index 0000000..8bc188b
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/TeamsheetComplete.vue
@@ -0,0 +1,22 @@
+
+
+
+Thank you for submitting your teamsheet. Your submission has been received.
+ If you wish to edit the submitted date up until the day of the tournament, you can do so using the following link:
+ Edit Teamsheet
+
+
+
+
diff --git a/frontend-vite/src/components/teamsheet/TeamsheetEdit.vue b/frontend-vite/src/components/teamsheet/TeamsheetEdit.vue
new file mode 100644
index 0000000..c13e940
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/TeamsheetEdit.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/frontend-vite/src/components/teamsheet/UploadTeamsheetComponent.vue b/frontend-vite/src/components/teamsheet/UploadTeamsheetComponent.vue
new file mode 100644
index 0000000..8c6d508
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/UploadTeamsheetComponent.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend-vite/src/components/teamsheet/UploadTeamsheetPage.vue b/frontend-vite/src/components/teamsheet/UploadTeamsheetPage.vue
new file mode 100644
index 0000000..274698d
--- /dev/null
+++ b/frontend-vite/src/components/teamsheet/UploadTeamsheetPage.vue
@@ -0,0 +1,25 @@
+
+
+
+
+ Please upload your teamsheet for any upcoming tournaments. The teamsheet should be a PDF file exported from Foireann.
+
+
+
+
+
diff --git a/frontend-vite/src/router/teamsheet_router.ts b/frontend-vite/src/router/teamsheet_router.ts
new file mode 100644
index 0000000..16d3dea
--- /dev/null
+++ b/frontend-vite/src/router/teamsheet_router.ts
@@ -0,0 +1,28 @@
+import AugmentTeamsheetData from "@/components/teamsheet/AugmentTeamsheetData.vue";
+import TeamsheetComplete from "@/components/teamsheet/TeamsheetComplete.vue";
+import UploadTeamsheetPage from "@/components/teamsheet/UploadTeamsheetPage.vue";
+import TeamsheetEdit from "@/components/teamsheet/TeamsheetEdit.vue";
+
+export const routes = [
+ {
+ path: "/",
+ component: UploadTeamsheetPage
+ },
+ {
+ name: "augment-data",
+ path: "/augment-data/:fileKey",
+ component: AugmentTeamsheetData,
+ props: true
+ },
+ {
+ name: "teamsheet-complete",
+ path: "/teamsheet-complete/:fileKey",
+ component: TeamsheetComplete
+ },
+ {
+ name: "teamsheet-edit",
+ path: "/edit/:fileKey",
+ component: TeamsheetEdit,
+ props: true
+ }
+]
diff --git a/frontend-vite/src/teamsheets.ts b/frontend-vite/src/teamsheets.ts
new file mode 100644
index 0000000..db876d6
--- /dev/null
+++ b/frontend-vite/src/teamsheets.ts
@@ -0,0 +1,58 @@
+import {createApp} from 'vue'
+import App from './TeamsheetDashboard.vue'
+import PrimeVue from 'primevue/config';
+import Button from "primevue/button";
+import InputText from "primevue/inputtext";
+import './index.css';
+import 'primevue/resources/themes/mdc-light-indigo/theme.css';
+import 'primevue/resources/primevue.min.css';
+import 'primeicons/primeicons.css';
+import InputNumber from "primevue/inputnumber";
+import ConfirmationService from 'primevue/confirmationservice';
+import VueFeather from 'vue-feather';
+import {createRouter, createWebHashHistory} from "vue-router";
+
+import {createPinia} from "pinia";
+import Message from "primevue/message";
+import Panel from "primevue/panel";
+import BlockUI from "primevue/blockui";
+import IconField from "primevue/iconfield";
+import InputIcon from "primevue/inputicon";
+import FileUpload from 'primevue/fileupload';
+import ProgressBar from 'primevue/progressbar';
+
+import {routes} from "@/router/teamsheet_router";
+import Dropdown from "primevue/dropdown";
+import Accordion from "primevue/accordion"; //optional for row
+import AccordionTab from 'primevue/accordiontab';
+
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ routes: routes
+})
+
+
+const pinia = createPinia()
+const app = createApp(App);
+app.use(PrimeVue)
+app.use(pinia)
+app.use(router)
+app.use(ConfirmationService)
+app.component('Button',Button)
+app.component('InputText',InputText)
+app.component('InputNumber',InputNumber)
+app.component('IconField',IconField)
+app.component('InputIcon',InputIcon)
+app.component('ProgressBar',ProgressBar)
+app.component('Dropdown', Dropdown)
+
+app.component('Message',Message)
+app.component('Panel',Panel)
+app.component('BlockUI',BlockUI)
+app.component('FileUpload',FileUpload)
+app.component('Accordion', Accordion)
+app.component('AccordionTab',AccordionTab)
+
+app.component(VueFeather.name!!,VueFeather)
+app.mount('#app')
diff --git a/frontend-vite/src/types/teamsheet_types.ts b/frontend-vite/src/types/teamsheet_types.ts
new file mode 100644
index 0000000..1ade42b
--- /dev/null
+++ b/frontend-vite/src/types/teamsheet_types.ts
@@ -0,0 +1,50 @@
+import {z} from "zod";
+
+
+export const PlayerDEO = z.object({
+ name: z.string(),
+ jerseyNumber: z.number().optional(),
+ playerNumber: z.number().nullish()
+})
+
+export type PlayerDEO = z.infer
+
+
+export const TeamsheetUploadSuccessDEO = z.object({
+ players: PlayerDEO.array(),
+ fileKey: z.string()
+})
+
+export type TeamsheetUploadSuccessDEO = z.infer
+export const TeamsheetWithClubAndTournamentDataDEO = z.object({
+ players: PlayerDEO.array(),
+ clubId: z.number(),
+ tournamentId: z.number(),
+ fileKey: z.string(),
+ registrarMail: z.string(),
+ registrarName: z.string(),
+ codeId: z.number()
+ })
+
+export type TeamsheetWithClubAndTournamentDataDEO = z.infer
+
+export function newTeamsheetWithClubAndTournamentDataDEO(): TeamsheetWithClubAndTournamentDataDEO {
+ return {
+ players: [],
+ clubId: -1,
+ tournamentId: -1,
+ fileKey: "",
+ registrarMail: "",
+ registrarName: "",
+ codeId: -1
+ }
+}
+
+
+
+export const ReplaceTeamsheetFileDEO = z.object({
+ oldfileKey: z.string(),
+ newTeamsheetData: TeamsheetWithClubAndTournamentDataDEO
+})
+
+export type ReplaceTeamsheetFileDEO = z.infer
diff --git a/frontend-vite/src/types/tournament_types.ts b/frontend-vite/src/types/tournament_types.ts
index 8f5549c..59db628 100644
--- a/frontend-vite/src/types/tournament_types.ts
+++ b/frontend-vite/src/types/tournament_types.ts
@@ -20,6 +20,11 @@ export const DatabaseTournament = Tournament.extend({
})
export type DatabaseTournament = z.infer
+export function tournamentByDateSortComparator(t1: Tournament | DatabaseTournament, t2: Tournament | DatabaseTournament) {
+ const t1Date = (t1.isLeague === true && t1.endDate) ? t1.endDate : t1.date
+ const t2Date = (t2.isLeague === true && t2.endDate) ? t2.endDate : t2.date
+ return t1Date.toMillis() - t2Date.toMillis()
+}
export function databaseTournamentToTournamentDAO(tournament: DatabaseTournament) {
return {
diff --git a/frontend-vite/src/utils/api/teamsheet_api.ts b/frontend-vite/src/utils/api/teamsheet_api.ts
new file mode 100644
index 0000000..64efe33
--- /dev/null
+++ b/frontend-vite/src/utils/api/teamsheet_api.ts
@@ -0,0 +1,43 @@
+import type {FileUploadUploadEvent} from "primevue/fileupload";
+import {TeamsheetUploadSuccessDEO, TeamsheetWithClubAndTournamentDataDEO} from "@/types/teamsheet_types";
+import {makePostRequest, parseAndHandleDEO} from "@/utils/api/api_utils";
+
+export async function onTeamsheetUploadComplete(event: FileUploadUploadEvent) :
+ Promise {
+ try {
+ const response = JSON.parse(event.xhr.response)
+ return parseAndHandleDEO(response, TeamsheetUploadSuccessDEO)
+ } catch (e) {
+ return Promise.reject(e)
+ }
+}
+
+
+export async function loadTeamsheetPlayersFromFileKey(fileKey: string):Promise {
+ const data = {fileKey: fileKey}
+ return makePostRequest("/api/teamsheet/get_players", data)
+ .then(data => parseAndHandleDEO(data, TeamsheetUploadSuccessDEO))
+}
+
+export async function setTeamsheetMetaData(data: TeamsheetWithClubAndTournamentDataDEO):Promise {
+ return makePostRequest("/api/teamsheet/set_metadata", data)
+ .then(data => parseAndHandleDEO(data, TeamsheetWithClubAndTournamentDataDEO))
+}
+
+export async function getTeamsheetMetaData(fileKey: string):Promise {
+ const payload = {fileKey: fileKey}
+ return makePostRequest("/api/teamsheet/get_metadata", payload)
+ .then(data => parseAndHandleDEO(data, TeamsheetWithClubAndTournamentDataDEO))
+}
+
+
+export async function editTeamsheetMetaData(data: TeamsheetWithClubAndTournamentDataDEO):Promise {
+ return makePostRequest("/api/teamsheet/edit_metadata", data)
+ .then(data => parseAndHandleDEO(data, TeamsheetWithClubAndTournamentDataDEO))
+}
+
+export async function replaceTeamsheetFileKey(oldfileKey: string, newTeamsheetData: TeamsheetWithClubAndTournamentDataDEO):Promise {
+ const payload = {oldfileKey: oldfileKey, newTeamsheetData: newTeamsheetData}
+ return makePostRequest("/api/teamsheet/replace_file", payload)
+ .then(data => parseAndHandleDEO(data, TeamsheetWithClubAndTournamentDataDEO))
+}
diff --git a/frontend-vite/src/utils/teamsheet_store.ts b/frontend-vite/src/utils/teamsheet_store.ts
new file mode 100644
index 0000000..2a79a07
--- /dev/null
+++ b/frontend-vite/src/utils/teamsheet_store.ts
@@ -0,0 +1,21 @@
+import {defineStore} from "pinia";
+import {usePublicStore} from "@/utils/public_store";
+import {ref} from "vue";
+import type {TeamsheetUploadSuccessDEO} from "@/types/teamsheet_types";
+
+export const useTeamsheetStore = defineStore("teamsheet", () => {
+
+ const publicStore = usePublicStore()
+
+ const uploadSuccessDEO = ref()
+
+ function newError(message: string) {
+ publicStore.newError(message)
+ }
+
+ return {
+ publicStore,
+ uploadSuccessDEO,
+ newError
+ }
+})
diff --git a/frontend-vite/teamsheets.html b/frontend-vite/teamsheets.html
new file mode 100644
index 0000000..f18c797
--- /dev/null
+++ b/frontend-vite/teamsheets.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ GGE Teamsheet Dashboard
+
+
+
+
+
+
diff --git a/frontend-vite/vite.config.ts b/frontend-vite/vite.config.ts
index 7eeeb2f..ea77323 100644
--- a/frontend-vite/vite.config.ts
+++ b/frontend-vite/vite.config.ts
@@ -67,6 +67,7 @@ export default defineConfig(({mode}) => {
onboarding: resolve(__dirname, 'onboarding.html'),
userDashboard: resolve(__dirname, 'user_dashboard.html'),
publicDashboard: resolve(__dirname, 'public_dashboard.html'),
+ teamsheets: resolve(__dirname, 'teamsheets.html'),
},
output: {
manualChunks(id) {
diff --git a/gaa-referee-report-common b/gaa-referee-report-common
index c7411d4..b58e760 160000
--- a/gaa-referee-report-common
+++ b/gaa-referee-report-common
@@ -1 +1 @@
-Subproject commit c7411d475847c56931f5c89185db6539fa940179
+Subproject commit b58e7604cd7fc973ffc53c39143e8d3bae764edc
diff --git a/gradle.properties b/gradle.properties
index dc60e7a..6323f92 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
-ktor_version=2.3.9
-kotlin_version=1.8.22
-logback_version=1.3.7
+ktor_version=2.3.12
+kotlin_version=2.0.0
+logback_version=1.4.12
exposed_version=0.48.0
kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 69a9715..6e66903 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Wed Sep 11 12:47:55 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2b97bc4..618be2d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1 +1,2 @@
rootProject.name = "gaa-referee-report"
+
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/ReportData.kt b/src/main/kotlin/eu/gaelicgames/referee/data/ReportData.kt
index cb82c40..6e9a3a3 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/ReportData.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/ReportData.kt
@@ -2,6 +2,8 @@ package eu.gaelicgames.referee.data
import eu.gaelicgames.referee.data.api.PitchPropertyDEO
import eu.gaelicgames.referee.util.lockedTransaction
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.id.EntityID
@@ -10,7 +12,10 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.date
import org.jetbrains.exposed.sql.javatime.datetime
+import org.jetbrains.exposed.sql.json.contains
+import org.jetbrains.exposed.sql.json.json
import java.time.LocalDate
+import java.time.LocalDateTime
object Teams : LongIdTable() {
@@ -484,3 +489,59 @@ class Pitch(id:EntityID):LongEntity(id) {
var goalDimensions by PitchGoalDimensionOption optionalReferencedOn Pitches.goalDimensions
var additionalInformation by Pitches.additionalInformation
}
+
+
+@Serializable
+data class RegisteredPlayer(
+ val name: String,
+ val jerseyNumber: Int? = null,
+ val foireannNumber: Long?
+)
+
+@Serializable
+data class PreviousFileKey(
+ val key:String,
+ @Serializable(with = LocalDateTimeCacheSerializer::class) val uploadedAt: LocalDateTime
+)
+
+val format = Json { prettyPrint = true }
+object TeamsheetRegistrations : LongIdTable() {
+ val team = reference("team", Teams)
+ val tournament = reference("tournament", Tournaments)
+ val code = reference("code", GameCodes)
+ val players = json>("players",format)
+ val uploadedAt = datetime("uploaded_at")
+ val fileKey = varchar("file_key", 100)
+ val registrarMail = varchar("registrar_mail", 100)
+ val registrarName = varchar("registrar_name", 100)
+ val verifyUUID = uuid("verify_uuid").nullable()
+ val verified = bool("verified").default(false)
+ val previousFileKeys = json>("previous_file_keys", format).default(emptyList())
+
+
+ /**
+ * Returns the found TeamsheetRegistration that has the given fileKey, or has a previousFileKey with the given fileKey
+ */
+ suspend fun findByFileKey(fileKey:String):TeamsheetRegistration? {
+ return lockedTransaction {
+ TeamsheetRegistration.find { TeamsheetRegistrations.fileKey eq fileKey }.firstOrNull() ?:
+ TeamsheetRegistration.find { TeamsheetRegistrations.previousFileKeys.contains("{\"key\":\"$fileKey\"}") }.firstOrNull()
+ }
+ }
+}
+class TeamsheetRegistration(id:EntityID):LongEntity(id) {
+ companion object : LongEntityClass(TeamsheetRegistrations)
+ var team by Team referencedOn TeamsheetRegistrations.team
+ var tournament by Tournament referencedOn TeamsheetRegistrations.tournament
+ var code by GameCode referencedOn TeamsheetRegistrations.code
+ var players by TeamsheetRegistrations.players
+ var uploadedAt by TeamsheetRegistrations.uploadedAt
+ var fileKey by TeamsheetRegistrations.fileKey
+ var registrarMail by TeamsheetRegistrations.registrarMail
+ var registrarName by TeamsheetRegistrations.registrarName
+ var verifyUUID by TeamsheetRegistrations.verifyUUID
+ var verified by TeamsheetRegistrations.verified
+ var previousFileKeys by TeamsheetRegistrations.previousFileKeys
+}
+
+
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/ClubAndCountyApi.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/ClubAndCountyApi.kt
index 0877cd6..5606ae8 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/ClubAndCountyApi.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/ClubAndCountyApi.kt
@@ -122,7 +122,7 @@ fun translateCodeToCompetitionStyle(codeName: String):String {
"Hurling" -> return "hurling"
"Camogie" -> return "camogie"
"Mens Football" -> return "football"
- "Ladies Football" -> return "football"
+ "Ladies Football" -> return "ladies_football"
else -> return ""
}
}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/GameReportDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/GameReportDEO.kt
index 265142c..b06d752 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/GameReportDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/GameReportDEO.kt
@@ -11,9 +11,11 @@ import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.between
import org.jetbrains.exposed.sql.transactions.transaction
+import org.slf4j.LoggerFactory
import java.time.LocalDate
import java.time.LocalDateTime
+val logger = LoggerFactory.getLogger(GameReportDEO::class.java)
suspend fun GameReportDEO.Companion.fromGameReport(report: GameReport): GameReportDEO {
return lockedTransaction {
@@ -58,14 +60,13 @@ suspend fun GameReportDEO.Companion.wrapRow(row: ResultRow): GameReportDEO {
}
-fun GameReportDEO.getRefereeId(): Long? {
- return runBlocking {
- lockedTransaction {
- this@getRefereeId.report?.let {
- TournamentReport.findById(it)?.referee?.id?.value
- }
+suspend fun GameReportDEO.getRefereeId(): Long? {
+ return lockedTransaction {
+ this@getRefereeId.report?.let {
+ TournamentReport.findById(it)?.referee?.id?.value
}
}
+
}
suspend fun GameReportDEO.createInDatabase(): Result {
@@ -80,9 +81,9 @@ suspend fun GameReportDEO.createInDatabase(): Result {
grUpdate.teamAPoints != null &&
grUpdate.teamBPoints != null
) {
- runBlocking {
- CacheUtil.deleteCachedReport(grUpdate.report)
- }
+
+ CacheUtil.deleteCachedReport(grUpdate.report)
+
return lockedTransaction {
val report = TournamentReport.findById(grUpdate.report)
val teamA = Team.findById(grUpdate.teamA)
@@ -150,7 +151,7 @@ suspend fun GameReportDEO.updateInDatabase(): Result {
val grUpdate = this@updateInDatabase
val originalGameReport = GameReport.findById(this@updateInDatabase.id)
if (originalGameReport != null) {
- runBlocking { clearCacheForGameReport(originalGameReport) }
+ clearCacheForGameReport(originalGameReport)
/*
if (grUpdate.report != null) {
@@ -241,7 +242,7 @@ suspend fun DeleteGameReportDEO.deleteChecked(user: User): Result {
GameReport.findById(deleteId)?.let {
if (it.report.referee.id == user.id) {
- runBlocking { deleteFromDatabase() }
+ deleteFromDatabase()
} else {
Result.failure(IllegalArgumentException("No rights - User is not the referee of this game"))
}
@@ -256,7 +257,7 @@ suspend fun DeleteGameReportDEO.deleteFromDatabase(): Result {
val originalGameReport = GameReport.findById(deleteId)
if (originalGameReport != null) {
- runBlocking { clearCacheForGameReport(originalGameReport) }
+ clearCacheForGameReport(originalGameReport)
DisciplinaryAction.find { DisciplinaryActions.game eq originalGameReport.id }.forEach {
it.delete()
@@ -284,22 +285,25 @@ suspend fun DeleteGameReportDEO.deleteFromDatabase(): Result {
}
-fun GameReportClassesDEO.Companion.load(): GameReportClassesDEO {
- return runBlocking {
- GameReportClassesDEO.getCache().getOrElse {
- lockedTransaction {
- val etos = ExtraTimeOption.all().map {
- ExtraTimeOptionDEO.fromExtraTimeOption(it)
- }
- val gts = GameType.all().map {
- GameTypeDEO.fromGameType(it)
- }
- val dbgrc = GameReportClassesDEO(etos, gts)
- runBlocking { dbgrc.setCache() }
- dbgrc
+suspend fun GameReportClassesDEO.Companion.load(): GameReportClassesDEO {
+
+ logger.debug("Loading GameReportClassesDEO from cache")
+ val cachedData = GameReportClassesDEO.getCache()
+ logger.debug("Cache hit success: ${cachedData.isSuccess}")
+ return cachedData.getOrElse {
+ lockedTransaction {
+ val etos = ExtraTimeOption.all().map {
+ ExtraTimeOptionDEO.fromExtraTimeOption(it)
+ }
+ val gts = GameType.all().map {
+ GameTypeDEO.fromGameType(it)
}
+ val dbgrc = GameReportClassesDEO(etos, gts)
+ runBlocking { dbgrc.setCache() }
+ dbgrc
}
}
+
}
fun ExtraTimeOptionDEO.Companion.fromExtraTimeOption(extraTimeOption: ExtraTimeOption): ExtraTimeOptionDEO {
@@ -343,18 +347,17 @@ suspend fun DisciplinaryActionDEO.Companion.wrapRow(row: ResultRow): Disciplinar
}
-fun DisciplinaryActionDEO.getRefereeId(): Long? {
- return runBlocking {
- lockedTransaction {
- TournamentReports.leftJoin(GameReports)
- .selectAll()
- .where { GameReports.id eq this@getRefereeId.game }
- .firstOrNull()
- ?.let {
- it[TournamentReports.referee].value
- }
- }
+suspend fun DisciplinaryActionDEO.getRefereeId(): Long? {
+ return lockedTransaction {
+ TournamentReports.leftJoin(GameReports)
+ .selectAll()
+ .where { GameReports.id eq this@getRefereeId.game }
+ .firstOrNull()
+ ?.let {
+ it[TournamentReports.referee].value
+ }
}
+
}
suspend fun DisciplinaryActionDEO.createInDatabase(): Result {
@@ -375,7 +378,7 @@ suspend fun DisciplinaryActionDEO.createInDatabase(): Result
val game = GameReport.findById(daUpdate.game)
if (rule != null && team != null && game != null) {
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
Result.success(DisciplinaryAction.new {
this.team = team
@@ -415,7 +418,7 @@ suspend fun DisciplinaryActionDEO.updateInDatabase(): Result
if (action != null) {
val game = action.game
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
daUpdate.firstName?.let { firstName ->
if (firstName.isNotBlank()) {
action.firstName = firstName
@@ -572,7 +575,7 @@ suspend fun DeleteDisciplinaryActionDEO.deleteChecked(user: User): Result {
if (action != null) {
val game = action.game
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
action.delete()
Result.success(true)
@@ -628,19 +631,19 @@ suspend fun InjuryDEO.Companion.wrapRow(row: ResultRow): InjuryDEO {
}
-fun InjuryDEO.getRefereeId(): Long? {
- return runBlocking {
- lockedTransaction {
- TournamentReports.leftJoin(GameReports)
- .selectAll()
- .where { GameReports.id eq this@getRefereeId.game }
- .firstOrNull()
- ?.let {
- it[TournamentReports.referee].value
- }
- }
+suspend fun InjuryDEO.getRefereeId(): Long? {
+ return lockedTransaction {
+ TournamentReports.leftJoin(GameReports)
+ .selectAll()
+ .where { GameReports.id eq this@getRefereeId.game }
+ .firstOrNull()
+ ?.let {
+ it[TournamentReports.referee].value
+ }
}
+
}
+
suspend fun InjuryDEO.createInDatabase(): Result {
val injuryUpdate = this
if (injuryUpdate.team != null &&
@@ -654,7 +657,7 @@ suspend fun InjuryDEO.createInDatabase(): Result {
val team = Team.findById(injuryUpdate.team)
val game = GameReport.findById(injuryUpdate.game)
if (team != null && game != null) {
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
Result.success(Injury.new {
this.team = team
@@ -688,7 +691,7 @@ suspend fun InjuryDEO.updateInDatabase(): Result {
val injury = Injury.findById(injuryUpdate.id)
if (injury != null) {
val game = injury.game
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
injuryUpdate.firstName?.let { firstName ->
injury.firstName = firstName
@@ -752,7 +755,7 @@ suspend fun DeleteInjuryDEO.deleteFromDatabase(): Result {
val injury = Injury.findById(deleteId)
if (injury != null) {
val game = injury.game
- runBlocking { clearCacheForGameReport(game) }
+ clearCacheForGameReport(game)
injury.delete()
Result.success(true)
@@ -776,10 +779,9 @@ suspend fun GameTypeDEO.Companion.fromGameType(gameType: GameType): GameTypeDEO
}
}
-fun GameTypeDEO.createInDatabase(): Result {
- runBlocking {
- GameReportClassesDEO.deleteCache()
- }
+suspend fun GameTypeDEO.createInDatabase(): Result {
+ GameReportClassesDEO.deleteCache()
+
val gUpdate = this
if (gUpdate.id == null && gUpdate.name.isNotBlank()) {
return Result.success(transaction {
@@ -794,9 +796,8 @@ fun GameTypeDEO.createInDatabase(): Result {
}
suspend fun GameTypeDEO.updateInDatabase(): Result {
- runBlocking {
- GameReportClassesDEO.deleteCache()
- }
+ GameReportClassesDEO.deleteCache()
+
val gUpdate = this
if (gUpdate.id != null) {
return lockedTransaction {
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/PitchReportDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/PitchReportDEO.kt
index bd45aec..f230cf6 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/PitchReportDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/PitchReportDEO.kt
@@ -3,7 +3,6 @@ package eu.gaelicgames.referee.data.api
import eu.gaelicgames.referee.data.*
import eu.gaelicgames.referee.util.CacheUtil
import eu.gaelicgames.referee.util.lockedTransaction
-import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.sql.ResultRow
@@ -11,9 +10,8 @@ import org.jetbrains.exposed.sql.Transaction
suspend fun PitchVariablesDEO.Companion.load(): PitchVariablesDEO {
- val cachedVars = runBlocking {
- CacheUtil.getCachedPitchVariables()
- }
+ val cachedVars = CacheUtil.getCachedPitchVariables()
+
if (cachedVars.isSuccess) {
return cachedVars.getOrThrow()
}
@@ -27,9 +25,9 @@ suspend fun PitchVariablesDEO.Companion.load(): PitchVariablesDEO {
goalPosts = PitchGoalpostsOption.all().map { it.toPitchPropertyDEO() },
goalDimensions = PitchGoalDimensionOption.all().map { it.toPitchPropertyDEO() },
)
- runBlocking {
- CacheUtil.cachePitchVariables(variables)
- }
+
+ CacheUtil.cachePitchVariables(variables)
+
variables
}
}
@@ -49,9 +47,9 @@ fun PitchPropertyType.toDBClass(): LongEntityClass {
suspend fun PitchVariableUpdateDEO.updateInDatabase(): Result {
val pvUpdate = this
- runBlocking {
- CacheUtil.deleteCachedPitchVariables()
- }
+
+ CacheUtil.deleteCachedPitchVariables()
+
return lockedTransaction {
val obj = pvUpdate.type.toDBClass()
.findById(pvUpdate.id)
@@ -66,9 +64,9 @@ suspend fun PitchVariableUpdateDEO.updateInDatabase(): Result {
val pvUpdate = this
- runBlocking {
- CacheUtil.deleteCachedPitchVariables()
- }
+
+ CacheUtil.deleteCachedPitchVariables()
+
return lockedTransaction {
val obj = pvUpdate.type.toDBClass()
.findById(pvUpdate.id)
@@ -88,9 +86,9 @@ suspend fun PitchVariableUpdateDEO.delete(): Result {
val pvUpdate = this
- runBlocking {
- CacheUtil.deleteCachedPitchVariables()
- }
+
+ CacheUtil.deleteCachedPitchVariables()
+
return lockedTransaction {
val obj = pvUpdate.type.toDBClass()
.findById(pvUpdate.id)
@@ -106,9 +104,9 @@ suspend fun PitchVariableUpdateDEO.enable(): Result {
suspend fun NewPitchVariableDEO.createInDatabase(): Result {
val pvUpdate = this
- runBlocking {
- CacheUtil.deleteCachedPitchVariables()
- }
+
+ CacheUtil.deleteCachedPitchVariables()
+
return lockedTransaction {
val obj = when (pvUpdate.type) {
PitchPropertyType.SURFACE -> PitchSurfaceOption.new { name = pvUpdate.name }
@@ -144,12 +142,12 @@ suspend fun PitchReportDEO.Companion.fromPitchReport(pitchReport: Pitch): PitchR
}
}
-fun Transaction.clearCacheForPitchReport(pitch: Pitch) {
+suspend fun Transaction.clearCacheForPitchReport(pitch: Pitch) {
val report = pitch.report
- runBlocking {
- clearCacheForTournamentReport(report)
- }
+
+ clearCacheForTournamentReport(report)
+
}
@@ -292,9 +290,8 @@ suspend fun DeletePitchReportDEO.deleteFromDatabase(): Result {
return lockedTransaction {
val pitch = Pitch.findById(deleteId)
if (pitch != null) {
- runBlocking {
- clearCacheForPitchReport(pitch)
- }
+ clearCacheForPitchReport(pitch)
+
pitch.delete()
Result.success(true)
} else {
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/ReportDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/ReportDEO.kt
index 15da919..a93ac88 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/ReportDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/ReportDEO.kt
@@ -10,13 +10,13 @@ import java.time.LocalDateTime
import java.util.*
-fun Transaction.clearCacheForTournamentReport(report: TournamentReport) {
- runBlocking {
- val tournamentID = report.tournament.id.value
- CacheUtil.deleteCachedCompleteTournamentReport(tournamentID)
- CacheUtil.deleteCachedPublicTournamentReport(tournamentID)
- CacheUtil.deleteCachedReport(report.id.value)
- }
+suspend fun Transaction.clearCacheForTournamentReport(report: TournamentReport) {
+
+ val tournamentID = report.tournament.id.value
+ CacheUtil.deleteCachedCompleteTournamentReport(tournamentID)
+ CacheUtil.deleteCachedPublicTournamentReport(tournamentID)
+ CacheUtil.deleteCachedReport(report.id.value)
+
}
@@ -46,7 +46,7 @@ suspend fun CompleteReportDEO.Companion.fromTournamentReport(
report
}
if (tournamentReport.isSubmitted) {
- runBlocking { CacheUtil.cacheReport(report) }
+ CacheUtil.cacheReport(report)
}
return report
}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/RuleDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/RuleDEO.kt
index 5d71515..1c62987 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/RuleDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/RuleDEO.kt
@@ -6,7 +6,6 @@ import eu.gaelicgames.referee.data.Rules
import eu.gaelicgames.referee.util.CacheUtil
import eu.gaelicgames.referee.util.RuleTranslationUtil
import eu.gaelicgames.referee.util.lockedTransaction
-import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.selectAll
@@ -59,9 +58,7 @@ suspend fun RuleDEO.Companion.allRules(): List {
suspend fun RuleDEO.updateInDatabase(): Result {
val rUpdate = this
- runBlocking {
- CacheUtil.deleteCachedRules()
- }
+ CacheUtil.deleteCachedRules()
return lockedTransaction {
val rule = Rule.findById(rUpdate.id)
if (rule != null) {
@@ -88,9 +85,9 @@ suspend fun RuleDEO.updateInDatabase(): Result {
suspend fun ModifyRulesDEOState.delete(): Result {
- runBlocking {
- CacheUtil.deleteCachedRules()
- }
+
+ CacheUtil.deleteCachedRules()
+
return lockedTransaction {
val rule = Rule.findById(this@delete.id)
if (rule != null) {
@@ -111,9 +108,8 @@ suspend fun ModifyRulesDEOState.delete(): Result {
}
suspend fun ModifyRulesDEOState.toggleDisabledState(): Result {
- runBlocking {
- CacheUtil.deleteCachedRules()
- }
+ CacheUtil.deleteCachedRules()
+
return lockedTransaction {
val rule = Rule.findById(this@toggleDisabledState.id)
if (rule != null) {
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamDEO.kt
index c0af078..28f307c 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamDEO.kt
@@ -3,7 +3,6 @@ package eu.gaelicgames.referee.data.api
import eu.gaelicgames.referee.data.*
import eu.gaelicgames.referee.util.CacheUtil
import eu.gaelicgames.referee.util.lockedTransaction
-import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.*
fun TeamDEO.Companion.fromTeam(input: Team, amalgamationTeams: List? = null): TeamDEO {
@@ -138,9 +137,9 @@ suspend fun TeamDEO.updateInDatabase(): Result {
suspend fun MergeTeamsDEO.updateInDatabase(): Result {
- runBlocking {
- CacheUtil.deleteCachedTeamList()
- }
+
+ CacheUtil.deleteCachedTeamList()
+
return lockedTransaction {
val team = Team.findById(baseTeam)
if (team != null) {
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamsheetDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamsheetDEO.kt
new file mode 100644
index 0000000..16b13da
--- /dev/null
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/TeamsheetDEO.kt
@@ -0,0 +1,178 @@
+package eu.gaelicgames.referee.data.api
+
+import ExtractedPlayer
+import TeamsheetReader
+import arrow.core.Either
+import arrow.core.left
+import arrow.core.raise.either
+import arrow.core.raise.ensure
+import arrow.core.right
+import eu.gaelicgames.referee.data.*
+import eu.gaelicgames.referee.util.ObjectStorage
+import eu.gaelicgames.referee.util.lockedTransaction
+import java.time.LocalDateTime
+import java.util.*
+
+
+fun ExtractedPlayer.toPlayerDEO(): PlayerDEO {
+ return PlayerDEO(this.romanName, null, this.number)
+}
+
+fun PlayerDEO.toRegisteredPlayer(): RegisteredPlayer {
+ return RegisteredPlayer(this.name, this.jerseyNumber, this.playerNumber)
+}
+
+fun PlayerDEO.Companion.fromRegisteredPlayer(player: RegisteredPlayer): PlayerDEO {
+ return PlayerDEO(player.name, player.jerseyNumber, player.foireannNumber)
+}
+
+suspend fun TeamsheetUploadSuccessDEO.Companion.fromBytes(
+ data: ByteArray
+): Either = either {
+ val playerExtractionResult = TeamsheetReader.readFromBytes(data)
+ val fileUUID = UUID.randomUUID()
+ val upload = ObjectStorage.uploadObject(fileUUID.toString(), data)
+ ensure(upload.isSuccess) {
+ upload.exceptionOrNull()?.printStackTrace()
+ TeamsheetFailure.TeamsheetStorageFailedDEO()
+ }
+ ensure(playerExtractionResult.isSuccess) { TeamsheetFailure.ExtractionFailedButUploadedDEO(fileUUID.toString()) }
+ TeamsheetUploadSuccessDEO(playerExtractionResult.getOrThrow().map { it.toPlayerDEO() }, fileUUID.toString())
+}
+
+
+suspend fun TeamsheetWithClubAndTournamentDataDEO.storeInDatabase(): Result {
+ val deo = this
+ return lockedTransaction {
+ val club = Team.findById(deo.clubId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Club with id ${deo.clubId} not found"))
+ val tournament = Tournament.findById(deo.tournamentId) ?: return@lockedTransaction Result.failure(
+ IllegalArgumentException("Tournament with id ${deo.tournamentId} not found")
+ )
+ val code = GameCode.findById(deo.codeId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Game code with id ${deo.codeId} not found"))
+
+ val registration = TeamsheetRegistration.new {
+ this.team = club
+ this.tournament = tournament
+ this.fileKey = deo.fileKey
+ this.registrarMail = deo.registrarMail
+ this.registrarName = deo.registrarName
+ this.code = code
+ this.players = deo.players.map { it.toRegisteredPlayer() }
+ this.uploadedAt = LocalDateTime.now()
+ }
+ Result.success(registration)
+
+ }
+}
+
+suspend fun TeamsheetWithClubAndTournamentDataDEO.updateInDatabase() : Result {
+ val deo = this
+ return lockedTransaction {
+ val registration = TeamsheetRegistrations.findByFileKey(deo.fileKey)
+ ?: return@lockedTransaction Result.failure(NoSuchElementException("No teamsheet with file key ${deo.fileKey} found"))
+
+ val club = Team.findById(deo.clubId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Club with id ${deo.clubId} not found"))
+ val tournament = Tournament.findById(deo.tournamentId) ?: return@lockedTransaction Result.failure(
+ IllegalArgumentException("Tournament with id ${deo.tournamentId} not found")
+ )
+ val code = GameCode.findById(deo.codeId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Game code with id ${deo.codeId} not found"))
+
+ registration.team = club
+ registration.tournament = tournament
+ registration.fileKey = deo.fileKey
+
+ registration.registrarMail = deo.registrarMail
+ registration.registrarName = deo.registrarName
+
+ registration.code = code
+ registration.players = deo.players.map { it.toRegisteredPlayer() }
+ registration.uploadedAt = LocalDateTime.now()
+
+ Result.success(registration)
+ }
+}
+
+
+fun TeamsheetFailure.toApiResponse(): Any {
+ return when (this) {
+ is TeamsheetFailure.ExtractionFailedButUploadedDEO -> {
+ this
+ }
+
+ is TeamsheetFailure.TeamsheetStorageFailedDEO -> {
+ ApiError(ApiErrorOptions.INSERTION_FAILED, "Teamsheet storage failed")
+ }
+ }
+}
+
+suspend fun TeamsheetFileKeyDEO.getPlayers(): Either {
+ val file = ObjectStorage.getObject(this.fileKey)
+ return file.fold(
+ onSuccess = { data ->
+ val playerExtractionResult = TeamsheetReader.readFromBytes(data)
+ if (!playerExtractionResult.isSuccess) {
+ TeamsheetFailure.ExtractionFailedButUploadedDEO(this.fileKey).left()
+ }
+ TeamsheetUploadSuccessDEO(
+ playerExtractionResult.getOrThrow().map { it.toPlayerDEO() },
+ this.fileKey
+ ).right()
+ },
+ onFailure = { TeamsheetFailure.TeamsheetStorageFailedDEO().left() }
+ )
+}
+
+suspend fun TeamsheetFileKeyDEO.getMetadata(): Result {
+ val deo = this
+ return lockedTransaction {
+ val registration = TeamsheetRegistration.find { TeamsheetRegistrations.fileKey eq deo.fileKey }.firstOrNull()
+ ?: return@lockedTransaction Result.failure(NoSuchElementException("No teamsheet with file key ${deo.fileKey} found"))
+ Result.success(
+ TeamsheetWithClubAndTournamentDataDEO(
+ players = registration.players.map { PlayerDEO.fromRegisteredPlayer(it) },
+ clubId = registration.team.id.value,
+ tournamentId = registration.tournament.id.value,
+ fileKey = registration.fileKey,
+ registrarMail = registration.registrarMail,
+ registrarName = registration.registrarName,
+ codeId = registration.code.id.value
+ )
+ )
+ }
+}
+
+suspend fun ReplaceTeamsheetFileDEO.storeInDatabase(): Result {
+ val deo = this@storeInDatabase
+ return lockedTransaction {
+ val registration = TeamsheetRegistrations.findByFileKey(deo.oldfileKey)
+ ?: return@lockedTransaction Result.failure(NoSuchElementException("No teamsheet with file key ${deo.oldfileKey} found"))
+
+ val previousFileKeyUploadedAt = registration.uploadedAt
+ val club = Team.findById(deo.newTeamsheetData.clubId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Club with id ${deo.newTeamsheetData.clubId} not found"))
+ val tournament = Tournament.findById(deo.newTeamsheetData.tournamentId) ?: return@lockedTransaction Result.failure(
+ IllegalArgumentException("Tournament with id ${deo.newTeamsheetData.tournamentId} not found")
+ )
+ val code = GameCode.findById(deo.newTeamsheetData.codeId)
+ ?: return@lockedTransaction Result.failure(IllegalArgumentException("Game code with id ${deo.newTeamsheetData.codeId} not found"))
+
+ registration.team = club
+ registration.tournament = tournament
+ registration.fileKey = deo.newTeamsheetData.fileKey
+
+ registration.registrarMail = deo.newTeamsheetData.registrarMail
+ registration.registrarName = deo.newTeamsheetData.registrarName
+
+ registration.code = code
+ registration.players = deo.newTeamsheetData.players.map { it.toRegisteredPlayer() }
+ registration.uploadedAt = LocalDateTime.now()
+ val previousFileKey = PreviousFileKey(deo.oldfileKey, previousFileKeyUploadedAt)
+ registration.previousFileKeys = registration.previousFileKeys + previousFileKey
+
+ Result.success(registration)
+ }
+}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/data/api/TournamentDEO.kt b/src/main/kotlin/eu/gaelicgames/referee/data/api/TournamentDEO.kt
index 137d7f8..e1a20dc 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/data/api/TournamentDEO.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/data/api/TournamentDEO.kt
@@ -4,7 +4,6 @@ import eu.gaelicgames.referee.data.*
import eu.gaelicgames.referee.util.CacheUtil
import eu.gaelicgames.referee.util.lockedTransaction
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.*
suspend fun TournamentDEO.Companion.fromTournament(input: Tournament): TournamentDEO {
@@ -62,15 +61,12 @@ suspend fun TournamentDEO.updateInDatabase(): Result {
)
}
}
- runBlocking {
- CacheUtil.deleteCachedCompleteTournamentReport(tournament.id.value)
- CacheUtil.deleteCachedPublicTournamentReport(tournament.id.value)
- }
+ CacheUtil.deleteCachedCompleteTournamentReport(tournament.id.value)
+ CacheUtil.deleteCachedPublicTournamentReport(tournament.id.value)
+
TournamentReport.find { TournamentReports.tournament eq tournament.id }.forEach {
- runBlocking {
- CacheUtil.deleteCachedReport(it.id.value)
- }
+ CacheUtil.deleteCachedReport(it.id.value)
}
tournament.name = thisDEO.name
@@ -150,27 +146,25 @@ suspend fun PublicTournamentReportDEO.Companion.fromTournament(input: Tournament
gameReports,
allTeams
)
- runBlocking {
- CacheUtil.cachePublicTournamentReport(ptr)
- }
+ CacheUtil.cachePublicTournamentReport(ptr)
+
ptr
}
}
-fun PublicTournamentReportDEO.Companion.fromTournamentId(id: Long): PublicTournamentReportDEO {
- return runBlocking {
- CacheUtil.getCachedPublicTournamentReport(id)
- .getOrElse {
- lockedTransaction {
- val tournament = Tournament.findById(id)
- ?: throw IllegalArgumentException("Tournament with id $id does not exist")
- fromTournament(tournament)
- }
+suspend fun PublicTournamentReportDEO.Companion.fromTournamentId(id: Long): PublicTournamentReportDEO {
+ return CacheUtil.getCachedPublicTournamentReport(id)
+ .getOrElse {
+ lockedTransaction {
+ val tournament = Tournament.findById(id)
+ ?: throw IllegalArgumentException("Tournament with id $id does not exist")
+ fromTournament(tournament)
}
+ }
+
- }
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -211,9 +205,8 @@ suspend fun CompleteTournamentReportDEO.Companion.fromTournament(input: Tourname
allTeams,
allPitchReports
)
- runBlocking {
- CacheUtil.cacheCompleteTournamentReport(deo)
- }
+ CacheUtil.cacheCompleteTournamentReport(deo)
+
deo
}
}
@@ -234,28 +227,28 @@ private fun getAllTeamsOfGameReports(gameReports: List): List {
val tournamentID = this.id
- runBlocking {
- CacheUtil.deleteCachedPublicTournamentReport(tournamentID)
- CacheUtil.deleteCachedCompleteTournamentReport(tournamentID)
- }
+
+ CacheUtil.deleteCachedPublicTournamentReport(tournamentID)
+ CacheUtil.deleteCachedCompleteTournamentReport(tournamentID)
+
return lockedTransaction {
addLogger(StdOutSqlLogger)
val tournament = Tournament.findById(tournamentID)
diff --git a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/PublicApiRouting.kt b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/PublicApiRouting.kt
index b43946d..e9e6c89 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/PublicApiRouting.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/PublicApiRouting.kt
@@ -1,15 +1,21 @@
package eu.gaelicgames.referee.plugins.routing
+import arrow.core.getOrElse
import eu.gaelicgames.referee.data.*
import eu.gaelicgames.referee.data.api.*
+import eu.gaelicgames.referee.plugins.receiveAndHandleDEO
import eu.gaelicgames.referee.resources.Api
import eu.gaelicgames.referee.util.lockedTransaction
import io.ktor.http.*
+import io.ktor.http.content.*
import io.ktor.server.application.*
+import io.ktor.server.request.*
import io.ktor.server.resources.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
+import io.ktor.server.resources.post
import org.jetbrains.exposed.sql.selectAll
+import java.io.ByteArrayOutputStream
fun Route.publicApiRouting() {
get {
@@ -65,4 +71,83 @@ fun Route.publicApiRouting() {
val response = ClubAndCountyApi.get()
call.respond(response)
}
+
+ post {
+ val multipartData = call.receiveMultipart()
+ val stream = ByteArrayOutputStream()
+ var fileName = ""
+ var fileDescription = ""
+ multipartData.forEachPart { part ->
+ when (part) {
+ is PartData.FormItem -> {
+ fileDescription = part.value
+ }
+
+ is PartData.FileItem -> {
+ fileName = part.originalFileName as String
+ val fileBytes = part.streamProvider().readBytes()
+ stream.write(fileBytes)
+ }
+
+ else -> {
+
+ }
+ }
+ part.dispose()
+ }
+ val byteArray = stream.toByteArray()
+ val sizeInMb = byteArray.size / 1024.0 / 1024.0
+ if (sizeInMb > 5) {
+ call.respond(ApiError(ApiErrorOptions.ILLEGAL_ARGUMENT, "File too large"))
+ return@post
+ }
+
+ call.respond(TeamsheetUploadSuccessDEO.fromBytes(byteArray).getOrElse {
+ it.toApiResponse()
+ })
+ }
+
+ post {
+ receiveAndHandleDEO { deo ->
+ deo.storeInDatabase().map { deo }.getOrElse {
+ ApiError(ApiErrorOptions.INSERTION_FAILED, it.message ?: "Unknown error")
+ }
+ }
+ }
+
+ post {
+ receiveAndHandleDEO {
+ it.getPlayers().getOrElse { fail ->
+ fail.toApiResponse()
+ }
+ }
+ }
+
+ post {
+ receiveAndHandleDEO {
+ it.getMetadata().getOrElse {
+ ApiError(ApiErrorOptions.ILLEGAL_ARGUMENT, it.message ?: "Unknown error")
+ }
+ }
+ }
+
+ get {
+ call.respond("This endpoint only accepts POST requests")
+ }
+
+ post {
+ receiveAndHandleDEO {
+ it.updateInDatabase().getOrElse {
+ ApiError(ApiErrorOptions.INSERTION_FAILED, it.message ?: "Unknown error")
+ }
+ }
+ }
+
+ post {
+ receiveAndHandleDEO {
+ it.storeInDatabase().getOrElse {
+ ApiError(ApiErrorOptions.INSERTION_FAILED, it.message ?: "Unknown error")
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/RefereeApiRouting.kt b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/RefereeApiRouting.kt
index f499a3e..359e5dd 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/RefereeApiRouting.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/RefereeApiRouting.kt
@@ -42,10 +42,8 @@ fun Route.refereeApiRouting() {
val reportId = get.id
if (reportId >= 0) {
val report = CacheUtil.getCachedReport(reportId).recoverCatching {
-
+ println("Report $reportId not found in cache, loading from db")
CompleteReportDEO.fromTournamentReportId(reportId).getOrThrow()
-
-
}
if (report.isSuccess) {
diff --git a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/SitesRouting.kt b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/SitesRouting.kt
index 7df1c5c..6836fd4 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/SitesRouting.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/plugins/routing/SitesRouting.kt
@@ -5,6 +5,7 @@ import eu.gaelicgames.referee.data.api.CompleteReportDEO
import eu.gaelicgames.referee.data.api.fromTournamentReport
import eu.gaelicgames.referee.resources.Api
import eu.gaelicgames.referee.resources.Report
+import eu.gaelicgames.referee.resources.TeamsheetRes
import eu.gaelicgames.referee.resources.UserRes
import eu.gaelicgames.referee.util.CacheUtil
import eu.gaelicgames.referee.util.lockedTransaction
@@ -178,6 +179,10 @@ fun Route.sites() {
respondWithStaticFileOnSystem("public_dashboard.html")
}
+
+ get {
+ respondWithStaticFileOnSystem("teamsheets.html")
+ }
}
private suspend fun PipelineContext.respondWithStaticFileOnSystem(
diff --git a/src/main/kotlin/eu/gaelicgames/referee/resources/Api.kt b/src/main/kotlin/eu/gaelicgames/referee/resources/Api.kt
index 9003e8b..8807d29 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/resources/Api.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/resources/Api.kt
@@ -353,4 +353,33 @@ class Api() {
@Resource("website_feed")
class WebsiteFeed(val parent: Api)
+
+ @Serializable
+ @Resource("teamsheet")
+ class Teamsheet(val parent: Api) {
+ @Serializable
+ @Resource("upload")
+ class Upload(val parent: Teamsheet)
+
+ @Serializable
+ @Resource("get_players")
+ class GetPlayers(val parent: Teamsheet)
+
+ @Serializable
+ @Resource("set_metadata")
+ class SetMetadata(val parent: Teamsheet)
+
+ @Serializable
+ @Resource("get_metadata")
+ class GetMetadata(val parent: Teamsheet)
+
+ @Serializable
+ @Resource("edit")
+ class Edit(val parent: Teamsheet)
+
+ @Serializable
+ @Resource("replace_teamsheet_file")
+ class ReplaceTeamsheetFile(val parent: Teamsheet)
+ }
+
}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/resources/BaseResources.kt b/src/main/kotlin/eu/gaelicgames/referee/resources/BaseResources.kt
index 71b2121..42a07b8 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/resources/BaseResources.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/resources/BaseResources.kt
@@ -11,4 +11,10 @@ class UserRes() {
@Resource("activate/{uuid}")
class Activate(val parent: UserRes, val uuid: String)
-}
\ No newline at end of file
+}
+
+
+@Serializable
+@Resource("/teamsheet")
+class TeamsheetRes() {
+}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/util/ConfigUtil.kt b/src/main/kotlin/eu/gaelicgames/referee/util/ConfigUtil.kt
index 456544c..5e07e0e 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/util/ConfigUtil.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/util/ConfigUtil.kt
@@ -1,5 +1,8 @@
package eu.gaelicgames.referee.util
+import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
+import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
+import aws.smithy.kotlin.runtime.collections.Attributes
import com.natpryce.konfig.*
import eu.gaelicgames.referee.data.User
import eu.gaelicgames.referee.data.UserRole
@@ -27,6 +30,13 @@ object GGERefereeConfig {
var postgresPassword : String
var claudeAccessToken : String
+
+ var objectStorageEndpoint : String
+
+ var objectStorageBucket : String
+
+ var objectStorageCredentialProvider: CredentialsProvider
+
object mailjet : PropertyGroup() {
val public by stringType
val secret by stringType
@@ -61,6 +71,13 @@ object GGERefereeConfig {
val accessToken by stringType
}
+ object objectStorage : PropertyGroup() {
+ val endpoint by stringType
+ val accessKey by stringType
+ val secretKey by stringType
+ val bucket by stringType
+ }
+
init {
val config = EnvironmentVariables() overriding
ConfigurationProperties.fromOptionalFile(File("gge-referee.properties"))
@@ -86,6 +103,19 @@ object GGERefereeConfig {
claudeAccessToken = config[claude.accessToken]
+ objectStorageEndpoint = config[objectStorage.endpoint]
+ objectStorageBucket = config[objectStorage.bucket]
+
+ objectStorageCredentialProvider = object : CredentialsProvider {
+ override suspend fun resolve(attributes: Attributes): Credentials {
+ return Credentials.invoke(
+ accessKeyId = config[objectStorage.accessKey],
+ secretAccessKey = config[objectStorage.secretKey]
+ )
+ }
+
+ }
+
}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/util/DatabaseUtil.kt b/src/main/kotlin/eu/gaelicgames/referee/util/DatabaseUtil.kt
index c52c439..3c2fe90 100644
--- a/src/main/kotlin/eu/gaelicgames/referee/util/DatabaseUtil.kt
+++ b/src/main/kotlin/eu/gaelicgames/referee/util/DatabaseUtil.kt
@@ -1,12 +1,9 @@
package eu.gaelicgames.referee.util
-import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import eu.gaelicgames.referee.data.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.newFixedThreadPoolContext
-import kotlinx.coroutines.newSingleThreadContext
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import org.jetbrains.exposed.dao.id.LongIdTable
@@ -101,7 +98,8 @@ object DatabaseHandler {
PitchGoalDimensionOptions,
Pitches,
ActivationTokens,
- TournamentReportShareLinks
+ TournamentReportShareLinks,
+ TeamsheetRegistrations
)
suspend fun createSchema() {
@@ -133,6 +131,9 @@ object DatabaseHandler {
//Migration 6 - Add Multilanguage Support for Rules
SchemaUtils.createMissingTablesAndColumns(Rules)
+
+ //Migration 7 - Add TeamsheetRegistrations
+ SchemaUtils.createMissingTablesAndColumns(TeamsheetRegistrations)
}
}
@@ -192,7 +193,11 @@ object DatabaseHandler {
}
- private suspend fun populate_name_only_table_from_csv(table: LongIdTable, filename: String, nameColumn: Column) {
+ private suspend fun populate_name_only_table_from_csv(
+ table: LongIdTable,
+ filename: String,
+ nameColumn: Column
+ ) {
val alreadyPopulated = lockedTransaction {
table.selectAll().count() != 0L
}
@@ -378,7 +383,7 @@ object DatabaseHandler {
@OptIn(ExperimentalCoroutinesApi::class)
suspend fun lockedTransaction(statement: suspend Transaction.() -> T): T {
- return newSuspendedTransaction(Dispatchers.IO,DatabaseHandler.db) {
+ return newSuspendedTransaction(Dispatchers.IO, DatabaseHandler.db) {
val t = statement()
commit()
t
diff --git a/src/main/kotlin/eu/gaelicgames/referee/util/EloRating.kt b/src/main/kotlin/eu/gaelicgames/referee/util/EloRating.kt
new file mode 100644
index 0000000..38bed0d
--- /dev/null
+++ b/src/main/kotlin/eu/gaelicgames/referee/util/EloRating.kt
@@ -0,0 +1,33 @@
+package eu.gaelicgames.referee.util
+
+import eu.gaelicgames.referee.data.GameReport
+import eu.gaelicgames.referee.data.Team
+import kotlin.math.pow
+
+data class RankedTeam(val team:Team, var rating: Double = 1500.0)
+class EloSystem(private val kFactor: Double = 32.0) {
+
+ fun updateRatings(game: GameReport, teams: List) {
+ val teamA = teams.find { it.team.id == game.teamA.id }!!
+ val teamB = teams.find { it.team.id == game.teamB.id }!!
+ val expectedScoreA = expectedScore(teamA.rating, teamB.rating)
+ val expectedScoreB = 1.0 - expectedScoreA
+
+ val actualScoreA = calculateActualScore(game.teamAGoals, game.teamAPoints, game.teamBGoals, game.teamBPoints)
+ val actualScoreB = 1.0 - actualScoreA
+
+ teamA.rating += kFactor * (actualScoreA - expectedScoreA)
+ teamB.rating += kFactor * (actualScoreB - expectedScoreB)
+ }
+
+ private fun expectedScore(ratingA: Double, ratingB: Double): Double {
+ return 1.0 / (1.0 + 10.0.pow((ratingB - ratingA) / 400.0))
+ }
+
+ private fun calculateActualScore(goalsA: Int, pointsA: Int, goalsB: Int, pointsB: Int): Double {
+ val scoreA = goalsA * 3 + pointsA
+ val scoreB = goalsB * 3 + pointsB
+ val scoreDiff = scoreA - scoreB
+ return 1.0 / (1.0 + Math.E.pow(-0.1 * scoreDiff))
+ }
+}
diff --git a/src/main/kotlin/eu/gaelicgames/referee/util/ObjectStorageUtil.kt b/src/main/kotlin/eu/gaelicgames/referee/util/ObjectStorageUtil.kt
new file mode 100644
index 0000000..187c48e
--- /dev/null
+++ b/src/main/kotlin/eu/gaelicgames/referee/util/ObjectStorageUtil.kt
@@ -0,0 +1,65 @@
+package eu.gaelicgames.referee.util
+
+import aws.sdk.kotlin.services.s3.S3Client
+import aws.sdk.kotlin.services.s3.model.GetObjectRequest
+import aws.sdk.kotlin.services.s3.model.PutObjectRequest
+import aws.smithy.kotlin.runtime.content.ByteStream
+import aws.smithy.kotlin.runtime.content.toByteArray
+import aws.smithy.kotlin.runtime.net.url.Url
+
+object ObjectStorage {
+ private val bucket = GGERefereeConfig.objectStorageBucket
+
+
+ private fun client(): S3Client {
+ return S3Client {
+ endpointUrl = Url.parse(GGERefereeConfig.objectStorageEndpoint)
+ credentialsProvider = GGERefereeConfig.objectStorageCredentialProvider
+ region = "eu-central-1" //This is a region that is actually not used as I have my custom endpoint anyway.
+ forcePathStyle = true
+ }
+ }
+ suspend fun uploadObject(key: String, data: ByteStream):Result {
+ val request = PutObjectRequest {
+ this.bucket = ObjectStorage.bucket
+ this.key = key
+ this.body = data
+ }
+
+ val response = kotlin.runCatching { client().use { s3 ->
+ s3.putObject(request)
+ }}
+
+ return response.fold(
+ onSuccess = { Result.success(Unit) },
+ onFailure = { Result.failure(it) }
+ )
+ }
+
+ suspend fun uploadObject(key: String, data: ByteArray):Result {
+ return uploadObject(key, ByteStream.fromBytes(data))
+ }
+
+ suspend fun getObject(key: String):Result {
+ val getObjectRequest = GetObjectRequest {
+ this.bucket = ObjectStorage.bucket
+ this.key = key
+ }
+
+ val response = kotlin.runCatching { client().use { s3 ->
+ s3.getObject(getObjectRequest) { resp ->
+ resp.body?.toByteArray()
+ }
+ }}
+
+ return response.fold(
+ onSuccess = {
+ if (it == null)
+ Result.failure(Exception("No data returned"))
+ else
+ Result.success(it)
+ },
+ onFailure = { Result.failure(it) }
+ )
+ }
+}
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 981d19e..63fb4af 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -4,10 +4,29 @@
%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+ logs/application.${bySecond}.log
+
+
+ logs/application.%d{yyyy-MM-dd}.log
+
+ 30
+
+ 3GB
+
+
+ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
-
-
-
+
+
+
diff --git a/src/test/kotlin/eu/gaelicgames/referee/data/api/GameReportDEOTest.kt b/src/test/kotlin/eu/gaelicgames/referee/data/api/GameReportDEOTest.kt
index 8271632..06aed16 100644
--- a/src/test/kotlin/eu/gaelicgames/referee/data/api/GameReportDEOTest.kt
+++ b/src/test/kotlin/eu/gaelicgames/referee/data/api/GameReportDEOTest.kt
@@ -291,9 +291,11 @@ internal class GameReportDEOTest {
@Test
fun gameReportClassesDEO_get() {
- val deo = GameReportClassesDEO.load()
- assert(deo.extraTimeOptions.isNotEmpty())
- assert(deo.gameTypes.isNotEmpty())
+ runBlocking {
+ val deo = GameReportClassesDEO.load()
+ assert(deo.extraTimeOptions.isNotEmpty())
+ assert(deo.gameTypes.isNotEmpty())
+ }
}
@Test