diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index b117216..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "image": "mcr.microsoft.com/devcontainers/universal:2", - "features": { - "ghcr.io/devcontainers/features/rust:1": {}, - "ghcr.io/devcontainers/features/node:2": {} - } -} diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 736b292..e97a16a 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -89,7 +89,7 @@ jobs: git fetch origin main --depth=1 || true # Detect changed files before resetting - ALL_COMPOSE_FILES="infra/compose/traefik.yml infra/compose/shared.yml infra/compose/scraper.yml infra/compose/elysia.yml infra/compose/react.yml infra/compose/rust-auth.yml" + ALL_COMPOSE_FILES="infra/compose/traefik.yml infra/compose/shared.yml infra/compose/scraper.yml" TRAEFIK_DYNAMIC_DIR="infra/traefik/dynamic" if git rev-parse HEAD >/dev/null 2>&1; then diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index 6813a70..10189ed 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -5,7 +5,7 @@ on: branches: - main paths: - - 'apps/**' + - 'apps/scraper/**' - '.github/workflows/docker-build-push.yml' - 'infra/**' - '!infra/compose/**' @@ -34,9 +34,6 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} scraper-api: ${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }} - elysia-api: ${{ steps.filter.outputs['elysia-api'] == 'true' || steps.dispatch.outputs['elysia-api'] == 'true' || github.event_name == 'workflow_dispatch' }} - react-web: ${{ steps.filter.outputs['react-web'] == 'true' || steps.dispatch.outputs['react-web'] == 'true' || github.event_name == 'workflow_dispatch' }} - rust-auth: ${{ steps.filter.outputs['rust-auth'] == 'true' || steps.dispatch.outputs['rust-auth'] == 'true' || github.event_name == 'workflow_dispatch' }} steps: - uses: actions/checkout@v6 with: @@ -63,9 +60,6 @@ jobs: } echo "scraper-api=$(changed '^(apps/scraper(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/scraper\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "elysia-api=$(changed '^(apps/elysia(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/elysia\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "react-web=$(changed '^(apps/react(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/react\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "rust-auth=$(changed '^(apps/rust-auth(/|$)|infra/docker/rust\.Dockerfile$|\.github/workflows/docker-build-push\.yml$|\.gitmodules$)')" >> "$GITHUB_OUTPUT" - name: Parse repository_dispatch payload id: dispatch @@ -87,7 +81,7 @@ jobs: fi case "$SERVICE" in - scraper-api|elysia-api|react-web|rust-auth) ;; + scraper-api) ;; *) exit 1 ;; @@ -95,7 +89,7 @@ jobs: if ! [[ "$SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"; exit 1; fi - SERVICES=(scraper-api elysia-api react-web rust-auth) + SERVICES=(scraper-api) for svc in "${SERVICES[@]}"; do if [ "$SERVICE" = "$svc" ]; then echo "${svc}=true" >> "$GITHUB_OUTPUT" @@ -113,9 +107,6 @@ jobs: } if [ "${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "scraper-api" "docker-scraper" "apps/scraper"; fi - if [ "${{ steps.filter.outputs['elysia-api'] == 'true' || steps.dispatch.outputs['elysia-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "elysia-api" "docker-elysia" "apps/elysia"; fi - if [ "${{ steps.filter.outputs['react-web'] == 'true' || steps.dispatch.outputs['react-web'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "react-web" "docker-react" "apps/react"; fi - if [ "${{ steps.filter.outputs['rust-auth'] == 'true' || steps.dispatch.outputs['rust-auth'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "rust-auth" "docker-rust-auth" "apps/rust-auth"; fi JSON_ARRAY="[$(IFS=,; echo "${SERVICES[*]}")]" echo "matrix=$JSON_ARRAY" >> $GITHUB_OUTPUT @@ -135,9 +126,6 @@ jobs: case "$SERVICE" in "scraper-api") REPO="https://github.com/asepharyana/asepharyana-hub-scraper.git" ;; - "elysia-api") REPO="https://github.com/asepharyana/asepharyana-hub-elysia.git" ;; - "react-web") REPO="https://github.com/asepharyana/asepharyana-hub-react.git" ;; - "rust-auth") REPO="https://github.com/asepharyana/asepharyana-hub-rust-auth.git" ;; *) echo "::error::Unsupported service '$SERVICE'" exit 1 @@ -219,9 +207,6 @@ jobs: echo "cache-registry=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}:buildcache" >> $GITHUB_OUTPUT case "$SVC_NAME" in "scraper-api") echo "dockerfile=infra/docker/scraper.Dockerfile" >> $GITHUB_OUTPUT ;; - "elysia-api") echo "dockerfile=infra/docker/elysia.Dockerfile" >> $GITHUB_OUTPUT ;; - "react-web") echo "dockerfile=infra/docker/react.Dockerfile" >> $GITHUB_OUTPUT ;; - "rust-auth") echo "dockerfile=infra/docker/rust.Dockerfile" >> $GITHUB_OUTPUT ;; esac - name: Build and Push Docker image @@ -266,15 +251,9 @@ jobs: declare -A SERVICES SERVICES["scraper-api"]="scraper.yml" - SERVICES["elysia-api"]="elysia.yml" - SERVICES["react-web"]="react.yml" - SERVICES["rust-auth"]="rust-auth.yml" declare -A PATHS PATHS["scraper-api"]="apps/scraper" - PATHS["elysia-api"]="apps/elysia" - PATHS["react-web"]="apps/react" - PATHS["rust-auth"]="apps/rust-auth" # Use git config for possible commits git config --local user.email "action@github.com" @@ -283,9 +262,6 @@ jobs: for id in "${!SERVICES[@]}"; do SHOULD_HAVE_RUN=false if [ "${{ needs.changes.outputs['scraper-api'] }}" == "true" ] && [ "$id" == "scraper-api" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['elysia-api'] }}" == "true" ] && [ "$id" == "elysia-api" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['react-web'] }}" == "true" ] && [ "$id" == "react-web" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['rust-auth'] }}" == "true" ] && [ "$id" == "rust-auth" ]; then SHOULD_HAVE_RUN=true; fi if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then SHOULD_HAVE_RUN=true; fi diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml deleted file mode 100644 index 705c005..0000000 --- a/.github/workflows/typecheck.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: TypeCheck - -on: - pull_request: - branches: [main] - paths: - - 'apps/react/src/**/*.ts' - - 'apps/react/src/**/*.tsx' - - 'apps/elysia/src/**/*.ts' - - 'apps/elysia/tsconfig.json' - - 'tsconfig.base.json' - -jobs: - typecheck: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - with: - submodules: recursive - - - uses: oven/setup-bun@v2 - with: - bun-version: latest - - - name: TypeCheck apps/react - working-directory: apps/react - run: | - bun install - npx tsc --noEmit - - - name: TypeCheck apps/elysia - working-directory: apps/elysia - run: | - bun install - bun run typecheck \ No newline at end of file diff --git a/.github/workflows/update-submodule.yml b/.github/workflows/update-submodule.yml index fd4f9f6..78a9043 100644 --- a/.github/workflows/update-submodule.yml +++ b/.github/workflows/update-submodule.yml @@ -36,7 +36,7 @@ jobs: fi case "$SERVICE" in - scraper-api|elysia-api|react-web|rust-auth) ;; + scraper-api) ;; *) echo "::error::Unsupported service '$SERVICE'" exit 1 diff --git a/.gitmodules b/.gitmodules index 833eaa0..b8513b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,3 @@ -[submodule "apps/elysia"] - path = apps/elysia - url = https://github.com/asepharyana/asepharyana-hub-elysia.git -[submodule "apps/react"] - path = apps/react - url = https://github.com/asepharyana/asepharyana-hub-react.git [submodule "apps/scraper"] path = apps/scraper url = https://github.com/asepharyana/asepharyana-hub-scraper.git -[submodule "apps/rust-auth"] - path = apps/rust-auth - url = https://github.com/asepharyana/asepharyana-hub-rust-auth.git diff --git a/.kilocode/mcp.json b/.kilocode/mcp.json deleted file mode 100644 index ee5d245..0000000 --- a/.kilocode/mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "next-devtools": { - "command": "npx", - "args": ["-y", "next-devtools-mcp@latest"] - } - } -} diff --git a/.kilocode/rules/rules.md b/.kilocode/rules/rules.md deleted file mode 100644 index 9b3d1c5..0000000 --- a/.kilocode/rules/rules.md +++ /dev/null @@ -1,81 +0,0 @@ -# Agent Protocol v10: Strategic Execution and Diagnostic Directives - -1. **Mandatory Executability.** - - **Directive:** All generated outputs must be syntactically correct and directly executable by the target system's interpreter, compiler, or shell. - - **Constraint:** Placeholders and pseudo-code are forbidden. Every output must be a complete, functional artifact. - -2. **Data and State Integrity.** - - **Directive:** All generated code must strictly adhere to declared data structures, schemas, and the target language's type system. - - **Constraint:** Any operation producing a type mismatch, schema violation, or logical inconsistency is an invalid operation and must be discarded. - -3. **Atomic and Consistent State Modification.** - - **Directive:** Modification of a shared resource must be performed as an atomic operation or within an ACID-compliant transaction. - - **Constraint:** Operations that can lead to race conditions or inconsistent state are prohibited. Immutability is the required default. - -4. **Zero-Trust Security (Inviolable Safety Constraint).** - - **Directive:** Secrets must not be stored as literal values in source code. They must be loaded at runtime from a secure external source. - - **Constraint:** Generated access policies must adhere to the Principle of Least Privilege. - -5. **Supply Chain Security (Inviolable Safety Constraint).** - - **Directive:** All external dependencies must be sourced from trusted repositories and defined in a lockfile for deterministic resolution. - - **Constraint:** The dependency graph must be scanned for known CVEs. Dependencies with critical vulnerabilities are prohibited. - -6. **Deterministic and Reproducible Builds.** - - **Directive:** From a given source commit, the build process must produce a byte-for-byte identical artifact in every execution. - - **Constraint:** All automated tests must be deterministic. A regression test codifying the fixed bug's failure condition must be included with the fix. - -7. **Structured, Traceable Logging.** - - **Directive:** All processes must emit structured (JSON) logs for significant events. All log entries for a request must contain the same unique trace ID. - - **Constraint:** Error conditions must be explicitly logged with context and propagated. Errors must not be silently suppressed. - -8. **Strict API Contract Enforcement.** - - **Directive:** All network communication must strictly conform to its published, versioned API contract. - - **Constraint:** Any network call violating the contract must be rejected. Breaking changes require a major version increment (SemVer). - -9. **Distributed System Consensus.** - - **Directive:** Changes to shared state across a distributed system are committed only after a formal consensus algorithm confirms quorum. - - **Constraint:** Nodes in a minority partition must enter a read-only or unavailable state to prevent a split-brain scenario. - -10. **Execution Planning and Pre-flight Validation (Think Before Acting).** - - **Directive:** For any multi-step task, a detailed execution plan (sequence of commands and file modifications) must be formulated before any state-modifying action is taken. - - **Constraint:** Before executing a command, the agent must first use a validation or dry-run flag (e.g., `--dry-run`, `--check`) if available. The operation may only proceed if the pre-flight check passes without error. - -11. **Post-Failure Root Cause Analysis (Evaluate Mistakes from Logs).** - - **Directive:** Upon command execution failure (non-zero exit code), the current execution plan must be halted, and the agent must enter a diagnostic mode. - - **Constraint:** In diagnostic mode, the agent is required to: 1) Capture and parse the complete `stdout` and `stderr` logs. 2) Identify the specific error message or stack trace. 3) Correlate the error with the last command to form a root cause hypothesis. 4) Formulate a new, corrective execution plan based on the analysis. - -12. **Context-Aware File System Operations.** - - **Directive:** Before modifying any file, its full content must be read to establish context. All edits must be based on an in-memory understanding of the file's current state. - - **Constraint:** Blind file operations, such as stream-based search-and-replace without structural validation, are strictly prohibited. - -13. **Idempotent State Transitions.** - - **Directive:** Operations that modify state must be designed to be idempotent wherever the protocol allows. - - **Constraint:** Executing the same operation multiple times must result in the same final system state as executing it only once. - -14. **Resource Lifecycle Management.** - - **Directive:** All finite system resources (e.g., file handles, network sockets) must be explicitly released after use. - - **Constraint:** The agent must generate code that prevents resource leaks, utilizing language-specific constructs like `try-with-resources` or `defer`. - -15. **Configuration as Code (CaC).** - - **Directive:** All configuration must be defined and versioned in source-controlled files. - - **Constraint:** Manual, out-of-band configuration changes are prohibited. Versioned files are the single source of truth. - -16. **Atomic and Semantic Version Control.** - - **Directive:** All code changes must be organized into logically atomic commits representing one complete unit of work. - - **Constraint:** Commit messages must adhere to a defined specification (e.g., Conventional Commits). - -17. **User Authority and Command Primacy.** - - **Directive:** User-provided instructions and corrections are the definitive source of truth and have the highest operational priority. - - **Constraint:** The agent must immediately adapt its process to align with user directives. Rejected solutions must not be proposed again. - -18. **Precedent-Based Improvement.** - - **Directive:** User-approved outputs and successful patterns must be recorded and prioritized as precedents for subsequent tasks. - - **Constraint:** Performance, security, and code quality must not degrade. - -19. **Optimization by Explicit Consent.** - - **Directive:** The agent may identify and propose optimizations with a technical justification and supporting metrics. - - **Constraint:** The agent is prohibited from applying any self-initiated optimization without an explicit "approve" command from the user. - -20. **System Hierarchy and Safety Overrides.** - - **Directive:** The operational control hierarchy is absolute: 1) **User Command**, 2) **Inviolable Safety Directives (#4, #5)**, 3) **Standard Operational Directives**. - - **Constraint:** If a command conflicts with an Inviolable Directive, the agent must halt, report the conflict and risk, and await a revised command. diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 0ced604..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "recommendations": [ - "nrwl.angular-console", - "biomejs.biome", - "ms-playwright.playwright", - "firsttris.vscode-jest-runner" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 943c90e..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[json]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "editor.codeActionsOnSave": { - "source.organizeImports.biome": "explicit" - } -} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index fb9521a..0a4d753 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,9 +5,6 @@ ``` asepharyana-hub/ ├── apps/ # Application services (Git submodules) -│ ├── elysia/ # Realtime API (Bun/Elysia/Drizzle/Redis) -│ ├── react/ # Frontend SPA (React/Vite/TanStack) -│ ├── rust-auth/ # IAM & auth service (Axum/SeaORM) │ └── scraper/ # Web scraper service ├── docs/ # Documentation │ ├── adr/ # Architecture Decision Records @@ -31,19 +28,11 @@ asepharyana-hub/ ## Technology Stack -### Backend Services +### Services -| Service | Language/Runtime | Framework | Database | Key Libraries | -| ------------- | ---------------- | ---------- | ------------------------ | ------------------------------------------------------------ | -| **rust-auth** | Rust | Axum 0.8 | PostgreSQL (sqlx) | sqlx, jsonwebtoken, argon2, redis, opentelemetry, prometheus | -| **elysia** | TypeScript/Bun | Elysia 1.4 | PostgreSQL (Drizzle ORM) | Drizzle ORM, Redis (ioredis), JWT (jose), OTel, Swagger | -| **scraper** | _(submodule)_ | — | — | — | - -### Frontend - -| Service | Framework | Build Tool | Key Libraries | -| --------- | --------- | ---------- | --------------------------------------------------------------------------------------------------- | -| **react** | React 19 | Vite 7 | TanStack Router + Query, Three.js/React Three Fiber, Tailwind CSS 4, Zustand, Recharts, tsParticles | +| Service | Language/Runtime | Framework | Database | Key Libraries | +| --------- | ---------------- | --------- | -------- | ------------- | +| **scraper** | _(submodule)_ | — | — | — | ### Infrastructure @@ -90,7 +79,7 @@ Shared services: Service compose files are combined during deployment: ```bash -docker compose -f traefik.yml -f shared.yml -f elysia.yml -f react.yml ... up -d +docker compose -f traefik.yml -f shared.yml -f scraper.yml up -d ``` ### Tailscale Networking diff --git a/apps/elysia b/apps/elysia deleted file mode 160000 index b8f4b58..0000000 --- a/apps/elysia +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b8f4b5806e793314847b1261cc71494fae0e5e5c diff --git a/apps/react b/apps/react deleted file mode 160000 index 124d2f8..0000000 --- a/apps/react +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 124d2f88bd615a1c623ae390de0bc1fefe99614b diff --git a/apps/rust-auth b/apps/rust-auth deleted file mode 160000 index 6e695f0..0000000 --- a/apps/rust-auth +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6e695f07e8f389777c05df3b10568c58c9aeb4c1 diff --git a/infra/README.md b/infra/README.md index 2af9394..9e23d3f 100644 --- a/infra/README.md +++ b/infra/README.md @@ -9,10 +9,7 @@ infra/ ├── compose/ # One compose file per stack/service │ ├── traefik.yml # Public reverse proxy │ ├── shared.yml # Shared Redis -│ ├── react.yml # React SPA -│ ├── scraper.yml # Scraper API -│ ├── elysia.yml # Elysia API -│ └── rust-auth.yml # Rust auth API +│ └── scraper.yml # Scraper API ├── docker/ # Dockerfiles and image runtime helpers ├── traefik/ # Static and dynamic Traefik configuration │ ├── dynamic/ # Routers, services, middlewares, TLS certs @@ -41,10 +38,7 @@ docker compose -f infra/compose/shared.yml up -d docker compose -f infra/compose/traefik.yml up -d docker compose \ - -f infra/compose/react.yml \ -f infra/compose/scraper.yml \ - -f infra/compose/elysia.yml \ - -f infra/compose/rust-auth.yml \ up -d ``` @@ -55,7 +49,6 @@ Common variables used by infra compose files: ```env DATABASE_URL= GITHUB_TOKEN= -JWT_SECRET= SHARED_REDIS_EXPOSE=127.0.0.1:6379:6379 ``` diff --git a/infra/compose/elysia.yml b/infra/compose/elysia.yml deleted file mode 100644 index e36d04c..0000000 --- a/infra/compose/elysia.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - elysia-api: - container_name: elysia-api - image: ghcr.io/asepharyana/asepharyana-hub/elysia-api:sha-fe998aa - restart: always - networks: - app-shared-net: - aliases: - - elysia-api - env_file: - - ../../.env - environment: - - REDIS_URL=redis://redis:6379 - - JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required} - - DATABASE_URL=${DATABASE_URL} - - GITHUB_TOKEN=${GITHUB_TOKEN} - - PORT=4092 - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otlp-metrics-backend:4317 - - OTEL_SERVICE_NAME=elysia-api - -networks: - app-shared-net: - name: app-shared-net - external: true diff --git a/infra/compose/react.yml b/infra/compose/react.yml deleted file mode 100644 index 8d4297a..0000000 --- a/infra/compose/react.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - react-web: - container_name: react-web - image: ghcr.io/asepharyana/asepharyana-hub/react-web:sha-124223c - restart: always - networks: [app-shared-net] - environment: - - VITE_API_URL=https://scraper.asepharyana.my.id/api - - VITE_ELYSIA_URL=https://elysia.asepharyana.my.id -networks: - app-shared-net: - name: app-shared-net - external: true diff --git a/infra/compose/rust-auth.yml b/infra/compose/rust-auth.yml deleted file mode 100644 index 66dfbe3..0000000 --- a/infra/compose/rust-auth.yml +++ /dev/null @@ -1,20 +0,0 @@ -services: - rust-auth: - container_name: rust-auth-api - image: ghcr.io/asepharyana/asepharyana-hub/rust-auth:sha-b199f8c - restart: always - networks: - app-shared-net: - aliases: - - rust-auth - env_file: - - ../../.env - environment: - - PORT=3000 - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otlp-metrics-backend:4317 - - OTEL_SERVICE_NAME=rust-auth - -networks: - app-shared-net: - name: app-shared-net - external: true diff --git a/infra/docker/elysia.Dockerfile b/infra/docker/elysia.Dockerfile deleted file mode 100644 index a7dae84..0000000 --- a/infra/docker/elysia.Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# build stage -FROM oven/bun:1-alpine AS builder -WORKDIR /app - -# install dependencies with cache mounts -COPY apps/elysia/package.json apps/elysia/bun.lock ./ -RUN --mount=type=cache,target=/root/.bun/install/cache \ - bun install --frozen-lockfile - -# build the application -COPY apps/elysia ./ -RUN bun run build - -# runtime stage -FROM oven/bun:1-distroless -WORKDIR /app - -# copy build artifacts -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/package.json ./ - -# distroless uses nonroot user (UID 65532) by default, or we can use it -USER nonroot - -EXPOSE 4092 -CMD ["run", "dist/index.js"] diff --git a/infra/docker/react-server.js b/infra/docker/react-server.js deleted file mode 100644 index 327eb88..0000000 --- a/infra/docker/react-server.js +++ /dev/null @@ -1,77 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { resolve, sep } from 'node:path'; - -const distDir = resolve('./dist'); -const indexPath = resolve(distDir, 'index.html'); - -const contentTypes = { - html: 'text/html; charset=utf-8', - css: 'text/css; charset=utf-8', - js: 'application/javascript; charset=utf-8', - json: 'application/json; charset=utf-8', - svg: 'image/svg+xml', - png: 'image/png', - jpg: 'image/jpeg', - jpeg: 'image/jpeg', - gif: 'image/gif', - webp: 'image/webp', - ico: 'image/x-icon', - woff: 'font/woff', - woff2: 'font/woff2', - ttf: 'font/ttf', -}; - -function responseFromFile(filePath, headers) { - try { - return new Response(readFileSync(filePath), { headers }); - } catch (error) { - if (error?.code === 'ENOENT') { - return new Response('Not Found', { status: 404 }); - } - - console.error('File read error:', error); - return new Response('Internal Server Error', { status: 500 }); - } -} - -function serveIndex() { - return responseFromFile(indexPath, { - 'Content-Type': contentTypes.html, - 'Cache-Control': 'no-cache', - }); -} - -function serveFile(filePath, pathname) { - const ext = filePath.split('.').pop() || ''; - return responseFromFile(filePath, { - 'Content-Type': contentTypes[ext] || 'application/octet-stream', - 'Cache-Control': pathname.startsWith('/assets/') - ? 'public, max-age=31536000, immutable' - : 'no-cache', - }); -} - -Bun.serve({ - port: 80, - hostname: '0.0.0.0', - fetch(req) { - const url = new URL(req.url); - const pathname = url.pathname; - const filePath = resolve(distDir, pathname.slice(1)); - const insideDist = filePath === distDir || filePath.startsWith(`${distDir}${sep}`); - - if (!insideDist) { - return new Response('Forbidden', { status: 403 }); - } - - if (pathname !== '/' && existsSync(filePath)) { - return serveFile(filePath, pathname); - } - - if (!pathname.includes('.') && existsSync(indexPath)) { - return serveIndex(); - } - - return new Response('Not Found', { status: 404 }); - }, -}); diff --git a/infra/docker/react.Dockerfile b/infra/docker/react.Dockerfile deleted file mode 100644 index c9761cc..0000000 --- a/infra/docker/react.Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# ─── Stage 1: Build ───────────────────────────────────────────────────────── -FROM oven/bun:1 AS builder -WORKDIR /app -COPY apps/react/package.json apps/react/bun.lock ./ -RUN bun install --frozen-lockfile -COPY apps/react . -RUN bun run build - -# ─── Stage 2: Runtime (Bun static server) ────────────────────────────────── -FROM oven/bun:1-alpine -WORKDIR /app -COPY infra/docker/react-server.js ./server.js -COPY --from=builder /app/dist ./dist - -EXPOSE 80 -CMD ["bun", "server.js"] \ No newline at end of file diff --git a/infra/docker/rust.Dockerfile b/infra/docker/rust.Dockerfile deleted file mode 100644 index a087171..0000000 --- a/infra/docker/rust.Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# Use cargo-chef for dependency caching -FROM lukemathwalker/cargo-chef:latest-rust-1.89.0 AS chef -WORKDIR /app - -FROM chef AS planner -COPY apps/rust-auth . -RUN cargo chef prepare --recipe-path recipe.json - -FROM chef AS builder -COPY --from=planner /app/recipe.json recipe.json -# Utilize buildkit cache mounts for cargo -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/app/target \ - cargo chef cook --release --recipe-path recipe.json - -# Build application -COPY apps/rust-auth . -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/app/target \ - cargo build --release && \ - cp target/release/rust-auth /app/rust-auth - -# Final runtime image -FROM debian:bookworm-slim AS runtime -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - libssl3 \ - && rm -rf /var/lib/apt/lists/* - -# Add non-root user -RUN groupadd -g 1001 appgroup && \ - useradd -u 1001 -g appgroup -s /bin/sh appuser - -WORKDIR /app -COPY --from=builder /app/rust-auth /app/rust-auth - -# Run as non-root -USER appuser - -EXPOSE 3000 -CMD ["./rust-auth"] diff --git a/infra/traefik/dynamic/apps.yaml b/infra/traefik/dynamic/apps.yaml index 2720dff..51d41bb 100644 --- a/infra/traefik/dynamic/apps.yaml +++ b/infra/traefik/dynamic/apps.yaml @@ -1,13 +1,5 @@ http: routers: - # ── React SPA (domain root) ── - react: - rule: 'Host(`asepharyana.my.id`) || Host(`asepharyana.web.id`)' - entryPoints: - - websecure - tls: {} - service: react-service - # ── Scraper API ── scraper: rule: 'Host(`scraper.asepharyana.my.id`) || Host(`api.asepharyana.my.id`) || Host(`scraper.asepharyana.web.id`) || Host(`api.asepharyana.web.id`)' @@ -18,40 +10,8 @@ http: - common-chain@file service: scraper-service - # ── Elysia API ── - elysia: - rule: 'Host(`elysia.asepharyana.my.id`) || Host(`elysia.asepharyana.web.id`)' - entryPoints: - - websecure - tls: {} - middlewares: - - common-chain@file - service: elysia-service - - # ── Rust Auth API ── - rust-auth: - rule: 'Host(`auth.asepharyana.my.id`) || Host(`auth.asepharyana.web.id`)' - entryPoints: - - websecure - tls: {} - middlewares: - - common-chain@file - service: rust-auth-service - services: - react-service: - loadBalancer: - servers: - - url: 'http://react-web:80' scraper-service: loadBalancer: servers: - url: 'http://scraper-api:4091' - elysia-service: - loadBalancer: - servers: - - url: 'http://elysia-api:4092' - rust-auth-service: - loadBalancer: - servers: - - url: 'http://rust-auth:3000'