add datasiswa-app project for gitops

This commit is contained in:
Syifa 2025-10-15 16:07:57 +07:00
commit e8393f7e03
12 changed files with 348 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# === Node modules ===
node_modules/
backend/node_modules/
frontend/node_modules/
# === Logs ===
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# === Environment files ===
.env
.env.local
.env.development
.env.production
.env.test
# === Build output ===
frontend/build/
frontend/dist/
backend/dist/
# === Dependency locks (optional) ===
package-lock.json
yarn.lock
# === System files ===
.DS_Store
Thumbs.db
# === Docker artifacts ===
*.pid
*.tar

147
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,147 @@
stages:
- build
- push
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_IMAGE_BACKEND: $CI_REGISTRY/$CI_PROJECT_PATH/backend
DOCKER_IMAGE_FRONTEND: $CI_REGISTRY/$CI_PROJECT_PATH/frontend
GITOPS_REPO: "https://gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git"
# ==========================================
# 🔧 BEFORE SCRIPT (GLOBAL)
# ==========================================
before_script:
- echo "🔑 Logging in to GitLab Container Registry..."
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
# ==========================================
# 🔹 BUILD BACKEND IMAGE
# ==========================================
build_backend:
stage: build
image: docker:latest
services:
- docker:dind
script:
- echo "🔧 Building backend image..."
- docker build -t "$DOCKER_IMAGE_BACKEND:latest" -t "$DOCKER_IMAGE_BACKEND:$CI_COMMIT_SHORT_SHA" ./backend
only:
- main
# ==========================================
# 🔹 BUILD FRONTEND IMAGE
# ==========================================
build_frontend:
stage: build
image: docker:latest
services:
- docker:dind
script:
- echo "🔧 Building frontend image..."
- docker build -t "$DOCKER_IMAGE_FRONTEND:latest" -t "$DOCKER_IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA" ./frontend
only:
- main
# ==========================================
# 🔹 PUSH BOTH IMAGES
# ==========================================
push_images:
stage: push
image: docker:latest
services:
- docker:dind
needs:
- build_backend
- build_frontend
script:
- echo "🚀 Pushing both images to GitLab Registry..."
- docker push "$DOCKER_IMAGE_BACKEND:latest"
- docker push "$DOCKER_IMAGE_BACKEND:$CI_COMMIT_SHORT_SHA"
- docker push "$DOCKER_IMAGE_FRONTEND:latest"
- docker push "$DOCKER_IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA"
only:
- main
# ==========================================
# 🔹 DEPLOY TO DEV (GitOps)
# ==========================================
deploy_dev:
stage: deploy
image: alpine:3.19
needs: [push_images]
variables:
KUSTOMIZE_PATH: "k8s/overlays/dev"
before_script:
- apk add --no-cache git bash sed
- git config --global user.email "gitlab-ci@example.com"
- git config --global user.name "GitLab CI Bot"
script:
- echo "📦 Cloning GitOps repo..."
- git clone "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git"
- cd datasiswa-gitops
- echo "🛠 Updating DEV image tags..."
- sed -i "s|registry.gitlab.com/.*/backend:.*|$DOCKER_IMAGE_BACKEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- sed -i "s|registry.gitlab.com/.*/frontend:.*|$DOCKER_IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- git add .
- git commit -m "🔄 Update DEV images to $CI_COMMIT_SHORT_SHA [skip ci]" || echo "⚠️ No changes to commit"
- git push "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git" dev
environment:
name: dev
only:
- main
# ==========================================
# 🔹 DEPLOY TO STAGING
# ==========================================
deploy_staging:
stage: deploy
image: alpine:3.19
variables:
KUSTOMIZE_PATH: "k8s/overlays/staging"
before_script:
- apk add --no-cache git bash sed
- git config --global user.email "gitlab-ci@example.com"
- git config --global user.name "GitLab CI Bot"
script:
- echo "📦 Cloning GitOps repo..."
- git clone "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git"
- cd datasiswa-gitops
- echo "🛠 Updating STAGING image tags..."
- sed -i "s|registry.gitlab.com/.*/backend:.*|$DOCKER_IMAGE_BACKEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- sed -i "s|registry.gitlab.com/.*/frontend:.*|$DOCKER_IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- git add .
- git commit -m "🔄 Update STAGING images to $CI_COMMIT_SHORT_SHA [skip ci]" || echo "⚠️ No changes to commit"
- git push "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git" staging
environment:
name: staging
only:
- staging
# ==========================================
# 🔹 DEPLOY TO PRODUCTION
# ==========================================
deploy_production:
stage: deploy
image: alpine:3.19
variables:
KUSTOMIZE_PATH: "k8s/overlays/production"
before_script:
- apk add --no-cache git bash sed
- git config --global user.email "gitlab-ci@example.com"
- git config --global user.name "GitLab CI Bot"
script:
- echo "📦 Cloning GitOps repo..."
- git clone "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git"
- cd datasiswa-gitops
- echo "🛠 Updating PRODUCTION image tags..."
- sed -i "s|registry.gitlab.com/.*/backend:.*|$DOCKER_IMAGE_BACKEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- sed -i "s|registry.gitlab.com/.*/frontend:.*|$DOCKER_IMAGE_FRONTEND:$CI_COMMIT_SHORT_SHA|g" "$KUSTOMIZE_PATH/patch-deployment.yaml"
- git add .
- git commit -m "🚀 Deploy PRODUCTION $CI_COMMIT_SHORT_SHA [skip ci]" || echo "⚠️ No changes to commit"
- git push "https://$GITOPS_USERNAME:$GITOPS_TOKEN@gitlab.com/mauuldya/datasiswa-workflow/datasiswa-gitops.git" main
environment:
name: production
only:
- production

