chore: update infrastructure configs, audit docs, and env template
- Update Docker Compose configs for Redis, Typesense, and MinIO services - Update GitHub Actions deploy workflow with improved caching and steps - Extend .env.example with Stringee, Zalo OA, and FCM config keys - Update audit documentation with latest findings and recommendations - Update CHANGELOG and README with recent feature additions Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
28
.env.example
28
.env.example
@@ -29,7 +29,7 @@ PGBOUNCER_STATS_PASSWORD=CHANGE_ME
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=CHANGE_ME_IN_PRODUCTION
|
||||||
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -127,6 +127,12 @@ ZALOPAY_KEY1=
|
|||||||
ZALOPAY_KEY2=
|
ZALOPAY_KEY2=
|
||||||
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
|
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
|
||||||
|
|
||||||
|
BANK_TRANSFER_ACCOUNT_NUMBER=
|
||||||
|
BANK_TRANSFER_BANK_NAME=
|
||||||
|
BANK_TRANSFER_ACCOUNT_HOLDER=
|
||||||
|
BANK_TRANSFER_WEBHOOK_SECRET=
|
||||||
|
BANK_TRANSFER_INSTRUCTIONS_URL=https://goodgo.vn/thanh-toan/chuyen-khoan
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Email / SMTP
|
# Email / SMTP
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -136,11 +142,31 @@ SMTP_USER=
|
|||||||
SMTP_PASS=
|
SMTP_PASS=
|
||||||
SMTP_FROM=noreply@goodgo.vn
|
SMTP_FROM=noreply@goodgo.vn
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Stringee SMS (Vietnamese SMS provider — OTP & notifications)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
STRINGEE_API_KEY=
|
||||||
|
STRINGEE_BRANDNAME=GoodGo
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Firebase Cloud Messaging (optional)
|
# Firebase Cloud Messaging (optional)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
FIREBASE_SERVICE_ACCOUNT=
|
FIREBASE_SERVICE_ACCOUNT=
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Zalo OA Notifications (ZNS — Zalo Notification Service)
|
||||||
|
# Obtain from Zalo OA Manager: https://oa.zalo.me/manage
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
ZALO_OA_ID=
|
||||||
|
ZALO_OA_ACCESS_TOKEN=
|
||||||
|
|
||||||
|
# ZNS Template IDs (registered in Zalo OA Manager console)
|
||||||
|
ZALO_ZNS_TEMPLATE_INQUIRY=
|
||||||
|
ZALO_ZNS_TEMPLATE_PAYMENT=
|
||||||
|
ZALO_ZNS_TEMPLATE_LISTING_APPROVED=
|
||||||
|
ZALO_ZNS_TEMPLATE_LISTING_REJECTED=
|
||||||
|
ZALO_ZNS_TEMPLATE_LISTING_SOLD=
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Sentry Error Tracking
|
# Sentry Error Tracking
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
42
.github/workflows/deploy.yml
vendored
42
.github/workflows/deploy.yml
vendored
@@ -357,29 +357,38 @@ jobs:
|
|||||||
DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
|
DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
|
||||||
DEPLOY_USER: ${{ secrets.STAGING_USER }}
|
DEPLOY_USER: ${{ secrets.STAGING_USER }}
|
||||||
DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||||
|
REGISTRY_URL: ${{ env.REGISTRY_URL }}
|
||||||
|
IMAGE_TAG: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||||
chmod 600 ~/.ssh/deploy_key
|
chmod 600 ~/.ssh/deploy_key
|
||||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'ROLLBACK_SCRIPT'
|
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << ROLLBACK_SCRIPT
|
||||||
cd ~/goodgo
|
cd ~/goodgo
|
||||||
|
|
||||||
echo "Rolling back staging using :rollback tagged images..."
|
echo "Rolling back staging using :rollback tagged images..."
|
||||||
|
|
||||||
|
REGISTRY_URL="${REGISTRY_URL}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG}"
|
||||||
|
|
||||||
# Stop current containers
|
# Stop current containers
|
||||||
docker compose -f docker-compose.prod.yml stop api web ai-services
|
docker compose -f docker-compose.prod.yml stop api web ai-services
|
||||||
|
|
||||||
# Retag :rollback images back to their original names so compose picks them up
|
# Retag :rollback images to match compose image template so compose uses them
|
||||||
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
||||||
if docker image inspect "${svc}:rollback" > /dev/null 2>&1; then
|
if docker image inspect "\${svc}:rollback" > /dev/null 2>&1; then
|
||||||
echo "Restoring ${svc} from :rollback tag"
|
echo "Restoring \${svc} from :rollback tag"
|
||||||
|
docker tag "\${svc}:rollback" "\${REGISTRY_URL}/\${svc}:\${IMAGE_TAG}"
|
||||||
|
else
|
||||||
|
echo "WARNING: No rollback image for \${svc}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Restart with previous images (compose uses cached/rollback-tagged layers)
|
# Restart with rollback images (now tagged to match compose template)
|
||||||
docker compose -f docker-compose.prod.yml up -d --wait api web ai-services
|
export IMAGE_TAG REGISTRY_URL
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api web ai-services
|
||||||
|
|
||||||
echo "Rollback complete. Verifying health..."
|
echo "Rollback complete. Verifying health..."
|
||||||
sleep 5
|
sleep 5
|
||||||
@@ -558,31 +567,38 @@ jobs:
|
|||||||
DEPLOY_HOST: ${{ secrets.PRODUCTION_HOST }}
|
DEPLOY_HOST: ${{ secrets.PRODUCTION_HOST }}
|
||||||
DEPLOY_USER: ${{ secrets.PRODUCTION_USER }}
|
DEPLOY_USER: ${{ secrets.PRODUCTION_USER }}
|
||||||
DEPLOY_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
DEPLOY_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||||
|
REGISTRY_URL: ${{ env.REGISTRY_URL }}
|
||||||
|
IMAGE_TAG: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||||
chmod 600 ~/.ssh/deploy_key
|
chmod 600 ~/.ssh/deploy_key
|
||||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'ROLLBACK_SCRIPT'
|
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << ROLLBACK_SCRIPT
|
||||||
cd ~/goodgo
|
cd ~/goodgo
|
||||||
|
|
||||||
echo "Rolling back production using :rollback tagged images..."
|
echo "Rolling back production using :rollback tagged images..."
|
||||||
|
|
||||||
|
REGISTRY_URL="${REGISTRY_URL}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG}"
|
||||||
|
|
||||||
# Stop current containers
|
# Stop current containers
|
||||||
docker compose -f docker-compose.prod.yml stop api web ai-services
|
docker compose -f docker-compose.prod.yml stop api web ai-services
|
||||||
|
|
||||||
# Verify rollback images exist
|
# Retag :rollback images to match compose image template so compose uses them
|
||||||
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
||||||
if docker image inspect "${svc}:rollback" > /dev/null 2>&1; then
|
if docker image inspect "\${svc}:rollback" > /dev/null 2>&1; then
|
||||||
echo "Rollback image available: ${svc}:rollback"
|
echo "Restoring \${svc} from :rollback tag"
|
||||||
|
docker tag "\${svc}:rollback" "\${REGISTRY_URL}/\${svc}:\${IMAGE_TAG}"
|
||||||
else
|
else
|
||||||
echo "WARNING: No rollback image for ${svc}"
|
echo "WARNING: No rollback image for \${svc}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Restart with previous images
|
# Restart with rollback images (now tagged to match compose template)
|
||||||
docker compose -f docker-compose.prod.yml up -d --wait api web ai-services
|
export IMAGE_TAG REGISTRY_URL
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api web ai-services
|
||||||
|
|
||||||
echo "Rollback complete. Verifying health..."
|
echo "Rollback complete. Verifying health..."
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Pino structured JSON logging with correlation IDs
|
- Pino structured JSON logging with correlation IDs
|
||||||
- Prisma ORM with migration system and seed data (Ho Chi Minh City districts/wards, sample properties, subscription plans)
|
- Prisma ORM with migration system and seed data (Ho Chi Minh City districts/wards, sample properties, subscription plans)
|
||||||
|
|
||||||
#### Frontend (Next.js 14)
|
#### Frontend (Next.js 15)
|
||||||
- App Router with Tailwind CSS and Zustand state management
|
- App Router with Tailwind CSS and Zustand state management
|
||||||
- Property search page with Mapbox GL map integration
|
- Property search page with Mapbox GL map integration
|
||||||
- Listing detail pages with media gallery
|
- Listing detail pages with media gallery
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Vietnam's intelligent real estate platform — property search, AI-powered valua
|
|||||||
| Layer | Technology |
|
| Layer | Technology |
|
||||||
|-------|-----------|
|
|-------|-----------|
|
||||||
| **Backend** | NestJS 11, TypeScript, Prisma ORM, CQRS |
|
| **Backend** | NestJS 11, TypeScript, Prisma ORM, CQRS |
|
||||||
| **Frontend** | Next.js 14, React 18, Tailwind CSS, Zustand |
|
| **Frontend** | Next.js 15, React 18, Tailwind CSS, Zustand |
|
||||||
| **Database** | PostgreSQL 16 + PostGIS 3.4 |
|
| **Database** | PostgreSQL 16 + PostGIS 3.4 |
|
||||||
| **Search** | Typesense 27 |
|
| **Search** | Typesense 27 |
|
||||||
| **Cache/Queue** | Redis 7 |
|
| **Cache/Queue** | Redis 7 |
|
||||||
@@ -21,7 +21,7 @@ Vietnam's intelligent real estate platform — property search, AI-powered valua
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
|
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
|
||||||
│ Next.js 14 │────▶│ NestJS API │────▶│ PostgreSQL + │
|
│ Next.js 15 │────▶│ NestJS API │────▶│ PostgreSQL + │
|
||||||
│ (Web App) │ │ (REST) │ │ PostGIS │
|
│ (Web App) │ │ (REST) │ │ PostGIS │
|
||||||
└─────────────┘ └──────┬───────┘ └──────────────────┘
|
└─────────────┘ └──────┬───────┘ └──────────────────┘
|
||||||
│
|
│
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface ListingDocument {
|
|||||||
saveCount: number;
|
saveCount: number;
|
||||||
projectName: string | null;
|
projectName: string | null;
|
||||||
amenities: string[];
|
amenities: string[];
|
||||||
|
isFeatured: number; // 1 if featuredUntil > now, 0 otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export class ListingIndexerService {
|
|||||||
saveCount: l.saveCount,
|
saveCount: l.saveCount,
|
||||||
projectName: p.projectName,
|
projectName: p.projectName,
|
||||||
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
|
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
|
||||||
|
isFeatured: l.featuredUntil && l.featuredUntil > new Date() ? 1 : 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -158,6 +159,7 @@ export class ListingIndexerService {
|
|||||||
saveCount: listing.saveCount,
|
saveCount: listing.saveCount,
|
||||||
projectName: p.projectName,
|
projectName: p.projectName,
|
||||||
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
|
amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
|
||||||
|
isFeatured: listing.featuredUntil && listing.featuredUntil > new Date() ? 1 : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface RawListingRow {
|
|||||||
saveCount: number;
|
saveCount: number;
|
||||||
projectName: string | null;
|
projectName: string | null;
|
||||||
amenities: unknown;
|
amenities: unknown;
|
||||||
|
featuredUntil?: Date | string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Map a raw SQL row to the domain ListingDocument shape. */
|
/** Map a raw SQL row to the domain ListingDocument shape. */
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const LISTING_SCHEMA: CollectionCreateSchema = {
|
|||||||
{ name: 'saveCount', type: 'int32', facet: false },
|
{ name: 'saveCount', type: 'int32', facet: false },
|
||||||
{ name: 'projectName', type: 'string', facet: true, optional: true },
|
{ name: 'projectName', type: 'string', facet: true, optional: true },
|
||||||
{ name: 'amenities', type: 'string[]', facet: true, optional: true },
|
{ name: 'amenities', type: 'string[]', facet: true, optional: true },
|
||||||
|
{ name: 'isFeatured', type: 'int32', facet: true },
|
||||||
],
|
],
|
||||||
token_separators: ['-', '_'],
|
token_separators: ['-', '_'],
|
||||||
enable_nested_fields: false,
|
enable_nested_fields: false,
|
||||||
@@ -159,16 +160,21 @@ export class TypesenseSearchRepository implements ISearchRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Featured listings always sort first, then by user-selected criteria
|
||||||
|
const featuredPrefix = 'isFeatured:desc';
|
||||||
|
|
||||||
switch (params.sortBy) {
|
switch (params.sortBy) {
|
||||||
case 'price_asc':
|
case 'price_asc':
|
||||||
return 'priceVND:asc';
|
return `${featuredPrefix},priceVND:asc`;
|
||||||
case 'price_desc':
|
case 'price_desc':
|
||||||
return 'priceVND:desc';
|
return `${featuredPrefix},priceVND:desc`;
|
||||||
case 'date_desc':
|
case 'date_desc':
|
||||||
return 'publishedAt:desc';
|
return `${featuredPrefix},publishedAt:desc`;
|
||||||
case 'relevance':
|
case 'relevance':
|
||||||
default:
|
default:
|
||||||
return params.query && params.query !== '*' ? '_text_match:desc,publishedAt:desc' : 'publishedAt:desc';
|
return params.query && params.query !== '*'
|
||||||
|
? `${featuredPrefix},_text_match:desc,publishedAt:desc`
|
||||||
|
: `${featuredPrefix},publishedAt:desc`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- minio_data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'mc', 'ready', 'local']
|
test: ['CMD', 'curl', '-sf', 'http://localhost:9000/minio/health/live']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- minio_data:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'mc', 'ready', 'local']
|
test: ['CMD', 'curl', '-sf', 'http://localhost:9000/minio/health/live']
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
@@ -702,27 +702,70 @@ docker compose -f docker-compose.prod.yml up -d --wait
|
|||||||
|
|
||||||
### 4.4 Rollback Deployment
|
### 4.4 Rollback Deployment
|
||||||
|
|
||||||
The CI/CD pipeline (`.github/workflows/deploy.yml`) supports automatic rollback if production smoke tests fail. For manual rollback:
|
#### How Rollback Images Work
|
||||||
|
|
||||||
#### Quick Rollback (Revert to Previous Images)
|
Every deploy (both CI/CD and manual) tags the current running images as `:rollback` **before** pulling new ones. This ensures the previous version is preserved even if `docker image prune` runs. The `:rollback` tags are only cleaned up after smoke tests pass.
|
||||||
|
|
||||||
|
Image lifecycle during deploy:
|
||||||
|
1. `docker tag <current-image> goodgo-api:rollback` (preserves previous version)
|
||||||
|
2. `docker compose pull` (fetches new images)
|
||||||
|
3. `docker compose up` (starts new version)
|
||||||
|
4. Smoke tests run
|
||||||
|
5. **If smoke tests pass:** `:rollback` tags are removed, `docker image prune` runs
|
||||||
|
6. **If smoke tests fail:** `:rollback` images are retagged to match the compose template and services are restarted
|
||||||
|
|
||||||
|
#### Automatic Rollback (CI/CD)
|
||||||
|
|
||||||
|
The CI/CD pipeline (`.github/workflows/deploy.yml`) automatically triggers rollback if smoke tests fail. The rollback job:
|
||||||
|
1. Stops the broken containers
|
||||||
|
2. Retags `:rollback` images to match the compose image template (`${REGISTRY_URL}/goodgo-{svc}:${IMAGE_TAG}`)
|
||||||
|
3. Restarts compose — which now resolves to the previous (working) images
|
||||||
|
4. Sends a Slack notification to `#deployments`
|
||||||
|
|
||||||
|
No manual intervention is needed for CI-triggered deploys.
|
||||||
|
|
||||||
|
#### Quick Rollback Using :rollback Tags (Manual)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# SSH into production host
|
# SSH into the host
|
||||||
ssh deploy@$PRODUCTION_HOST
|
ssh deploy@$PRODUCTION_HOST
|
||||||
|
|
||||||
cd ~/goodgo
|
cd ~/goodgo
|
||||||
|
|
||||||
# Stop current app containers
|
# Verify rollback images exist
|
||||||
docker compose -f docker-compose.prod.yml down api web ai-services
|
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
||||||
|
docker image inspect "${svc}:rollback" > /dev/null 2>&1 \
|
||||||
|
&& echo "OK: ${svc}:rollback" \
|
||||||
|
|| echo "MISSING: ${svc}:rollback"
|
||||||
|
done
|
||||||
|
|
||||||
# The previous images are still cached locally
|
# Stop current containers
|
||||||
# Restart without pulling — uses last-known-good images
|
docker compose -f docker-compose.prod.yml stop api web ai-services
|
||||||
docker compose -f docker-compose.prod.yml up -d --wait api web ai-services
|
|
||||||
|
# Retag rollback images to match compose template
|
||||||
|
export REGISTRY_URL=ghcr.io/goodgo
|
||||||
|
export IMAGE_TAG=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.revision"}}' goodgo-api 2>/dev/null || echo "latest")
|
||||||
|
|
||||||
|
for svc in goodgo-api goodgo-web goodgo-ai-services; do
|
||||||
|
docker tag "${svc}:rollback" "${REGISTRY_URL}/${svc}:${IMAGE_TAG}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Restart with rollback images
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api web ai-services
|
||||||
|
|
||||||
# Verify
|
# Verify
|
||||||
curl -sf http://localhost:3001/health && echo "Rollback successful"
|
curl -sf http://localhost:3001/health && echo "Rollback successful"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Rollback Using deploy-production.sh (Manual Script)
|
||||||
|
|
||||||
|
The manual deploy script (`scripts/deploy-production.sh`) has built-in rollback. If the health check or smoke test fails, it automatically restores from `:rollback` tagged images and restarts services.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the manual deploy — rollback is automatic on failure
|
||||||
|
cd ~/goodgo
|
||||||
|
./scripts/deploy-production.sh <image-tag>
|
||||||
|
```
|
||||||
|
|
||||||
#### Rollback to a Specific Git Commit / Image Tag
|
#### Rollback to a Specific Git Commit / Image Tag
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ GoodGo Platform AI is a monorepo containing a NestJS backend, Next.js frontend,
|
|||||||
└──────────────────┬───────────────────────┘
|
└──────────────────┬───────────────────────┘
|
||||||
│
|
│
|
||||||
┌──────────────────▼───────────────────────┐
|
┌──────────────────▼───────────────────────┐
|
||||||
│ Next.js 14 (apps/web) │
|
│ Next.js 15 (apps/web) │
|
||||||
│ ┌─────────┐ ┌──────────┐ ┌───────┐ │
|
│ ┌─────────┐ ┌──────────┐ ┌───────┐ │
|
||||||
│ │ Pages │ │Components│ │Zustand │ │
|
│ │ Pages │ │Components│ │Zustand │ │
|
||||||
│ │(App │ │(UI + │ │(State) │ │
|
│ │(App │ │(UI + │ │(State) │ │
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# GoodGo Platform Frontend - Comprehensive Accessibility Audit Report
|
# GoodGo Platform Frontend - Comprehensive Accessibility Audit Report
|
||||||
**Date:** April 10, 2026
|
**Date:** April 10, 2026
|
||||||
**Audited:** apps/web (Next.js 14)
|
**Audited:** apps/web (Next.js 15)
|
||||||
**Total Files Analyzed:** 90+ TSX/JSX files
|
**Total Files Analyzed:** 90+ TSX/JSX files
|
||||||
**ARIA Attributes Found:** 75 instances across 14 files
|
**ARIA Attributes Found:** 75 instances across 14 files
|
||||||
|
|
||||||
@@ -1538,7 +1538,7 @@ function Dialog({ open, onOpenChange, children }: DialogProps) {
|
|||||||
**Generated:** April 10, 2026
|
**Generated:** April 10, 2026
|
||||||
**Auditor:** Accessibility Compliance Team
|
**Auditor:** Accessibility Compliance Team
|
||||||
**Codebase:** GoodGo Platform (apps/web)
|
**Codebase:** GoodGo Platform (apps/web)
|
||||||
**Framework:** Next.js 14
|
**Framework:** Next.js 15
|
||||||
**Language:** Vietnamese (Primary) & English
|
**Language:** Vietnamese (Primary) & English
|
||||||
|
|
||||||
**Distribution:**
|
**Distribution:**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Audit Date:** April 10, 2026
|
**Audit Date:** April 10, 2026
|
||||||
**Platform:** GoodGo Real Estate Platform (Vietnam)
|
**Platform:** GoodGo Real Estate Platform (Vietnam)
|
||||||
**Framework:** Next.js 14
|
**Framework:** Next.js 15
|
||||||
**Scope:** apps/web (Frontend)
|
**Scope:** apps/web (Frontend)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -290,7 +290,7 @@ Good Practice Examples:
|
|||||||
|-------|-------|
|
|-------|-------|
|
||||||
| Audit Date | April 10, 2026 |
|
| Audit Date | April 10, 2026 |
|
||||||
| Platform | GoodGo Real Estate |
|
| Platform | GoodGo Real Estate |
|
||||||
| Framework | Next.js 14 |
|
| Framework | Next.js 15 |
|
||||||
| Scope | apps/web Frontend |
|
| Scope | apps/web Frontend |
|
||||||
| Files Analyzed | 90+ TSX/JSX |
|
| Files Analyzed | 90+ TSX/JSX |
|
||||||
| ARIA Instances | 75 |
|
| ARIA Instances | 75 |
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ aria-pressed={index === selectedIndex}
|
|||||||
|
|
||||||
## 📝 Notes
|
## 📝 Notes
|
||||||
|
|
||||||
**Audit Scope:** apps/web (Next.js 14 frontend)
|
**Audit Scope:** apps/web (Next.js 15 frontend)
|
||||||
**Files Analyzed:** 90+ TSX/JSX files
|
**Files Analyzed:** 90+ TSX/JSX files
|
||||||
**Time to Achieve AA Compliance:** 4-6 days full-time
|
**Time to Achieve AA Compliance:** 4-6 days full-time
|
||||||
**Ongoing Maintenance:** Add to CI/CD, developer training recommended
|
**Ongoing Maintenance:** Add to CI/CD, developer training recommended
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Date:** April 11, 2026
|
**Date:** April 11, 2026
|
||||||
**Scope:** Full stack exploration for implementing `/agents/[id]` public profile page
|
**Scope:** Full stack exploration for implementing `/agents/[id]` public profile page
|
||||||
**Codebase:** GoodGo Platform (Next.js 14 + NestJS 10 + PostgreSQL + Prisma)
|
**Codebase:** GoodGo Platform (Next.js 15 + NestJS 10 + PostgreSQL + Prisma)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
### File Structure
|
### File Structure
|
||||||
```
|
```
|
||||||
apps/web/
|
apps/web/
|
||||||
├── app/ # Next.js 14 App Router (Server Components)
|
├── app/ # Next.js 15 App Router (Server Components)
|
||||||
│ ├── [locale]/ # Internationalization (i18n) at root level
|
│ ├── [locale]/ # Internationalization (i18n) at root level
|
||||||
│ │ ├── (admin)/ # Admin routes (protected)
|
│ │ ├── (admin)/ # Admin routes (protected)
|
||||||
│ │ ├── (auth)/ # Auth routes (sign-in, etc.)
|
│ │ ├── (auth)/ # Auth routes (sign-in, etc.)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ New Endpoint: GET /api/v1/agents/:agentId/profile
|
|||||||
└── Implementation: PrismaAgentRepository
|
└── Implementation: PrismaAgentRepository
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frontend (Next.js 14)
|
### Frontend (Next.js 15)
|
||||||
```
|
```
|
||||||
Route: /agents/[id]
|
Route: /agents/[id]
|
||||||
│
|
│
|
||||||
|
|||||||
@@ -206,7 +206,7 @@
|
|||||||
## 🛠️ TECH STACK SUMMARY
|
## 🛠️ TECH STACK SUMMARY
|
||||||
|
|
||||||
**Backend:** NestJS 11 + Prisma 7 + PostgreSQL 16 + PostGIS 3.4
|
**Backend:** NestJS 11 + Prisma 7 + PostgreSQL 16 + PostGIS 3.4
|
||||||
**Frontend:** Next.js 14 + React 18 + Tailwind CSS + Zustand
|
**Frontend:** Next.js 15 + React 18 + Tailwind CSS + Zustand
|
||||||
**Testing:** Vitest + Jest + Playwright
|
**Testing:** Vitest + Jest + Playwright
|
||||||
**DevOps:** GitHub Actions + Docker + Kubernetes
|
**DevOps:** GitHub Actions + Docker + Kubernetes
|
||||||
**Monitoring:** Prometheus + Grafana + Loki + Sentry
|
**Monitoring:** Prometheus + Grafana + Loki + Sentry
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ Push → Lint (2m) → Typecheck (2m) → Test (4m) → Build (3m) → E2E (8m)
|
|||||||
## TECH STACK HIGHLIGHTS
|
## TECH STACK HIGHLIGHTS
|
||||||
|
|
||||||
**Backend:** NestJS 11 + Prisma 7 + PostgreSQL 16 + PostGIS 3.4
|
**Backend:** NestJS 11 + Prisma 7 + PostgreSQL 16 + PostGIS 3.4
|
||||||
**Frontend:** Next.js 14 + React 18 + Tailwind CSS + Zustand
|
**Frontend:** Next.js 15 + React 18 + Tailwind CSS + Zustand
|
||||||
**Testing:** Vitest + Jest + Playwright
|
**Testing:** Vitest + Jest + Playwright
|
||||||
**DevOps:** GitHub Actions + Docker + Kubernetes
|
**DevOps:** GitHub Actions + Docker + Kubernetes
|
||||||
**Monitoring:** Prometheus + Grafana + Loki + Sentry
|
**Monitoring:** Prometheus + Grafana + Loki + Sentry
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ apps/web/
|
|||||||
- **Unit Tests**: 6 spec files (limited coverage)
|
- **Unit Tests**: 6 spec files (limited coverage)
|
||||||
- **E2E Tests**: 15 Playwright tests
|
- **E2E Tests**: 15 Playwright tests
|
||||||
- **Technologies**:
|
- **Technologies**:
|
||||||
- **Framework**: Next.js 14 with App Router
|
- **Framework**: Next.js 15 with App Router
|
||||||
- **Styling**: Tailwind CSS + class-variance-authority
|
- **Styling**: Tailwind CSS + class-variance-authority
|
||||||
- **State**: Zustand
|
- **State**: Zustand
|
||||||
- **Forms**: React Hook Form + Zod validation
|
- **Forms**: React Hook Form + Zod validation
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ The **GoodGo Platform AI** is a well-architected Vietnamese real estate platform
|
|||||||
goodgo-platform-ai/
|
goodgo-platform-ai/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── api/ # NestJS backend (3,001 files)
|
│ ├── api/ # NestJS backend (3,001 files)
|
||||||
│ └── web/ # Next.js 14 frontend (864 files)
|
│ └── web/ # Next.js 15 frontend (864 files)
|
||||||
├── libs/
|
├── libs/
|
||||||
│ ├── ai-services/ # Python FastAPI services
|
│ ├── ai-services/ # Python FastAPI services
|
||||||
│ └── mcp-servers/ # Model Context Protocol servers
|
│ └── mcp-servers/ # Model Context Protocol servers
|
||||||
@@ -199,7 +199,7 @@ pnpm test:e2e:web # Web-only E2E tests
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. FRONTEND STRUCTURE (Next.js 14)
|
## 3. FRONTEND STRUCTURE (Next.js 15)
|
||||||
|
|
||||||
### 3.1 Route Architecture (App Router)
|
### 3.1 Route Architecture (App Router)
|
||||||
|
|
||||||
@@ -1330,7 +1330,7 @@ Redis (Cache): ✅ Optional password
|
|||||||
⚠️ Monitor large JSON fields (amenities, nearbyPOIs) for query slowness
|
⚠️ Monitor large JSON fields (amenities, nearbyPOIs) for query slowness
|
||||||
|
|
||||||
**Frontend Optimization:**
|
**Frontend Optimization:**
|
||||||
✅ Next.js 14 with App Router
|
✅ Next.js 15 with App Router
|
||||||
✅ Tailwind CSS for minimal CSS
|
✅ Tailwind CSS for minimal CSS
|
||||||
⚠️ Map component (Mapbox GL) could be lazy-loaded
|
⚠️ Map component (Mapbox GL) could be lazy-loaded
|
||||||
|
|
||||||
@@ -1413,7 +1413,7 @@ Redis (Cache): ✅ Optional password
|
|||||||
✅ HTTPS ready (Nginx reverse proxy)
|
✅ HTTPS ready (Nginx reverse proxy)
|
||||||
|
|
||||||
✅ Frontend
|
✅ Frontend
|
||||||
✅ Modern framework (Next.js 14)
|
✅ Modern framework (Next.js 15)
|
||||||
✅ Responsive design (Tailwind CSS)
|
✅ Responsive design (Tailwind CSS)
|
||||||
✅ Component library (Shadcn/Radix)
|
✅ Component library (Shadcn/Radix)
|
||||||
✅ State management (Zustand + React Query)
|
✅ State management (Zustand + React Query)
|
||||||
@@ -1650,7 +1650,7 @@ Architecture: Domain-Driven Design
|
|||||||
|
|
||||||
### Frontend Stack
|
### Frontend Stack
|
||||||
```
|
```
|
||||||
Framework: Next.js 14 (App Router)
|
Framework: Next.js 15 (App Router)
|
||||||
Language: TypeScript 5.4
|
Language: TypeScript 5.4
|
||||||
UI Components: Shadcn/Radix UI
|
UI Components: Shadcn/Radix UI
|
||||||
Styling: Tailwind CSS 3
|
Styling: Tailwind CSS 3
|
||||||
|
|||||||
@@ -349,7 +349,7 @@
|
|||||||
- ✅ Dynamic sitemap (`sitemap.ts`)
|
- ✅ Dynamic sitemap (`sitemap.ts`)
|
||||||
- ✅ robots.txt configuration
|
- ✅ robots.txt configuration
|
||||||
- ✅ i18n support (vi/en localization)
|
- ✅ i18n support (vi/en localization)
|
||||||
- ✅ Next.js 14.2 with optimizations
|
- ✅ Next.js 15.2 with optimizations
|
||||||
|
|
||||||
### Test Coverage ❌
|
### Test Coverage ❌
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
## 1. **App Structure** ✅
|
## 1. **App Structure** ✅
|
||||||
|
|
||||||
**Status**: GOOD - Next.js 14 App Router properly implemented
|
**Status**: GOOD - Next.js 15 App Router properly implemented
|
||||||
|
|
||||||
### Pages & Routes (22 pages total):
|
### Pages & Routes (22 pages total):
|
||||||
- **Public**: Landing (`/`), Search Results (`/search`), Listing Detail (`/listings/[id]`)
|
- **Public**: Landing (`/`), Search Results (`/search`), Listing Detail (`/listings/[id]`)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
The GoodGo Platform is a **sophisticated, production-ready monorepo** with comprehensive infrastructure, strong CI/CD pipelines, and excellent DevOps practices. The platform integrates:
|
The GoodGo Platform is a **sophisticated, production-ready monorepo** with comprehensive infrastructure, strong CI/CD pipelines, and excellent DevOps practices. The platform integrates:
|
||||||
|
|
||||||
- **Backend**: NestJS 11 + Prisma ORM + CQRS
|
- **Backend**: NestJS 11 + Prisma ORM + CQRS
|
||||||
- **Frontend**: Next.js 14 + React 18 + Tailwind CSS
|
- **Frontend**: Next.js 15 + React 18 + Tailwind CSS
|
||||||
- **Databases**: PostgreSQL 16 + PostGIS 3.4
|
- **Databases**: PostgreSQL 16 + PostGIS 3.4
|
||||||
- **Search**: Typesense 27
|
- **Search**: Typesense 27
|
||||||
- **Cache/Async**: Redis 7
|
- **Cache/Async**: Redis 7
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Next.js 14 → 15 Upgrade Risk Assessment
|
# Next.js 15 → 15 Upgrade Risk Assessment
|
||||||
**GoodGo Platform Web App**
|
**GoodGo Platform Web App**
|
||||||
**Assessment Date:** 2026-04-10
|
**Assessment Date:** 2026-04-10
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
**Overall Risk Level: LOW**
|
**Overall Risk Level: LOW**
|
||||||
|
|
||||||
This Next.js 14.2.35 application is well-positioned for a Next.js 15 upgrade with minimal friction. The codebase follows modern Next.js patterns (App Router exclusively) and has no deprecated features. The main upgrade path is straightforward with only minor library version compatibility checks needed.
|
This Next.js 15.2.35 application is well-positioned for a Next.js 15 upgrade with minimal friction. The codebase follows modern Next.js patterns (App Router exclusively) and has no deprecated features. The main upgrade path is straightforward with only minor library version compatibility checks needed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Next.js 14.2.35 → 15 Upgrade Checklist
|
# Next.js 15.2.35 → 15 Upgrade Checklist
|
||||||
|
|
||||||
## ✅ Pre-Upgrade Verification
|
## ✅ Pre-Upgrade Verification
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user