Compare commits
169 Commits
release/1.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8351da9233 | |||
| 7ddeaf9816 | |||
| fa4e2d15f9 | |||
| 0272db8933 | |||
| 751413a90a | |||
| fe1b906416 | |||
| fef7aaa1be | |||
| 517f635fbf | |||
| a6059faeea | |||
| ada4d07787 | |||
| 969ede75b1 | |||
| 362ed8e346 | |||
| 5c6cf1fde8 | |||
| dc732c7ed0 | |||
| 82d5d4111a | |||
| 3881c28920 | |||
| d4f8820df2 | |||
| 506a90f7d1 | |||
| a86b4301ed | |||
| 10aaf15585 | |||
| 73a711d8ec | |||
| b9bbb68ee9 | |||
| 30e2edad91 | |||
| d0312ba705 | |||
| dc151f705e | |||
| b5a5bd1dcd | |||
| 02985c819f | |||
| 4ad0737636 | |||
| 347f920297 | |||
| c125edb16e | |||
| f0d55765a8 | |||
| b39ff5387b | |||
| 0696657b78 | |||
| 431038123f | |||
| 1c320a86bd | |||
| 3d4c4df2fe | |||
| 440c6f088a | |||
| ba9c23cf9e | |||
| c429e6d59b | |||
| e9a8e9d682 | |||
| 82fc878c4d | |||
| a92b1267d8 | |||
| 3de18321e7 | |||
| 572299cb21 | |||
| 89bdea0109 | |||
| 4c35b3802f | |||
| b78e710f86 | |||
| c01ffd48e1 | |||
| 188ab83006 | |||
| e5eb0d74cc | |||
| 08c6f33ef4 | |||
| ff6c9b1927 | |||
| 1dec8c8651 | |||
| 4388e2a85f | |||
| 9543e5fb69 | |||
| b2f4b47e05 | |||
| fb93faad5c | |||
| 814550903a | |||
| 7ac437e714 | |||
| 0fd1c9e922 | |||
| 0fae75c2bf | |||
| 217e47ed20 | |||
| f8ae4a2665 | |||
| ef15461ce3 | |||
| 9b4102f474 | |||
| 04edd6f025 | |||
| 9e740965cd | |||
| 8b759a663b | |||
| 1c68fb9fed | |||
| d604efda37 | |||
| b0e822092b | |||
| 17210d5da9 | |||
| d67df1b618 | |||
| 267be7073a | |||
| acdb5c8bfc | |||
| 54c8949856 | |||
| 1157f0eeea | |||
| 99f4666c51 | |||
| 3057d6209a | |||
| 014c6f01a4 | |||
| bfb2d525dd | |||
| de196ce36a | |||
| 41957e97de | |||
| 79e0160e50 | |||
| 3446fb901b | |||
| 7adaab5733 | |||
| 80ef645fc6 | |||
| fe818f5f4c | |||
| 8808556fe1 | |||
| bac0751241 | |||
| 830d05dc9d | |||
| dcde1bd0df | |||
| 88dc37bd0a | |||
| dd1f3a9039 | |||
| c6636c0f5a | |||
| cdde92015d | |||
| aeff0bf11c | |||
| 01f093f082 | |||
| 6bd3005b95 | |||
| 33b21b6b13 | |||
| 993c8413cc | |||
| 2ebd6a6a95 | |||
| 52f0e2e63a | |||
| 6f7801056c | |||
| d7096e9610 | |||
| 045e04eb51 | |||
| 2ff51c4e91 | |||
| 6f3b95d41d | |||
| 1dcb5f2d8e | |||
| 604348dbe7 | |||
| 67262d3a20 | |||
| cc114d68ce | |||
| a5ee3c69a7 | |||
| ef92fba5db | |||
| aec6f305ee | |||
| 94697dd7e2 | |||
| 49a177d557 | |||
| 4fa05e3616 | |||
| 8e592ce458 | |||
| 02ad17673f | |||
| ae7670eb7e | |||
| a9065dcda2 | |||
| 988a23aadf | |||
| 75be3de9ee | |||
| 13591019ca | |||
| 02b4c7b4fe | |||
| 9a1874935c | |||
| da0b4d3d02 | |||
| 1281bc00aa | |||
| 42869666b8 | |||
| c981df6bc9 | |||
| 3f6c46d7ae | |||
| 68851347e9 | |||
| 42a7910b3e | |||
| 7d32db0b08 | |||
| bb5f184139 | |||
| 5d2ad902fb | |||
| b6ea3877b5 | |||
| f04b73eacf | |||
| e413e7d434 | |||
| 19d5c8235f | |||
| e59eba56bb | |||
| b99c6e7764 | |||
| 787a1bca4c | |||
| 0548cec1d7 | |||
| c047094add | |||
| 76282069d2 | |||
| fb7237e248 | |||
| e84c4aedbd | |||
| fa40c1480a | |||
| e03939e3c9 | |||
| 264f296ab7 | |||
| eeccb6f1f6 | |||
| 5697159eaa | |||
| 32c57e5b4f | |||
| d69511997b | |||
| 5aa90d8cee | |||
| 44fe1e6709 | |||
| 211676866f | |||
| 56978664e6 | |||
| 64ed86131a | |||
| c8343cfd47 | |||
| 9c284cd3e1 | |||
| a1a84a6e21 | |||
| 0a3fd4029e | |||
| d6eea4cc57 | |||
| 599823deeb | |||
| 7699362a60 | |||
| ffa3aeb4fc |
102
.gitea/workflows/be-img-build-and-push.yaml
Normal file
102
.gitea/workflows/be-img-build-and-push.yaml
Normal file
@ -0,0 +1,102 @@
|
||||
name: Full Build and Docker Push Workflow
|
||||
run-name: ${{ gitea.actor }} is running a full build and Docker push workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Builder:
|
||||
runs-on: catthacker-ubuntu-latest
|
||||
steps:
|
||||
- name: Info
|
||||
run: |
|
||||
echo "Triggered by ${{ gitea.event_name }} event"
|
||||
echo "Branch: ${{ gitea.ref }}"
|
||||
echo "Repository: ${{ gitea.repository }}"
|
||||
|
||||
- name: Update apt-get and install Maven
|
||||
run: |
|
||||
apt-get update -y
|
||||
apt-get install -y maven
|
||||
|
||||
- name: Clone the repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
java -version
|
||||
mvn -version
|
||||
mvn -B clean test -Dspring.profiles.active=test
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
#Build Docker image - Images must follow this naming convention: - {registry}/{owner}/{image}
|
||||
- name: Build Docker image
|
||||
env:
|
||||
GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }}
|
||||
OWNER: ${{ gitea.actor }}
|
||||
IMAGE_NAME: my-app
|
||||
run: |
|
||||
#docker build -t $GITEA_REGISTRY_URL/$OWNER/my-app:latest .
|
||||
# registry.domain.com/<username>/my-app:latest e.g. git.bepastem.com/eberen/my-app:latest
|
||||
docker build -t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:latest .
|
||||
|
||||
- name: Build and push Docker image using Buildx
|
||||
env:
|
||||
GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }}
|
||||
IMAGE_NAME: my-app-backend
|
||||
APP_VERSION: v1.0.0
|
||||
OWNER: ${{ gitea.actor }}
|
||||
LOGIN_TOKEN: ${{ secrets.LOGIN_TOKEN }}
|
||||
run: |
|
||||
docker buildx build --platform linux/amd64 -t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:$APP_VERSION .
|
||||
#docker buildx build \
|
||||
# --platform linux/amd64 \
|
||||
# #-t $GITEA_REGISTRY_URL/$OWNER/$IMAGE_NAME:latest \
|
||||
# #--push \
|
||||
# .
|
||||
|
||||
- name: Push Docker image to Gitea Registry
|
||||
env:
|
||||
GITEA_USERNAME: ${{ gitea.actor }}
|
||||
LOGIN_TOKEN: ${{ secrets.LOGIN_TOKEN }}
|
||||
GITEA_REGISTRY_URL: ${{ vars.REGISTRY_URL }}
|
||||
run: |
|
||||
# Login securely
|
||||
echo "$LOGIN_TOKEN" | docker login $GITEA_REGISTRY_URL --username $GITEA_USERNAME --password-stdin
|
||||
docker push $GITEA_REGISTRY_URL/$GITEA_USERNAME/my-app:latest
|
||||
docker logout $GITEA_REGISTRY_URL
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
docker images -a
|
||||
docker system prune -f
|
||||
docker images
|
||||
echo "Cleanup done."
|
||||
|
||||
- name: Prepare deploy script
|
||||
run: |
|
||||
mkdir -p .gitea/scripts
|
||||
echo "echo Deploying..." > .gitea/scripts/deploy-backend.sh
|
||||
chmod +x .gitea/scripts/deploy-backend.sh
|
||||
|
||||
- name: Deploy to server via deploy script
|
||||
run: |
|
||||
ls -a
|
||||
ls .gitea/scripts/
|
||||
chmod +x .gitea/scripts/deploy-backend.sh
|
||||
#.gitea/scripts/deploy-backend.sh ${{ gitea.actor }} ${{ vars.REGISTRY_URL }} my-app-backend v1.0.0
|
||||
.gitea/scripts/deploy-backend.sh
|
||||
137
.gitea/workflows/checks-and-policy.yml
Normal file
137
.gitea/workflows/checks-and-policy.yml
Normal file
@ -0,0 +1,137 @@
|
||||
name: checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
frontend-jobs:
|
||||
name: Set up Node and other necessary dependencies for Frontend Tests and Build
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # skip
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Info
|
||||
run: |
|
||||
echo "Triggered by ${{ gitea.event_name }} event"
|
||||
echo "Branch: ${{ gitea.ref }}"
|
||||
echo "Repository: ${{ gitea.repository }}"
|
||||
|
||||
- name: Install Node.js, npm, and Yarn (optimized)
|
||||
run: |
|
||||
set -e
|
||||
echo "Preparing Node.js, npm, and Yarn setup..."
|
||||
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
echo "→ Using apt (Debian/Ubuntu)..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq curl ca-certificates >/dev/null
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1
|
||||
apt-get install -y -qq nodejs >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
echo "→ Using apk (Alpine)..."
|
||||
apk add --no-cache curl nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
echo "→ Using dnf (Fedora/RHEL)..."
|
||||
dnf install -y -q curl ca-certificates nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
else
|
||||
echo "No supported package manager found (apt, apk, dnf)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js, npm, and Yarn installed successfully:"
|
||||
node -v
|
||||
npm -v
|
||||
yarn -v
|
||||
|
||||
- name: Checkout repository manually
|
||||
env:
|
||||
TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
CLONE_URL: ${{ vars.CLONE_URL }}
|
||||
run: |
|
||||
echo "Cloning from $CLONE_URL"
|
||||
echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN"
|
||||
CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#")
|
||||
git clone --quiet "$CLONE_URL_WITH_AUTH" .
|
||||
echo "${{ gitea.repository }} cloned successfully."
|
||||
|
||||
# Install frontend dependencies and run tests
|
||||
- name: Install dependencies, run tests and build frontend
|
||||
#uses: actions/cache@v3
|
||||
working-directory: ./frontend
|
||||
with:
|
||||
path: |
|
||||
~/.yarn/cache
|
||||
./node_modules
|
||||
key: frontend-${{ runner.os }}-yarn-${{ hashFiles('frontend/yarn.lock') }}
|
||||
restore-keys: |
|
||||
frontend-${{ runner.os }}-yarn-
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
yarn lint
|
||||
yarn build
|
||||
#yarn test --watchAll=false --ci
|
||||
|
||||
# Ensure SSH and SCP are installed and functional
|
||||
- name: Setup SSH and SCP
|
||||
run: |
|
||||
set -e
|
||||
echo "Ensuring SSH and SCP are available..."
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq openssh-client >/dev/null
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
apk add --no-cache openssh >/dev/null
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y -q openssh-clients >/dev/null
|
||||
else
|
||||
echo "No supported package manager found for SSH installation."
|
||||
exit 1
|
||||
fi
|
||||
echo "SSH and SCP successfully installed:"
|
||||
ssh -V
|
||||
scp -V || echo "SCP version info not available but command exists."
|
||||
|
||||
- name: Publish frontend .next build to Plesk server
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ vars.SSH_PRIVATE_KEY }}
|
||||
SERVER_IP: ${{ vars.SERVER_IP }}
|
||||
DOMAIN_NAME: ${{ vars.DOMAIN_NAME }} # optional, helps locate vhost
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
echo "Deploying to server: $SERVER_IP"
|
||||
echo "Deploying to domain: $DOMAIN_NAME"
|
||||
|
||||
#Define Plesk web root (update DOMAIN_NAME in Gitea variables)
|
||||
WEB_ROOT="/var/www/vhosts/${DOMAIN_NAME}/httpdocs"
|
||||
|
||||
cat ~/.ssh/id_rsa | head -3
|
||||
|
||||
# Convert if it's a PEM key
|
||||
if grep -q "BEGIN PRIVATE KEY" ~/.ssh/id_rsa; then
|
||||
echo "Converting PKCS#8 key to OpenSSH-compatible RSA key..."
|
||||
openssl rsa -in ~/.ssh/id_rsa -out ~/.ssh/id_rsa.openssh >/dev/null 2>&1
|
||||
mv ~/.ssh/id_rsa.openssh ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
fi
|
||||
|
||||
echo "SSH key ready for use:"
|
||||
ssh-keygen -lf ~/.ssh/id_rsa
|
||||
|
||||
ssh -o StrictHostKeyChecking=no root@$SERVER_IP "mkdir -p $WEB_ROOT/.next"
|
||||
ssh -o StrictHostKeyChecking=no root@$SERVER_IP "mkdir -p $WEB_ROOT/public/.htaccess"
|
||||
|
||||
#Copy only the .next build folder to the server
|
||||
scp -o StrictHostKeyChecking=no -r frontend/.next root@$SERVER_IP:$WEB_ROOT/
|
||||
|
||||
echo "Frontend .next build successfully deployed to $SERVER_IP:$WEB_ROOT/.next"
|
||||
112
.gitea/workflows/demo.yaml
Normal file
112
.gitea/workflows/demo.yaml
Normal file
@ -0,0 +1,112 @@
|
||||
name: Gitea Actions Demo
|
||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Explore-Gitea-Actions-2:
|
||||
name: "Explore Gitea Actions - 2"
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # skip
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Info
|
||||
run: |
|
||||
echo "Triggered by ${{ gitea.event_name }} event"
|
||||
echo "Branch: ${{ gitea.ref }}"
|
||||
echo "Repository: ${{ gitea.repository }}"
|
||||
|
||||
- name: Install Node.js, npm, and Yarn (optimized)
|
||||
run: |
|
||||
set -e
|
||||
echo "Preparing Node.js, npm, and Yarn setup..."
|
||||
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
echo "→ Using apt (Debian/Ubuntu)..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq curl ca-certificates >/dev/null
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1
|
||||
apt-get install -y -qq nodejs >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
echo "→ Using apk (Alpine)..."
|
||||
apk add --no-cache curl nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
echo "→ Using dnf (Fedora/RHEL)..."
|
||||
dnf install -y -q curl ca-certificates nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
else
|
||||
echo "No supported package manager found (apt, apk, dnf)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js, npm, and Yarn installed successfully:"
|
||||
node -v
|
||||
npm -v
|
||||
yarn -v
|
||||
|
||||
- name: Checkout repository manually
|
||||
env:
|
||||
TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
CLONE_URL: ${{ vars.CLONE_URL }}
|
||||
run: |
|
||||
echo "Cloning from $CLONE_URL"
|
||||
echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN"
|
||||
CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#")
|
||||
git clone --quiet "$CLONE_URL_WITH_AUTH" .
|
||||
echo "${{ gitea.repository }} cloned successfully."
|
||||
|
||||
- name: List repo files
|
||||
run: |
|
||||
ls .
|
||||
node -v
|
||||
npm -v
|
||||
yarn -v
|
||||
|
||||
- name: Done
|
||||
run: echo "Workflow finished."
|
||||
|
||||
#blish-backend-docker-image:
|
||||
#name: Build and publish backend Docker image to server
|
||||
#runs-on: ubuntu-latest
|
||||
#needs: backend-jobs
|
||||
#steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Set up Docker
|
||||
# run: |
|
||||
# apt-get update -y
|
||||
# apt-get install -y docker.io
|
||||
#
|
||||
# - name: Build Docker image
|
||||
# run: |
|
||||
# docker build -t backend-app:latest .
|
||||
#
|
||||
# - name: Save Docker image as tarball
|
||||
# run: docker save backend-app:latest -o backend-app.tar
|
||||
#
|
||||
# - name: Copy Docker image to remote server and load it
|
||||
# env:
|
||||
# SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
# SERVER_IP: <ip>
|
||||
# run: |
|
||||
# mkdir -p ~/.ssh
|
||||
# echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
# chmod 600 ~/.ssh/id_rsa
|
||||
# scp -o StrictHostKeyChecking=no backend-app.tar root@$SERVER_IP:/tmp/backend-app.tar
|
||||
# ssh -o StrictHostKeyChecking=no root@$SERVER_IP "
|
||||
# docker load -i /tmp/backend-app.tar &&
|
||||
# docker tag backend-app:latest backend-app:latest &&
|
||||
# echo 'Backend Docker image loaded successfully'
|
||||
# "
|
||||
11
.gitea/workflows/scripts/deploy-backend.sh
Normal file
11
.gitea/workflows/scripts/deploy-backend.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
docker compose -f docker-compose-dev.yml down --remove-orphans --volumes
|
||||
|
||||
docker system prune -f
|
||||
|
||||
docker compose -f docker-compose-dev.yml up -d
|
||||
|
||||
echo "Started all containers successfully"
|
||||
159
.gitea/workflows/test.yml
Normal file
159
.gitea/workflows/test.yml
Normal file
@ -0,0 +1,159 @@
|
||||
name: checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
frontend-jobs:
|
||||
name: Set up Node and other necessary dependencies for Frontend Tests and Build
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # skip
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Info
|
||||
run: |
|
||||
echo "Triggered by ${{ gitea.event_name }} event"
|
||||
echo "Branch: ${{ gitea.ref }}"
|
||||
echo "Repository: ${{ gitea.repository }}"
|
||||
|
||||
- name: Install Node.js, npm, and Yarn (optimized)
|
||||
run: |
|
||||
set -e
|
||||
echo "Preparing Node.js, npm, and Yarn setup..."
|
||||
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
echo "→ Using apt (Debian/Ubuntu)..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq curl ca-certificates >/dev/null
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null 2>&1
|
||||
apt-get install -y -qq nodejs >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
echo "→ Using apk (Alpine)..."
|
||||
apk add --no-cache curl nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
echo "→ Using dnf (Fedora/RHEL)..."
|
||||
dnf install -y -q curl ca-certificates nodejs npm >/dev/null
|
||||
npm install -g yarn --silent
|
||||
|
||||
else
|
||||
echo "No supported package manager found (apt, apk, dnf)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js, npm, and Yarn installed successfully:"
|
||||
node -v
|
||||
npm -v
|
||||
yarn -v
|
||||
|
||||
- name: Checkout repository manually
|
||||
env:
|
||||
TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
CLONE_URL: ${{ vars.CLONE_URL }}
|
||||
run: |
|
||||
echo "Cloning from $CLONE_URL"
|
||||
echo "Cloning ALL_REPO_TOKEN $ALL_REPO_TOKEN"
|
||||
CLONE_URL_WITH_AUTH=$(echo "$CLONE_URL" | sed "s#https://#https://$ALL_REPO_TOKEN@#")
|
||||
git clone --quiet "$CLONE_URL_WITH_AUTH" .
|
||||
echo "${{ gitea.repository }} cloned successfully."
|
||||
|
||||
# Install frontend dependencies and run tests
|
||||
- name: Install frontend dependencies and run tests
|
||||
#uses: actions/cache@v3
|
||||
working-directory: ./frontend
|
||||
with:
|
||||
path: |
|
||||
~/.yarn/cache
|
||||
./node_modules
|
||||
key: frontend-${{ runner.os }}-yarn-${{ hashFiles('frontend/yarn.lock') }}
|
||||
restore-keys: |
|
||||
frontend-${{ runner.os }}-yarn-
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
#yarn test --watchAll=false --ci
|
||||
|
||||
#Check frontend linting
|
||||
- name: Check frontend linting
|
||||
working-directory: ./frontend
|
||||
run: yarn lint
|
||||
|
||||
backend-jobs:
|
||||
name: Set up Java for Backend Tests and Build
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # skip
|
||||
runs-on: ubuntu-latest
|
||||
#container:
|
||||
#image: eclipse-temurin:21-jdk
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Java 21 + Maven
|
||||
run: |
|
||||
echo "Detecting package manager..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
PM=apt
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
PM=apk
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
PM=yum
|
||||
else
|
||||
echo "No known package manager found. Will install JDK manually."
|
||||
PM=none
|
||||
fi
|
||||
|
||||
echo "Package manager detected: $PM"
|
||||
|
||||
if [ "$PM" = "apt" ]; then
|
||||
apt-get update -y
|
||||
apt-get install -y openjdk-21-jdk maven wget tar
|
||||
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
|
||||
elif [ "$PM" = "apk" ]; then
|
||||
apk update
|
||||
apk add openjdk21 maven wget tar
|
||||
JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
elif [ "$PM" = "yum" ]; then
|
||||
yum install -y java-21-openjdk-devel maven wget tar
|
||||
JAVA_HOME=/usr/lib/jvm/java-21-openjdk
|
||||
else
|
||||
# Fallback: manual download
|
||||
JDK_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
|
||||
mkdir -p /opt/jdk
|
||||
wget -q -O /tmp/jdk.tar.gz $JDK_URL
|
||||
tar -xzf /tmp/jdk.tar.gz -C /opt/jdk --strip-components=1
|
||||
JAVA_HOME=/opt/jdk
|
||||
fi
|
||||
|
||||
# Export JAVA_HOME and update PATH for subsequent steps
|
||||
echo "JAVA_HOME=$JAVA_HOME" >> $GITEA_ENV
|
||||
echo "$JAVA_HOME/bin" >> $GITEA_PATH
|
||||
export JAVA_HOME=$JAVA_HOME
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
|
||||
# Verify Java and Maven installation
|
||||
java -version
|
||||
mvn -version
|
||||
|
||||
- name: Verify Java setup
|
||||
run: |
|
||||
java -version
|
||||
|
||||
- name: Verify Java setup
|
||||
run: |
|
||||
ls -l $JAVA_HOME/bin/java
|
||||
file $JAVA_HOME/bin/java || true
|
||||
java -version
|
||||
|
||||
# Run backend tests and build
|
||||
- name: Test & build backend
|
||||
working-directory: .
|
||||
run:
|
||||
mvn clean test -Dspring.profiles.active=test
|
||||
|
||||
- name: Done
|
||||
run: echo "Workflow successfully completed."
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@ -0,0 +1,47 @@
|
||||
# ===== Stage 1: Build with Maven =====
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
|
||||
ARG MAVEN_VERSION=3.9.9
|
||||
ENV MAVEN_HOME=/opt/maven
|
||||
ENV PATH=${MAVEN_HOME}/bin:${PATH}
|
||||
|
||||
# Install required tools quietly and set up Maven
|
||||
RUN set -eux; \
|
||||
apk add --no-cache curl tar bash openssl >/dev/null; \
|
||||
echo "Downloading Maven ${MAVEN_VERSION}..."; \
|
||||
curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o /tmp/maven.tar.gz; \
|
||||
mkdir -p /opt >/dev/null; \
|
||||
tar -xzf /tmp/maven.tar.gz -C /opt >/dev/null; \
|
||||
ln -s /opt/apache-maven-${MAVEN_VERSION} ${MAVEN_HOME}; \
|
||||
rm /tmp/maven.tar.gz; \
|
||||
echo "✅ Maven ${MAVEN_VERSION} installed successfully."
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy pom.xml and prefetch dependencies silently
|
||||
COPY pom.xml .
|
||||
RUN mvn -B -q dependency:go-offline
|
||||
|
||||
# Copy source code
|
||||
COPY src ./src
|
||||
|
||||
# Build project (skip tests, quiet mode)
|
||||
RUN mvn -B -q clean package -DskipTests
|
||||
|
||||
# ===== Stage 2: Runtime image =====
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
|
||||
RUN addgroup -S spring && adduser -S spring -G spring
|
||||
USER spring:spring
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /build/target/*.jar app.jar
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# Optional: Health check (Spring Boot actuator)
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health | grep '"status":"UP"' || exit 1
|
||||
|
||||
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
|
||||
68
README.md
68
README.md
@ -1 +1,69 @@
|
||||
# my-app
|
||||
|
||||
1. RELEASE BRANCH FOR MY_APP
|
||||
2. PATCH (HOTFIX) RELEASES FOR MY_APP
|
||||
|
||||
# 1. RELEASE BRANCH FOR MY_APP
|
||||
|
||||
## Step 1: Update main/master branch, ensure both local and remote are same, to do so,
|
||||
|
||||
- `git checkout main`
|
||||
- `git pull`
|
||||
- `git pull --ff-only` (ignore)
|
||||
|
||||
## Step 2: Create a versioned release branch with pattern: release/v<major.minor.patch> e.g. release/v1.4.0 where 1.4.0 is the version to be released
|
||||
|
||||
- `git checkout -b release/v<major>.<minor>.<patch>`
|
||||
|
||||
## Step 3: Update the app version, commit and tag
|
||||
|
||||
- update the app version in:
|
||||
1. _frontend/package.json_ manually
|
||||
2. _frontend/package.json_ by running `yarn install` after 1. above
|
||||
3. _src/build.gradle_ manually
|
||||
4. _Swagger config_ manually
|
||||
5. _Dockerfile_ manually
|
||||
_env (only during image build & deployment)_ manually
|
||||
- add changes
|
||||
`git add .`
|
||||
- commit changes
|
||||
`git commit -m "Set version to v<major>.<minor>.<patch>"`
|
||||
- push changes
|
||||
`git push --set-upstream origin release/v<major>.<minor>.<patch>`
|
||||
- Create and review the release pull request, then MERGE the release branch
|
||||
`git merge --no-ff release/v<major>.<minor>.<patch> -m "Merge release v<major>.<minor>.<patch>"`.
|
||||
- checkout and update main/master branch
|
||||
`git checkout main/master` & `git pull origin main/master`
|
||||
- Create tag with the version number (format is very important) `git tag v<major>.<minor>.<patch> -m "Release v<major>.<minor>.<patch>"`
|
||||
- Push tag
|
||||
`git push origin v<major>.<minor>.<patch>`
|
||||
- Create a GitHub Release from the tag - (from UI)
|
||||
|
||||
# 2. PATCH (hotfix) RELEASES FOR MY_APP
|
||||
|
||||
## Step 1: Update main/master branch, ensure both local and remote are same, to do so,
|
||||
|
||||
- `git checkout main`
|
||||
- `git pull`
|
||||
|
||||
## Step 2: Create a versioned hotfix branch with pattern: hotfix/v<major.minor.patch> e.g. hotfix/v1.4.1 where 1.4.1 is the hotfix version to be released
|
||||
|
||||
- `git checkout -b hotfix/v<major>.<minor>.<patch>`
|
||||
|
||||
## Step 3: Update the app version, commit and tag
|
||||
|
||||
- update the app version in:
|
||||
1. _frontend/package.json_ manually
|
||||
2. _frontend/package.json_ by running `yarn install` after 1. above
|
||||
3. _src/build.gradle_ manually
|
||||
4. _Swagger config_ manually
|
||||
5. _Dockerfile_ manually
|
||||
_env (only during image build & deployment)_ manually
|
||||
- add changes `git add .`
|
||||
- commit changes `git commit -m "Set hotfix to v<major>.<minor>.<patch>"`
|
||||
- push chnages `git push --set-upstream origin hotfix/v<major>.<minor>.<patch>`
|
||||
- Create and review the hotfix pull request, then MERGE the hotfix branch `git merge --no-ff hotfix/v<major>.<minor>.<patch> -m "Merge hotfix v<major>.<minor>.<patch>"`.
|
||||
- checkout and update main/master branch `git checkout main/master` & `git pull origin main/master`
|
||||
- Create tag with the hotfix version number (format is very important) `git tag v<major>.<minor>.<patch> -m "Hotfix v<major>.<minor>.<patch>"`
|
||||
- Push tag `git push origin v<major>.<minor>.<patch>`
|
||||
- Create a GitHub Release from the tag - (from UI)
|
||||
|
||||
8
build-image.sh
Normal file
8
build-image.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#! /bin/bash
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
export $(grep -v '^#' C:/atrizult/projects/.env | xargs)
|
||||
|
||||
#Build
|
||||
docker buildx build --no-cache -t eberen/services:backend-${VERSION} .
|
||||
echo "Docker Image Built Successfully"
|
||||
202
docker/docker-compose-dev.yml
Normal file
202
docker/docker-compose-dev.yml
Normal file
@ -0,0 +1,202 @@
|
||||
networks:
|
||||
hps-proxy-network:
|
||||
driver: bridge
|
||||
external: false
|
||||
|
||||
volumes:
|
||||
auth_api_data:
|
||||
acc_api_data:
|
||||
lab_api_data:
|
||||
dash_api_data:
|
||||
audit_api_data:
|
||||
|
||||
services:
|
||||
keycloak_api:
|
||||
image: quay.io/keycloak/keycloak:26.2.5
|
||||
container_name: keycloak
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
environment:
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
||||
KC_DB: mariadb
|
||||
KC_DB_URL_HOST: dev-keycloak.healthprosuite.com #host.docker.internal
|
||||
KC_DB_URL_PORT: 3306
|
||||
KC_DB_URL_DATABASE: keycloak
|
||||
KC_DB_USERNAME: keycloakdevuser
|
||||
KC_DB_PASSWORD: keycloakdevuser
|
||||
KC_HEALTH_ENABLED: true
|
||||
KC_METRICS_ENABLED: true
|
||||
KC_HTTP_ENABLED: true
|
||||
KC_HOSTNAME: https://dev-keycloak.healthprosuite.com #host.docker.internal
|
||||
KC_HOSTNAME_STRICT: false
|
||||
KC_HOSTNAME_STRICT_HTTPS: true #Only for local dev
|
||||
KC_PROXY: edge
|
||||
|
||||
command: start-dev
|
||||
#command: start --optimized #production mode
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:management
|
||||
container_name: rabbitmq
|
||||
hostname: "${RABBITMQ_HOST}"
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:5672:5672" # RabbitMQ communication port
|
||||
- "127.0.0.1:15672:15672" # RabbitMQ Management UI
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: admin
|
||||
RABBITMQ_DEFAULT_PASS: securepassword
|
||||
RABBITMQ_NODENAME: rabbit@localhost
|
||||
RABBITMQ_PORT: 5672
|
||||
RABBITMQ_VHOST: /
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
volumes:
|
||||
- ../rabbitmq_data:/var/lib/rabbitmq
|
||||
|
||||
auth_api:
|
||||
#container_name: auth_backend
|
||||
image: healthprosuite/services:auth-backend-1.0.0
|
||||
ports:
|
||||
- "127.0.0.1:5021:5021"
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE}"
|
||||
SPRING_DATASOURCE_URL: "${SPRING_DATASOURCE_URL_AUTH}"
|
||||
SPRING_DATASOURCE_USERNAME: "${SPRING_DATASOURCE_USERNAME_AUTH}"
|
||||
SPRING_DATASOURCE_PASSWORD: "${SPRING_DATASOURCE_PASSWORD_AUTH}"
|
||||
SPRING_DATASOURCE_DRIVERCLASSNAME: "${SPRING_DATASOURCE_DRIVERCLASSNAME}"
|
||||
AUTHSERVICE_JWT_SECRET: "${AUTHSERVICE_JWT_SECRET}"
|
||||
SPRING_RABBITMQ_HOST: "${SPRING_RABBITMQ_HOST}"
|
||||
SPRING_RABBITMQ_USERNAME: "${SPRING_RABBITMQ_USERNAME}"
|
||||
SPRING_RABBITMQ_PASSWORD: "${SPRING_RABBITMQ_PASSWORD}"
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENT_ID: oic-dashboard-oauth2-client-credentials
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENT_SECRET: VrWiz6aQoyPWwzcdBoNj4CR26ZJqPS4m
|
||||
AUTHSERVICE_KEYCLOAK_HOST: https://dev-keycloak.healthprosuite.com #http://host.docker.internal:8080
|
||||
volumes:
|
||||
- auth_api_data:/app
|
||||
- ~/development/healthprosuite/hospitals:/hospitals/logo
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
- keycloak_api
|
||||
|
||||
acc_api:
|
||||
container_name: acc_backend
|
||||
image: healthprosuite/services:acc-backend-${APP_VERSION}
|
||||
ports:
|
||||
- "127.0.0.1:5022:5022"
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE}"
|
||||
SPRING_DATASOURCE_URL: "${SPRING_DATASOURCE_URL_ACC}"
|
||||
SPRING_DATASOURCE_USERNAME: "${SPRING_DATASOURCE_USERNAME_ACC}"
|
||||
SPRING_DATASOURCE_PASSWORD: "${SPRING_DATASOURCE_PASSWORD_ACC}"
|
||||
SPRING_DATASOURCE_DRIVERCLASSNAME: "${SPRING_DATASOURCE_DRIVERCLASSNAME}"
|
||||
SPRING_RABBITMQ_HOST: "${SPRING_RABBITMQ_HOST}"
|
||||
SPRING_RABBITMQ_USERNAME: "${SPRING_RABBITMQ_USERNAME}"
|
||||
SPRING_RABBITMQ_PASSWORD: "${SPRING_RABBITMQ_PASSWORD}"
|
||||
ACCOUNTING_SERVICE_JWT_SECRET: "${AUTHSERVICE_JWT_SECRET}"
|
||||
volumes:
|
||||
- acc_api_data:/app
|
||||
- ~/hospitals:/hospitals
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
- auth_api
|
||||
|
||||
lab_api:
|
||||
container_name: lab_backend
|
||||
image: healthprosuite/services:lab-backend-${APP_VERSION}
|
||||
ports:
|
||||
- "127.0.0.1:5023:5023"
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE}"
|
||||
SPRING_DATASOURCE_URL: "${SPRING_DATASOURCE_URL_LAB}"
|
||||
SPRING_DATASOURCE_USERNAME: "${SPRING_DATASOURCE_USERNAME_LAB}"
|
||||
SPRING_DATASOURCE_PASSWORD: "${SPRING_DATASOURCE_PASSWORD_LAB}"
|
||||
SPRING_DATASOURCE_DRIVERCLASSNAME: "${SPRING_DATASOURCE_DRIVERCLASSNAME}"
|
||||
SPRING_RABBITMQ_HOST: "${SPRING_RABBITMQ_HOST}"
|
||||
SPRING_RABBITMQ_USERNAME: "${SPRING_RABBITMQ_USERNAME}"
|
||||
SPRING_RABBITMQ_PASSWORD: "${SPRING_RABBITMQ_PASSWORD}"
|
||||
LAB_SERVICE_JWT_SECRET: "${AUTHSERVICE_JWT_SECRET}"
|
||||
volumes:
|
||||
- lab_api_data:/app
|
||||
- ~/development/healthprosuite/laboratory:/laboratory
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
- auth_api
|
||||
- acc_api
|
||||
|
||||
audit_api:
|
||||
container_name: audit_backend
|
||||
image: healthprosuite/services:audit-backend-${APP_VERSION}
|
||||
ports:
|
||||
- "127.0.0.1:5025:5025"
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE}"
|
||||
SPRING_DATASOURCE_URL: "${SPRING_DATASOURCE_URL_AUDIT}"
|
||||
SPRING_DATASOURCE_USERNAME: "${SPRING_DATASOURCE_USERNAME_AUDIT}"
|
||||
SPRING_DATASOURCE_PASSWORD: "${SPRING_DATASOURCE_PASSWORD_AUDIT}"
|
||||
SPRING_RABBITMQ_HOST: "${SPRING_RABBITMQ_HOST}"
|
||||
AUDIT_SERVICE_JWT_SECRET: "${AUTHSERVICE_JWT_SECRET}"
|
||||
volumes:
|
||||
- audit_api_data:/app
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
- auth_api
|
||||
- lab_api
|
||||
- acc_api
|
||||
|
||||
dash_api:
|
||||
container_name: dash_backend
|
||||
image: healthprosuite/services:dash-backend-${APP_VERSION}
|
||||
#profiles:
|
||||
#- skipme
|
||||
ports:
|
||||
- "127.0.0.1:2020:2020"
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: "${SPRING_PROFILES_ACTIVE}"
|
||||
SPRING_DATASOURCE_URL: "${SPRING_DATASOURCE_URL_DASH}"
|
||||
SPRING_DATASOURCE_USERNAME: "${SPRING_DATASOURCE_USERNAME_DASH}"
|
||||
SPRING_DATASOURCE_PASSWORD: "${SPRING_DATASOURCE_PASSWORD_DASH}"
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENT_ID: oic-dashboard-oauth2-client-credentials
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KEYCLOAK_CLIENT_SECRET: VrWiz6aQoyPWwzcdBoNj4CR26ZJqPS4m
|
||||
SPRING_SECURITY_OAUTH2_RESOURCE_SERVER_JWT_JWT_SET_URI: https://dev-keycloak.healthprosuite.com
|
||||
SPRING_KEYCLOAK_HOST: https://dev-keycloak.healthprosuite.com #http://127.0.0.1:8080 #http://host.docker.internal:8080
|
||||
APPLICATION_WEB_CLIENT_HOST: https://dev-auth-service.healthprosuite.com/auth/v1 #http://host.docker.internal:5021/auth/v1
|
||||
APPLICATION_KEYCLOAK_SERVER_URL: https://dev-keycloak.healthprosuite.com #http://127.0.0.1:8080 #http://host.docker.internal:8080
|
||||
APPLICATION_KEYCLOAK_REALM: Dashboard-HPS-Realm
|
||||
volumes:
|
||||
- dash_api_data:/app
|
||||
networks:
|
||||
- hps-proxy-network
|
||||
depends_on:
|
||||
- auth_api
|
||||
- acc_api
|
||||
- keycloak_api
|
||||
|
||||
icd-api:
|
||||
container_name: icd-api
|
||||
image: whoicd/icd-api
|
||||
ports:
|
||||
- "0.0.0.0:8000:80"
|
||||
environment:
|
||||
- acceptLicense=true # Required parameter to agree with license
|
||||
- saveAnalytics=${SAVE_ANALYTICS:-true} # Optional: set to true to send analytics to WHO
|
||||
- include=${INCLUDE:-2024-01_en} # Optional: set the language or release version (e.g., 2024-01_en, 2024-01_es for Spanish)
|
||||
41
frontend/.gitignore
vendored
Normal file
41
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
36
frontend/README.md
Normal file
36
frontend/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
16
frontend/eslint.config.mjs
Normal file
16
frontend/eslint.config.mjs
Normal file
@ -0,0 +1,16 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
7
frontend/next.config.ts
Normal file
7
frontend/next.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
25
frontend/package.json
Normal file
25
frontend/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.4.7",
|
||||
"@eslint/eslintrc": "^3"
|
||||
}
|
||||
}
|
||||
8
frontend/public/.htaccess
Normal file
8
frontend/public/.htaccess
Normal file
@ -0,0 +1,8 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteRule ^index\.html$ - [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule . /index.html [L]
|
||||
</IfModule>
|
||||
1
frontend/public/file.svg
Normal file
1
frontend/public/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
frontend/public/globe.svg
Normal file
1
frontend/public/globe.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/public/next.svg
Normal file
1
frontend/public/next.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/public/vercel.svg
Normal file
1
frontend/public/vercel.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
frontend/public/window.svg
Normal file
1
frontend/public/window.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
BIN
frontend/src/app/favicon.ico
Normal file
BIN
frontend/src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
42
frontend/src/app/globals.css
Normal file
42
frontend/src/app/globals.css
Normal file
@ -0,0 +1,42 @@
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
32
frontend/src/app/layout.tsx
Normal file
32
frontend/src/app/layout.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable}`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
167
frontend/src/app/page.module.css
Normal file
167
frontend/src/app/page.module.css
Normal file
@ -0,0 +1,167 @@
|
||||
.page {
|
||||
--gray-rgb: 0, 0, 0;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
|
||||
|
||||
--button-primary-hover: #383838;
|
||||
--button-secondary-hover: #f2f2f2;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 20px 1fr 20px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
min-height: 100svh;
|
||||
padding: 80px;
|
||||
gap: 64px;
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.page {
|
||||
--gray-rgb: 255, 255, 255;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
||||
|
||||
--button-primary-hover: #ccc;
|
||||
--button-secondary-hover: #1a1a1a;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
grid-row-start: 2;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
font-family: var(--font-geist-mono);
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.main li:not(:last-of-type) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.main code {
|
||||
font-family: inherit;
|
||||
background: var(--gray-alpha-100);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
appearance: none;
|
||||
border-radius: 128px;
|
||||
height: 48px;
|
||||
padding: 0 20px;
|
||||
border: 1px solid transparent;
|
||||
transition:
|
||||
background 0.2s,
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a.primary {
|
||||
background: var(--foreground);
|
||||
color: var(--background);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
border-color: var(--gray-alpha-200);
|
||||
min-width: 158px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-row-start: 3;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer img {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
a.primary:hover {
|
||||
background: var(--button-primary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
a.secondary:hover {
|
||||
background: var(--button-secondary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.page {
|
||||
padding: 32px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.main {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.logo {
|
||||
filter: invert();
|
||||
}
|
||||
}
|
||||
95
frontend/src/app/page.tsx
Normal file
95
frontend/src/app/page.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol>
|
||||
<li>
|
||||
Get started by editing <code>src/app/page.tsx</code>.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
frontend/tsconfig.json
Normal file
27
frontend/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
2703
frontend/yarn.lock
Normal file
2703
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
15
pom.xml
15
pom.xml
@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.ebrains</groupId>
|
||||
<artifactId>cruddemo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>1.0.0</version>
|
||||
<name>cruddemo</name>
|
||||
<description>Demo database crud project for Spring Boot</description>
|
||||
<url/>
|
||||
@ -27,7 +27,7 @@
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -45,6 +45,17 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
package com.ebrains.cruddemo;
|
||||
|
||||
import com.ebrains.cruddemo.dao.StudentDAO;
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CruddemoApplication {
|
||||
@ -17,108 +11,4 @@ public class CruddemoApplication {
|
||||
SpringApplication.run(CruddemoApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner commandLineRunner(StudentDAO studentDAO) {
|
||||
return runner ->{
|
||||
createMultipleStudent(studentDAO);
|
||||
//queryForStudent(studentDAO);
|
||||
//queryForStudentByLastName(studentDAO);
|
||||
//updateStudent(studentDAO);
|
||||
//deleteStudent(studentDAO);
|
||||
//deleteAllStudent(studentDAO);
|
||||
};
|
||||
}
|
||||
|
||||
private void deleteAllStudent(StudentDAO studentDAO) {
|
||||
int numOfRowDeleted = studentDAO.deleteAll();
|
||||
System.out.println("Number of row deleted: " + numOfRowDeleted);
|
||||
}
|
||||
|
||||
|
||||
private void deleteStudent(StudentDAO studentDAO) {
|
||||
Student student = studentDAO.findById(3);
|
||||
System.out.println("Student to be deleted: "+student);
|
||||
studentDAO.delete(3);
|
||||
}
|
||||
|
||||
private void updateStudent(StudentDAO studentDAO) {
|
||||
//retrieve the student base on id
|
||||
Student student = studentDAO.findById(1);
|
||||
|
||||
//change the lastname
|
||||
System.out.println("updating the student ...");
|
||||
student.setLastName("Lobby");
|
||||
|
||||
//update the student
|
||||
studentDAO.update(student);
|
||||
|
||||
//Display the updated student
|
||||
System.out.println("student updated: "+student);
|
||||
}
|
||||
|
||||
private void queryForStudentByLastName(StudentDAO studentDAO) {
|
||||
//get the list of student
|
||||
List<Student> students = studentDAO.findByLastName("Doe");
|
||||
for (Student student : students) {
|
||||
System.out.println(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryForStudent(StudentDAO studentDAO) {
|
||||
//get the list of student
|
||||
List<Student> students = studentDAO.findAll();
|
||||
|
||||
//display the student
|
||||
for (Student student : students) {
|
||||
System.out.println(student);
|
||||
}
|
||||
}
|
||||
|
||||
private void createStudent(StudentDAO studentDAO) {
|
||||
System.out.println("Creating student ....");
|
||||
Student student = new Student("paul","okeke","paul@gmail.com");
|
||||
|
||||
//saving the student object
|
||||
System.out.println("saving student ....");
|
||||
studentDAO.save(student);
|
||||
|
||||
System.out.println("Student saved successfully.Generated Id: " + student.getId());
|
||||
}
|
||||
|
||||
private void createMultipleStudent(StudentDAO studentDAO) {
|
||||
System.out.println("Creating student ....");
|
||||
Student student = new Student("John","smith","smith@gmail.com");
|
||||
Student student1 = new Student("Mary","Doe","doe@gmail.com");
|
||||
Student student2= new Student("Eric","onu","onu@gmail.com");
|
||||
|
||||
//saving the multiple student object
|
||||
studentDAO.save(student);
|
||||
studentDAO.save(student1);
|
||||
studentDAO.save(student2);
|
||||
|
||||
System.out.println("Students saved successfully");
|
||||
}
|
||||
|
||||
private void readStudent(StudentDAO studentDAO) {
|
||||
//create a student object
|
||||
System.out.println("Creating student ....");
|
||||
Student student = new Student("Onowu","Igbo","igbo@gmail.com");
|
||||
|
||||
//save the save
|
||||
studentDAO.save(student);
|
||||
System.out.println("Student saved successfully");
|
||||
|
||||
//display id of the saved student
|
||||
long theId= student.getId();
|
||||
System.out.println("saved student successfully.Generated Id: " + theId);
|
||||
|
||||
//retrieved the student based on the id ie the primary key
|
||||
System.out.println("retrieving student saved successfully");
|
||||
//Student savedStudent= studentDAO.findById(theId);
|
||||
|
||||
//display the student
|
||||
//System.out.println("student retrieved successfully: " + savedStudent);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
package com.ebrains.cruddemo.dao;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface StudentDAO {
|
||||
void save(Student student);
|
||||
|
||||
Student findById(Integer id);
|
||||
|
||||
List<Student> findAll();
|
||||
|
||||
List<Student> findByLastName(String lastName);
|
||||
|
||||
void update(Student student);
|
||||
|
||||
void delete(Integer id);
|
||||
|
||||
int deleteAll();
|
||||
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
package com.ebrains.cruddemo.dao;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class StudentDAOImpl implements StudentDAO {
|
||||
//we define feilds for entity manager
|
||||
private EntityManager entityManager;
|
||||
|
||||
//eject entity manager using constructor injection
|
||||
public StudentDAOImpl(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
//implement the save method
|
||||
//this method saves the studnet to the database
|
||||
@Transactional
|
||||
@Override
|
||||
public void save(Student student) {
|
||||
entityManager.persist(student);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Student findById(Integer id) {
|
||||
//Student.class is the entity class
|
||||
return entityManager.find(Student.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Student> findAll() {
|
||||
//create the query
|
||||
TypedQuery<Student> query = entityManager.createQuery("from Student", Student.class);
|
||||
//TypedQuery<Student> query = entityManager.createQuery("from Student order by lastName desc", Student.class);
|
||||
|
||||
//return the list of student
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Student> findByLastName(String lastName) {
|
||||
//create the query
|
||||
TypedQuery<Student> query = entityManager.createQuery("from Student where lastName=:theData", Student.class);
|
||||
|
||||
//set query parameter
|
||||
query.setParameter("theData", lastName);
|
||||
|
||||
//return the list of student with the last name
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void update(Student student) {
|
||||
entityManager.merge(student);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Integer id) {
|
||||
//retrieve the student
|
||||
Student student = entityManager.find(Student.class, id);
|
||||
|
||||
//delete the student
|
||||
entityManager.remove(student);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteAll() {
|
||||
return entityManager.createQuery("delete from Student").executeUpdate();
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,70 +1,20 @@
|
||||
package com.ebrains.cruddemo.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
@Entity
|
||||
@Table(name="student")
|
||||
@Entity(name="student")
|
||||
@Table
|
||||
@Data
|
||||
public class Student {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
@Column
|
||||
private Long id;
|
||||
@Column(name = "first_name")
|
||||
@Column
|
||||
private String firstName;
|
||||
@Column(name = "last_name")
|
||||
@Column
|
||||
private String lastName;
|
||||
@Column(name = "email")
|
||||
@Column
|
||||
private String email;
|
||||
|
||||
public Student() {
|
||||
|
||||
}
|
||||
public Student(String firstName, String lastName, String email) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Student{" +
|
||||
"id=" + id +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package com.ebrains.cruddemo.repository;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface StudentRepository extends CrudRepository<Student, Long> {
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.ebrains.cruddemo.service;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public interface StudentService {
|
||||
Student getStudentById(long id);
|
||||
void saveStudent(Student student);
|
||||
void deleteStudentById(long id);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.ebrains.cruddemo.serviceimpl;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import com.ebrains.cruddemo.repository.StudentRepository;
|
||||
import com.ebrains.cruddemo.service.StudentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class StudentServiceImpl implements StudentService {
|
||||
private final StudentRepository studentRepository;
|
||||
|
||||
@Override
|
||||
public Student getStudentById(long id) {
|
||||
return studentRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveStudent(Student student) {
|
||||
studentRepository.save(student);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentById(long id) {
|
||||
studentRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
6
src/main/resources/application-test.properties
Normal file
6
src/main/resources/application-test.properties
Normal file
@ -0,0 +1,6 @@
|
||||
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
|
||||
@ -1,9 +1,10 @@
|
||||
spring.application.name=cruddemo
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/student_tracker
|
||||
spring.datasource.username=springstudent
|
||||
spring.datasource.password=springstudent
|
||||
spring.datasource.url=jdbc:mysql://localhost:33070/student_tracker
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=password
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
|
||||
|
||||
#Turn off the springboot logo
|
||||
spring.main.banner-mode=off
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.ebrains.cruddemo.serviceimpl;
|
||||
|
||||
import com.ebrains.cruddemo.entity.Student;
|
||||
import com.ebrains.cruddemo.repository.StudentRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import java.util.Optional;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ActiveProfiles("test")
|
||||
public class StudentServiceImplTest {
|
||||
|
||||
private final StudentRepository studentRepository = mock(StudentRepository.class);
|
||||
|
||||
@InjectMocks
|
||||
private StudentServiceImpl studentService;
|
||||
|
||||
@Test
|
||||
void getStudentById_found_returnsStudent() {
|
||||
Student s = new Student();
|
||||
when(studentRepository.findById(1L)).thenReturn(Optional.of(s));
|
||||
|
||||
Student result = studentService.getStudentById(1L);
|
||||
|
||||
assertSame(s, result);
|
||||
verify(studentRepository).findById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStudentById_notFound_returnsNull() {
|
||||
when(studentRepository.findById(2L)).thenReturn(Optional.empty());
|
||||
|
||||
Student result = studentService.getStudentById(2L);
|
||||
|
||||
assertNull(result);
|
||||
verify(studentRepository).findById(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveStudent_delegatesToRepository() {
|
||||
Student s = new Student();
|
||||
|
||||
studentService.saveStudent(s);
|
||||
|
||||
verify(studentRepository).save(s);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteStudentById_delegatesToRepository() {
|
||||
studentService.deleteStudentById(3L);
|
||||
|
||||
verify(studentRepository).deleteById(3L);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user