add datasiswa-app project for gitops
This commit is contained in:
commit
e8393f7e03
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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"]
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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}`));
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
host: "localhost",
|
||||||
|
user: "root", // user MySQL kamu
|
||||||
|
password: "", // password MySQL kamu
|
||||||
|
database: "datasiswa" // nama database kamu
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9729b56c157549586f396d49bdc48b1e436d1841
|
||||||
Loading…
Reference in New Issue