commit e8393f7e03c6c5f4bb551789560f0c1e1bfbc922 Author: Syifa Date: Wed Oct 15 16:07:57 2025 +0700 add datasiswa-app project for gitops diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02f994a --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..ac68972 --- /dev/null +++ b/.gitlab-ci.yml @@ -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 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..eec74ec --- /dev/null +++ b/backend/Dockerfile @@ -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"] \ No newline at end of file diff --git a/backend/db.js b/backend/db.js new file mode 100644 index 0000000..b9a0181 --- /dev/null +++ b/backend/db.js @@ -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; diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..349cfc7 --- /dev/null +++ b/backend/package.json @@ -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" + } +} diff --git a/backend/routes/auth.js b/backend/routes/auth.js new file mode 100644 index 0000000..6bf2a46 --- /dev/null +++ b/backend/routes/auth.js @@ -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; diff --git a/backend/routes/siswa.js b/backend/routes/siswa.js new file mode 100644 index 0000000..274c8a1 --- /dev/null +++ b/backend/routes/siswa.js @@ -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; diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..6daba83 --- /dev/null +++ b/backend/server.js @@ -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}`)); diff --git a/database/config.js b/database/config.js new file mode 100644 index 0000000..8cfaa94 --- /dev/null +++ b/database/config.js @@ -0,0 +1,6 @@ +module.exports = { + host: "localhost", + user: "root", // user MySQL kamu + password: "", // password MySQL kamu + database: "datasiswa" // nama database kamu +}; diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/database/seed.sql b/database/seed.sql new file mode 100644 index 0000000..e69de29 diff --git a/frontend b/frontend new file mode 160000 index 0000000..9729b56 --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 9729b56c157549586f396d49bdc48b1e436d1841