18
backend/Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 🔒 Ganti kepemilikan direktori ke user "node" bawaan image
RUN chown -R node:node /app
# 🔒 Jalankan container dengan user non-root
USER node
EXPOSE 5000
CMD ["node", "server.js"]

18
backend/db.js Normal file
View File

@ -0,0 +1,18 @@
const mysql = require("mysql2");
const db = mysql.createConnection({
host: process.env.DB_HOST || "mysql-app-syifa",
user: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "",
database: process.env.DB_NAME || "datasiswa",
});
db.connect((err) => {
if (err) {
console.error("❌ Database connection failed:", err);
} else {
console.log("✅ Connected to MySQL database");
}
});
module.exports = db;

19
backend/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.15.1"
}
}

39
backend/routes/auth.js Normal file
View File

@ -0,0 +1,39 @@
const express = require("express");
const router = express.Router();
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const db = require("../db");
// Register
router.post("/register", (req, res) => {
const { nama, email, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 8);
db.query(
"INSERT INTO users (nama, email, password) VALUES (?, ?, ?)",
[nama, email, hashedPassword],
(err, result) => {
if (err) return res.status(500).json(err);
res.json({ message: "User registered!" });
}
);
});
// Login
router.post("/login", (req, res) => {
const { email, password } = req.body;
db.query("SELECT * FROM users WHERE email = ?", [email], (err, result) => {
if (err) return res.status(500).json(err);
if (result.length === 0) return res.status(404).json({ message: "User not found" });
const user = result[0];
const valid = bcrypt.compareSync(password, user.password);
if (!valid) return res.status(401).json({ message: "Invalid password" });
const token = jwt.sign({ id: user.id, email: user.email }, "SECRET_KEY", { expiresIn: "1h" });
res.json({ message: "Login success", token, user });
});
});
module.exports = router;

44
backend/routes/siswa.js Normal file
View File

@ -0,0 +1,44 @@
const express = require("express");
const router = express.Router();
const db = require("../db");
// Get semua siswa
router.get("/", (req, res) => {
db.query("SELECT * FROM siswa", (err, result) => {
if (err) return res.status(500).json({ message: err.message });
res.json(result); // data array
});
});
// Tambah siswa
router.post("/add", (req, res) => {
const { nama, kelas, umur } = req.body;
if (!nama || !kelas || !umur) {
return res.status(400).json({ message: "Semua field wajib diisi" });
}
db.query(
"INSERT INTO siswa (nama, kelas, umur) VALUES (?, ?, ?)",
[nama, kelas, umur],
(err, result) => {
if (err) return res.status(500).json({ message: err.message });
res.json({ message: "Siswa berhasil ditambahkan!", id: result.insertId });
}
);
});
// Hapus siswa berdasarkan ID
router.delete("/delete/:id", (req, res) => {
const { id } = req.params;
db.query("DELETE FROM siswa WHERE id = ?", [id], (err, result) => {
if (err) return res.status(500).json({ message: err.message });
if (result.affectedRows === 0)
return res.status(404).json({ message: "Siswa tidak ditemukan" });
res.json({ message: "Siswa berhasil dihapus!" });
});
});
module.exports = router;

22
backend/server.js Normal file
View File

@ -0,0 +1,22 @@
const express = require("express");
const cors = require("cors");
const authRoutes = require("./routes/auth");
const siswaRoutes = require("./routes/siswa");
const app = express();
app.use(cors());
app.use(express.json());
// Health check endpoint
app.get("/api/health", (req, res) => {
res.status(200).json({ status: "OK" });
});
// Routes
app.use("/api/auth", authRoutes);
app.use("/api/siswa", siswaRoutes);
app.use("/api/siswa/add", siswaRoutes);
app.use("/api/siswa/delete", siswaRoutes);
const PORT = 5000;
app.listen(PORT, "0.0.0.0", () => console.log(`🚀 Server running on http://localhost:${PORT}`));

6
database/config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
host: "localhost",
user: "root", // user MySQL kamu
password: "", // password MySQL kamu
database: "datasiswa" // nama database kamu
};

0
database/schema.sql Normal file
View File

0
database/seed.sql Normal file
View File

1
frontend Submodule

@ -0,0 +1 @@
Subproject commit 9729b56c157549586f396d49bdc48b1e436d1841