diff --git a/.env.test b/.env.test index bd11f6f..2c1ec80 100644 --- a/.env.test +++ b/.env.test @@ -70,3 +70,8 @@ MOMO_SECRET_KEY=TEST_MOMO_SECRET_KEY ZALOPAY_APP_ID=TEST_ZALOPAY_APP ZALOPAY_KEY1=TEST_ZALOPAY_KEY1 ZALOPAY_KEY2=TEST_ZALOPAY_KEY2 +BANK_TRANSFER_ACCOUNT_NUMBER=0123456789 +BANK_TRANSFER_BANK_NAME=Vietcombank +BANK_TRANSFER_ACCOUNT_HOLDER=CONG_TY_GOODGO +BANK_TRANSFER_WEBHOOK_SECRET=test-bank-transfer-webhook-secret-minimum-32-chars +BANK_TRANSFER_INSTRUCTIONS_URL=http://localhost:3010/thanh-toan/chuyen-khoan diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43a8ebb..66e4bb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,79 +149,10 @@ jobs: name: E2E Tests needs: ci runs-on: ubuntu-latest - timeout-minutes: 20 - - services: - postgres: - image: postgis/postgis:16-3.4 - env: - POSTGRES_DB: goodgo_test - POSTGRES_USER: goodgo - POSTGRES_PASSWORD: goodgo_test_secret - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U goodgo -d goodgo_test" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - --health-start-period 30s - - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - typesense: - image: typesense/typesense:27.1 - ports: - - 8108:8108 - env: - TYPESENSE_API_KEY: ts_ci_key - TYPESENSE_DATA_DIR: /data - options: >- - --health-cmd "curl -sf http://localhost:8108/health || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - minio: - image: minio/minio:latest - ports: - - 9000:9000 - env: - MINIO_ROOT_USER: ci_minio_user - MINIO_ROOT_PASSWORD: ci_minio_secret_key_32chars!! - options: >- - --health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + timeout-minutes: 45 env: - DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test - REDIS_URL: redis://localhost:6379 - TYPESENSE_URL: http://localhost:8108 - TYPESENSE_HOST: localhost - TYPESENSE_PORT: 8108 - TYPESENSE_API_KEY: ts_ci_key - MINIO_ENDPOINT: localhost - MINIO_PORT: 9000 - MINIO_ACCESS_KEY: ci_minio_user - MINIO_SECRET_KEY: ci_minio_secret_key_32chars!! - MINIO_BUCKET: goodgo-uploads - NODE_ENV: test - JWT_SECRET: e2e-test-jwt-secret-key - JWT_REFRESH_SECRET: e2e-test-refresh-secret-key - VNPAY_TMN_CODE: TESTCODE - VNPAY_HASH_SECRET: TESTHASHSECRET - VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html - VNPAY_RETURN_URL: http://localhost:3000/payment/return + CI: true steps: - name: Checkout @@ -239,6 +170,12 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Load E2E environment + run: awk 'NF && $1 !~ /^#/' .env.test >> "$GITHUB_ENV" + + - name: Start CI service stack + run: docker compose --env-file .env.ci -f docker-compose.ci.yml up -d --wait + - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@v4 @@ -281,3 +218,7 @@ jobs: name: playwright-traces path: test-results/ retention-days: 7 + + - name: Stop CI service stack + if: always() + run: docker compose --env-file .env.ci -f docker-compose.ci.yml down -v diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 3ff7430..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: CodeQL Analysis - -on: - push: - branches: [master] - pull_request: - branches: [master] - schedule: - # Run weekly on Monday at 06:17 UTC — off-peak to avoid :00/:30 congestion - - cron: "17 6 * * 1" - -concurrency: - group: codeql-${{ github.ref }} - cancel-in-progress: true - -permissions: - actions: read - contents: read - security-events: write - -jobs: - analyze: - name: CodeQL (${{ matrix.language }}) - runs-on: ubuntu-latest - timeout-minutes: 30 - - strategy: - fail-fast: false - matrix: - language: [javascript-typescript] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # Use extended security queries for deeper analysis - queries: security-extended,security-and-quality - config: | - paths: - - apps/ - - libs/ - paths-ignore: - - node_modules/ - - "**/dist/" - - "**/*.spec.ts" - - "**/*.test.ts" - - "**/__tests__/" - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{ matrix.language }}" - # SARIF results are automatically uploaded to GitHub Security tab - upload: always diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 19fd6fe..cd48771 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -14,98 +14,10 @@ jobs: e2e: name: Playwright E2E runs-on: ubuntu-latest - timeout-minutes: 20 - - services: - postgres: - image: postgis/postgis:16-3.4 - env: - POSTGRES_DB: goodgo_test - POSTGRES_USER: goodgo - POSTGRES_PASSWORD: goodgo_test_secret - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U goodgo -d goodgo_test" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - --health-start-period 30s - - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - typesense: - image: typesense/typesense:27.1 - ports: - - 8108:8108 - env: - TYPESENSE_API_KEY: ts_ci_key - TYPESENSE_DATA_DIR: /data - options: >- - --health-cmd "curl -sf http://localhost:8108/health || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - minio: - image: minio/minio:latest - ports: - - 9000:9000 - env: - MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }} - MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }} - options: >- - --health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + timeout-minutes: 45 env: - DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test - REDIS_URL: redis://localhost:6379 - REDIS_HOST: localhost - REDIS_PORT: 6379 - TYPESENSE_URL: http://localhost:8108 - TYPESENSE_HOST: localhost - TYPESENSE_PORT: 8108 - TYPESENSE_PROTOCOL: http - TYPESENSE_API_KEY: ts_ci_key - MINIO_ENDPOINT: localhost - MINIO_PORT: 9000 - MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }} - MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }} - MINIO_BUCKET: goodgo-uploads - NODE_ENV: test CI: true - # API and Web ports for Playwright webServer - API_PORT: 3001 - WEB_PORT: 3000 - API_BASE_URL: http://localhost:3001/api/v1/ - WEB_BASE_URL: http://localhost:3000 - NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1 - JWT_SECRET: e2e-test-jwt-secret-key-minimum-32-chars-long-enough - JWT_REFRESH_SECRET: e2e-test-refresh-secret-key-minimum-32-chars-ok - JWT_EXPIRES_IN: 15m - JWT_REFRESH_EXPIRES_IN: 7d - BCRYPT_ROUNDS: 4 - VNPAY_TMN_CODE: TESTCODE - VNPAY_HASH_SECRET: TESTHASHSECRETTESTHASHSECRETTEST - VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html - VNPAY_RETURN_URL: http://localhost:3000/payment/return - GOOGLE_CLIENT_ID: test-google-client-id - GOOGLE_CLIENT_SECRET: test-google-client-secret - GOOGLE_CALLBACK_URL: http://localhost:3001/api/v1/auth/google/callback - ZALO_APP_ID: test-zalo-app-id - ZALO_APP_SECRET: test-zalo-app-secret - ZALO_CALLBACK_URL: http://localhost:3001/api/v1/auth/zalo/callback steps: - name: Checkout @@ -123,6 +35,12 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Load E2E environment + run: awk 'NF && $1 !~ /^#/' .env.test >> "$GITHUB_ENV" + + - name: Start CI service stack + run: docker compose --env-file .env.ci -f docker-compose.ci.yml up -d --wait + - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@v4 @@ -165,3 +83,7 @@ jobs: name: playwright-traces path: test-results/ retention-days: 7 + + - name: Stop CI service stack + if: always() + run: docker compose --env-file .env.ci -f docker-compose.ci.yml down -v diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 8e54752..dff6db8 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -15,7 +15,6 @@ concurrency: permissions: contents: read - security-events: write jobs: # ── Dependency Audit ───────────────────────────────────────────── @@ -96,25 +95,8 @@ jobs: cache-from: type=gha,scope=api-scan cache-to: type=gha,mode=max,scope=api-scan - - name: Run Trivy vulnerability scanner (API) - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: "goodgo-api:scan" - format: "sarif" - output: "trivy-api-results.sarif" - severity: "CRITICAL,HIGH" - # Ignore unfixed vulns to reduce noise - ignore-unfixed: true - - - name: Upload Trivy SARIF (API) - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: "trivy-api-results.sarif" - category: "trivy-api" - - name: Trivy table output (API) - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: "goodgo-api:scan" format: "table" @@ -144,24 +126,8 @@ jobs: cache-from: type=gha,scope=web-scan cache-to: type=gha,mode=max,scope=web-scan - - name: Run Trivy vulnerability scanner (Web) - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: "goodgo-web:scan" - format: "sarif" - output: "trivy-web-results.sarif" - severity: "CRITICAL,HIGH" - ignore-unfixed: true - - - name: Upload Trivy SARIF (Web) - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: "trivy-web-results.sarif" - category: "trivy-web" - - name: Trivy table output (Web) - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: "goodgo-web:scan" format: "table" @@ -191,24 +157,8 @@ jobs: cache-from: type=gha,scope=ai-scan cache-to: type=gha,mode=max,scope=ai-scan - - name: Run Trivy vulnerability scanner (AI) - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: "goodgo-ai:scan" - format: "sarif" - output: "trivy-ai-results.sarif" - severity: "CRITICAL,HIGH" - ignore-unfixed: true - - - name: Upload Trivy SARIF (AI) - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: "trivy-ai-results.sarif" - category: "trivy-ai" - - name: Trivy table output (AI) - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: "goodgo-ai:scan" format: "table" @@ -225,26 +175,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Run Trivy filesystem scanner - uses: aquasecurity/trivy-action@0.28.0 - with: - scan-type: "fs" - scan-ref: "." - format: "sarif" - output: "trivy-fs-results.sarif" - severity: "CRITICAL,HIGH" - ignore-unfixed: true - scanners: "vuln,secret,misconfig" - - - name: Upload Trivy SARIF (filesystem) - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: "trivy-fs-results.sarif" - category: "trivy-filesystem" - - name: Trivy filesystem table output - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" scan-ref: "." diff --git a/apps/api/src/modules/osm-sync/infrastructure/osm-sync.service.ts b/apps/api/src/modules/osm-sync/infrastructure/osm-sync.service.ts index ab17e5e..6d4664c 100644 --- a/apps/api/src/modules/osm-sync/infrastructure/osm-sync.service.ts +++ b/apps/api/src/modules/osm-sync/infrastructure/osm-sync.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@nestjs/common'; -import { OsmSyncStatus } from '@prisma/client'; import { spawn } from 'node:child_process'; import { createHash } from 'node:crypto'; import * as path from 'node:path'; +import { Injectable } from '@nestjs/common'; +import { OsmSyncStatus } from '@prisma/client'; import { LoggerService, PrismaService } from '@modules/shared'; /** diff --git a/apps/api/src/modules/payments/payments.module.ts b/apps/api/src/modules/payments/payments.module.ts index 2573a48..b2d4362 100644 --- a/apps/api/src/modules/payments/payments.module.ts +++ b/apps/api/src/modules/payments/payments.module.ts @@ -25,6 +25,7 @@ import { PaymentGatewayFactory } from './infrastructure/services/payment-gateway import { PAYMENT_GATEWAY_FACTORY } from './infrastructure/services/payment-gateway.interface'; import { VnpayService } from './infrastructure/services/vnpay.service'; import { ZalopayService } from './infrastructure/services/zalopay.service'; +import { AdminPaymentsController } from './presentation/controllers/admin-payments.controller'; import { OrdersController } from './presentation/controllers/orders.controller'; import { PaymentsController } from './presentation/controllers/payments.controller'; @@ -47,7 +48,7 @@ const QueryHandlers = [ @Module({ imports: [CqrsModule], - controllers: [OrdersController, PaymentsController], + controllers: [AdminPaymentsController, OrdersController, PaymentsController], providers: [ // Repositories { provide: ESCROW_REPOSITORY, useClass: PrismaEscrowRepository }, diff --git a/apps/api/src/modules/poi/application/queries/list-poi-by-bbox/list-poi-by-bbox.handler.ts b/apps/api/src/modules/poi/application/queries/list-poi-by-bbox/list-poi-by-bbox.handler.ts index ddd173a..8d23424 100644 --- a/apps/api/src/modules/poi/application/queries/list-poi-by-bbox/list-poi-by-bbox.handler.ts +++ b/apps/api/src/modules/poi/application/queries/list-poi-by-bbox/list-poi-by-bbox.handler.ts @@ -1,6 +1,6 @@ import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; -import { LoggerService, PrismaService } from '@modules/shared'; import type { Feature, FeatureCollection } from 'geojson'; +import { LoggerService, PrismaService } from '@modules/shared'; import { ListPoiByBboxQuery } from './list-poi-by-bbox.query'; interface BboxRow { diff --git a/apps/api/src/modules/shared/shared.module.ts b/apps/api/src/modules/shared/shared.module.ts index fe1b6c6..24ac202 100644 --- a/apps/api/src/modules/shared/shared.module.ts +++ b/apps/api/src/modules/shared/shared.module.ts @@ -17,8 +17,8 @@ import { // import { EVENT_BUS, RedisStreamsEventBus } from './infrastructure/event-bus'; import { EventBusService } from './infrastructure/event-bus.service'; import { FieldEncryptionService } from './infrastructure/field-encryption.service'; -import { GeoLookupService } from './infrastructure/geo-lookup.service'; import { GlobalExceptionFilter } from './infrastructure/filters/global-exception.filter'; +import { GeoLookupService } from './infrastructure/geo-lookup.service'; import { DeprecationInterceptor, VersionInterceptor } from './infrastructure/interceptors'; import { LoggerService } from './infrastructure/logger.service'; import { CorrelationIdMiddleware } from './infrastructure/middleware/correlation-id.middleware'; diff --git a/apps/web/components/design-system/footer.tsx b/apps/web/components/design-system/footer.tsx index 328d6c8..884041d 100644 --- a/apps/web/components/design-system/footer.tsx +++ b/apps/web/components/design-system/footer.tsx @@ -51,6 +51,12 @@ const SOCIAL_ICON: Record = { youtube: ExternalLink, }; +const SOCIAL_LABEL: Record = { + facebook: 'GoodGo Facebook', + instagram: 'GoodGo Instagram', + youtube: 'GoodGo YouTube', +}; + /* -------------------------------------------------------------------------- */ /* Component */ /* -------------------------------------------------------------------------- */ @@ -123,8 +129,10 @@ export function Footer({ target="_blank" rel="noopener noreferrer" className="inline-flex h-9 w-9 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground" + aria-label={SOCIAL_LABEL[s.platform] ?? s.platform} + title={SOCIAL_LABEL[s.platform] ?? s.platform} > - {Icon && } + {Icon &&