T-1: initialize monorepo structure with backend and frontend scaffolds
Backend: Spring Boot 3.5.11 on Java 25, Maven with wrapper, hexagonal architecture package layout (domain/application/adapter/config), health endpoint with integration test. Originally planned for Spring Boot 4.0 but pivoted due to massive package reorganization in 4.0 (see addenda in research and plan docs). Frontend: Vue 3 scaffolded via create-vue with TypeScript, Vue Router, Vitest, ESLint, Prettier. Pivoted from Svelte due to ecosystem maturity concerns (broken router ecosystem for Svelte 5). Also: extended .gitignore for Java/Maven and Node/Vue artifacts, updated CLAUDE.md with tech stack, build commands, agent documentation sections, and document integrity rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
45
.gitignore
vendored
45
.gitignore
vendored
@@ -9,3 +9,48 @@ Thumbs.db
|
|||||||
|
|
||||||
# Claude Code (machine-local)
|
# Claude Code (machine-local)
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
.rodney/
|
||||||
|
.agent-tests/
|
||||||
|
|
||||||
|
# Java/Maven
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
*.nar
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
.factorypath
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Node.js/Vue/Vite
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Editor swap files
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
.#*
|
||||||
|
|||||||
28
CLAUDE.md
28
CLAUDE.md
@@ -20,6 +20,7 @@ These are the non-negotiable principles of this project. Every decision — arch
|
|||||||
- Always write tests before implementation (TDD). Red → Green → Refactor.
|
- Always write tests before implementation (TDD). Red → Green → Refactor.
|
||||||
- Refactoring is permitted freely as long as it does not alter the fundamental architecture.
|
- Refactoring is permitted freely as long as it does not alter the fundamental architecture.
|
||||||
- No vibe coding. Every line of code must be intentional and traceable to a requirement.
|
- No vibe coding. Every line of code must be intentional and traceable to a requirement.
|
||||||
|
- Document integrity: when a decision is revised (pivot), add an addendum with rationale — never rewrite or delete the original decision. Traceability over tidiness.
|
||||||
|
|
||||||
### Privacy
|
### Privacy
|
||||||
|
|
||||||
@@ -54,6 +55,33 @@ These are the non-negotiable principles of this project. Every decision — arch
|
|||||||
- A docker-compose example in the README is sufficient.
|
- A docker-compose example in the README is sufficient.
|
||||||
- Documentation lives in the README. No wiki, no elaborate docs site.
|
- Documentation lives in the README. No wiki, no elaborate docs site.
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
|
||||||
|
- **Backend:** Java 25 (LTS, installed via SDKMAN), Spring Boot 3.5.x, Maven with wrapper (`./mvnw`)
|
||||||
|
- **Frontend:** Vue 3, TypeScript, Vue Router, Vite, Vitest, ESLint, Prettier
|
||||||
|
- **Node.js:** 24 LTS (for Docker/CI; development tolerates newer versions)
|
||||||
|
- **Base package:** `de.fete`, hexagonal architecture (single Maven module, package-level separation)
|
||||||
|
- **No Pinia** — Composition API (`ref`/`reactive`) + localStorage is sufficient
|
||||||
|
- **No JPA until T-4** — added when database infrastructure is ready
|
||||||
|
|
||||||
|
### Build Commands
|
||||||
|
|
||||||
|
| What | Command |
|
||||||
|
|------|---------|
|
||||||
|
| Backend test | `cd backend && ./mvnw test` |
|
||||||
|
| Backend run | `cd backend && ./mvnw spring-boot:run` |
|
||||||
|
| Frontend install | `cd frontend && npm install` |
|
||||||
|
| Frontend build | `cd frontend && npm run build` |
|
||||||
|
| Frontend test | `cd frontend && npm run test:unit` |
|
||||||
|
| Frontend dev | `cd frontend && npm run dev` |
|
||||||
|
|
||||||
|
### Agent Documentation
|
||||||
|
|
||||||
|
- Research reports: `docs/agents/research/`
|
||||||
|
- Implementation plans: `docs/agents/plan/`
|
||||||
|
- Agent test reports (browser verification): `.agent-tests/` (gitignored)
|
||||||
|
- Use the `browser-interactive-testing` skill (rodney/showboat) for visual verification — this is an agent tool, not manual work.
|
||||||
|
|
||||||
### Ralph Loops
|
### Ralph Loops
|
||||||
|
|
||||||
- Autonomous work is done via Ralph Loops. See [.claude/rules/ralph-loops.md](.claude/rules/ralph-loops.md) for documentation.
|
- Autonomous work is done via Ralph Loops. See [.claude/rules/ralph-loops.md](.claude/rules/ralph-loops.md) for documentation.
|
||||||
|
|||||||
2
Ideen.md
2
Ideen.md
@@ -75,6 +75,6 @@ Die folgenden Punkte wurden in Diskussionen bereits geklärt und sind verbindlic
|
|||||||
* App wird als einzelner Docker-Container ausgeliefert, verbindet sich per Konfiguration (env variable) mit der externen Postgres-Instanz
|
* App wird als einzelner Docker-Container ausgeliefert, verbindet sich per Konfiguration (env variable) mit der externen Postgres-Instanz
|
||||||
* Techstack:
|
* Techstack:
|
||||||
* Backend: Java (neuste LTS Version), Spring Boot, Maven, Hexagonal/Onion Architecture
|
* Backend: Java (neuste LTS Version), Spring Boot, Maven, Hexagonal/Onion Architecture
|
||||||
* Frontend: Svelte (mit Vite als Bundler)
|
* Frontend: Vue 3 (mit Vite als Bundler, TypeScript, Vue Router)
|
||||||
* Architekturentscheidungen die NOCH NICHT getroffen wurden (hier darf nichts eigenmächtig entschieden werden!):
|
* Architekturentscheidungen die NOCH NICHT getroffen wurden (hier darf nichts eigenmächtig entschieden werden!):
|
||||||
* (derzeit keine offenen Architekturentscheidungen)
|
* (derzeit keine offenen Architekturentscheidungen)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ A privacy-focused, self-hostable web app for event announcements and RSVPs. An a
|
|||||||
| Layer | Technology |
|
| Layer | Technology |
|
||||||
|--------------|------------------------------------------|
|
|--------------|------------------------------------------|
|
||||||
| Backend | Java (latest LTS), Spring Boot, Maven |
|
| Backend | Java (latest LTS), Spring Boot, Maven |
|
||||||
| Frontend | Svelte, Vite |
|
| Frontend | Vue 3, Vite, TypeScript |
|
||||||
| Database | PostgreSQL (external, not bundled) |
|
| Database | PostgreSQL (external, not bundled) |
|
||||||
| Architecture | SPA + RESTful API, hexagonal/onion backend |
|
| Architecture | SPA + RESTful API, hexagonal/onion backend |
|
||||||
| Deployment | Single Docker container |
|
| Deployment | Single Docker container |
|
||||||
@@ -104,7 +104,7 @@ If you use the Unsplash header image feature, mount a persistent volume for stor
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Java (latest LTS) + Maven
|
- Java (latest LTS) + Maven
|
||||||
- Node.js + npm
|
- Node.js (latest LTS) + npm
|
||||||
- PostgreSQL (or Docker for Testcontainers)
|
- PostgreSQL (or Docker for Testcontainers)
|
||||||
|
|
||||||
### Project structure
|
### Project structure
|
||||||
@@ -112,7 +112,7 @@ If you use the Unsplash header image feature, mount a persistent volume for stor
|
|||||||
```
|
```
|
||||||
fete/
|
fete/
|
||||||
├── backend/ # Spring Boot application (Maven)
|
├── backend/ # Spring Boot application (Maven)
|
||||||
├── frontend/ # Svelte SPA (Vite)
|
├── frontend/ # Vue 3 SPA (Vite, TypeScript)
|
||||||
├── spec/ # User stories, personas, implementation phases
|
├── spec/ # User stories, personas, implementation phases
|
||||||
├── Dockerfile # Multi-stage build for production
|
├── Dockerfile # Multi-stage build for production
|
||||||
└── CLAUDE.md # Project statutes and AI agent instructions
|
└── CLAUDE.md # Project statutes and AI agent instructions
|
||||||
|
|||||||
3
backend/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
3
backend/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
wrapperVersion=3.3.4
|
||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
|
||||||
295
backend/mvnw
vendored
Executable file
295
backend/mvnw
vendored
Executable file
@@ -0,0 +1,295 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -euf
|
||||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||||
|
|
||||||
|
# OS specific support.
|
||||||
|
native_path() { printf %s\\n "$1"; }
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN* | MINGW*)
|
||||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||||
|
native_path() { cygpath --path --windows "$1"; }
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# set JAVACMD and JAVACCMD
|
||||||
|
set_java_home() {
|
||||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||||
|
if [ -n "${JAVA_HOME-}" ]; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v java
|
||||||
|
)" || :
|
||||||
|
JAVACCMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v javac
|
||||||
|
)" || :
|
||||||
|
|
||||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# hash string like Java String::hashCode
|
||||||
|
hash_string() {
|
||||||
|
str="${1:-}" h=0
|
||||||
|
while [ -n "$str" ]; do
|
||||||
|
char="${str%"${str#?}"}"
|
||||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||||
|
str="${str#?}"
|
||||||
|
done
|
||||||
|
printf %x\\n $h
|
||||||
|
}
|
||||||
|
|
||||||
|
verbose() { :; }
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf %s\\n "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
# MWRAPPER-139:
|
||||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||||
|
# Needed for removing poorly interpreted newline sequences when running in more
|
||||||
|
# exotic environments such as mingw bash on Windows.
|
||||||
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "${key-}" in
|
||||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
|
esac
|
||||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
|
case "${distributionUrl##*/}" in
|
||||||
|
maven-mvnd-*bin.*)
|
||||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||||
|
*)
|
||||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||||
|
distributionPlatform=linux-amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
|
;;
|
||||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||||
|
|
||||||
|
exec_maven() {
|
||||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -d "$MAVEN_HOME" ]; then
|
||||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
exec_maven "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${distributionUrl-}" in
|
||||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||||
|
trap clean HUP INT TERM EXIT
|
||||||
|
else
|
||||||
|
die "cannot create temp dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
verbose "Downloading from: $distributionUrl"
|
||||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
# select .zip or .tar.gz
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# verbose opt
|
||||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||||
|
|
||||||
|
# normalize http auth
|
||||||
|
case "${MVNW_PASSWORD:+has-password}" in
|
||||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||||
|
verbose "Found wget ... using wget"
|
||||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||||
|
verbose "Found curl ... using curl"
|
||||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||||
|
elif set_java_home; then
|
||||||
|
verbose "Falling back to use Java to download"
|
||||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
cat >"$javaSource" <<-END
|
||||||
|
public class Downloader extends java.net.Authenticator
|
||||||
|
{
|
||||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
setDefault( new Downloader() );
|
||||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END
|
||||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||||
|
verbose " - Compiling Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||||
|
verbose " - Running Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
if [ -n "${distributionSha256Sum-}" ]; then
|
||||||
|
distributionSha256Result=false
|
||||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
elif command -v sha256sum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $distributionSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
if command -v unzip >/dev/null; then
|
||||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||||
|
else
|
||||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
actualDistributionDir=""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$distributionUrlNameMain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
# enable globbing to iterate over items
|
||||||
|
set +f
|
||||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$(basename "$dir")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||||
|
die "Could not find Maven distribution directory in extracted archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
||||||
189
backend/mvnw.cmd
vendored
Normal file
189
backend/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<# : batch portion
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
@REM or more contributor license agreements. See the NOTICE file
|
||||||
|
@REM distributed with this work for additional information
|
||||||
|
@REM regarding copyright ownership. The ASF licenses this file
|
||||||
|
@REM to you under the Apache License, Version 2.0 (the
|
||||||
|
@REM "License"); you may not use this file except in compliance
|
||||||
|
@REM with the License. You may obtain a copy of the License at
|
||||||
|
@REM
|
||||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@REM
|
||||||
|
@REM Unless required by applicable law or agreed to in writing,
|
||||||
|
@REM software distributed under the License is distributed on an
|
||||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
@REM KIND, either express or implied. See the License for the
|
||||||
|
@REM specific language governing permissions and limitations
|
||||||
|
@REM under the License.
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
@REM Apache Maven Wrapper startup batch script, version 3.3.4
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||||
|
@SET __MVNW_CMD__=
|
||||||
|
@SET __MVNW_ERROR__=
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||||
|
@SET PSModulePath=
|
||||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||||
|
)
|
||||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=
|
||||||
|
@SET __MVNW_ARG0_NAME__=
|
||||||
|
@SET MVNW_USERNAME=
|
||||||
|
@SET MVNW_PASSWORD=
|
||||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
|
@GOTO :EOF
|
||||||
|
: end batch / begin powershell #>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
if ($env:MVNW_VERBOSE -eq "true") {
|
||||||
|
$VerbosePreference = "Continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||||
|
if (!$distributionUrl) {
|
||||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||||
|
"maven-mvnd-*" {
|
||||||
|
$USE_MVND = $true
|
||||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||||
|
$MVN_CMD = "mvnd.cmd"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
$USE_MVND = $false
|
||||||
|
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
if ($env:MVNW_REPOURL) {
|
||||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
|
}
|
||||||
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_WRAPPER_DISTS = $null
|
||||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||||
|
} else {
|
||||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||||
|
trap {
|
||||||
|
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
Write-Verbose "Downloading from: $distributionUrl"
|
||||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
$webclient = New-Object System.Net.WebClient
|
||||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||||
|
if ($distributionSha256Sum) {
|
||||||
|
if ($USE_MVND) {
|
||||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||||
|
}
|
||||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
$actualDistributionDir = ""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||||
|
$actualDistributionDir = $distributionUrlNameMain
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||||
|
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||||
|
$actualDistributionDir = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||||
|
try {
|
||||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
|
} catch {
|
||||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||||
|
Write-Error "fail to move MAVEN_HOME"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
46
backend/pom.xml
Normal file
46
backend/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.5.11</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>de.fete</groupId>
|
||||||
|
<artifactId>fete-backend</artifactId>
|
||||||
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
|
<name>fete-backend</name>
|
||||||
|
<description>Privacy-focused event announcements and RSVPs</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>25</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
12
backend/src/main/java/de/fete/FeteApplication.java
Normal file
12
backend/src/main/java/de/fete/FeteApplication.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package de.fete;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class FeteApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(FeteApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package de.fete.adapter.in.web;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class HealthController {
|
||||||
|
|
||||||
|
@GetMapping("/health")
|
||||||
|
public Map<String, String> health() {
|
||||||
|
return Map.of("status", "ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Inbound web adapter: REST controllers, DTOs, and request/response mappers.
|
||||||
|
*
|
||||||
|
* <p>May depend on application and domain layers.
|
||||||
|
*/
|
||||||
|
package de.fete.adapter.in.web;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Outbound persistence adapter: JPA entities, Spring Data repositories, and mappers.
|
||||||
|
*
|
||||||
|
* <p>May depend on application and domain layers.
|
||||||
|
*/
|
||||||
|
package de.fete.adapter.out.persistence;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Application services: use case implementations.
|
||||||
|
*
|
||||||
|
* <p>May depend on domain only. Uses {@code @Service} for component scanning.
|
||||||
|
*/
|
||||||
|
package de.fete.application.service;
|
||||||
6
backend/src/main/java/de/fete/config/package-info.java
Normal file
6
backend/src/main/java/de/fete/config/package-info.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Configuration: Spring {@code @Configuration} and {@code @Bean} definitions.
|
||||||
|
*
|
||||||
|
* <p>Wiring layer — may depend on all other packages.
|
||||||
|
*/
|
||||||
|
package de.fete.config;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Domain model: entities and value objects.
|
||||||
|
*
|
||||||
|
* <p>Plain Java — no framework annotations allowed.
|
||||||
|
* This package has no dependencies on other packages.
|
||||||
|
*/
|
||||||
|
package de.fete.domain.model;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Driving ports: use case interfaces that the application layer implements.
|
||||||
|
*
|
||||||
|
* <p>Plain Java interfaces — no framework annotations allowed.
|
||||||
|
*/
|
||||||
|
package de.fete.domain.port.in;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Driven ports: repository and external service interfaces.
|
||||||
|
*
|
||||||
|
* <p>Plain Java interfaces — no framework annotations allowed.
|
||||||
|
*/
|
||||||
|
package de.fete.domain.port.out;
|
||||||
1
backend/src/main/resources/application.properties
Normal file
1
backend/src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
spring.application.name=fete
|
||||||
31
backend/src/test/java/de/fete/FeteApplicationTest.java
Normal file
31
backend/src/test/java/de/fete/FeteApplicationTest.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package de.fete;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
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.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
class FeteApplicationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
// Spring context starts successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void healthEndpointReturns200() throws Exception {
|
||||||
|
mockMvc.perform(get("/health"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.status").value("ok"));
|
||||||
|
}
|
||||||
|
}
|
||||||
179
docs/agents/plan/2026-03-04-t1-monorepo-setup.md
Normal file
179
docs/agents/plan/2026-03-04-t1-monorepo-setup.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Implementation Plan: T-1 — Initialize Monorepo Structure
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
This is the first setup task for the fete project — a privacy-focused, self-hostable PWA for event announcements and RSVPs. The repository currently contains only documentation, specs, and Ralph loop infrastructure. No application code exists yet.
|
||||||
|
|
||||||
|
The tech stack was decided during the research phase (see `docs/agents/research/2026-03-04-t1-monorepo-setup.md`):
|
||||||
|
- **Backend:** Java 25 (latest LTS), ~~Spring Boot 4.0~~ Spring Boot 3.5.11 (see addendum), Maven, hexagonal architecture, base package `de.fete`
|
||||||
|
- **Java installation:** Via SDKMAN! (`sdk install java 25-open`), no system-level JDK
|
||||||
|
- **Frontend:** Vue 3, Vite, TypeScript, Vue Router, Vitest
|
||||||
|
- **Node.js:** Latest LTS (24)
|
||||||
|
|
||||||
|
## Phase 1: Backend Scaffold
|
||||||
|
|
||||||
|
### 1.1 Create directory structure
|
||||||
|
|
||||||
|
Create `backend/` with the hexagonal architecture package layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── pom.xml
|
||||||
|
├── mvnw, mvnw.cmd
|
||||||
|
├── .mvn/wrapper/maven-wrapper.properties
|
||||||
|
└── src/
|
||||||
|
├── main/
|
||||||
|
│ ├── java/de/fete/
|
||||||
|
│ │ ├── FeteApplication.java
|
||||||
|
│ │ ├── domain/
|
||||||
|
│ │ │ ├── model/package-info.java
|
||||||
|
│ │ │ └── port/
|
||||||
|
│ │ │ ├── in/package-info.java
|
||||||
|
│ │ │ └── out/package-info.java
|
||||||
|
│ │ ├── application/
|
||||||
|
│ │ │ └── service/package-info.java
|
||||||
|
│ │ ├── adapter/
|
||||||
|
│ │ │ ├── in/web/package-info.java
|
||||||
|
│ │ │ └── out/persistence/package-info.java
|
||||||
|
│ │ └── config/package-info.java
|
||||||
|
│ └── resources/
|
||||||
|
│ └── application.properties
|
||||||
|
└── test/
|
||||||
|
└── java/de/fete/
|
||||||
|
└── FeteApplicationTest.java
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Create `pom.xml`
|
||||||
|
|
||||||
|
- Parent: `spring-boot-starter-parent` (latest 4.0.x, or fall back to 3.5.x if 4.0 is unavailable)
|
||||||
|
- GroupId: `de.fete`, ArtifactId: `fete-backend`
|
||||||
|
- Java version: 25
|
||||||
|
- Dependencies:
|
||||||
|
- `spring-boot-starter-web` (embedded Tomcat, Spring MVC)
|
||||||
|
- `spring-boot-starter-test` (scope: test)
|
||||||
|
- NO JPA yet (deferred to T-4)
|
||||||
|
|
||||||
|
### 1.3 Add Maven Wrapper
|
||||||
|
|
||||||
|
Generate via `mvn wrapper:wrapper` (or manually create the wrapper files). This ensures contributors and Docker builds don't need a Maven installation.
|
||||||
|
|
||||||
|
### 1.4 Create application class
|
||||||
|
|
||||||
|
`FeteApplication.java` in `de.fete` — minimal `@SpringBootApplication` with `main()`.
|
||||||
|
|
||||||
|
### 1.5 Create a minimal health endpoint
|
||||||
|
|
||||||
|
A simple `@RestController` in `adapter/in/web/` that responds to `GET /health` with HTTP 200 and a JSON body like `{"status": "ok"}`. This satisfies:
|
||||||
|
- The user's requirement that a REST request returns 200
|
||||||
|
- Prepares for T-2's health-check requirement
|
||||||
|
|
||||||
|
### 1.6 Create package-info.java markers
|
||||||
|
|
||||||
|
One per leaf package, documenting the package's purpose and architectural constraints. These serve as Git directory markers AND developer documentation.
|
||||||
|
|
||||||
|
### 1.7 Write integration test
|
||||||
|
|
||||||
|
`FeteApplicationTest.java`:
|
||||||
|
- `@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)`
|
||||||
|
- Test 1: Spring context loads successfully
|
||||||
|
- Test 2: HTTP GET to `/health` returns 200
|
||||||
|
|
||||||
|
### 1.8 Verify backend
|
||||||
|
|
||||||
|
- [x] `cd backend && ./mvnw test` — tests pass (context loads + health endpoint returns 200)
|
||||||
|
- [x] `cd backend && ./mvnw spring-boot:run` — app starts on port 8080
|
||||||
|
- [x] `curl http://localhost:8080/health` — returns 200 with `{"status": "ok"}`
|
||||||
|
|
||||||
|
## Phase 2: Frontend Scaffold
|
||||||
|
|
||||||
|
### 2.1 Scaffold Vue project
|
||||||
|
|
||||||
|
Run `npm create vue@latest` in `frontend/` with these options:
|
||||||
|
- TypeScript: Yes
|
||||||
|
- Vue Router: Yes
|
||||||
|
- Pinia: No
|
||||||
|
- Vitest: Yes
|
||||||
|
- ESLint: Yes
|
||||||
|
- Prettier: Yes
|
||||||
|
- E2E: No
|
||||||
|
|
||||||
|
If the interactive prompts can't be automated, create the project manually based on the `create-vue` template output.
|
||||||
|
|
||||||
|
### 2.2 Add `composables/` directory
|
||||||
|
|
||||||
|
Create `src/composables/` (empty, with a `.gitkeep` or initial placeholder) — convention for Composition API composables needed later.
|
||||||
|
|
||||||
|
### 2.3 Verify frontend builds and tests
|
||||||
|
|
||||||
|
- [x] `cd frontend && npm install`
|
||||||
|
- [x] `cd frontend && npm run build` — builds successfully
|
||||||
|
- [x] `cd frontend && npm run test:unit` — Vitest runs (sample test passes)
|
||||||
|
|
||||||
|
### 2.4 Verify via browser
|
||||||
|
|
||||||
|
- [x] `cd frontend && npm run dev` — starts dev server
|
||||||
|
- [x] Use `browser-interactive-testing` skill to visit the dev server URL and verify the page loads
|
||||||
|
|
||||||
|
## Phase 3: Shared Files
|
||||||
|
|
||||||
|
### 3.1 Update `.gitignore`
|
||||||
|
|
||||||
|
Extend the existing `.gitignore` with sections for:
|
||||||
|
- Java/Maven (target/, *.class, *.jar, Maven release files, crash logs)
|
||||||
|
- Node.js/Vue/Vite (node_modules/, dist/, build/, vite temp files)
|
||||||
|
- Environment files (.env, .env.* but NOT .env.example)
|
||||||
|
- Editor swap files (*.swp, *.swo, *~)
|
||||||
|
- Spring Boot (.springBeans, .sts4-cache)
|
||||||
|
|
||||||
|
Keep existing entries (IDE, OS, Claude settings) intact.
|
||||||
|
|
||||||
|
### 3.2 Verify .gitignore
|
||||||
|
|
||||||
|
Run `git status` after building both projects to confirm build artifacts are properly ignored.
|
||||||
|
|
||||||
|
- [x] `git status` shows no `target/`, `node_modules/`, `dist/`, or other build artifacts
|
||||||
|
|
||||||
|
## Verification Checklist (Done Criteria)
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- [x] `cd backend && ./mvnw test` passes (integration test: context loads + GET /health → 200)
|
||||||
|
- [x] `cd backend && ./mvnw spring-boot:run` starts successfully
|
||||||
|
- [x] `curl http://localhost:8080/health` returns HTTP 200
|
||||||
|
- [x] Hexagonal package structure exists with package-info.java markers
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- [x] `cd frontend && npm run build` succeeds
|
||||||
|
- [x] `cd frontend && npm run test:unit` runs and passes
|
||||||
|
- [x] Browser verification via `browser-interactive-testing` skill confirms page loads
|
||||||
|
|
||||||
|
### Shared
|
||||||
|
- [x] `.gitignore` covers Java/Maven + Node/Vue artifacts
|
||||||
|
- [x] `git status` shows no unintended tracked build artifacts
|
||||||
|
- [x] T-1 acceptance criteria from `spec/setup-tasks.md` are met
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
|
||||||
|
**Create:**
|
||||||
|
- `backend/pom.xml`
|
||||||
|
- `backend/mvnw`, `backend/mvnw.cmd`, `backend/.mvn/wrapper/maven-wrapper.properties`
|
||||||
|
- `backend/src/main/java/de/fete/FeteApplication.java`
|
||||||
|
- `backend/src/main/java/de/fete/adapter/in/web/HealthController.java`
|
||||||
|
- `backend/src/main/java/de/fete/domain/model/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/domain/port/in/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/domain/port/out/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/application/service/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/adapter/in/web/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/adapter/out/persistence/package-info.java`
|
||||||
|
- `backend/src/main/java/de/fete/config/package-info.java`
|
||||||
|
- `backend/src/main/resources/application.properties`
|
||||||
|
- `backend/src/test/java/de/fete/FeteApplicationTest.java`
|
||||||
|
- `frontend/` (entire scaffolded project via create-vue)
|
||||||
|
|
||||||
|
**Modify:**
|
||||||
|
- `.gitignore` (extend with Java/Maven + Node/Vue sections)
|
||||||
|
|
||||||
|
## Addendum: Spring Boot 4.0 → 3.5 Pivot
|
||||||
|
|
||||||
|
During implementation, Spring Boot 4.0.3 was abandoned in favor of **3.5.11**. The 4.0 release reorganized test infrastructure into new modules and packages (`TestRestTemplate` → `spring-boot-resttestclient`, `AutoConfigureMockMvc` → unknown location) without adequate migration documentation. Multiple attempts to resolve compilation and auto-configuration errors failed, making it impractical for the scaffold phase.
|
||||||
|
|
||||||
|
The pivot has no impact on project architecture or future tasks. Migration to 4.x can be revisited once the ecosystem matures. See the research report addendum for full details.
|
||||||
477
docs/agents/research/2026-03-04-t1-monorepo-setup.md
Normal file
477
docs/agents/research/2026-03-04-t1-monorepo-setup.md
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
---
|
||||||
|
date: 2026-03-04T00:19:03+01:00
|
||||||
|
git_commit: 7b460dd322359dc1fa3ca0dc950a91c607163977
|
||||||
|
branch: master
|
||||||
|
topic: "T-1: Initialize monorepo structure — Tech stack research"
|
||||||
|
tags: [research, codebase, t-1, scaffolding, spring-boot, vue, maven, vite, hexagonal-architecture]
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Research: T-1 — Initialize Monorepo Structure
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
|
||||||
|
What are the current versions, scaffolding approaches, and architectural patterns needed to implement T-1 (Initialize monorepo structure) with the specified tech stack: Java (latest LTS), Spring Boot, Maven, hexagonal/onion architecture backend + Svelte with Vite frontend?
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This research covers all technical aspects needed for T-1. The spec requires a monorepo with `backend/` and `frontend/` directories, both building successfully as empty scaffolds. The key findings and open decisions are:
|
||||||
|
|
||||||
|
1. **Java 25** is the current LTS (Sep 2025). Spring Boot **4.0.3** is the latest stable — but **3.5.x** is more battle-tested. This is an architectural decision.
|
||||||
|
2. **Svelte 5** is stable (since Oct 2024). The SPA router ecosystem for plain Svelte 5 is weak — **SvelteKit in SPA mode** is the pragmatic alternative.
|
||||||
|
3. **Hexagonal architecture**: Single Maven module with package-level separation + ArchUnit enforcement. Base package `com.fete`.
|
||||||
|
4. **TypeScript** for the frontend is recommended but is a decision point.
|
||||||
|
|
||||||
|
## Detailed Findings
|
||||||
|
|
||||||
|
### 1. Java Version
|
||||||
|
|
||||||
|
**Java 25 (LTS)** — released September 16, 2025.
|
||||||
|
|
||||||
|
- Premier support through Sep 2030, extended support through Sep 2033.
|
||||||
|
- Supersedes Java 21 (Sep 2023) as the current LTS.
|
||||||
|
- Java 21 is still supported but free updates end Sep 2026.
|
||||||
|
- Next LTS: Java 29 (Sep 2027).
|
||||||
|
|
||||||
|
**Recommendation:** Java 25. Longest support runway, both Spring Boot 3.5 and 4.0 support it.
|
||||||
|
|
||||||
|
### 2. Spring Boot Version
|
||||||
|
|
||||||
|
Two actively supported lines as of March 2026:
|
||||||
|
|
||||||
|
| Version | Latest Patch | OSS Support Ends | Java Baseline | Key Dependencies |
|
||||||
|
|---------|-------------|-------------------|---------------|-----------------|
|
||||||
|
| **4.0.x** | 4.0.3 | Dec 2026 | Java 17+ (up to 25) | Spring Framework 7.0, Jakarta EE 11, Hibernate 7.1, Jackson 3.0, Tomcat 11.0 |
|
||||||
|
| **3.5.x** | 3.5.11 | Jun 2026 | Java 17+ (up to 25) | Spring Framework 6.x, Jakarta EE 10, Hibernate 6.x, Jackson 2.x, Tomcat 10.x |
|
||||||
|
|
||||||
|
**Trade-offs:**
|
||||||
|
|
||||||
|
| Factor | Spring Boot 4.0 | Spring Boot 3.5 |
|
||||||
|
|--------|----------------|-----------------|
|
||||||
|
| Support runway | Dec 2026 (longer) | Jun 2026 (shorter) |
|
||||||
|
| Ecosystem maturity | Jackson 3.0 + Hibernate 7.1 are new major versions; fewer community examples | Battle-tested, large ecosystem of examples |
|
||||||
|
| Migration burden | None (greenfield) | None (greenfield) |
|
||||||
|
| Forward-looking | Yes | Will need migration to 4.x eventually |
|
||||||
|
|
||||||
|
**Decision needed:** Spring Boot 4.0 (forward-looking) vs. 3.5 (more battle-tested). Both support Java 25.
|
||||||
|
|
||||||
|
### 3. Maven
|
||||||
|
|
||||||
|
- **Maven version:** 3.9.12 (latest stable; Maven 4.0.0 is still RC).
|
||||||
|
- **Maven Wrapper:** Yes, include it. Modern "only-script" distribution — no binary JAR in repo. Scripts `mvnw`/`mvnw.cmd` + `.mvn/wrapper/maven-wrapper.properties` are committed.
|
||||||
|
- Benefits: deterministic builds in Docker, no Maven pre-install requirement for contributors or CI.
|
||||||
|
|
||||||
|
**Minimal dependencies for Spring Boot + PostgreSQL:**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>4.0.3</version> <!-- or 3.5.11 -->
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: For the empty scaffold (T-1), JPA autoconfig must be excluded or deferred since there's no DB yet. Either omit `spring-boot-starter-data-jpa` until T-4, or add `spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration` to `application.properties`.
|
||||||
|
|
||||||
|
### 4. Svelte + Vite Frontend
|
||||||
|
|
||||||
|
**Current versions:**
|
||||||
|
- Svelte: **5.53.6** (stable since Oct 2024, runes-based reactivity)
|
||||||
|
- Vite: **7.3.1** (stable; Vite 8 is in beta)
|
||||||
|
|
||||||
|
**Svelte 5 key changes from Svelte 4:**
|
||||||
|
- Reactive state: `let count = $state(0)` (explicit rune) instead of implicit `let count = 0`
|
||||||
|
- Derived values: `$derived()` replaces `$:` blocks
|
||||||
|
- Props: `$props()` replaces `export let`
|
||||||
|
- Reactivity works in `.svelte.ts` files too (not just components)
|
||||||
|
|
||||||
|
### 5. SvelteKit vs. Plain Svelte — Decision Point
|
||||||
|
|
||||||
|
The spec says "Svelte with Vite as bundler" for an SPA with a separate REST backend.
|
||||||
|
|
||||||
|
**Option A: Plain Svelte + Vite + third-party router**
|
||||||
|
|
||||||
|
| Pro | Con |
|
||||||
|
|-----|-----|
|
||||||
|
| Simpler mental model | Router ecosystem broken for Svelte 5: `svelte-spa-router` has no Svelte 5 support (issue #318) |
|
||||||
|
| Literal spec wording | Only unmaintained/low-adoption alternatives exist |
|
||||||
|
| No unused SSR concepts | Violates dependency statute ("actively maintained") |
|
||||||
|
|
||||||
|
**Option B: SvelteKit in SPA mode (adapter-static + `ssr: false`)**
|
||||||
|
|
||||||
|
| Pro | Con |
|
||||||
|
|-----|-----|
|
||||||
|
| Built-in file-based routing (maintained by Svelte team) | Has SSR concepts that exist but are unused |
|
||||||
|
| First-class Vitest integration (T-4 requirement) | Slightly larger framework footprint |
|
||||||
|
| SPA config is 3 lines of code | SEO concerns (irrelevant for this app) |
|
||||||
|
| Output is static HTML/JS/CSS — no Node.js runtime needed | |
|
||||||
|
| `npx sv create` scaffolds TS/ESLint/Vitest in one step | |
|
||||||
|
|
||||||
|
**SPA configuration (total effort):**
|
||||||
|
1. `npm i -D @sveltejs/adapter-static`
|
||||||
|
2. `svelte.config.js`: `adapter: adapter({ fallback: '200.html' })`
|
||||||
|
3. `src/routes/+layout.js`: `export const ssr = false;`
|
||||||
|
|
||||||
|
**Decision needed:** SvelteKit in SPA mode (pragmatic, solves the router problem) vs. plain Svelte + Vite (minimalist, but router ecosystem is a real problem).
|
||||||
|
|
||||||
|
### 6. TypeScript vs. JavaScript — Decision Point
|
||||||
|
|
||||||
|
**Arguments for TypeScript:**
|
||||||
|
- Java backend is strongly typed; TS catches API contract drift at compile time.
|
||||||
|
- Svelte 5 has first-class TS support, including TS in markup.
|
||||||
|
- The API client layer (T-4) benefits most from type safety.
|
||||||
|
- Zero-config in both SvelteKit and the `svelte-ts` Vite template.
|
||||||
|
- Rich Harris: "TypeScript for apps, JSDoc for libraries." This is an app.
|
||||||
|
|
||||||
|
**Arguments against:**
|
||||||
|
- KISS/grugbrain principle — TS adds cognitive overhead.
|
||||||
|
- Svelte's compiler already catches many errors.
|
||||||
|
|
||||||
|
**Verdict from research:** TS overhead in Svelte 5 is minimal (`let count: number = $state(0)` vs. `let count = $state(0)`). The real value is in the API client layer and shared types.
|
||||||
|
|
||||||
|
### 7. Node.js Version
|
||||||
|
|
||||||
|
| Version | Status | End of Life |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| Node.js 24 | Active LTS | ~April 2028 |
|
||||||
|
| Node.js 22 | Active LTS | April 2027 |
|
||||||
|
| Node.js 20 | Maintenance LTS | April 2026 (EOL imminent) |
|
||||||
|
|
||||||
|
**Recommendation:** Target Node.js 22 LTS as minimum. Docker image should use `node:22-alpine`.
|
||||||
|
|
||||||
|
### 8. Hexagonal Architecture — Package Structure
|
||||||
|
|
||||||
|
**Approach:** Single Maven module with package-level separation. Multi-module Maven is overkill for a small app. ArchUnit test enforces dependency rules.
|
||||||
|
|
||||||
|
**Package structure:**
|
||||||
|
|
||||||
|
```
|
||||||
|
com.fete
|
||||||
|
├── FeteApplication.java # @SpringBootApplication
|
||||||
|
├── domain/
|
||||||
|
│ ├── model/ # Entities, value objects (plain Java, no framework annotations)
|
||||||
|
│ └── port/
|
||||||
|
│ ├── in/ # Driving port interfaces (use cases)
|
||||||
|
│ └── out/ # Driven port interfaces (repositories)
|
||||||
|
├── application/
|
||||||
|
│ └── service/ # Use case implementations (@Service)
|
||||||
|
├── adapter/
|
||||||
|
│ ├── in/
|
||||||
|
│ │ └── web/ # REST controllers, DTOs, mappers
|
||||||
|
│ └── out/
|
||||||
|
│ └── persistence/ # JPA entities, Spring Data repos, mappers
|
||||||
|
└── config/ # @Configuration classes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dependency flow (strict):**
|
||||||
|
```
|
||||||
|
domain → nothing
|
||||||
|
application → domain only
|
||||||
|
adapter → application + domain
|
||||||
|
config → everything (wiring)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Spring annotations by layer:**
|
||||||
|
|
||||||
|
| Layer | Annotations | Rationale |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| `domain.model` | None | Plain Java — no framework coupling |
|
||||||
|
| `domain.port` | None | Plain Java interfaces |
|
||||||
|
| `application.service` | `@Service` only | Pragmatic compromise for component scanning |
|
||||||
|
| `adapter.in.web` | `@RestController`, `@GetMapping`, etc. | Framework adapter layer |
|
||||||
|
| `adapter.out.persistence` | `@Entity`, `@Repository`, `@Table`, etc. | Framework adapter layer |
|
||||||
|
| `config` | `@Configuration`, `@Bean` | Wiring layer |
|
||||||
|
|
||||||
|
**Domain purity:** Persistence has its own JPA entity classes (e.g., `EventJpaEntity`) separate from domain model classes. Mappers convert between them.
|
||||||
|
|
||||||
|
**Empty directory markers:** Use `package-info.java` in each leaf package. Documents package purpose, allows Git to track the directory, and aids component scanning.
|
||||||
|
|
||||||
|
**Base package:** `com.fete` (Maven convention, clean, short).
|
||||||
|
|
||||||
|
### 9. .gitignore
|
||||||
|
|
||||||
|
The existing `.gitignore` covers IDE files (`.idea/`, `.vscode/`, `*.iml`), OS files (`.DS_Store`, `Thumbs.db`), and Claude settings. The following sections need to be added:
|
||||||
|
|
||||||
|
**Java/Maven:**
|
||||||
|
- `*.class`, `*.jar`, `*.war`, `*.ear`, `*.nar` — compiled artifacts
|
||||||
|
- `target/` — Maven build output
|
||||||
|
- Maven release plugin files (`pom.xml.tag`, `pom.xml.releaseBackup`, etc.)
|
||||||
|
- `.mvn/wrapper/maven-wrapper.jar` — downloaded automatically
|
||||||
|
- Eclipse files (`.classpath`, `.project`, `.settings/`, `.factorypath`)
|
||||||
|
- Spring Boot (`.springBeans`, `.sts4-cache`)
|
||||||
|
- Java crash logs (`hs_err_pid*`, `replay_pid*`)
|
||||||
|
- `*.log`
|
||||||
|
|
||||||
|
**Node.js/Svelte/Vite:**
|
||||||
|
- `node_modules/`
|
||||||
|
- `dist/`, `build/` — build output
|
||||||
|
- `.svelte-kit/` — SvelteKit generated files
|
||||||
|
- `vite.config.js.timestamp-*`, `vite.config.ts.timestamp-*` — Vite temp files
|
||||||
|
- `.env`, `.env.*` (but NOT `.env.example`)
|
||||||
|
- `npm-debug.log*`
|
||||||
|
|
||||||
|
**Editor files:**
|
||||||
|
- `*.swp`, `*.swo`, `*~` — Vim/editor backup files
|
||||||
|
- `\#*\#`, `.#*` — Emacs
|
||||||
|
|
||||||
|
**Committed (NOT ignored):**
|
||||||
|
- `mvnw`, `mvnw.cmd`, `.mvn/wrapper/maven-wrapper.properties`
|
||||||
|
- `package-lock.json`
|
||||||
|
|
||||||
|
### 10. Existing Repository State
|
||||||
|
|
||||||
|
The repository currently contains:
|
||||||
|
- `CLAUDE.md` — Project statutes
|
||||||
|
- `README.md` — With tech stack docs and docker-compose example
|
||||||
|
- `LICENSE` — GPL
|
||||||
|
- `.gitignore` — Partial (IDE + OS only)
|
||||||
|
- `Ideen.md` — German idea document
|
||||||
|
- `spec/` — User stories, personas, setup tasks, implementation phases
|
||||||
|
- `.ralph/` — Ralph loop infrastructure
|
||||||
|
- `ralph.sh` — Ralph loop runner
|
||||||
|
- `review-findings.md` — Review notes
|
||||||
|
|
||||||
|
No `backend/` or `frontend/` directories exist yet. No `Dockerfile` exists yet (listed in README project structure, but deferred to T-2).
|
||||||
|
|
||||||
|
## Decisions Required Before Implementation
|
||||||
|
|
||||||
|
These are architectural decisions that require approval per CLAUDE.md governance statutes:
|
||||||
|
|
||||||
|
| # | Decision | Options | Recommendation |
|
||||||
|
|---|----------|---------|----------------|
|
||||||
|
| 1 | Spring Boot version | 4.0.3 (latest, longer support) vs. 3.5.11 (battle-tested, shorter support) | 4.0.3 — greenfield project, no migration burden, longer support |
|
||||||
|
| 2 | SvelteKit vs. plain Svelte | SvelteKit SPA mode vs. plain Svelte + third-party router | SvelteKit SPA mode — router ecosystem for plain Svelte 5 is broken |
|
||||||
|
| 3 | TypeScript vs. JavaScript | TypeScript (type safety on API boundary) vs. JavaScript (simpler) | TypeScript — minimal overhead in Svelte 5, catches API contract drift |
|
||||||
|
| 4 | Spring Boot JPA in T-1? | Include `spring-boot-starter-data-jpa` now (exclude autoconfig) vs. add it in T-4 | Defer to T-4 — T-1 is "empty scaffold", JPA needs a datasource |
|
||||||
|
|
||||||
|
## Code References
|
||||||
|
|
||||||
|
- `spec/setup-tasks.md` — T-1 acceptance criteria
|
||||||
|
- `spec/implementation-phases.md:9-14` — Phase 0 task order
|
||||||
|
- `CLAUDE.md:36-43` — Dependency statutes
|
||||||
|
- `Ideen.md:76-78` — Tech stack decisions (already made)
|
||||||
|
- `.gitignore` — Current state (needs extension)
|
||||||
|
- `README.md:112-119` — Documented project structure (target)
|
||||||
|
|
||||||
|
## Architecture Documentation
|
||||||
|
|
||||||
|
### Target Repository Layout (after T-1)
|
||||||
|
|
||||||
|
```
|
||||||
|
fete/
|
||||||
|
├── backend/
|
||||||
|
│ ├── pom.xml
|
||||||
|
│ ├── mvnw
|
||||||
|
│ ├── mvnw.cmd
|
||||||
|
│ ├── .mvn/wrapper/maven-wrapper.properties
|
||||||
|
│ └── src/
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── java/com/fete/
|
||||||
|
│ │ │ ├── FeteApplication.java
|
||||||
|
│ │ │ ├── domain/model/ (package-info.java)
|
||||||
|
│ │ │ ├── domain/port/in/ (package-info.java)
|
||||||
|
│ │ │ ├── domain/port/out/ (package-info.java)
|
||||||
|
│ │ │ ├── application/service/ (package-info.java)
|
||||||
|
│ │ │ ├── adapter/in/web/ (package-info.java)
|
||||||
|
│ │ │ ├── adapter/out/persistence/(package-info.java)
|
||||||
|
│ │ │ └── config/ (package-info.java)
|
||||||
|
│ │ └── resources/
|
||||||
|
│ │ └── application.properties
|
||||||
|
│ └── test/java/com/fete/
|
||||||
|
│ └── FeteApplicationTest.java
|
||||||
|
├── frontend/
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── package-lock.json
|
||||||
|
│ ├── svelte.config.js
|
||||||
|
│ ├── vite.config.ts
|
||||||
|
│ ├── tsconfig.json
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── app.html
|
||||||
|
│ │ ├── routes/
|
||||||
|
│ │ │ ├── +layout.js (ssr = false)
|
||||||
|
│ │ │ └── +page.svelte
|
||||||
|
│ │ └── lib/
|
||||||
|
│ └── static/
|
||||||
|
├── spec/
|
||||||
|
├── .gitignore (extended)
|
||||||
|
├── CLAUDE.md
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
└── Ideen.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Commands (Target State)
|
||||||
|
|
||||||
|
| What | Command |
|
||||||
|
|------|---------|
|
||||||
|
| Backend build | `cd backend && ./mvnw package` |
|
||||||
|
| Backend test | `cd backend && ./mvnw test` |
|
||||||
|
| Frontend install | `cd frontend && npm install` |
|
||||||
|
| Frontend build | `cd frontend && npm run build` |
|
||||||
|
| Frontend test | `cd frontend && npm test` |
|
||||||
|
| Frontend dev | `cd frontend && npm run dev` |
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
All resolved — see Follow-up Research below.
|
||||||
|
|
||||||
|
## Follow-up Research: Frontend Pivot to Vue 3 (2026-03-04)
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
During decision review, the developer raised concerns about the Svelte 5 ecosystem maturity (specifically the broken third-party router situation signaling a smaller, less mature ecosystem). After comparing Svelte 5 vs Vue 3 on ecosystem size, community support, team size, and stability, the decision was made to pivot from Svelte to **Vue 3**.
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
- Vue 3 has a significantly larger ecosystem and community
|
||||||
|
- Official, battle-tested packages for all needs (Vue Router, Pinia, Vitest)
|
||||||
|
- Vite was created by Evan You (Vue's creator) — first-class integration
|
||||||
|
- Vue 3 Composition API is modern and elegant while being mature (stable since 2020)
|
||||||
|
- Larger team, broader funding, more StackOverflow answers and tutorials
|
||||||
|
|
||||||
|
### Vue 3 Stack — Research Findings
|
||||||
|
|
||||||
|
**Current versions (March 2026):**
|
||||||
|
|
||||||
|
| Package | Version | Notes |
|
||||||
|
|---------|---------|-------|
|
||||||
|
| Vue | 3.5.29 | Stable. Vue 3.6 (Vapor Mode) is in beta |
|
||||||
|
| Vue Router | 5.0.3 | Includes file-based routing from unplugin-vue-router. Drop-in from v4 for manual routes |
|
||||||
|
| Vite | 7.3.1 | Stable. Vite 8 (Rolldown) is in beta |
|
||||||
|
| Vitest | 4.0.18 | Stable. Browser Mode graduated from experimental |
|
||||||
|
| @vue/test-utils | 2.4.6 | Official component testing utilities |
|
||||||
|
| create-vue | 3.22.0 | Official scaffolding tool |
|
||||||
|
| Node.js | 24 LTS | Latest LTS, support through ~April 2028 |
|
||||||
|
|
||||||
|
**Scaffolding:** `npm create vue@latest` (official Vue CLI scaffolding). Interactive prompts offer TypeScript, Vue Router, Pinia, Vitest, ESLint, Prettier out of the box.
|
||||||
|
|
||||||
|
**Selected options for fete:**
|
||||||
|
- TypeScript: **Yes**
|
||||||
|
- Vue Router: **Yes**
|
||||||
|
- Pinia: **No** — Composition API (`ref`/`reactive`) + localStorage is sufficient for this app's simple state
|
||||||
|
- Vitest: **Yes**
|
||||||
|
- ESLint: **Yes**
|
||||||
|
- Prettier: **Yes**
|
||||||
|
- E2E testing: **No** (not needed for T-1)
|
||||||
|
|
||||||
|
**Project structure (scaffolded by create-vue):**
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/
|
||||||
|
├── public/
|
||||||
|
│ └── favicon.ico
|
||||||
|
├── src/
|
||||||
|
│ ├── assets/ # Static assets (CSS, images)
|
||||||
|
│ ├── components/ # Reusable components
|
||||||
|
│ ├── composables/ # Composition API composables (added manually)
|
||||||
|
│ ├── router/ # Vue Router config (index.ts)
|
||||||
|
│ ├── views/ # Route-level page components
|
||||||
|
│ ├── App.vue # Root component
|
||||||
|
│ └── main.ts # Entry point
|
||||||
|
├── index.html
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── tsconfig.app.json
|
||||||
|
├── tsconfig.node.json
|
||||||
|
├── vite.config.ts
|
||||||
|
├── eslint.config.js
|
||||||
|
├── .prettierrc.json
|
||||||
|
├── env.d.ts
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key conventions:**
|
||||||
|
- `src/views/` for route page components (not `src/pages/` — that's Nuxt)
|
||||||
|
- `src/components/` for reusable components
|
||||||
|
- `src/composables/` for Composition API composables (e.g., `useStorage.ts`)
|
||||||
|
- `src/router/index.ts` for route definitions
|
||||||
|
|
||||||
|
### Resolved Decisions
|
||||||
|
|
||||||
|
| # | Decision | Resolution |
|
||||||
|
|---|----------|------------|
|
||||||
|
| 1 | Spring Boot version | ~~**4.0.3**~~ → **3.5.11** — see addendum below |
|
||||||
|
| 2 | Frontend framework | **Vue 3** — pivot from Svelte due to ecosystem maturity concerns |
|
||||||
|
| 3 | TypeScript | **Yes** — confirmed by developer |
|
||||||
|
| 4 | Node.js version | **24 LTS** (latest LTS) |
|
||||||
|
| 5 | Base package | **`de.fete`** (not `com.fete`) |
|
||||||
|
| 6 | JPA in T-1 | **Defer to T-4** — T-1 is empty scaffold, JPA needs a datasource |
|
||||||
|
| 7 | State management | **No Pinia** — Composition API + localStorage sufficient |
|
||||||
|
|
||||||
|
### Addendum: Spring Boot 4.0 → 3.5 Pivot (2026-03-04)
|
||||||
|
|
||||||
|
During T-1 implementation, Spring Boot 4.0.3 proved unworkable for the scaffold phase. The 4.0 release massively reorganized internal packages — test infrastructure classes (`TestRestTemplate`, `AutoConfigureMockMvc`, etc.) were moved into new modules with different package paths. The Spring Boot 4.0 Migration Guide did not cover these changes adequately, and resolving the issues required extensive trial-and-error with undocumented class locations and missing transitive dependencies.
|
||||||
|
|
||||||
|
**Decision:** Pivot to **Spring Boot 3.5.11** (latest 3.5.x patch). This is the battle-tested line with OSS support through June 2026. Since this is a greenfield project, migrating to 4.x later (once the ecosystem and documentation have matured) is straightforward.
|
||||||
|
|
||||||
|
**Impact:** None on architecture or feature scope. The hexagonal package structure, dependency choices, and all other decisions remain unchanged. Only the Spring Boot parent version in `pom.xml` changed.
|
||||||
|
|
||||||
|
### Updated Target Repository Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
fete/
|
||||||
|
├── backend/
|
||||||
|
│ ├── pom.xml
|
||||||
|
│ ├── mvnw
|
||||||
|
│ ├── mvnw.cmd
|
||||||
|
│ ├── .mvn/wrapper/maven-wrapper.properties
|
||||||
|
│ └── src/
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── java/de/fete/
|
||||||
|
│ │ │ ├── FeteApplication.java
|
||||||
|
│ │ │ ├── domain/model/ (package-info.java)
|
||||||
|
│ │ │ ├── domain/port/in/ (package-info.java)
|
||||||
|
│ │ │ ├── domain/port/out/ (package-info.java)
|
||||||
|
│ │ │ ├── application/service/ (package-info.java)
|
||||||
|
│ │ │ ├── adapter/in/web/ (package-info.java)
|
||||||
|
│ │ │ ├── adapter/out/persistence/(package-info.java)
|
||||||
|
│ │ │ └── config/ (package-info.java)
|
||||||
|
│ │ └── resources/
|
||||||
|
│ │ └── application.properties
|
||||||
|
│ └── test/java/de/fete/
|
||||||
|
│ └── FeteApplicationTest.java
|
||||||
|
├── frontend/
|
||||||
|
│ ├── public/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── assets/
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ ├── composables/
|
||||||
|
│ │ ├── router/index.ts
|
||||||
|
│ │ ├── views/
|
||||||
|
│ │ ├── App.vue
|
||||||
|
│ │ └── main.ts
|
||||||
|
│ ├── index.html
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── package-lock.json
|
||||||
|
│ ├── tsconfig.json
|
||||||
|
│ ├── vite.config.ts
|
||||||
|
│ └── eslint.config.js
|
||||||
|
├── spec/
|
||||||
|
├── .gitignore (extended)
|
||||||
|
├── CLAUDE.md
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
└── Ideen.md
|
||||||
|
```
|
||||||
8
frontend/.editorconfig
Normal file
8
frontend/.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
1
frontend/.gitattributes
vendored
Normal file
1
frontend/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
39
frontend/.gitignore
vendored
Normal file
39
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
__screenshots__/
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
*.timestamp-*-*.mjs
|
||||||
10
frontend/.oxlintrc.json
Normal file
10
frontend/.oxlintrc.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue", "vitest"],
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"correctness": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/.prettierrc.json
Normal file
6
frontend/.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
54
frontend/README.md
Normal file
54
frontend/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# frontend
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
1
frontend/env.d.ts
vendored
Normal file
1
frontend/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
32
frontend/eslint.config.ts
Normal file
32
frontend/eslint.config.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { globalIgnores } from 'eslint/config'
|
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import pluginVitest from '@vitest/eslint-plugin'
|
||||||
|
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||||
|
import skipFormatting from 'eslint-config-prettier/flat'
|
||||||
|
|
||||||
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
|
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||||
|
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||||
|
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{vue,ts,mts,tsx}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||||
|
|
||||||
|
...pluginVue.configs['flat/essential'],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
|
||||||
|
{
|
||||||
|
...pluginVitest.configs.recommended,
|
||||||
|
files: ['src/**/__tests__/*'],
|
||||||
|
},
|
||||||
|
|
||||||
|
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
|
||||||
|
|
||||||
|
skipFormatting,
|
||||||
|
)
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6690
frontend/package-lock.json
generated
Normal file
6690
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
frontend/package.json
Normal file
49
frontend/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build",
|
||||||
|
"lint": "run-s lint:*",
|
||||||
|
"lint:oxlint": "oxlint . --fix",
|
||||||
|
"lint:eslint": "eslint . --fix --cache",
|
||||||
|
"format": "prettier --write --experimental-cli src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.29",
|
||||||
|
"vue-router": "^5.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node24": "^24.0.4",
|
||||||
|
"@types/jsdom": "^28.0.0",
|
||||||
|
"@types/node": "^24.11.0",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
|
"@vitest/eslint-plugin": "^1.6.9",
|
||||||
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"eslint": "^10.0.2",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-oxlint": "~1.50.0",
|
||||||
|
"eslint-plugin-vue": "~10.8.0",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"oxlint": "~1.50.0",
|
||||||
|
"prettier": "3.8.1",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.6",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
|
"vue-tsc": "^3.2.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
85
frontend/src/App.vue
Normal file
85
frontend/src/App.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
|
import HelloWorld from './components/HelloWorld.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<HelloWorld msg="You did it!" />
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<RouterLink to="/">Home</RouterLink>
|
||||||
|
<RouterLink to="/about">About</RouterLink>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
header {
|
||||||
|
line-height: 1.5;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.router-link-exact-active {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.router-link-exact-active:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:first-of-type {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
padding-right: calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 0 2rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .wrapper {
|
||||||
|
display: flex;
|
||||||
|
place-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
text-align: left;
|
||||||
|
margin-left: -1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
86
frontend/src/assets/base.css
Normal file
86
frontend/src/assets/base.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
1
frontend/src/assets/logo.svg
Normal file
1
frontend/src/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
35
frontend/src/assets/main.css
Normal file
35
frontend/src/assets/main.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
frontend/src/components/HelloWorld.vue
Normal file
41
frontend/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
msg: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="greetings">
|
||||||
|
<h1 class="green">{{ msg }}</h1>
|
||||||
|
<h3>
|
||||||
|
You’ve successfully created a project with
|
||||||
|
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h1 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
position: relative;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
95
frontend/src/components/TheWelcome.vue
Normal file
95
frontend/src/components/TheWelcome.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import WelcomeItem from './WelcomeItem.vue'
|
||||||
|
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||||
|
import ToolingIcon from './icons/IconTooling.vue'
|
||||||
|
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||||
|
import CommunityIcon from './icons/IconCommunity.vue'
|
||||||
|
import SupportIcon from './icons/IconSupport.vue'
|
||||||
|
|
||||||
|
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<DocumentationIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Documentation</template>
|
||||||
|
|
||||||
|
Vue’s
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||||
|
provides you with all information you need to get started.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<ToolingIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Tooling</template>
|
||||||
|
|
||||||
|
This project is served and bundled with
|
||||||
|
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||||
|
recommended IDE setup is
|
||||||
|
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
||||||
|
+
|
||||||
|
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener"
|
||||||
|
>Vue - Official</a
|
||||||
|
>. If you need to test your components and web pages, check out
|
||||||
|
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
||||||
|
and
|
||||||
|
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||||
|
/
|
||||||
|
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
More instructions are available in
|
||||||
|
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||||
|
>.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<EcosystemIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Ecosystem</template>
|
||||||
|
|
||||||
|
Get official tools and libraries for your project:
|
||||||
|
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||||
|
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||||
|
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||||
|
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||||
|
you need more resources, we suggest paying
|
||||||
|
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||||
|
a visit.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<CommunityIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Community</template>
|
||||||
|
|
||||||
|
Got stuck? Ask your question on
|
||||||
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
||||||
|
(our official Discord server), or
|
||||||
|
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||||
|
>StackOverflow</a
|
||||||
|
>. You should also follow the official
|
||||||
|
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||||
|
Bluesky account or the
|
||||||
|
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||||
|
X account for latest news in the Vue world.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<SupportIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Support Vue</template>
|
||||||
|
|
||||||
|
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||||
|
us by
|
||||||
|
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||||
|
</WelcomeItem>
|
||||||
|
</template>
|
||||||
87
frontend/src/components/WelcomeItem.vue
Normal file
87
frontend/src/components/WelcomeItem.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</i>
|
||||||
|
<div class="details">
|
||||||
|
<h3>
|
||||||
|
<slot name="heading"></slot>
|
||||||
|
</h3>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
color: var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.item {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
left: -26px;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:before {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:after {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:first-of-type:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-of-type:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
frontend/src/components/__tests__/HelloWorld.spec.ts
Normal file
11
frontend/src/components/__tests__/HelloWorld.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import HelloWorld from '../HelloWorld.vue'
|
||||||
|
|
||||||
|
describe('HelloWorld', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
||||||
|
expect(wrapper.text()).toContain('Hello Vitest')
|
||||||
|
})
|
||||||
|
})
|
||||||
7
frontend/src/components/icons/IconCommunity.vue
Normal file
7
frontend/src/components/icons/IconCommunity.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
frontend/src/components/icons/IconDocumentation.vue
Normal file
7
frontend/src/components/icons/IconDocumentation.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
frontend/src/components/icons/IconEcosystem.vue
Normal file
7
frontend/src/components/icons/IconEcosystem.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
7
frontend/src/components/icons/IconSupport.vue
Normal file
7
frontend/src/components/icons/IconSupport.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
19
frontend/src/components/icons/IconTooling.vue
Normal file
19
frontend/src/components/icons/IconTooling.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
class="iconify iconify--mdi"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
0
frontend/src/composables/.gitkeep
Normal file
0
frontend/src/composables/.gitkeep
Normal file
11
frontend/src/main.ts
Normal file
11
frontend/src/main.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
23
frontend/src/router/index.ts
Normal file
23
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/AboutView.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
15
frontend/src/views/AboutView.vue
Normal file
15
frontend/src/views/AboutView.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.about {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
frontend/src/views/HomeView.vue
Normal file
9
frontend/src/views/HomeView.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import TheWelcome from '../components/TheWelcome.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<TheWelcome />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
18
frontend/tsconfig.app.json
Normal file
18
frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
// Extra safety for array and object lookups, but may have false positives.
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
// Path mapping for cleaner imports.
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
frontend/tsconfig.json
Normal file
14
frontend/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
frontend/tsconfig.node.json
Normal file
28
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node24/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||||
|
// Bundler mode provides a smoother developer experience.
|
||||||
|
"module": "preserve",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
|
||||||
|
// Include Node.js types and avoid accidentally including other `@types/*` packages.
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
|
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
frontend/tsconfig.vitest.json
Normal file
19
frontend/tsconfig.vitest.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
|
||||||
|
// Override to include only test files and clear exclusions.
|
||||||
|
// Application code imported in tests is automatically included via module resolution.
|
||||||
|
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||||
|
"exclude": [],
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
// Vitest runs in a different environment than the application code.
|
||||||
|
// Adjust lib and types accordingly.
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"],
|
||||||
|
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
frontend/vite.config.ts
Normal file
18
frontend/vite.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
14
frontend/vitest.config.ts
Normal file
14
frontend/vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- [ ] Single repository with `backend/` and `frontend/` directories
|
- [ ] Single repository with `backend/` and `frontend/` directories
|
||||||
- [ ] Backend: Java (latest LTS), Spring Boot, Maven, hexagonal/onion architecture scaffold
|
- [ ] Backend: Java (latest LTS), Spring Boot, Maven, hexagonal/onion architecture scaffold
|
||||||
- [ ] Frontend: Svelte with Vite as bundler
|
- [ ] Frontend: Vue 3 with Vite as bundler, TypeScript, Vue Router
|
||||||
- [ ] Shared top-level files: README, Dockerfile, CLAUDE.md, LICENSE (GPL), .gitignore
|
- [ ] Shared top-level files: README, Dockerfile, CLAUDE.md, LICENSE (GPL), .gitignore
|
||||||
- [ ] Both projects build successfully with no source code (empty scaffold)
|
- [ ] Both projects build successfully with no source code (empty scaffold)
|
||||||
- [ ] .gitignore covers build artifacts, IDE files, and dependency directories for both Java/Maven and Node/Svelte
|
- [ ] .gitignore covers build artifacts, IDE files, and dependency directories for both Java/Maven and Node/Vue
|
||||||
|
|
||||||
**Dependencies:** None
|
**Dependencies:** None
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- [ ] Gitea Actions workflow file in `.gitea/workflows/` runs on push: test, build, publish Docker image
|
- [ ] Gitea Actions workflow file in `.gitea/workflows/` runs on push: test, build, publish Docker image
|
||||||
- [ ] Backend tests run via Maven
|
- [ ] Backend tests run via Maven
|
||||||
- [ ] Frontend tests run via Vite/Vitest
|
- [ ] Frontend tests run via Vitest
|
||||||
- [ ] Docker image is published to the Gitea container registry on the same instance
|
- [ ] Docker image is published to the Gitea container registry on the same instance
|
||||||
- [ ] Pipeline fails visibly if any test fails or the build breaks
|
- [ ] Pipeline fails visibly if any test fails or the build breaks
|
||||||
- [ ] Docker image is only published if all tests pass and the build succeeds
|
- [ ] Docker image is only published if all tests pass and the build succeeds
|
||||||
@@ -61,10 +61,10 @@
|
|||||||
|
|
||||||
**Acceptance Criteria:**
|
**Acceptance Criteria:**
|
||||||
- [ ] Database migration framework (Flyway or Liquibase) is configured in the backend with a first empty migration that runs successfully against a PostgreSQL instance
|
- [ ] Database migration framework (Flyway or Liquibase) is configured in the backend with a first empty migration that runs successfully against a PostgreSQL instance
|
||||||
- [ ] SPA router is configured in the Svelte frontend so pages can be navigated by URL path
|
- [ ] SPA router is configured in the Vue frontend (Vue Router) so pages can be navigated by URL path
|
||||||
- [ ] API client layer exists in the frontend (a fetch wrapper or similar) for making requests to the backend REST API
|
- [ ] API client layer exists in the frontend (a fetch wrapper or similar) for making requests to the backend REST API
|
||||||
- [ ] Backend test infrastructure is set up: JUnit 5 with Spring Boot Test, plus integration test support using Testcontainers (PostgreSQL) so tests can run against a real database without external setup
|
- [ ] Backend test infrastructure is set up: JUnit 5 with Spring Boot Test, plus integration test support using Testcontainers (PostgreSQL) so tests can run against a real database without external setup
|
||||||
- [ ] Frontend test infrastructure is set up: Vitest configured and a sample test runs successfully
|
- [ ] Frontend test infrastructure is set up: Vitest with @vue/test-utils configured and a sample test runs successfully
|
||||||
- [ ] Both test suites (backend and frontend) can be executed via their respective build tools (`mvn test` and `npm test` / `npx vitest`)
|
- [ ] Both test suites (backend and frontend) can be executed via their respective build tools (`mvn test` and `npm test` / `npx vitest`)
|
||||||
|
|
||||||
**Dependencies:** T-1, T-2
|
**Dependencies:** T-1, T-2
|
||||||
|
|||||||
Reference in New Issue
Block a user