Compare commits

..

15 Commits

Author SHA1 Message Date
siujamo f6255d396c Merge branch 'develop' into 'main'
fix: revert CI branch guard, keep --provenance removal

See merge request onixbyte/delta-force-guide-server!5
2026-05-26 11:00:16 +08:00
siujamo 26cea1db82 chore: add gradle.properties to .gitignore 2026-05-26 10:58:24 +08:00
siujamo 1f42921689 fix: revert CI branch guard, keep --provenance removal
Revert the main-branch-only rule since $CI_COMMIT_BRANCH is unset for tag
pipelines. The --provenance=false removal remains as the fix for legacy builder.
2026-05-26 10:56:51 +08:00
siujamo 8f102f54c7 Merge branch 'develop' into 'main'
fix: restrict GitLab CI to main branch and drop --provenance flag

See merge request onixbyte/delta-force-guide-server!4
2026-05-26 10:48:38 +08:00
siujamo 9fe292963c fix: restrict GitLab CI to main branch and drop --provenance flag
- Add main branch guard to pipeline rules
- Remove unsupported --provenance=false flag incompatible with legacy builder
2026-05-26 10:47:28 +08:00
siujamo d3681916b2 Merge branch 'develop' into 'main'
v1.3.1: Versioning and Daily Password

See merge request onixbyte/delta-force-guide-server!3
2026-05-26 10:43:12 +08:00
siujamo d27f6455d8 docs: add README, LICENCE, and production config template
- Add MIT LICENCE file
- Add comprehensive README with tech stack, API overview, and architecture docs
- Add example production configuration template
- Remove gradle.properties in favour of build-time version injection
@
2026-05-26 10:42:01 +08:00
siujamo 0671937ecd feat: add versioning entrypoint 2026-05-26 10:18:27 +08:00
siujamo e2a40795c5 chore: opt-in to Node.js 24 for GitHub Actions to clear deprecation warning
Set the environment variable FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 to true
to force the workflow and runner to execute all JavaScript actions using
Node.js 24. This resolves the future deprecation warning for Node.js 20.
2026-05-25 16:01:19 +08:00
siujamo a8ff1cabad chore: rewrite GitHub Actions to build, publish release JAR, and push image to GHCR
Update build-and-deploy.yml workflow to:
1. Run single job 'build-and-release' to bypass artifact transfers.
2. Build JAR with -PartefactVersion parameter.
3. Upload the compiled JAR asset directly into GitHub Releases.
4. Build and push the Docker image directly to GitHub Container Registry (ghcr.io).
2026-05-25 15:52:54 +08:00
siujamo e4dca61f98 chore: merge CI stages into a single release job to optimize speed
Merge build, package, and deploy stages into a single 'release' job. By building
the jar and running docker commands in the same container using local docker socket,
we completely bypass the need for GitLab artifact uploading/downloading. This significantly
reduces network overhead and speeds up release deployment.
2026-05-25 15:43:23 +08:00
siujamo e7da3a76b7 ci: recover artefact uploading 2026-05-25 14:45:26 +08:00
siujamo 5cea825bc0 chore: remove gitlab artifacts to avoid slow uploads
Remove artifacts uploading from the build stage. Since we use a shared
docker socket on the same runner host, the package stage can access the
locally built jar file directly without needing gitlab coordinator upload/download.
2026-05-25 14:39:33 +08:00
siujamo bd2748e25c fix: disable provenance in docker build to fix GitLab Registry 0B display
Add `--provenance=false` flag to `docker build` command. This stops Docker BuildKit
from generating OCI Referrers/attestations, which are not correctly parsed by GitLab
Container Registry and cause the UI to display 0B size and "missing manifest digest" errors.
2026-05-25 13:58:14 +08:00
siujamo 0f1093774f fix: use GitLab predefined environment variables for container registry
Replace custom registry variables with GitLab's predefined CI_REGISTRY,
CI_REGISTRY_IMAGE, and CI_REGISTRY_USER to ensure the built-in CI_JOB_TOKEN
has correct push permissions.
2026-05-25 11:47:22 +08:00
12 changed files with 356 additions and 111 deletions
+34 -75
View File
@@ -6,15 +6,17 @@ on:
env:
APP_NAME: delta-force-guide-server
IMAGE_REGISTRY: ${{ vars.GITLAB_REGISTRY }}
IMAGE_NAME: ${{ vars.GITLAB_IMAGE_NAME }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ================================================================
# Job 1 — Package: build the JAR with Gradle
# Single Job: Build, Upload JAR to Release, and Push to GHCR
# ================================================================
package:
build-and-release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
@@ -28,54 +30,47 @@ jobs:
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
# 使用 Release Tag 做为 Gradle 属性传入
- name: Build with Gradle
run: ./gradlew build
- name: Upload JAR artifact
uses: actions/upload-artifact@v4
with:
name: app-jar
path: build/libs/delta-force-guide-server-*.jar
retention-days: 1
# ================================================================
# Job 2 — Build & push Docker image to GitHub Container Registry
# ================================================================
build-and-push:
needs: package
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download JAR artifact
uses: actions/download-artifact@v4
with:
name: app-jar
path: build/libs
run: ./gradlew bootJar -x test -PartefactVersion="${{ github.event.release.tag_name }}"
- name: Resolve JAR file path
id: jar
run: echo "file=$(ls build/libs/delta-force-guide-server-*.jar | head -1)" >> "$GITHUB_OUTPUT"
run: |
JAR_PATH=$(find build/libs -name '*.jar' | head -1)
echo "file=$JAR_PATH" >> "$GITHUB_OUTPUT"
# 上传 JAR 包到 GitHub Release 中
- name: Upload JAR to GitHub Release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.jar.outputs.file }}
asset_name: ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.jar
tag: ${{ github.event.release.tag_name }}
overwrite: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitLab Container Registry
# 登录到 GitHub Container Registry (GHCR)
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ vars.GITLAB_REGISTRY_USER }}
password: ${{ secrets.GITLAB_REGISTRY_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# 镜像打标签准备
- name: Generate image tags
id: meta
run: |
echo "version=${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
echo "latest=${{ env.IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT"
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
REPO_LC=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')
echo "tag_version=ghcr.io/$OWNER_LC/$REPO_LC:${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
echo "tag_latest=ghcr.io/$OWNER_LC/$REPO_LC:latest" >> "$GITHUB_OUTPUT"
# 构建并上传镜像到 GHCR
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
@@ -84,44 +79,8 @@ jobs:
build-args: JAR_FILE=${{ steps.jar.outputs.file }}
push: true
tags: |
${{ steps.meta.outputs.version }}
${{ steps.meta.outputs.latest }}
${{ steps.meta.outputs.tag_version }}
${{ steps.meta.outputs.tag_latest }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ================================================================
# Job 3 — Deploy on the target server via SSH
# ================================================================
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
set -e
echo '=== Pulling image ==='
echo '${{ secrets.GITLAB_REGISTRY_PASSWORD }}' | docker login ${{ env.IMAGE_REGISTRY }} \
-u ${{ vars.GITLAB_REGISTRY_USER }} --password-stdin
docker pull ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
echo '=== Stopping old container ==='
docker stop ${{ env.APP_NAME }} || true
docker rm ${{ env.APP_NAME }} || true
echo '=== Starting new container ==='
docker run -d \
--name ${{ env.APP_NAME }} \
--restart unless-stopped \
-p ${DEPLOY_PORT:-8080}:8080 \
${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
echo '=== Cleaning up old images ==='
docker image prune -f
echo '=== Deployment complete ==='
+2
View File
@@ -155,3 +155,5 @@ gradle-app.setting
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
gradle.properties
+10 -35
View File
@@ -1,16 +1,12 @@
variables:
REGISTRY: registry.onixbyte.cn
IMAGE_NAME: delta-force-guide
GRADLE_OPTS: -Dorg.gradle.daemon=false
DOCKER_HOST: unix:///var/run/docker.sock
stages:
- build
- package
- deploy
- release
build:
stage: build
release:
stage: release
image: amazoncorretto:21-alpine
cache:
key: gradle
@@ -19,41 +15,20 @@ build:
- .gradle/caches
before_script:
- chmod +x gradlew
- apk add --no-cache docker-cli
script:
- ./gradlew bootJar -x test -PartefactVersion="$CI_COMMIT_TAG"
artifacts:
paths:
- build/libs/*.jar
expire_in: 30 min
rules:
- if: $CI_COMMIT_TAG
package:
stage: package
image: docker:27
needs:
- build
script:
- JAR_FILE=$(find build/libs -name '*.jar' | head -1)
- echo "Building Docker image for tag $CI_COMMIT_TAG with JAR $JAR_FILE"
- docker build
-f Dockerfile.ci
--build-arg JAR_FILE="$JAR_FILE"
-t "$REGISTRY/$IMAGE_NAME:$CI_COMMIT_TAG"
-t "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
.
- docker tag "$REGISTRY/$IMAGE_NAME:$CI_COMMIT_TAG" "$REGISTRY/$IMAGE_NAME:latest"
rules:
- if: $CI_COMMIT_TAG
deploy:
stage: deploy
image: docker:27
needs:
- package
script:
- echo "Pushing image $REGISTRY/$IMAGE_NAME:$CI_COMMIT_TAG"
- docker login "$REGISTRY" -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
- docker push "$REGISTRY/$IMAGE_NAME:$CI_COMMIT_TAG"
- docker push "$REGISTRY/$IMAGE_NAME:latest"
- docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" "$CI_REGISTRY_IMAGE:latest"
- echo "Pushing image $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
- docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
- docker push "$CI_REGISTRY_IMAGE:latest"
rules:
- if: $CI_COMMIT_TAG
+21
View File
@@ -0,0 +1,21 @@
MIT Licence
Copyright (c) 2026 OnixByte
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+150
View File
@@ -0,0 +1,150 @@
# Delta Force Guide Server
REST API backend for managing **Delta Force** game firearm builds and modifications. Provides endpoints to browse, create, and share weapon configurations including attachments, tuning setups, tags, and build reviews.
## Tech Stack
| Layer | Technology |
|-----------|------------------------------------------------------------|
| Language | Java 21 (Amazon Corretto) |
| Framework | Spring Boot 3.x (Web, Security, Data JPA, Cache, Actuator) |
| Build | Gradle (Kotlin DSL) |
| Database | PostgreSQL (Flyway migrations, JSONB columns) |
| Cache | Redis (2-hour TTL) |
| Auth | Custom JWT via httpOnly cookies + BCrypt |
| API Docs | springdoc-openapi (Swagger UI, dev profile only) |
| Container | Multi-stage Docker image (Amazon Corretto 21 → Alpine) |
| CI/CD | GitHub Actions — builds on release publish |
## Quick Start
### Prerequisites
- JDK 21 (Amazon Corretto recommended)
- PostgreSQL
- Redis
### Configure
Copy and customise the development config:
```bash
cp config/application-prod.yaml.example \
config/application-dev.yaml
```
Fill in your datasource and Redis connection details, then place the production config at `config/`, then enable it by environment variable `SPRING_PROFILES_ACTIVE`.
### Build
```bash
# Compile (skip tests)
./gradlew build -x test
# Run all tests
./gradlew test
# Build executable JAR
./gradlew bootJar
```
### Run
```bash
SPRING_PROFILES_ACTIVE=prod java -jar build/libs/delta-force-guide-server-$version.jar
```
Swagger UI is available at `http://localhost:8080/swagger-ui.html` when the `dev` profile is active.
## Data Model
- **Firearm** — weapon base stats (name, type, level, calibre, fire rate, armour/body damage, review)
- **Modification** — a build attached to a firearm, with name, code, tags (JSONB), accessories including nested tuning objects (JSONB), author notes, and video links
- **App User** — registered user (username, email) with hashed credentials via BCrypt
Tags and accessories are stored as PostgreSQL JSONB columns using [Hypersistence Utils](https://github.com/vladmihalcea/hypersistence-utils), enabling flexible per-build metadata and filtering with the `@>` operator.
## Architecture
```
Controller → Service → Manager → Repository / Mapper
(HTTP) (logic) (@Transactional) (data access)
```
The call chain is strictly enforced — skipping layers is not permitted. All request/response objects are Java records with static `from()` factory methods for entity-to-DTO conversion.
## Docker
```bash
# Build image
docker pull registry.onixbyte.cn/onixbyte/delta-force-guide-server:latest
# Run container
docker run -p 8080:8080 \
-v /path/to/config:/app/config \
-e SPRING_PROFILES_ACTIVE=$your_active_profiles
delta-force-guide-server
```
Pre-built images are published to **Self-hosted GitLab Container Registry** (`registry.onixbyte.cn/onixbyte/delta-force-guide-server`) on every release.
## CI/CD
GitLab CI triggers on **tags**. The pipeline:
1. Builds the boot JAR with the release tag as the version
2. Uploads the JAR as a release asset
3. Builds and pushes a multi-arch Docker image to GHCR tagged with both `:latest` and `:<version>`
No tests are run in CI by design — tests are expected to pass locally before a release is cut.
## Profiles
| Profile | Purpose |
|---------|-----------------------------------------------------------------------|
| `dev` | Enables Swagger UI, connects to dev DB/Redis at `dfguide.onixbyte.cn` |
| default | Production mode, no Swagger, uses production datasource |
## Project Structure
```
src/main/java/com/onixbyte/deltaforceguide/
├── client/ External service HTTP clients
├── config/ Spring bean definitions (Security, CORS, Cache, Jackson, MyBatis)
├── controller/ REST controllers
├── domain/
│ ├── converter/ JPA attribute converters
│ ├── dto/ Request/response records
│ └── entity/ JPA entities
├── enumeration/ Enums (FirearmType)
├── exception/ Custom BizException with HTTP status mapping
├── filter/ TokenAuthenticationFilter (JWT through OncePerRequestFilter)
├── manager/ @Transactional wrappers around repositories
├── mapper/ MyBatis mapper interfaces (reserved for future use)
├── properties/ @ConfigurationProperties records
├── repository/ Spring Data JPA repositories
├── security/
│ ├── authentication/ Custom UsernamePasswordAuthentication
│ └── provider/ UsernamePasswordAuthenticationProvider
├── service/ Business logic
├── shared/ Constants and utility classes
└── utils/ General-purpose helpers
```
## Versioning
The application exposes its version at `/versions` (GET).
## Licence
This project is licensed under the [MIT Licence](LICENCE). Copyright &copy; 2026 OnixByte.
+55
View File
@@ -0,0 +1,55 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/dfguide_dev
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
data:
redis:
host: localhost
port: 6379
database: 0
# password: 6hLFVqfGPviTYukn # Uncomment if password is necessary
logging:
pattern:
# dateformat: dd MMM yyyy HH:mm:ss.SSS # Modify this for custom date format.
app:
common:
version: 1.3.0.8-dev # Application version, you can change to any version you like, used for communication with frontend.
cors:
allowed-origins: # Cross-origin allowed origins
- "http://localhost:5173" # Dev server for vite.
- "http://localhost:4173" # Preview server for vite.
allow-credentials: true # Must be set to `true` since we are using cookie. You can change it only when you have modified how this application executing authentication.
allow-private-network: false
allowed-headers:
- "Content-Type"
- "Authorization"
allowed-methods:
- GET
- POST
- PUT
- PATCH
- DELETE
exposed-headers:
- "Content-Type"
- "Authorization"
max-age: PT2H
jwt:
issuer: dfguide.local # Issuer host
secret: qwertyuiopasdfghjklzxcvbnm123456 # JWT singing secret, a 32-byte long or longer string is recommended
valid-time: PT2H # JWT valid duration
cookie: # Cookie settings.
http-only: true
secure: false
same-site: lax
path: '/'
max-age: PT2H
springdoc:
api-docs:
enabled: true # Set to `false` if you do not need api docs (recommended in production mode).
swagger-ui:
enabled: true # Set to `false` if you do not need swagger ui (recommended in production mode).
-1
View File
@@ -1 +0,0 @@
artefactVersion = 1.2.0
@@ -0,0 +1,10 @@
package com.onixbyte.deltaforceguide.config;
import com.onixbyte.deltaforceguide.properties.AppProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
}
@@ -0,0 +1,24 @@
package com.onixbyte.deltaforceguide.controller;
import com.onixbyte.deltaforceguide.service.AppService;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/versions")
public class VersionController {
private final AppService appService;
public VersionController(AppService appService) {
this.appService = appService;
}
@Operation(description = "获取当前应用版本号")
@GetMapping
public String getVersion() {
return appService.getVersion();
}
}
@@ -0,0 +1,23 @@
package com.onixbyte.deltaforceguide.manager;
import com.onixbyte.deltaforceguide.properties.AppProperties;
import org.springframework.stereotype.Component;
@Component
public class AppManager {
private final AppProperties appProperties;
public AppManager(AppProperties appProperties) {
this.appProperties = appProperties;
}
/**
* Retrieves the application version.
*
* @return the version string of this application
*/
public String getVersion() {
return appProperties.version();
}
}
@@ -0,0 +1,9 @@
package com.onixbyte.deltaforceguide.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app.common")
public record AppProperties(
String version
) {
}
@@ -0,0 +1,18 @@
package com.onixbyte.deltaforceguide.service;
import com.onixbyte.deltaforceguide.manager.AppManager;
import org.springframework.stereotype.Service;
@Service
public class AppService {
private final AppManager appManager;
public AppService(AppManager appManager) {
this.appManager = appManager;
}
public String getVersion() {
return appManager.getVersion();
}
}