Refactor & Enhancements:
- Refactored `validateApp` in `AppsService` to return `Optional<App>` instead of `null`.
- Updated `JwtTokenService` to handle `expiration` parameter and use `App` object for token creation.
- Improved `TokenController` to work with the updated service layer.
- Fixed typo in `Report.vue` ("Airfreight" to "Air freight").
- Updated application properties to use `SPRING_PROFILES_ACTIVE`.
- Added `.dockerignore`, `dockerfile`, and `docker-compose.yml`, enabling Docker support.
- Removed unused Maven plugins and updated `vite.config.js` build directory.
- Introduced Gitea CI workflows for building and pushing Docker images.
This commit is contained in:
parent
0c51bf7c3d
commit
c071609eb2
13 changed files with 157 additions and 72 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
target/
|
||||||
|
src/frontend/node_modules/
|
||||||
|
src/frontend/dist/
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
51
.gitea/workflows/build.yml
Normal file
51
.gitea/workflows/build.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.avatic.de
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: git.avatic.de/${{ gitea.repository_owner }}/lcc
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha,prefix={{branch}}-
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=registry,ref=git.avatic.de/${{ gitea.repository_owner }}/lcc:buildcache
|
||||||
|
cache-to: type=registry,ref=git.avatic.de/${{ gitea.repository_owner }}/lcc:buildcache,mode=max
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,6 +12,8 @@ target/
|
||||||
.settings
|
.settings
|
||||||
.springBeans
|
.springBeans
|
||||||
.sts4-cache
|
.sts4-cache
|
||||||
|
.env.example
|
||||||
|
.env
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea
|
.idea
|
||||||
|
|
|
||||||
54
docker-compose.yml
Normal file
54
docker-compose.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: lcc-mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||||
|
MYSQL_DATABASE: ${DB_DATABASE}
|
||||||
|
MYSQL_USER: ${DB_USER}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
networks:
|
||||||
|
- lcc-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
lcc-app:
|
||||||
|
#image: git.avatic.de/avatic/lcc:latest
|
||||||
|
build: .
|
||||||
|
container_name: lcc-app
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
DB_DATABASE: ${DB_DATABASE}
|
||||||
|
DB_USER: ${DB_USER}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
ALLOWED_CORS_DOMAIN: ${ALLOWED_CORS_DOMAIN}
|
||||||
|
LCC_BASE_URL: ${LCC_BASE_URL}
|
||||||
|
AZURE_MAPS_CLIENT_ID: ${AZURE_MAPS_CLIENT_ID}
|
||||||
|
AZURE_MAPS_SUBSCRIPTION_KEY: ${AZURE_MAPS_SUBSCRIPTION_KEY}
|
||||||
|
AZURE_TENANT_ID: ${AZURE_TENANT_ID}
|
||||||
|
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
|
||||||
|
AZURE_CLIENT_SECRET: ${AZURE_CLIENT_SECRET}
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${DB_DATABASE}
|
||||||
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
networks:
|
||||||
|
- lcc-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
lcc-network:
|
||||||
|
driver: bridge
|
||||||
20
dockerfile
Normal file
20
dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
FROM node:20-alpine AS frontend-build
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY src/frontend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY src/frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM maven:3.9-eclipse-temurin-23 AS backend-build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pom.xml ./
|
||||||
|
COPY src ./src
|
||||||
|
# copy frontend
|
||||||
|
COPY --from=frontend-build /app/frontend/dist ./src/main/resources/static
|
||||||
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
|
FROM eclipse-temurin:23-jre-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=backend-build /app/target/*.jar app.jar
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
50
pom.xml
50
pom.xml
|
|
@ -174,56 +174,6 @@
|
||||||
</argLine>
|
</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<version>3.1.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>npm build the vue app</id>
|
|
||||||
<phase>generate-resources</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>exec</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<workingDirectory>src/frontend</workingDirectory>
|
|
||||||
<executable>npm</executable>
|
|
||||||
<arguments>
|
|
||||||
<argument>run</argument>
|
|
||||||
<argument>build</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.microsoft.azure</groupId>
|
|
||||||
<artifactId>azure-container-apps-maven-plugin</artifactId>
|
|
||||||
<version>0.1.0</version>
|
|
||||||
<configuration>
|
|
||||||
<subscriptionId>your-subscription-id</subscriptionId>
|
|
||||||
<resourceGroup>your-resource-group</resourceGroup>
|
|
||||||
<appEnvironmentName>your-app-environment-name</appEnvironmentName>
|
|
||||||
<region>your-region</region>
|
|
||||||
<appName>lcc</appName>
|
|
||||||
<containers>
|
|
||||||
<container>
|
|
||||||
<type>code</type>
|
|
||||||
<directory>${project.basedir}</directory>
|
|
||||||
</container>
|
|
||||||
</containers>
|
|
||||||
<ingress>
|
|
||||||
<external>true</external>
|
|
||||||
<targetPort>8080</targetPort>
|
|
||||||
</ingress>
|
|
||||||
<scale>
|
|
||||||
<minReplicas>0</minReplicas>
|
|
||||||
<maxReplicas>10</maxReplicas>
|
|
||||||
</scale>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="report-content-row" v-if="((report.costs.air_freight_cost ?? null) !== null)">
|
<div class="report-content-row" v-if="((report.costs.air_freight_cost ?? null) !== null)">
|
||||||
<div>Airfreight costs</div>
|
<div>Air freight costs</div>
|
||||||
<div class="report-content-data-cell">{{ report.costs.air_freight_cost.total.toFixed(2) }}</div>
|
<div class="report-content-data-cell">{{ report.costs.air_freight_cost.total.toFixed(2) }}</div>
|
||||||
<div class="report-content-data-cell">{{ (report.costs.air_freight_cost.percentage * 100).toFixed(2) }}</div>
|
<div class="report-content-data-cell">{{ (report.costs.air_freight_cost.percentage * 100).toFixed(2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
base: '/',
|
base: '/',
|
||||||
build: {
|
build: {
|
||||||
outDir: '../../src/main/resources/static',
|
outDir: 'dist',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
assetsDir: 'assets',
|
assetsDir: 'assets',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package de.avatic.lcc.controller.token;
|
package de.avatic.lcc.controller.token;
|
||||||
|
|
||||||
import de.avatic.lcc.dto.users.AppDTO;
|
|
||||||
import de.avatic.lcc.model.db.users.App;
|
import de.avatic.lcc.model.db.users.App;
|
||||||
import de.avatic.lcc.model.db.users.Group;
|
|
||||||
import de.avatic.lcc.repositories.users.JwtTokenService;
|
import de.avatic.lcc.repositories.users.JwtTokenService;
|
||||||
import de.avatic.lcc.service.apps.AppsService;
|
import de.avatic.lcc.service.apps.AppsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
@ -14,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/oauth2/token")
|
@RequestMapping("/oauth2/token")
|
||||||
|
|
@ -35,26 +33,25 @@ public class TokenController {
|
||||||
@RequestParam("client_secret") String clientSecret,
|
@RequestParam("client_secret") String clientSecret,
|
||||||
@RequestParam(value = "scope", required = false) String scope) {
|
@RequestParam(value = "scope", required = false) String scope) {
|
||||||
|
|
||||||
|
long expiration = 3600;
|
||||||
|
|
||||||
if (!"client_credentials".equals(grantType)) {
|
if (!"client_credentials".equals(grantType)) {
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
.body(Map.of("error", "unsupported_grant_type"));
|
.body(Map.of("error", "unsupported_grant_type"));
|
||||||
}
|
}
|
||||||
|
|
||||||
App app = appService.validateApp(clientId, clientSecret);
|
Optional<App> app = appService.validateApp(clientId, clientSecret);
|
||||||
if (app == null) {
|
if (app.isEmpty()) {
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||||
.body(Map.of("error", "invalid_client"));
|
.body(Map.of("error", "invalid_client"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = jwtTokenService.createApplicationToken(
|
String token = jwtTokenService.createApplicationToken(app.get(), expiration);
|
||||||
clientId,
|
|
||||||
app.getGroups().stream().map(Group::getName).toList()
|
|
||||||
);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
return ResponseEntity.ok(Map.of(
|
||||||
"access_token", token,
|
"access_token", token,
|
||||||
"token_type", "Bearer",
|
"token_type", "Bearer",
|
||||||
"expires_in", 3600
|
"expires_in", expiration
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package de.avatic.lcc.repositories.users;
|
package de.avatic.lcc.repositories.users;
|
||||||
|
|
||||||
|
import de.avatic.lcc.model.db.users.App;
|
||||||
|
import de.avatic.lcc.model.db.users.Group;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
|
@ -24,17 +26,17 @@ public class JwtTokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String createApplicationToken(String clientId, List<String> groups) {
|
public String createApplicationToken(App app, long expiration) {
|
||||||
long expirationMs = 3600000;
|
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.issuer(baseUrl)
|
.issuer(baseUrl)
|
||||||
.subject(clientId)
|
.subject(app.getClientId())
|
||||||
.audience().add(baseUrl + "/api").and()
|
.audience().add(baseUrl + "/api").and()
|
||||||
.issuedAt(new Date())
|
.issuedAt(new Date())
|
||||||
.expiration(new Date(System.currentTimeMillis() + expirationMs))
|
.expiration(new Date(System.currentTimeMillis() + (expiration*1000)))
|
||||||
.claim("client_id", clientId)
|
.claim("client_id", app.getId())
|
||||||
.claim("groups", groups)
|
.claim("groups", app.getGroups().stream().map(Group::getName).toList())
|
||||||
.claim("token_type", "ext_app")
|
.claim("token_type", "ext_app")
|
||||||
.signWith(signingKey)
|
.signWith(signingKey)
|
||||||
.compact();
|
.compact();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AppsService {
|
public class AppsService {
|
||||||
|
|
@ -39,15 +40,15 @@ public class AppsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public App validateApp(String clientId, String clientSecret) {
|
public Optional<App> validateApp(String clientId, String clientSecret) {
|
||||||
var app = appRepository.getByClientId(clientId);
|
var app = appRepository.getByClientId(clientId);
|
||||||
|
|
||||||
if (app.isPresent() &&
|
if (app.isPresent() &&
|
||||||
passwordEncoder.matches(clientSecret, app.get().getClientSecret())) {
|
passwordEncoder.matches(clientSecret, app.get().getClientSecret())) {
|
||||||
return app.get();
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Optional.empty();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ public class ReportingService {
|
||||||
var periodId = tuple.get().periodId();
|
var periodId = tuple.get().periodId();
|
||||||
var setId = tuple.get().propertySetId();
|
var setId = tuple.get().propertySetId();
|
||||||
|
|
||||||
|
//TODO check user node id and node id for null
|
||||||
|
|
||||||
var jobs = new ArrayList<>(nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, setId, nodeId, materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
var jobs = new ArrayList<>(nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, setId, nodeId, materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
||||||
jobs.addAll(userNodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValidUserNodeId(periodId, setId,nodeId ,materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
jobs.addAll(userNodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValidUserNodeId(periodId, setId,nodeId ,materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
spring.config.import=classpath:env.properties
|
spring.profiles.active=${SPRING_PROFILES_ACTIVE}
|
||||||
spring.application.name=lcc
|
spring.application.name=lcc
|
||||||
spring.datasource.url=jdbc:mysql://localhost:3306/${DB_DATABASE}
|
spring.datasource.url=jdbc:mysql://localhost:3306/${DB_DATABASE}
|
||||||
spring.datasource.username=${DB_USER}
|
spring.datasource.username=${DB_USER}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue