chore: setup
Some checks failed
Quality Check / Static Analysis (push) Has been cancelled
Quality Check / Testing (push) Has been cancelled
Quality Check / Validate OAS (push) Has been cancelled
Quality Check / Linting (push) Has been cancelled

This commit is contained in:
Snoweuph 2025-02-01 14:18:34 +01:00
commit 2c97878a2f
Signed by: snoweuph
GPG key ID: BEFC41DA223CEC55
43 changed files with 1222 additions and 0 deletions

View file

@ -0,0 +1,29 @@
name: Feature
title: '[Feature]: '
about: 'Vorlage Für ein Feature Ticket'
ref: 'trunk'
body:
- type: input
id: ticket
attributes:
label: Ticket
description: Für Welches Ticket ist diese Pull Request?
placeholder: TD-1
validations:
required: true
- type: textarea
id: beschreibung
attributes:
label: Beschreibung
description: Was hast du genau gemacht in diesem Ticket?
placeholder: ...
validations:
required: true
- type: textarea
id: more
attributes:
label: Weitere Infos
description: Gibt es noch etwas das ich wissen muss um das Ticket zu Reviewn?
placeholder: ...
validations:
required: false

View file

@ -0,0 +1,5 @@
FROM amazoncorretto:21-alpine
ADD tower_defence_server.jar tower_defence_server.jar
ENTRYPOINT ["java","-jar","/tower_defence_server.jar"]

View file

