From e8c8ffadfe1d4eada980637367422145d4b19caf Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 3 Jan 2026 11:01:19 +0000 Subject: [PATCH 01/17] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e02ea8ee..e80a7e02 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,7 @@ stages: # ========================================================== default: tags: - - self-hosted + - server-development-biznet interruptible: true # ========================================================== From 79112e0da80d483ff18ec12589fc7f7b7e60102e Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Fri, 9 Jan 2026 10:52:56 +0700 Subject: [PATCH 02/17] Penyesuaian flow repo --- .gitlab-ci.yml | 304 +++++++++++++++++++------------------------- Dockerfile | 43 ++++--- docker-compose.yaml | 39 ------ 3 files changed, 162 insertions(+), 224 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e80a7e02..60e132fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,185 +1,149 @@ stages: - build + - migrate - deploy + - seed -# ========================================================== -# ✅ Global defaults -# ========================================================== default: tags: - - server-development-biznet - interruptible: true + - self-hosted-stg -# ========================================================== -# 🏗️ Build Template -# ========================================================== -.build_template: &build_template +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + when: always + - when: never + +variables: + DOCKER_BUILDKIT: "1" + IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}" + IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" + IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest" + DEPLOY_DIR: "/opt/deploy/stg-lti-api" + +# ========================= +# BUILD (AUTO) +# ========================= +build_staging: stage: build - image: node:20-alpine - cache: - key: npm-cache - paths: - - node_modules/ - variables: - NPM_CONFIG_PRODUCTION: 'false' - NODE_ENV: '' - script: - - echo "Installing dependencies..." - - npm ci --no-audit --no-fund - - echo "Build env used:" - - echo "NEXT_PUBLIC_LTI_URL=$NEXT_PUBLIC_LTI_URL" - - echo "NEXT_PUBLIC_SSO_LOGIN_URL=$NEXT_PUBLIC_SSO_LOGIN_URL" - - echo "NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL" - - echo "Building Next.js static export..." - - npx next build - - | - mkdir -p out - cat < out/build-info.json - { - "commit": "$CI_COMMIT_SHORT_SHA", - "pipeline": "$CI_PIPELINE_ID", - "built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", - "NEXT_PUBLIC_LTI_URL": "$NEXT_PUBLIC_LTI_URL", - "NEXT_PUBLIC_SSO_LOGIN_URL": "$NEXT_PUBLIC_SSO_LOGIN_URL", - "NEXT_PUBLIC_API_BASE_URL": "$NEXT_PUBLIC_API_BASE_URL" - } - EOF - artifacts: - name: 'out-$CI_COMMIT_SHORT_SHA' - paths: - - out/ - expire_in: 1 week + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + script: | + set -e + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" -# ========================================================== -# 🚀 Deploy Template -# ========================================================== -.deploy_template: &deploy_template + echo "✅ Build image: $IMAGE_NAME" + docker build -t "$IMAGE_NAME" -f Dockerfile . + + echo "✅ Push image: $IMAGE_NAME" + docker push "$IMAGE_NAME" + + echo "✅ Tag latest: $IMAGE_LATEST" + docker tag "$IMAGE_NAME" "$IMAGE_LATEST" + docker push "$IMAGE_LATEST" + +# ========================= +# MIGRATE (AUTO) - migrations diambil dari repo GitLab +# ========================= +migrate_staging: + stage: migrate + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: build_staging + artifacts: false + script: | + set -e + + # ✅ Load env dari server (.env hanya ada di server) + cd "$DEPLOY_DIR" + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + set -a + . ./.env + set +a + + # ✅ Generate DATABASE_URL dari DB_* + test -n "$DB_HOST" || (echo "❌ DB_HOST empty" && exit 1) + test -n "$DB_PORT" || (echo "❌ DB_PORT empty" && exit 1) + test -n "$DB_USER" || (echo "❌ DB_USER empty" && exit 1) + test -n "$DB_PASSWORD" || (echo "❌ DB_PASSWORD empty" && exit 1) + test -n "$DB_NAME" || (echo "❌ DB_NAME empty" && exit 1) + + export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" + echo "✅ DATABASE_URL ready" + + # ✅ migrations dari repo + echo "✅ Checking migrations from repo..." + ls -lah "$CI_PROJECT_DIR/internal/database/migrations" + + echo "✅ Running migrations via migrate/migrate container" + set +e + docker run --rm \ + -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ + migrate/migrate:v4.15.2 \ + -path=/migrations -database "$DATABASE_URL" up + code=$? + set -e + + if [ $code -eq 0 ]; then + echo "✅ Migration applied successfully" + elif [ $code -eq 1 ]; then + echo "✅ No change (already up to date)" + else + echo "❌ Migration failed with exit code $code" + exit $code + fi + +# ========================= +# DEPLOY (AUTO) +# ========================= +deploy_staging: stage: deploy - image: - name: amazon/aws-cli:latest - entrypoint: ['/bin/sh', '-c'] - script: - - set -e - - aws --version - - echo "Cleaning up newline characters in AWS credentials..." - - export AWS_ACCESS_KEY_ID=$(echo $AWS_ACCESS_KEY_ID | tr -d '\r\n') - - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_SECRET_ACCESS_KEY | tr -d '\r\n') - - echo "Deploying to s3://$S3_BUCKET in region $AWS_REGION" - - aws s3api head-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" || aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" --create-bucket-configuration LocationConstraint="$AWS_REGION" - - aws s3 sync ./out "s3://$S3_BUCKET" --delete --region "$AWS_REGION" --endpoint-url "https://s3.ap-southeast-3.amazonaws.com" - - # CloudFront invalidation - - | - STATUS="success" - if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then - echo "Invalidating CloudFront cache..." - if ! aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"; then - echo "CloudFront invalidation failed." - STATUS="failed" - fi - else - echo "No CloudFront distribution specified — skipping invalidation" - fi - - # Notifikasi Discord - - | - RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}" - - if [ "$CI_COMMIT_BRANCH" = "development" ]; then - ENVIRONMENT_NAME="WEB-LTI-DEV" - elif [ "$CI_COMMIT_BRANCH" = "staging" ]; then - ENVIRONMENT_NAME="WEB-LTI-STAGING" - else - ENVIRONMENT_NAME="UNKNOWN" - fi - - if [ "$STATUS" = "success" ]; then - COLOR=3066993 - TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded" - DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully." - else - COLOR=15158332 - TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed" - DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues." - fi - - jq -n \ - --arg title "$TITLE" \ - --arg desc "$DESC" \ - --arg color "$COLOR" \ - --arg repo "$CI_PROJECT_PATH" \ - --arg actor "$GITLAB_USER_LOGIN" \ - --arg commit "$CI_COMMIT_SHA" \ - --arg run_url "$RUN_URL" \ - '{ - username: "CI Bot - LTI WEB", - embeds: [{ - title: $title, - description: $desc, - color: ($color|tonumber), - fields: [ - {name: "Repository", value: $repo, inline: true}, - {name: "Actor", value: $actor, inline: true}, - {name: "Commit", value: $commit, inline: false}, - {name: "Pipeline", value: ("[Open run](" + $run_url + ")"), inline: false} - ] - }] - }' > payload.json - - curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL" - -# ========================================================== -# ==== DEVELOPMENT (Branch development) ====== -# ========================================================== -build:dev: - <<: *build_template rules: - - if: '$CI_COMMIT_BRANCH == "development"' - environment: - name: development - variables: - NEXT_PUBLIC_LTI_URL: 'https://dev-lti-erp.mbugroup.id' - NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-auth-erp.mbugroup.id' - NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id/api' - NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: migrate_staging + artifacts: false + - job: build_staging + artifacts: false + script: | + set -e -deploy:dev: - <<: *deploy_template - needs: ['build:dev'] - rules: - - if: '$CI_COMMIT_BRANCH == "development"' - variables: - S3_BUCKET: 'dev-lti-erp.mbugroup.id' - AWS_REGION: 'ap-southeast-3' - CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV' - environment: - name: development - url: https://dev-lti-erp.mbugroup.id + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" -# ========================================================== -# ====== STAGING (Branch staging) ====== -# ========================================================== -build:staging: - <<: *build_template - rules: - - if: '$CI_COMMIT_BRANCH == "staging"' - environment: - name: staging - variables: - NEXT_PUBLIC_LTI_URL: 'https://stg-lti-erp.mbugroup.id' - NEXT_PUBLIC_SSO_LOGIN_URL: 'https://stg-auth-erp.mbugroup.id' - NEXT_PUBLIC_API_BASE_URL: 'https://stg-api-lti.mbugroup.id/api' - NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + cd "$DEPLOY_DIR" + test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) -deploy:staging: - <<: *deploy_template - needs: ['build:staging'] + docker compose pull + docker compose up -d --force-recreate + docker image prune -f + +# ========================= +# SEED (MANUAL) +# ========================= +seed_staging: + stage: seed rules: - - if: '$CI_COMMIT_BRANCH == "staging"' - variables: - S3_BUCKET: 'stg-lti-erp.mbugroup.id' - AWS_REGION: 'ap-southeast-3' - CLOUDFRONT_DISTRIBUTION_ID: 'E2V6PPO1AUIU7H' - environment: - name: staging - url: https://stg-lti-erp.mbugroup.id + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: deploy_staging + artifacts: false + when: manual + allow_failure: false + script: | + set -e + + cd "$DEPLOY_DIR" + test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + + echo "✅ Pull latest seed image" + docker compose pull seed || true + + echo "🌱 Running seeder..." + docker compose run --rm seed + + echo "✅ Seed completed" diff --git a/Dockerfile b/Dockerfile index a3a2e197..32e0688d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,38 @@ -FROM node:20-alpine - -RUN apk add --no-cache git bash build-base curl +# ========================= +# Builder stage +# ========================= +FROM golang:1.23-alpine AS builder +RUN apk add --no-cache git ca-certificates tzdata WORKDIR /app -COPY package*.json ./ -RUN npm ci +COPY go.mod go.sum ./ +RUN go mod download COPY . . -# Buat config agar Next tahu output: export -RUN echo "const config = { output: 'export', images: { unoptimized: true } }; export default config;" > next.config.mjs +# Build API binary +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o lti-api ./cmd/api -# Build project (Next.js 15 otomatis static export) -RUN NEXT_DISABLE_TURBOPACK=1 npx next build +# Build SEED binary (pastikan cmd/seed ada) +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o lti-seed ./cmd/seed -# Copy static assets dan hasil build agar bisa diakses -RUN mkdir -p .next/server/app/_next && \ - cp -r .next/static .next/server/app/_next/static && \ - cp -r public/* .next/server/app/ +# ========================= +# Runtime stage +# ========================= +FROM alpine:3.20 -EXPOSE 3000 +RUN apk add --no-cache ca-certificates tzdata curl bash postgresql-client \ + && adduser -D -H -u 10001 appuser -CMD ["npx", "serve", ".next/server/app", "-l", "3000"] \ No newline at end of file +WORKDIR /app + +COPY --from=builder /app/lti-api /app/lti-api +COPY --from=builder /app/lti-seed /app/lti-seed + +USER appuser +EXPOSE 8081 + +CMD ["/app/lti-api"] diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index b89f441b..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,39 +0,0 @@ -version: '3.9' - -services: - dev-web-lti: - container_name: dev-web-lti - build: - context: . - dockerfile: Dockerfile - ports: - - '3002:3000' - env_file: - - .env - environment: - NODE_ENV: production - APP_ENV: production - networks: - - dev-lti-network - restart: always - deploy: - resources: - limits: - cpus: '3.0' - memory: 3G - reservations: - cpus: '1.0' - memory: 512M - extra_hosts: - - 'host.docker.internal:host-gateway' - # Optional: aktifkan healthcheck jika punya endpoint - # healthcheck: - # test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/healthz || exit 1"] - # interval: 10s - # timeout: 3s - # retries: 10 - # start_period: 15s - -networks: - dev-lti-network: - external: true From 3bc5a5b75e9095e75dfd45ee68bd83eb59b3f429 Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Fri, 9 Jan 2026 13:16:42 +0700 Subject: [PATCH 03/17] delete .gitlab --- .gitlab-ci.yml | 149 ------------------------------------------------- 1 file changed, 149 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 60e132fd..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,149 +0,0 @@ -stages: - - build - - migrate - - deploy - - seed - -default: - tags: - - self-hosted-stg - -workflow: - rules: - - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' - when: always - - when: never - -variables: - DOCKER_BUILDKIT: "1" - IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}" - IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" - IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest" - DEPLOY_DIR: "/opt/deploy/stg-lti-api" - -# ========================= -# BUILD (AUTO) -# ========================= -build_staging: - stage: build - rules: - - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' - script: | - set -e - docker info - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - - echo "✅ Build image: $IMAGE_NAME" - docker build -t "$IMAGE_NAME" -f Dockerfile . - - echo "✅ Push image: $IMAGE_NAME" - docker push "$IMAGE_NAME" - - echo "✅ Tag latest: $IMAGE_LATEST" - docker tag "$IMAGE_NAME" "$IMAGE_LATEST" - docker push "$IMAGE_LATEST" - -# ========================= -# MIGRATE (AUTO) - migrations diambil dari repo GitLab -# ========================= -migrate_staging: - stage: migrate - rules: - - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' - needs: - - job: build_staging - artifacts: false - script: | - set -e - - # ✅ Load env dari server (.env hanya ada di server) - cd "$DEPLOY_DIR" - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) - set -a - . ./.env - set +a - - # ✅ Generate DATABASE_URL dari DB_* - test -n "$DB_HOST" || (echo "❌ DB_HOST empty" && exit 1) - test -n "$DB_PORT" || (echo "❌ DB_PORT empty" && exit 1) - test -n "$DB_USER" || (echo "❌ DB_USER empty" && exit 1) - test -n "$DB_PASSWORD" || (echo "❌ DB_PASSWORD empty" && exit 1) - test -n "$DB_NAME" || (echo "❌ DB_NAME empty" && exit 1) - - export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" - echo "✅ DATABASE_URL ready" - - # ✅ migrations dari repo - echo "✅ Checking migrations from repo..." - ls -lah "$CI_PROJECT_DIR/internal/database/migrations" - - echo "✅ Running migrations via migrate/migrate container" - set +e - docker run --rm \ - -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ - migrate/migrate:v4.15.2 \ - -path=/migrations -database "$DATABASE_URL" up - code=$? - set -e - - if [ $code -eq 0 ]; then - echo "✅ Migration applied successfully" - elif [ $code -eq 1 ]; then - echo "✅ No change (already up to date)" - else - echo "❌ Migration failed with exit code $code" - exit $code - fi - -# ========================= -# DEPLOY (AUTO) -# ========================= -deploy_staging: - stage: deploy - rules: - - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' - needs: - - job: migrate_staging - artifacts: false - - job: build_staging - artifacts: false - script: | - set -e - - docker info - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - - cd "$DEPLOY_DIR" - test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) - - docker compose pull - docker compose up -d --force-recreate - docker image prune -f - -# ========================= -# SEED (MANUAL) -# ========================= -seed_staging: - stage: seed - rules: - - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' - needs: - - job: deploy_staging - artifacts: false - when: manual - allow_failure: false - script: | - set -e - - cd "$DEPLOY_DIR" - test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) - - echo "✅ Pull latest seed image" - docker compose pull seed || true - - echo "🌱 Running seeder..." - docker compose run --rm seed - - echo "✅ Seed completed" From 149e525ff49deb4c755c149e6eda600b97634db5 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 10 Jan 2026 02:15:18 +0000 Subject: [PATCH 04/17] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..e80a7e02 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,185 @@ +stages: + - build + - deploy + +# ========================================================== +# ✅ Global defaults +# ========================================================== +default: + tags: + - server-development-biznet + interruptible: true + +# ========================================================== +# 🏗️ Build Template +# ========================================================== +.build_template: &build_template + stage: build + image: node:20-alpine + cache: + key: npm-cache + paths: + - node_modules/ + variables: + NPM_CONFIG_PRODUCTION: 'false' + NODE_ENV: '' + script: + - echo "Installing dependencies..." + - npm ci --no-audit --no-fund + - echo "Build env used:" + - echo "NEXT_PUBLIC_LTI_URL=$NEXT_PUBLIC_LTI_URL" + - echo "NEXT_PUBLIC_SSO_LOGIN_URL=$NEXT_PUBLIC_SSO_LOGIN_URL" + - echo "NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL" + - echo "Building Next.js static export..." + - npx next build + - | + mkdir -p out + cat < out/build-info.json + { + "commit": "$CI_COMMIT_SHORT_SHA", + "pipeline": "$CI_PIPELINE_ID", + "built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", + "NEXT_PUBLIC_LTI_URL": "$NEXT_PUBLIC_LTI_URL", + "NEXT_PUBLIC_SSO_LOGIN_URL": "$NEXT_PUBLIC_SSO_LOGIN_URL", + "NEXT_PUBLIC_API_BASE_URL": "$NEXT_PUBLIC_API_BASE_URL" + } + EOF + artifacts: + name: 'out-$CI_COMMIT_SHORT_SHA' + paths: + - out/ + expire_in: 1 week + +# ========================================================== +# 🚀 Deploy Template +# ========================================================== +.deploy_template: &deploy_template + stage: deploy + image: + name: amazon/aws-cli:latest + entrypoint: ['/bin/sh', '-c'] + script: + - set -e + - aws --version + - echo "Cleaning up newline characters in AWS credentials..." + - export AWS_ACCESS_KEY_ID=$(echo $AWS_ACCESS_KEY_ID | tr -d '\r\n') + - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_SECRET_ACCESS_KEY | tr -d '\r\n') + - echo "Deploying to s3://$S3_BUCKET in region $AWS_REGION" + - aws s3api head-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" || aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" --create-bucket-configuration LocationConstraint="$AWS_REGION" + - aws s3 sync ./out "s3://$S3_BUCKET" --delete --region "$AWS_REGION" --endpoint-url "https://s3.ap-southeast-3.amazonaws.com" + + # CloudFront invalidation + - | + STATUS="success" + if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then + echo "Invalidating CloudFront cache..." + if ! aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"; then + echo "CloudFront invalidation failed." + STATUS="failed" + fi + else + echo "No CloudFront distribution specified — skipping invalidation" + fi + + # Notifikasi Discord + - | + RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}" + + if [ "$CI_COMMIT_BRANCH" = "development" ]; then + ENVIRONMENT_NAME="WEB-LTI-DEV" + elif [ "$CI_COMMIT_BRANCH" = "staging" ]; then + ENVIRONMENT_NAME="WEB-LTI-STAGING" + else + ENVIRONMENT_NAME="UNKNOWN" + fi + + if [ "$STATUS" = "success" ]; then + COLOR=3066993 + TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded" + DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully." + else + COLOR=15158332 + TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed" + DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues." + fi + + jq -n \ + --arg title "$TITLE" \ + --arg desc "$DESC" \ + --arg color "$COLOR" \ + --arg repo "$CI_PROJECT_PATH" \ + --arg actor "$GITLAB_USER_LOGIN" \ + --arg commit "$CI_COMMIT_SHA" \ + --arg run_url "$RUN_URL" \ + '{ + username: "CI Bot - LTI WEB", + embeds: [{ + title: $title, + description: $desc, + color: ($color|tonumber), + fields: [ + {name: "Repository", value: $repo, inline: true}, + {name: "Actor", value: $actor, inline: true}, + {name: "Commit", value: $commit, inline: false}, + {name: "Pipeline", value: ("[Open run](" + $run_url + ")"), inline: false} + ] + }] + }' > payload.json + + curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL" + +# ========================================================== +# ==== DEVELOPMENT (Branch development) ====== +# ========================================================== +build:dev: + <<: *build_template + rules: + - if: '$CI_COMMIT_BRANCH == "development"' + environment: + name: development + variables: + NEXT_PUBLIC_LTI_URL: 'https://dev-lti-erp.mbugroup.id' + NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-auth-erp.mbugroup.id' + NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id/api' + NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + +deploy:dev: + <<: *deploy_template + needs: ['build:dev'] + rules: + - if: '$CI_COMMIT_BRANCH == "development"' + variables: + S3_BUCKET: 'dev-lti-erp.mbugroup.id' + AWS_REGION: 'ap-southeast-3' + CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV' + environment: + name: development + url: https://dev-lti-erp.mbugroup.id + +# ========================================================== +# ====== STAGING (Branch staging) ====== +# ========================================================== +build:staging: + <<: *build_template + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + environment: + name: staging + variables: + NEXT_PUBLIC_LTI_URL: 'https://stg-lti-erp.mbugroup.id' + NEXT_PUBLIC_SSO_LOGIN_URL: 'https://stg-auth-erp.mbugroup.id' + NEXT_PUBLIC_API_BASE_URL: 'https://stg-api-lti.mbugroup.id/api' + NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + +deploy:staging: + <<: *deploy_template + needs: ['build:staging'] + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + variables: + S3_BUCKET: 'stg-lti-erp.mbugroup.id' + AWS_REGION: 'ap-southeast-3' + CLOUDFRONT_DISTRIBUTION_ID: 'E2V6PPO1AUIU7H' + environment: + name: staging + url: https://stg-lti-erp.mbugroup.id From 56bde974adff39f1e1752f65ca4a7bbeac1d6067 Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Tue, 13 Jan 2026 15:36:56 +0700 Subject: [PATCH 05/17] chore: gitlab ci yml 2026 01 13 --- .gitlab-ci.yml | 186 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..3218fed3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,186 @@ +stages: + - build + - deploy + +# ========================================================== +# ✅ Global defaults +# ========================================================== +default: + tags: + - server-development-biznet + interruptible: true + +# ========================================================== +# 🏗️ Build Template +# ========================================================== +.build_template: &build_template + stage: build + image: node:20-alpine + cache: + key: npm-cache + paths: + - node_modules/ + variables: + NPM_CONFIG_PRODUCTION: 'false' + NODE_ENV: '' + script: + - echo "Installing dependencies..." + - npm ci --no-audit --no-fund + - echo "Build env used:" + - echo "NEXT_PUBLIC_LTI_URL=$NEXT_PUBLIC_LTI_URL" + - echo "NEXT_PUBLIC_SSO_LOGIN_URL=$NEXT_PUBLIC_SSO_LOGIN_URL" + - echo "NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL" + - echo "Building Next.js static export..." + - npx next build + - | + mkdir -p out + cat < out/build-info.json + { + "commit": "$CI_COMMIT_SHORT_SHA", + "pipeline": "$CI_PIPELINE_ID", + "built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", + "NEXT_PUBLIC_LTI_URL": "$NEXT_PUBLIC_LTI_URL", + "NEXT_PUBLIC_SSO_LOGIN_URL": "$NEXT_PUBLIC_SSO_LOGIN_URL", + "NEXT_PUBLIC_API_BASE_URL": "$NEXT_PUBLIC_API_BASE_URL" + } + EOF + artifacts: + name: 'out-$CI_COMMIT_SHORT_SHA' + paths: + - out/ + expire_in: 1 week + +# ========================================================== +# 🚀 Deploy Template +# ========================================================== +.deploy_template: &deploy_template + stage: deploy + image: + name: amazon/aws-cli:latest + entrypoint: ['/bin/sh', '-c'] + script: + - set -e + - aws --version + - echo "Cleaning up newline characters in AWS credentials..." + - export AWS_ACCESS_KEY_ID=$(echo $AWS_ACCESS_KEY_ID | tr -d '\r\n') + - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_SECRET_ACCESS_KEY | tr -d '\r\n') + - echo "Deploying to s3://$S3_BUCKET in region $AWS_REGION" + - aws s3api head-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" || aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" --create-bucket-configuration LocationConstraint="$AWS_REGION" + - aws s3 sync ./out "s3://$S3_BUCKET" --delete --region "$AWS_REGION" --endpoint-url "https://s3.ap-southeast-3.amazonaws.com" + + # CloudFront invalidation + - | + STATUS="success" + if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then + echo "Invalidating CloudFront cache..." + if ! aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"; then + echo "CloudFront invalidation failed." + STATUS="failed" + fi + else + echo "No CloudFront distribution specified — skipping invalidation" + fi + + # Notifikasi Discord + - | + RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}" + + if [ "$CI_COMMIT_BRANCH" = "development" ]; then + ENVIRONMENT_NAME="WEB-LTI-DEV" + elif [ "$CI_COMMIT_BRANCH" = "staging" ]; then + ENVIRONMENT_NAME="WEB-LTI-STAGING" + else + ENVIRONMENT_NAME="UNKNOWN" + fi + + if [ "$STATUS" = "success" ]; then + COLOR=3066993 + TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded" + DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully." + else + COLOR=15158332 + TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed" + DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues." + fi + + jq -n \ + --arg title "$TITLE" \ + --arg desc "$DESC" \ + --arg color "$COLOR" \ + --arg repo "$CI_PROJECT_PATH" \ + --arg actor "$GITLAB_USER_LOGIN" \ + --arg commit "$CI_COMMIT_SHA" \ + --arg run_url "$RUN_URL" \ + '{ + username: "CI Bot - LTI WEB", + embeds: [{ + title: $title, + description: $desc, + color: ($color|tonumber), + fields: [ + {name: "Repository", value: $repo, inline: true}, + {name: "Actor", value: $actor, inline: true}, + {name: "Commit", value: $commit, inline: false}, + {name: "Pipeline", value: ("[Open run](" + $run_url + ")"), inline: false} + ] + }] + }' > payload.json + + curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL" + +# ========================================================== +# ==== DEVELOPMENT (Branch development) ====== +# ========================================================== +build:dev: + <<: *build_template + rules: + - if: '$CI_COMMIT_BRANCH == "development"' + environment: + name: development + variables: + NEXT_PUBLIC_LTI_URL: 'https://dev-lti-erp.mbugroup.id' + NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-auth-erp.mbugroup.id' + NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id/api' + NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + +deploy:dev: + <<: *deploy_template + needs: ['build:dev'] + rules: + - if: '$CI_COMMIT_BRANCH == "development"' + variables: + S3_BUCKET: 'dev-lti-erp.mbugroup.id' + AWS_REGION: 'ap-southeast-3' + CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV' + environment: + name: development + url: https://dev-lti-erp.mbugroup.id + +# ========================================================== +# ====== STAGING (Branch staging) ====== +# ========================================================== +build:staging: + <<: *build_template + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + environment: + name: staging + variables: + NEXT_PUBLIC_LTI_URL: 'https://stg-lti-erp.mbugroup.id' + NEXT_PUBLIC_SSO_LOGIN_URL: 'https://stg-auth-erp.mbugroup.id' + NEXT_PUBLIC_API_BASE_URL: 'https://stg-api-lti.mbugroup.id/api' + NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + +deploy:staging: + <<: *deploy_template + needs: ['build:staging'] + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + variables: + S3_BUCKET: 'stg-lti-erp.mbugroup.id' + AWS_REGION: 'ap-southeast-3' + CLOUDFRONT_DISTRIBUTION_ID: 'E2V6PPO1AUIU7H' + environment: + name: staging + url: https://stg-lti-erp.mbugroup.id + From 35471fc5979a145a2aa8e49d37d8347454b4a1fd Mon Sep 17 00:00:00 2001 From: kris Date: Wed, 14 Jan 2026 02:29:31 +0000 Subject: [PATCH 06/17] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e80a7e02..3a15cfe5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -183,3 +183,31 @@ deploy:staging: environment: name: staging url: https://stg-lti-erp.mbugroup.id + +# ========================================================== +# ====== STAGING (Branch production) ====== +# ========================================================== +build:production: + <<: *build_template + rules: + - if: '$CI_COMMIT_BRANCH == "production"' + environment: + name: staging + variables: + NEXT_PUBLIC_LTI_URL: 'https://lti-erp.mbugroup.id' + NEXT_PUBLIC_SSO_LOGIN_URL: 'https://auth-erp.mbugroup.id' + NEXT_PUBLIC_API_BASE_URL: 'https://api-lti.mbugroup.id/api' + NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + +deploy:production: + <<: *deploy_template + needs: ['build:production'] + rules: + - if: '$CI_COMMIT_BRANCH == "production"' + variables: + S3_BUCKET: 'lti-erp.mbugroup.id' + AWS_REGION: 'ap-southeast-3' + CLOUDFRONT_DISTRIBUTION_ID: 'E1SSLXKYYITASJ' + environment: + name: staging + url: https://lti-erp.mbugroup.id From 8fd442621a259cc7bf1999115ac4546ed3283eb4 Mon Sep 17 00:00:00 2001 From: kris Date: Wed, 14 Jan 2026 02:52:03 +0000 Subject: [PATCH 07/17] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3a15cfe5..e1e9573c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -205,7 +205,7 @@ deploy:production: rules: - if: '$CI_COMMIT_BRANCH == "production"' variables: - S3_BUCKET: 'lti-erp.mbugroup.id' + S3_BUCKET: 'production-lti-erp.mbugroup.id' AWS_REGION: 'ap-southeast-3' CLOUDFRONT_DISTRIBUTION_ID: 'E1SSLXKYYITASJ' environment: From bac6766fa2bf68453a2026cfb7faa3a9a24d1797 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Sun, 8 Mar 2026 16:06:13 +0700 Subject: [PATCH 08/17] refactor(FE): Refactor transfer logic to use maxSourceQuantity state --- .../TransferToLayingFormModal.tsx | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx index 2487f10b..b01a479d 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx @@ -229,6 +229,8 @@ const TransferToLayingFormModal = () => { ProjectFlock | undefined >(undefined); + const [maxSourceQuantity, setMaxSourceQuantity] = useState(0); + const selectedFlockDestinationRawData = isResponseSuccess( flockDestinationRawData ) @@ -353,19 +355,14 @@ const TransferToLayingFormModal = () => { return { available: countAvailable, unavailable: countUnavailable }; }, [mappedFlockDestinationKandangsMaxTargetQty]); - const totalEnteredChickenForTransfer = - formik.values.flockSourceKandangs.reduce( - (acc, item) => acc + Number(item.quantity), - 0 - ); - const totalTransferedChicken = formik.values.flockDestinationKandangs.reduce( (acc, item) => acc + Number(item.quantity), 0 ); + // Sisa transfer = Max available dari kandang asal - Total yang sudah diisi di kandang tujuan const totalAvailableChickenForTransfer = - totalEnteredChickenForTransfer - totalTransferedChicken; + maxSourceQuantity - totalTransferedChicken; const isNextButtonDisabled = useMemo(() => { if (step === 1) { @@ -397,6 +394,7 @@ const TransferToLayingFormModal = () => { formik.setFieldValue('maxTotalQuantity', ''); formik.setFieldValue('reason', ''); formik.setFieldTouched('reason', false); + setMaxSourceQuantity(0); setStep(2); }; @@ -404,6 +402,7 @@ const TransferToLayingFormModal = () => { const flockSourceChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldValue('flockSource', val); formik.setFieldValue('flockSourceKandangs', []); + setMaxSourceQuantity(0); }; const flockDestinationChangeHandler = ( @@ -469,6 +468,16 @@ const TransferToLayingFormModal = () => { formik.setFieldValue('maxTotalQuantity', totalTransferedChicken); }, [totalTransferedChicken, formik.values.flockDestinationKandangs]); + // Auto-fill source kandang quantity from total destination quantity + useEffect(() => { + if (formik.values.flockSourceKandangs.length > 0) { + formik.setFieldValue( + 'flockSourceKandangs.0.quantity', + totalTransferedChicken + ); + } + }, [totalTransferedChicken]); + return ( <> { k.kandang.value === item.project_flock_kandang_id ); - const flockSourceKandangCheckboxChangeHandler: FormEventHandler< - HTMLInputElement - > = (e) => { - const checked = (e.target as HTMLInputElement) - .checked; - if (checked) { + const flockSourceKandangRadioChangeHandler = () => { + if (isAvailable) { formik.setFieldValue('flockSourceKandangs', [ - ...formik.values.flockSourceKandangs, { kandang: { value: item.project_flock_kandang_id, @@ -600,15 +604,7 @@ const TransferToLayingFormModal = () => { maxQuantity: item.available_qty, }, ]); - } else { - formik.setFieldValue( - 'flockSourceKandangs', - formik.values.flockSourceKandangs.filter( - (k) => - k.kandang.value !== - item.project_flock_kandang_id - ) - ); + setMaxSourceQuantity(item.available_qty); } }; @@ -618,28 +614,22 @@ const TransferToLayingFormModal = () => { className='w-full p-3 flex flex-row items-center justify-between' >
-