@ -0,0 +1,94 @@
name: Build Application
on:
push:
tags:
- 'v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?'
- "v*"
jobs:
build:
runs-on: stable
container:
image: git.euph.dev/actions/runner-java-21:latest
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: "Prepare Gradle"
run: gradle clean
- name: "Build Jar"
run: gradle bootJar -Pversion=${{ github.ref_name }}
- name: Upload Jar as Artifact
uses: "https://git.euph.dev/actions/upload-artifact@v3"
with:
name: tower_defence_server.jar
path: build/libs/Tower-Defence-Server-${{ github.ref_name }}.jar
- name: "Stop Gradle"
run: gradle --stop
build-docker:
runs-on: docker
needs:
- build
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- name: Download artifact from previous job
uses: "https://git.euph.dev/actions/download-artifact@v3"
with:
name: tower_defence_server.jar
path: .forgejo/workflows/
- name: Login to Registry
uses: "https://git.euph.dev/actions/docker-login@v3"
with:
registry: git.euph.dev
username: ${{ secrets.DEPLOY_USER }}
password: ${{ secrets.DEPLOY_SECRET }}
- name: debug
run: |
ls .forgejo/workflows
- name: Build and push Web Image
uses: "https://git.euph.dev/actions/docker-build-push@v5"
with:
context: ".forgejo/workflows/"
push: true
tags: |
git.euph.dev/towerdefence/server:${{ github.ref_name }}
${{ contains(github.ref_name, 'rc') == false && 'git.euph.dev/towerdefence/server:latest' || '' }}
release:
runs-on: stable
container:
image: git.euph.dev/actions/runner-basic:latest
needs:
- build
- build-docker
steps:
- name: Download Server Jar
uses: "https://git.euph.dev/actions/download-artifact@v3"
with:
name: tower_defence_server.jar
path: release
- name: Create Release
uses: "https://git.euph.dev/actions/release@v2"
with:
direction: upload
tag: ${{ github.ref_name }}
token: ${{ secrets.DEPLOY_TOKEN }}
prerelease: ${{ contains( github.ref_name, "rc") }}
release-dir: release
release-notes: |
# Tower Defence - Server ${{ github.ref_name }}
Read the [Documentation](https://git.euph.dev/TowerDefence/Dokumentation/wiki/Server/Config) to see how to setup the server.

108
.forgejo/workflows/qs.yml Normal file
View file

@ -0,0 +1,108 @@
name: "Quality Check"
on:
- push
- pull_request
jobs:
oas:
name: "Validate OAS"
runs-on: stable
container:
image: "git.euph.dev/actions/runner-java-21:latest"
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: "Prepare Gradle"
run: gradle clean
- name: "Validate OAS Spec"
run: gradle validateSwagger
- name: "Stop Gradle"
run: gradle --stop
linting:
name: "Linting"
runs-on: stable
container:
image: "git.euph.dev/actions/runner-java-21:latest"
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: "Prepare Gradle"
run: gradle clean
- name: "Generate OAS Boilerplate"
run: gradle generateSwaggerCode
- name: "Linting Main"
run: gradle checkstyleMain
- name: "Linting Test"
run: gradle checkstyleTest
- name: "Stop Gradle"
run: gradle --stop
static:
name: "Static Analysis"
runs-on: stable
container:
image: "git.euph.dev/actions/runner-java-21:latest"
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: "Prepare Gradle"
run: gradle clean
- name: "Generate OAS Boilerplate"
run: gradle generateSwaggerCode
- name: "Static Analysis Main"
run: gradle spotbugsMain
- name: "Static Analysis Test"
run: gradle spotbugsTest
- name: "Stop Gradle"
run: gradle --stop
test:
name: "Testing"
runs-on: stable
container:
image: "git.euph.dev/actions/runner-java-21:latest"
steps:
- name: "Checkout"
uses: "https://git.euph.dev/actions/checkout@v3"
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: "Prepare Gradle"
run: gradle clean
- name: "Generate OAS Boilerplate"
run: gradle generateSwaggerCode
- name: "Run Tests"
run: gradle test
- name: "Stop Gradle"
run: gradle --stop

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

38
.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
.gradle
gradlew
gradlew.bat
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

11
.run/Check.run.xml Normal file
View file

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Check" type="CompoundRunConfigurationType">
<toRun name="Main Lint" type="GradleRunConfiguration" />
<toRun name="Main Static Analysis" type="GradleRunConfiguration" />
<toRun name="OAS Validate" type="GradleRunConfiguration" />
<toRun name="Test" type="GradleRunConfiguration" />
<toRun name="Test Lint" type="GradleRunConfiguration" />
<toRun name="Test Static Analysis" type="GradleRunConfiguration" />
<method v="2" />
</configuration>
</component>

11
.run/Docker.run.xml Normal file
View file

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envFilePath" value="" />
<option name="sourceFilePath" value="compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

24
.run/Main Lint.run.xml Normal file
View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Main Lint" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="checkstyleMain" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Main Static Analysis" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="spotbugsMain" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

24
.run/OAS Generate.run.xml Normal file
View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="OAS Generate" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="generateSwaggerCode" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

24
.run/OAS Validate.run.xml Normal file
View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="OAS Validate" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="validateSwagger" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

10
.run/Run.run.xml Normal file
View file

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<module name="Tower-Defence-Server.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.towerdefence.server.ServerApplication" />
<method v="2">
<option name="Make" enabled="true" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="Postgres" run_configuration_type="docker-deploy" />
</method>
</configuration>
</component>

24
.run/Test Lint.run.xml Normal file
View file

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test Lint" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="checkstyleTest" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,22 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test Static Analysis" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

12
.run/Test.run.xml Normal file
View file

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test" type="JUnit" factoryName="JUnit">
<module name="Project_Management_Tool.test" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

1
License.md Normal file
View file

@ -0,0 +1 @@
See License [here](https://git.euph.dev/TowerDefence/.profile/src/branch/main/License.md)

2
Readme.md Normal file
View file

@ -0,0 +1,2 @@
[![QS Badge](https://git.euph.dev/TowerDefence/Server/actions/workflows/qs.yml/badge.svg?branch=trunk&style=for-the-badge&label=QS)](https://git.euph.dev/TowerDefence/Server/actions?workflow=qs.yml)
# Tower Defence - Server

5
api/.gen-ignore Normal file
View file

@ -0,0 +1,5 @@
**/*ApiController.java
**/*OpenAPISpringBoot.java
**/*application.properties
**/io/swagger/configuration/HomeController.java
**/io/swagger/configuration/SwaggerUiConfiguration.java

110
api/api.yml Normal file
View file

@ -0,0 +1,110 @@
openapi: 3.0.0
info:
title: Tower Defence Server
description: An API for talking to the Tower Defence Server
version: 0.0.1
servers:
- url: /api/v1
- url: http://localhost:8080/api/v1
security:
- JWTAuth: []
components:
securitySchemes:
JWTAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
#############################################
# UUID #
#############################################
UUID:
description: Unique identifier compatible with [RFC9562](https://datatracker.ietf.org/doc/html/rfc9562)
type: string
format: uuid
example: f0981749-f550-46cd-b9ce-b6ca7cd0251f
#############################################
# AdminAuthInfo #
#############################################
ServerHealth:
type: object
properties:
okay:
type: boolean
required:
- okay
#############################################
# AdminAuthInfo #
#############################################
AdminAuthInfo:
type: object
properties:
username:
type: string
required:
- username
responses:
401Unauthorized:
description: "401 - Unauthorized"
404NotFound:
description: "Not Found"
content:
text/plain:
schema:
type: string
409Conflict:
description: "409 - Conflict"
content:
text/plain:
schema:
type: string
500InternalError:
description: "500 - Internal Server Error"
content:
text/plain:
schema:
type: string
503ServiceUnavailable:
description: "503 - Service Unavailable"
content:
text/plain:
schema:
type: string
paths:
/server/health:
get:
operationId: "ServerGetHealthcheck"
tags:
- server
description: "Endpoint for doing a Healthcheck of the Server"
responses:
200:
description: "A Health-Report of the server"
content:
application/json:
schema:
$ref: "#/components/schemas/ServerHealth"
500:
$ref: "#/components/responses/500InternalError"
503:
$ref: "#/components/responses/503ServiceUnavailable"
/admin/authenticated:
get:
operationId: "AdminGetAuthenticated"
tags:
- admin
description: "Endpoint for Checking if you're authenticated as an admin"
responses:
200:
description: "A Minimal Admin Info for testing if the admin is logged in"
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAuthInfo"
401:
$ref: "#/components/responses/401Unauthorized"
500:
$ref: "#/components/responses/500InternalError"
503:
$ref: "#/components/responses/503ServiceUnavailable"

11
api/gen-config.json Normal file
View file

@ -0,0 +1,11 @@
{
"modelPackage": "de.towerdefence.server.oas.models",
"apiPackage": "de.towerdefence.server.oas",
"invokerPackage": "de.towerdefence.server",
"java8": false,
"java11": true,
"dateLibrary": "java8-localdatetime",
"library": "spring-boot3",
"defaultInterfaces": false,
"serializableModel": true
}

126
build.gradle.kts Normal file
View file

@ -0,0 +1,126 @@
import com.github.spotbugs.snom.Confidence
import com.github.spotbugs.snom.Effort
import com.github.spotbugs.snom.SpotBugsTask
import org.hidetake.gradle.swagger.generator.GenerateSwaggerCode
repositories {
mavenCentral()
}
plugins {
java
checkstyle
id("com.github.spotbugs") version "6.0.23"
id("org.springframework.boot") version "3.4.2"
id("io.spring.dependency-management") version "1.1.7"
id("org.hidetake.swagger.generator") version "2.19.2"
}
checkstyle {
toolVersion = "10.21.2"
configDirectory = file("src/main/resources/")
configFile = file("src/main/resources/checkstyle.xml")
}
spotbugs {
toolVersion = "4.8.6"
effort.set(Effort.MAX)
reportLevel.set(Confidence.LOW)
}
group = "de.towerdefence"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
dependencies {
// Spring
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
developmentOnly("org.springframework.boot:spring-boot-devtools")
// Postgres
runtimeOnly("org.postgresql:postgresql")
// Lombok
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
testRuntimeOnly("com.h2database:h2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
//OAS
swaggerCodegen("io.swagger.codegen.v3:swagger-codegen-cli:3.0.61")
implementation("io.swagger.core.v3:swagger-annotations:2.2.22")
implementation("jakarta.xml.bind:jakarta.xml.bind-api") //Needed for XML/HTML Validation
}
tasks.withType<Test> {
useJUnitPlatform()
}
swaggerSources {
register("api") {
setInputFile(file("${rootDir}/api/api.yml"))
code.configFile = file("${rootDir}/api/gen-config.json")
val validationTask = validation
code(delegateClosureOf<GenerateSwaggerCode> {
language = "spring"
code.rawOptions =
listOf("--ignore-file-override=" + file("${rootDir}/api/.gen-ignore").absolutePath)
dependsOn(validationTask)
})
}
}
tasks {
withType<Checkstyle> {
reports {
xml.required.set(true)
html.required.set(false)
}
}
withType<SpotBugsTask> {
excludeFilter.set(file("${rootDir}/src/main/resources/spotbugs-exclude.xml"))
}
processResources {
dependsOn(generateSwaggerCode)
}
withType<Test> {
useJUnitPlatform()
}
named("compileJava").configure {
dependsOn(swaggerSources.getByName("api").code)
}
}
sourceSets {
main {
java.srcDir("${swaggerSources.getByName("api").code.outputDir}/src/main/java")
resources.srcDir("${swaggerSources.getByName("api").code.outputDir}/src/main/resources")
}
}

26
compose.yml Normal file
View file

@ -0,0 +1,26 @@
services:
postgres:
container_name: tower_defence_postgres
image: postgres:16
environment:
- POSTGRES_DB=td
- POSTGRES_USER=td_user
- POSTGRES_PASSWORD=td123
ports:
- "5432:5432"
volumes:
- "tower_defence_data:/var/lib/postgresql/data"
swagger:
container_name: tower_defence_swagger
image: swaggerapi/swagger-ui:latest
environment:
SWAGGER_JSON: "/data/api.yml"
ports:
- "8090:8080"
volumes:
- "./api/api.yml:/data/api.yml:Z"
volumes:
tower_defence_data:
name: tower_defence_data

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
version="0.0.1-SNAPSHOT"

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

6
http/getToken.http Normal file
View file

@ -0,0 +1,6 @@
POST https://keycloak.szut.dev/auth/realms/szut/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&client_id=employee-management-service&username=user&password=test
> {%client.log(response.body.access_token)%}

1
settings.gradle.kts Normal file
View file

@ -0,0 +1 @@
rootProject.name = "Tower-Defence-Server"

View file

@ -0,0 +1,30 @@
package de.towerdefence.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
@Configuration
public class Config implements WebMvcConfigurer {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View file

@ -0,0 +1,50 @@
package de.towerdefence.server;
import io.swagger.configuration.LocalDateConverter;
import io.swagger.configuration.LocalDateTimeConverter;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.io.Serial;
@SpringBootApplication
@ComponentScan(basePackages = { "de.towerdefence.server", "de.towerdefence.server.oas" , "io.swagger.configuration"})
public class ServerApplication implements CommandLineRunner {
@Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
new SpringApplication(ServerApplication.class).run(args);
}
@Configuration
static class CustomDateConfig extends WebMvcConfigurationSupport {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LocalDateConverter("yyyy-MM-dd"));
registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS"));
}
}
static class ExitException extends RuntimeException implements ExitCodeGenerator {
@Serial
private static final long serialVersionUID = 1L;
@Override
public int getExitCode() {
return 10;
}
}
}

View file

@ -0,0 +1,39 @@
package de.towerdefence.server.admin;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.towerdefence.server.auth.UserSession;
import de.towerdefence.server.oas.AdminApi;
import de.towerdefence.server.oas.models.AdminAuthInfo;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Optional;
@Controller
@RequestMapping("${openapi.api.base-path:/api/v1}")
public class AdminApiController implements AdminApi {
@Autowired
UserSession userSession;
@Override
public Optional<ObjectMapper> getObjectMapper() {
return Optional.empty();
}
@Override
public Optional<HttpServletRequest> getRequest() {
return Optional.empty();
}
@Override
public ResponseEntity<AdminAuthInfo> adminGetAuthenticated() {
AdminAuthInfo authInfo = new AdminAuthInfo();
authInfo.setUsername(this.userSession.getUsername());
return ResponseEntity.ok(authInfo);
}
}

View file

@ -0,0 +1,60 @@
package de.towerdefence.server.auth;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.HttpSessionEventPublisher;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class AuthConfig {
private static final String API_VERSION = "v1";
private final JWT jwt;
AuthConfig(JWT jwt) {
this.jwt = jwt;
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(sessionRegistry());
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/" + API_VERSION + "/admin/**")
.authenticated()
.anyRequest()
.permitAll()
)
.oauth2ResourceServer(resourceServer -> resourceServer
.jwt(jwt -> jwt.jwtAuthenticationConverter(this.jwt.jwtAuthenticationConverter()))
)
.build();
}
}

View file

@ -0,0 +1,71 @@
package de.towerdefence.server.auth;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component
public class JWT implements LogoutHandler {
private static final String REALM_ACCESS_CLAIM = "realm_access";
private static final String ROLES_CLAIM = "roles";
private static final String ROLE_PREFIX = "ROLE_";
private static final String OIDC_LOGOUT_ROUTE = "/protocol/openid-connect/logout";
private static final String OIDC_TOKEN_HINT_QUERY_PARAMETER = "id_token_hin";
@Autowired
private RestTemplate template;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
OidcUser user = (OidcUser) authentication.getPrincipal();
String endSessionEndpoint = user.getIssuer() + OIDC_LOGOUT_ROUTE;
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(endSessionEndpoint)
.queryParam(OIDC_TOKEN_HINT_QUERY_PARAMETER, user.getIdToken().getTokenValue());
ResponseEntity<String> logoutResponse = template.getForEntity(builder.toUriString(), String.class);
if (logoutResponse.getStatusCode().is2xxSuccessful()) {
System.out.println("Logged out successfully");
} else {
System.out.println("Failed to logout");
}
}
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
Map<String, Object> realmAccess = jwt.getClaim(REALM_ACCESS_CLAIM);
if (realmAccess == null || !realmAccess.containsKey(ROLES_CLAIM)) {
return grantedAuthorities;
}
Object rolesClaim = realmAccess.get(ROLES_CLAIM);
if (!(rolesClaim instanceof List<?>)) {
return grantedAuthorities;
}
for (Object role : (List<?>) rolesClaim) {
assert role instanceof String;
grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role));
}
return grantedAuthorities;
});
return jwtAuthenticationConverter;
}
}

View file

@ -0,0 +1,20 @@
package de.towerdefence.server.auth;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.security.oauth2.jwt.Jwt;
@Component
public class UserSession {
public String getToken() {
return getPrincipal().getTokenValue();
}
public String getUsername() {
return (String) getPrincipal().getClaims().get("preferred_username");
}
private Jwt getPrincipal() {
return (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}

View file

@ -0,0 +1,32 @@
package de.towerdefence.server.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.towerdefence.server.oas.ServerApi;
import de.towerdefence.server.oas.models.ServerHealth;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Optional;
@Controller
@RequestMapping("${openapi.api.base-path:/api/v1}")
public class ServerApiController implements ServerApi {
@Override
public Optional<ObjectMapper> getObjectMapper() {
return Optional.empty();
}
@Override
public Optional<HttpServletRequest> getRequest() {
return Optional.empty();
}
@Override
public ResponseEntity<ServerHealth> serverGetHealthcheck() {
ServerHealth health = new ServerHealth();
health.setOkay(true);
return ResponseEntity.ok(health);
}
}

View file

@ -0,0 +1,47 @@
package de.towerdefence.server.server;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;
public class ServerWebsocketHandler extends TextWebSocketHandler {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final Map<WebSocketSession, ScheduledFuture<?>> sessionTaskMap = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
ScheduledFuture<?> scheduledTask = scheduler.scheduleAtFixedRate(
() -> sendCurrentTime(session),
0,
1,
TimeUnit.MILLISECONDS
);
sessionTaskMap.put(session, scheduledTask);
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
String responseMessage = "You are Connected to the Tower Defence Server Websocket";
session.sendMessage(new TextMessage(responseMessage));
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendCurrentTime(WebSocketSession session) {
ScheduledFuture<?> task = sessionTaskMap.get(session);
try {
session.sendMessage(new TextMessage(String.valueOf(System.currentTimeMillis())));
} catch (IllegalStateException | IOException e) {
task.cancel(true);
sessionTaskMap.remove(session);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,15 @@
package de.towerdefence.server.server;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ServerWebsocketHandler(), "/ws/server").setAllowedOrigins("*");
}
}

View file

@ -0,0 +1,18 @@
# General
spring.application.name=Tower Defence Server
server.port=8080
# DB
spring.datasource.url=jdbc:postgresql://${TD_DB_HOST:localhost}:${TD_DB_PORT:5432}/${TD_DB_NAME:td}
spring.datasource.username=${TD_DB_USER:td_user}
spring.datasource.password=${TD_DB_PASS:td123}
spring.jpa.hibernate.ddl-auto=create-drop
# TODO: Replace with our own IAM (After completion of the project)
# JWT Auth
spring.security.oauth2.client.registration.keycloak.client-id=employee-management-service
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://keycloak.szut.dev/auth/realms/szut
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://keycloak.szut.dev/auth/realms/szut

View file

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress files="build[\\/]" checks="." />
</suppressions>

View file

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/checkstyle-ignore.xml" />
</module>
<property name="severity" value="error" />
<property name="tabWidth" value="4" />
<module name="LineLength">
<property name="max" value="120" />
</module>
<module name="FileTabCharacter">
<property name="eachLine" value="true" />
</module>
<module name="NewlineAtEndOfFile" />
</module>

View file

@ -0,0 +1,12 @@
<FindBugsFilter
xmlns="https://raw.githubusercontent.com/spotbugs/spotbugs/4.8.6/spotbugs/etc/findbugsfilter.xsd">
<Match>
<Source name="~.*" />
<Bug code="EI,EI2" /> <!-- We don't care about these codes -->
</Match>
<Match>
<!--Ignore
Auto Generated Code -->
<Source name="~.*build/.*" />
</Match>
</FindBugsFilter>

View file

@ -0,0 +1,35 @@
package de.towerdefence.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
@ActiveProfiles("test")
public abstract class IntegrationTest {
protected final static String baseUri = "/api/v1";
@Autowired
protected MockMvc mvc;
@Autowired
protected ObjectMapper objectMapper;
@BeforeEach
void setUp() {
//repository.deleteAll();
}
@AfterEach
void cleanUp() {
//repository.deleteAll();
}
}

View file

@ -0,0 +1,6 @@
spring.datasource.url=jdbc:h2:mem:test_db
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop