Compare commits
2 Commits
6b23bfb756
...
579856795a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
579856795a | ||
|
|
bafc3ddc2f |
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -195,8 +195,8 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- 9000:9000
|
- 9000:9000
|
||||||
env:
|
env:
|
||||||
MINIO_ROOT_USER: ci_minio_user
|
MINIO_ROOT_USER: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||||
MINIO_ROOT_PASSWORD: ci_minio_secret_key_32chars!!
|
MINIO_ROOT_PASSWORD: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
--health-cmd "curl -sf http://localhost:9000/minio/health/live || exit 1"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
@@ -206,22 +206,41 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
|
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
|
||||||
REDIS_URL: redis://localhost:6379
|
REDIS_URL: redis://localhost:6379
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
REDIS_PORT: 6379
|
||||||
TYPESENSE_URL: http://localhost:8108
|
TYPESENSE_URL: http://localhost:8108
|
||||||
TYPESENSE_HOST: localhost
|
TYPESENSE_HOST: localhost
|
||||||
TYPESENSE_PORT: 8108
|
TYPESENSE_PORT: 8108
|
||||||
|
TYPESENSE_PROTOCOL: http
|
||||||
TYPESENSE_API_KEY: ts_ci_key
|
TYPESENSE_API_KEY: ts_ci_key
|
||||||
MINIO_ENDPOINT: localhost
|
MINIO_ENDPOINT: localhost
|
||||||
MINIO_PORT: 9000
|
MINIO_PORT: 9000
|
||||||
MINIO_ACCESS_KEY: ci_minio_user
|
MINIO_ACCESS_KEY: ${{ vars.CI_MINIO_ACCESS_KEY || 'ci_minio_user' }}
|
||||||
MINIO_SECRET_KEY: ci_minio_secret_key_32chars!!
|
MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
|
||||||
MINIO_BUCKET: goodgo-uploads
|
MINIO_BUCKET: goodgo-uploads
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
JWT_SECRET: e2e-test-jwt-secret-key
|
CI: true
|
||||||
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key
|
# 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_TMN_CODE: TESTCODE
|
||||||
VNPAY_HASH_SECRET: TESTHASHSECRET
|
VNPAY_HASH_SECRET: TESTHASHSECRETTESTHASHSECRETTEST
|
||||||
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
||||||
VNPAY_RETURN_URL: http://localhost:3000/payment/return
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { type DomainEvent } from '@modules/shared';
|
||||||
|
|
||||||
|
export class BrokerCertStatusChangedEvent implements DomainEvent {
|
||||||
|
readonly eventName = 'agent.broker_cert_status_changed';
|
||||||
|
readonly occurredAt = new Date();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly aggregateId: string,
|
||||||
|
public readonly previousStatus: string,
|
||||||
|
public readonly newStatus: string,
|
||||||
|
public readonly certExpiryAt: Date | null,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { type DomainEvent } from '@modules/shared';
|
||||||
|
|
||||||
|
export class PhoneLoginOtpRequestedEvent implements DomainEvent {
|
||||||
|
readonly eventName = 'user.phone_login_otp';
|
||||||
|
readonly occurredAt = new Date();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly aggregateId: string,
|
||||||
|
public readonly phone: string,
|
||||||
|
public readonly otpCode: string,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
import { type PhoneLoginOtpRequestedEvent } from '@modules/auth/domain/events/phone-login-otp-requested.event';
|
||||||
|
import { LoggerService } from '@modules/shared';
|
||||||
|
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PhoneLoginOtpRequestedListener {
|
||||||
|
constructor(
|
||||||
|
private readonly commandBus: CommandBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@OnEvent('user.phone_login_otp', { async: true })
|
||||||
|
async handle(event: PhoneLoginOtpRequestedEvent): Promise<void> {
|
||||||
|
this.logger.log(
|
||||||
|
`Handling phone login OTP for user ${event.aggregateId}`,
|
||||||
|
'PhoneLoginOtpRequestedListener',
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new SendNotificationCommand(
|
||||||
|
event.aggregateId,
|
||||||
|
'SMS',
|
||||||
|
'user.phone_login_otp',
|
||||||
|
{ otpCode: event.otpCode },
|
||||||
|
undefined,
|
||||||
|
event.phone,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ describe('CreatePaymentHandler', () => {
|
|||||||
let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
|
let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
|
||||||
let mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: ReturnType<typeof vi.fn> };
|
let mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: ReturnType<typeof vi.fn> };
|
||||||
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||||
|
let mockSbvCompliance: { checkPaymentCompliance: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPaymentRepo = {
|
mockPaymentRepo = {
|
||||||
@@ -35,11 +36,16 @@ describe('CreatePaymentHandler', () => {
|
|||||||
|
|
||||||
mockEventBus = { publish: vi.fn() };
|
mockEventBus = { publish: vi.fn() };
|
||||||
|
|
||||||
|
mockSbvCompliance = {
|
||||||
|
checkPaymentCompliance: vi.fn().mockReturnValue({ allowed: true, sbvLargeTransaction: false, amlFlagged: false }),
|
||||||
|
};
|
||||||
|
|
||||||
const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };
|
const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };
|
||||||
|
|
||||||
handler = new CreatePaymentHandler(
|
handler = new CreatePaymentHandler(
|
||||||
mockPaymentRepo as any,
|
mockPaymentRepo as any,
|
||||||
mockGatewayFactory as any,
|
mockGatewayFactory as any,
|
||||||
|
mockSbvCompliance as any,
|
||||||
mockEventBus as any,
|
mockEventBus as any,
|
||||||
mockLogger as any,
|
mockLogger as any,
|
||||||
);
|
);
|
||||||
|
|||||||
181
apps/web/app/[locale]/(public)/chinh-sach-bao-mat/page.tsx
Normal file
181
apps/web/app/[locale]/(public)/chinh-sach-bao-mat/page.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { Shield } from 'lucide-react';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Chính sách bảo mật | GoodGo',
|
||||||
|
description: 'Chính sách bảo mật và quyền riêng tư của nền tảng bất động sản GoodGo.',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PrivacyPolicyPage() {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-3xl px-4 py-12 md:py-16">
|
||||||
|
<div className="mb-8 flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||||
|
<Shield className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight text-foreground md:text-3xl">
|
||||||
|
Chính sách bảo mật
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mb-8 text-sm text-foreground-muted">Cập nhật lần cuối: 23/04/2026</p>
|
||||||
|
|
||||||
|
<div className="prose prose-sm max-w-none space-y-8 text-foreground">
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">1. Giới thiệu</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
GoodGo ("chúng tôi", "của chúng tôi") cam kết bảo vệ quyền riêng tư của bạn. Chính
|
||||||
|
sách này mô tả cách chúng tôi thu thập, sử dụng và bảo vệ thông tin cá nhân khi bạn
|
||||||
|
sử dụng nền tảng bất động sản GoodGo tại{' '}
|
||||||
|
<a href="https://goodgo.vn" className="text-primary hover:underline">
|
||||||
|
goodgo.vn
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
||||||
|
2. Thông tin chúng tôi thu thập
|
||||||
|
</h2>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Thông tin tài khoản:</strong> Họ tên, địa chỉ
|
||||||
|
email, số điện thoại khi bạn đăng ký.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Thông tin hồ sơ:</strong> Ảnh đại diện, thông
|
||||||
|
tin nghề nghiệp (nếu là môi giới), giấy tờ KYC.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Dữ liệu sử dụng:</strong> Lịch sử tìm kiếm, tin
|
||||||
|
đăng đã xem, tìm kiếm đã lưu, thao tác trên nền tảng.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Thông tin thanh toán:</strong> Lịch sử giao
|
||||||
|
dịch (không lưu số thẻ; thanh toán qua VNPay, MoMo, ZaloPay).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Dữ liệu kỹ thuật:</strong> Địa chỉ IP, loại
|
||||||
|
trình duyệt, thiết bị, cookie phiên làm việc.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
||||||
|
3. Mục đích sử dụng thông tin
|
||||||
|
</h2>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Cung cấp và cải thiện dịch vụ tìm kiếm, đăng tin bất động sản.</li>
|
||||||
|
<li>Xác minh danh tính (KYC) và ngăn chặn gian lận.</li>
|
||||||
|
<li>Xử lý thanh toán và quản lý đăng ký dịch vụ.</li>
|
||||||
|
<li>Gửi thông báo về tin đăng, giá thị trường và các cập nhật hệ thống.</li>
|
||||||
|
<li>
|
||||||
|
Phân tích thị trường bất động sản để cung cấp thông tin giá trị cho người dùng.
|
||||||
|
</li>
|
||||||
|
<li>Tuân thủ nghĩa vụ pháp lý theo quy định của pháp luật Việt Nam.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">4. Chia sẻ thông tin</h2>
|
||||||
|
<p className="mb-3 leading-relaxed text-foreground-muted">
|
||||||
|
Chúng tôi không bán thông tin cá nhân của bạn. Thông tin chỉ được chia sẻ trong các
|
||||||
|
trường hợp sau:
|
||||||
|
</p>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Đối tác dịch vụ:</strong> Nhà cung cấp thanh
|
||||||
|
toán (VNPay, MoMo, ZaloPay), dịch vụ email, lưu trữ đám mây — chỉ ở mức cần thiết.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Yêu cầu pháp lý:</strong> Khi có yêu cầu từ cơ
|
||||||
|
quan nhà nước có thẩm quyền theo quy định pháp luật.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong className="text-foreground">Bảo vệ quyền lợi:</strong> Khi cần thiết để
|
||||||
|
bảo vệ quyền lợi, tài sản hoặc sự an toàn của GoodGo, người dùng, hoặc công chúng.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">5. Bảo mật dữ liệu</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Chúng tôi áp dụng các biện pháp bảo mật kỹ thuật và tổ chức phù hợp, bao gồm mã hóa
|
||||||
|
TLS/HTTPS, kiểm soát truy cập, và giám sát hệ thống liên tục. Mật khẩu được băm bằng
|
||||||
|
thuật toán bcrypt. Mã thông báo xác thực (JWT) có thời hạn ngắn và được làm mới an
|
||||||
|
toàn.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">6. Cookie</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Chúng tôi sử dụng cookie cần thiết để duy trì phiên đăng nhập và cookie phân tích để
|
||||||
|
cải thiện trải nghiệm người dùng. Bạn có thể điều chỉnh cài đặt cookie trong trình
|
||||||
|
duyệt, tuy nhiên một số tính năng có thể không hoạt động nếu vô hiệu hóa cookie cần
|
||||||
|
thiết.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">7. Quyền của bạn</h2>
|
||||||
|
<p className="mb-3 leading-relaxed text-foreground-muted">
|
||||||
|
Theo quy định pháp luật Việt Nam về bảo vệ dữ liệu cá nhân, bạn có quyền:
|
||||||
|
</p>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Truy cập và xem thông tin cá nhân của bạn.</li>
|
||||||
|
<li>Yêu cầu chỉnh sửa thông tin không chính xác.</li>
|
||||||
|
<li>Yêu cầu xóa tài khoản và dữ liệu liên quan.</li>
|
||||||
|
<li>Phản đối việc xử lý dữ liệu cho mục đích tiếp thị.</li>
|
||||||
|
<li>Rút lại sự đồng ý đã cấp trước đó.</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mt-3 leading-relaxed text-foreground-muted">
|
||||||
|
Để thực hiện các quyền này, vui lòng liên hệ:{' '}
|
||||||
|
<a href="mailto:privacy@goodgo.vn" className="text-primary hover:underline">
|
||||||
|
privacy@goodgo.vn
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">8. Lưu giữ dữ liệu</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Dữ liệu tài khoản được lưu giữ trong suốt thời gian tài khoản hoạt động và tối đa 90
|
||||||
|
ngày sau khi xóa tài khoản (trừ khi pháp luật yêu cầu lưu giữ lâu hơn). Nhật ký giao
|
||||||
|
dịch được lưu giữ 5 năm theo quy định tài chính.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">9. Thay đổi chính sách</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Chúng tôi có thể cập nhật chính sách này theo thời gian. Các thay đổi quan trọng sẽ
|
||||||
|
được thông báo qua email hoặc thông báo trên nền tảng trước ít nhất 30 ngày.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">10. Liên hệ</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Mọi thắc mắc về chính sách bảo mật, vui lòng liên hệ:
|
||||||
|
</p>
|
||||||
|
<address className="mt-2 not-italic text-foreground-muted">
|
||||||
|
<strong className="text-foreground">GoodGo Technology Co., Ltd.</strong>
|
||||||
|
<br />
|
||||||
|
TP. Hồ Chí Minh, Việt Nam
|
||||||
|
<br />
|
||||||
|
Email:{' '}
|
||||||
|
<a href="mailto:privacy@goodgo.vn" className="text-primary hover:underline">
|
||||||
|
privacy@goodgo.vn
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
|
Điện thoại: 1900 xxxx
|
||||||
|
</address>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
169
apps/web/app/[locale]/(public)/dieu-khoan-su-dung/page.tsx
Normal file
169
apps/web/app/[locale]/(public)/dieu-khoan-su-dung/page.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import { FileText } from 'lucide-react';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Điều khoản sử dụng | GoodGo',
|
||||||
|
description: 'Điều khoản và điều kiện sử dụng nền tảng bất động sản GoodGo.',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TermsOfServicePage() {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-3xl px-4 py-12 md:py-16">
|
||||||
|
<div className="mb-8 flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight text-foreground md:text-3xl">
|
||||||
|
Điều khoản sử dụng
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mb-8 text-sm text-foreground-muted">Cập nhật lần cuối: 23/04/2026</p>
|
||||||
|
|
||||||
|
<div className="prose prose-sm max-w-none space-y-8 text-foreground">
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">1. Chấp nhận điều khoản</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Bằng cách truy cập hoặc sử dụng nền tảng GoodGo (bao gồm website goodgo.vn và các
|
||||||
|
ứng dụng liên quan), bạn đồng ý bị ràng buộc bởi các điều khoản sử dụng này. Nếu bạn
|
||||||
|
không đồng ý với bất kỳ điều khoản nào, vui lòng ngừng sử dụng dịch vụ.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">2. Mô tả dịch vụ</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
GoodGo là nền tảng bất động sản thông minh cung cấp các dịch vụ bao gồm:
|
||||||
|
</p>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Đăng tin và tìm kiếm bất động sản mua bán, cho thuê.</li>
|
||||||
|
<li>Phân tích thị trường, chỉ số giá và xu hướng khu vực.</li>
|
||||||
|
<li>Công cụ định giá tự động (AVM) sử dụng trí tuệ nhân tạo.</li>
|
||||||
|
<li>Quản lý hồ sơ môi giới và đánh giá chất lượng.</li>
|
||||||
|
<li>Kết nối người mua, người bán và nhà đầu tư.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
||||||
|
3. Điều kiện sử dụng tài khoản
|
||||||
|
</h2>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Bạn phải đủ 18 tuổi hoặc có sự đồng ý của người giám hộ hợp pháp.</li>
|
||||||
|
<li>Thông tin đăng ký phải chính xác, đầy đủ và cập nhật.</li>
|
||||||
|
<li>
|
||||||
|
Bạn chịu trách nhiệm bảo mật thông tin đăng nhập và toàn bộ hoạt động trong tài
|
||||||
|
khoản của mình.
|
||||||
|
</li>
|
||||||
|
<li>Mỗi người chỉ được sở hữu một tài khoản cá nhân.</li>
|
||||||
|
<li>
|
||||||
|
Tài khoản không được chuyển nhượng hoặc chia sẻ cho người khác mà không có sự đồng
|
||||||
|
ý của GoodGo.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">4. Quy định đăng tin</h2>
|
||||||
|
<p className="mb-3 leading-relaxed text-foreground-muted">
|
||||||
|
Khi đăng tin trên GoodGo, bạn cam kết:
|
||||||
|
</p>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Thông tin về bất động sản là chính xác, trung thực và không gây hiểu lầm.</li>
|
||||||
|
<li>Bạn có quyền hợp pháp để đăng thông tin bất động sản đó.</li>
|
||||||
|
<li>Hình ảnh và mô tả phù hợp với thực tế của bất động sản.</li>
|
||||||
|
<li>
|
||||||
|
Không đăng nội dung vi phạm pháp luật, xâm phạm quyền sở hữu trí tuệ, hoặc gây
|
||||||
|
phương hại đến người khác.
|
||||||
|
</li>
|
||||||
|
<li>Không đăng tin giả, tin lặp hoặc tin mồi nhử.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">5. Hành vi bị cấm</h2>
|
||||||
|
<p className="mb-3 leading-relaxed text-foreground-muted">Người dùng không được phép:</p>
|
||||||
|
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
|
||||||
|
<li>Sử dụng nền tảng để gian lận, lừa đảo hoặc hoạt động bất hợp pháp.</li>
|
||||||
|
<li>Thu thập dữ liệu bằng bot, scraper hoặc các phương tiện tự động khác.</li>
|
||||||
|
<li>Cố tình làm gián đoạn hoặc tấn công hệ thống GoodGo.</li>
|
||||||
|
<li>Giả mạo danh tính cá nhân, tổ chức hoặc nhân viên GoodGo.</li>
|
||||||
|
<li>Đăng nội dung vi phạm pháp luật Việt Nam.</li>
|
||||||
|
<li>Can thiệp vào quyền sử dụng dịch vụ của người dùng khác.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
||||||
|
6. Đăng ký dịch vụ và thanh toán
|
||||||
|
</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Một số tính năng yêu cầu đăng ký gói dịch vụ trả phí. Thanh toán được xử lý qua các
|
||||||
|
cổng thanh toán an toàn (VNPay, MoMo, ZaloPay). Phí dịch vụ được hiển thị rõ ràng
|
||||||
|
trước khi xác nhận giao dịch. Chính sách hoàn tiền áp dụng theo từng gói dịch vụ cụ
|
||||||
|
thể và quy định pháp luật hiện hành.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">7. Quyền sở hữu trí tuệ</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Toàn bộ nội dung, giao diện, mã nguồn, logo, nhãn hiệu và tài liệu trên nền tảng
|
||||||
|
GoodGo là tài sản sở hữu trí tuệ của chúng tôi hoặc được cấp phép hợp lệ. Bạn được
|
||||||
|
cấp phép sử dụng cá nhân, phi thương mại. Mọi hành vi sao chép, phân phối hoặc sử
|
||||||
|
dụng thương mại mà không có sự đồng ý bằng văn bản là bị nghiêm cấm.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">8. Giới hạn trách nhiệm</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
GoodGo hoạt động như nền tảng kết nối và không chịu trách nhiệm về tính pháp lý của
|
||||||
|
bất động sản đăng tin, tranh chấp phát sinh giữa các bên, hoặc thiệt hại gián tiếp
|
||||||
|
từ việc sử dụng nền tảng. Thông tin giá và phân tích thị trường chỉ mang tính tham
|
||||||
|
khảo, không phải lời khuyên tài chính hay pháp lý.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
||||||
|
9. Đình chỉ và chấm dứt tài khoản
|
||||||
|
</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
GoodGo có quyền đình chỉ hoặc chấm dứt tài khoản khi phát hiện vi phạm điều khoản
|
||||||
|
này, hành vi gian lận, hoặc theo yêu cầu của cơ quan có thẩm quyền. Người dùng có
|
||||||
|
thể tự xóa tài khoản bất cứ lúc nào từ trang cài đặt hồ sơ.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">10. Luật áp dụng</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Điều khoản này được điều chỉnh bởi pháp luật Cộng hòa Xã hội Chủ nghĩa Việt Nam.
|
||||||
|
Mọi tranh chấp phát sinh sẽ được giải quyết tại Tòa án nhân dân có thẩm quyền tại
|
||||||
|
TP. Hồ Chí Minh.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="mb-3 text-lg font-semibold text-foreground">11. Liên hệ</h2>
|
||||||
|
<p className="leading-relaxed text-foreground-muted">
|
||||||
|
Mọi thắc mắc về điều khoản sử dụng, vui lòng liên hệ:
|
||||||
|
</p>
|
||||||
|
<address className="mt-2 not-italic text-foreground-muted">
|
||||||
|
<strong className="text-foreground">GoodGo Technology Co., Ltd.</strong>
|
||||||
|
<br />
|
||||||
|
TP. Hồ Chí Minh, Việt Nam
|
||||||
|
<br />
|
||||||
|
Email:{' '}
|
||||||
|
<a href="mailto:legal@goodgo.vn" className="text-primary hover:underline">
|
||||||
|
legal@goodgo.vn
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
|
Điện thoại: 1900 xxxx
|
||||||
|
</address>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -117,6 +117,13 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
|||||||
{ label: t('common.register'), href: '/register' },
|
{ label: t('common.register'), href: '/register' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('footer.legal'),
|
||||||
|
links: [
|
||||||
|
{ label: t('footer.privacyPolicy'), href: '/chinh-sach-bao-mat' },
|
||||||
|
{ label: t('footer.termsOfService'), href: '/dieu-khoan-su-dung' },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -163,6 +170,10 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
|||||||
description={t('footer.description')}
|
description={t('footer.description')}
|
||||||
linkGroups={footerLinkGroups}
|
linkGroups={footerLinkGroups}
|
||||||
copyright={t('common.allRightsReserved')}
|
copyright={t('common.allRightsReserved')}
|
||||||
|
legalLinks={[
|
||||||
|
{ label: t('footer.privacyPolicy'), href: '/chinh-sach-bao-mat' },
|
||||||
|
{ label: t('footer.termsOfService'), href: '/dieu-khoan-su-dung' },
|
||||||
|
]}
|
||||||
contact={{
|
contact={{
|
||||||
address: 'TP. Hồ Chí Minh, Việt Nam',
|
address: 'TP. Hồ Chí Minh, Việt Nam',
|
||||||
phone: '1900 xxxx',
|
phone: '1900 xxxx',
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export interface FooterProps {
|
|||||||
};
|
};
|
||||||
/** Social links. */
|
/** Social links. */
|
||||||
socials?: { platform: 'facebook' | 'instagram' | 'youtube'; href: string }[];
|
socials?: { platform: 'facebook' | 'instagram' | 'youtube'; href: string }[];
|
||||||
|
/** Optional legal links shown in the bottom bar (privacy policy, ToS, etc.). */
|
||||||
|
legalLinks?: { label: string; href: string }[];
|
||||||
/** Custom link renderer for framework-specific Link. */
|
/** Custom link renderer for framework-specific Link. */
|
||||||
renderLink: (props: {
|
renderLink: (props: {
|
||||||
href: string;
|
href: string;
|
||||||
@@ -63,6 +65,7 @@ export function Footer({
|
|||||||
copyright,
|
copyright,
|
||||||
contact,
|
contact,
|
||||||
socials,
|
socials,
|
||||||
|
legalLinks,
|
||||||
renderLink,
|
renderLink,
|
||||||
}: FooterProps) {
|
}: FooterProps) {
|
||||||
return (
|
return (
|
||||||
@@ -156,8 +159,21 @@ export function Footer({
|
|||||||
|
|
||||||
{/* Bottom bar */}
|
{/* Bottom bar */}
|
||||||
<div className="border-t border-border">
|
<div className="border-t border-border">
|
||||||
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4">
|
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-2 px-4 py-4">
|
||||||
<p className="text-xs text-muted-foreground">{copyright}</p>
|
<p className="text-xs text-muted-foreground">{copyright}</p>
|
||||||
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
|
{legalLinks && legalLinks.length > 0 && (
|
||||||
|
<nav aria-label="Legal links" className="flex items-center gap-3">
|
||||||
|
{legalLinks.map((link) =>
|
||||||
|
renderLink({
|
||||||
|
href: link.href,
|
||||||
|
className:
|
||||||
|
'text-xs text-muted-foreground transition-colors hover:text-primary',
|
||||||
|
children: link.label,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Building2 className="h-3 w-3 text-muted-foreground" />
|
<Building2 className="h-3 w-3 text-muted-foreground" />
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
@@ -166,6 +182,7 @@ export function Footer({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,10 @@
|
|||||||
"description": "Smart real estate platform in Vietnam",
|
"description": "Smart real estate platform in Vietnam",
|
||||||
"propertyTypes": "Property types",
|
"propertyTypes": "Property types",
|
||||||
"areas": "Areas",
|
"areas": "Areas",
|
||||||
"support": "Support"
|
"support": "Support",
|
||||||
|
"legal": "Legal",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"termsOfService": "Terms of Service"
|
||||||
},
|
},
|
||||||
"propertyTypes": {
|
"propertyTypes": {
|
||||||
"APARTMENT": "Apartment",
|
"APARTMENT": "Apartment",
|
||||||
|
|||||||
@@ -118,7 +118,10 @@
|
|||||||
"description": "Nền tảng bất động sản thông minh tại Việt Nam",
|
"description": "Nền tảng bất động sản thông minh tại Việt Nam",
|
||||||
"propertyTypes": "Loại BĐS",
|
"propertyTypes": "Loại BĐS",
|
||||||
"areas": "Khu vực",
|
"areas": "Khu vực",
|
||||||
"support": "Hỗ trợ"
|
"support": "Hỗ trợ",
|
||||||
|
"legal": "Pháp lý",
|
||||||
|
"privacyPolicy": "Chính sách bảo mật",
|
||||||
|
"termsOfService": "Điều khoản sử dụng"
|
||||||
},
|
},
|
||||||
"propertyTypes": {
|
"propertyTypes": {
|
||||||
"APARTMENT": "Căn hộ",
|
"APARTMENT": "Căn hộ",
|
||||||
|
|||||||
159
docs/runbooks/sentry.md
Normal file
159
docs/runbooks/sentry.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# Sentry Alert Routing — Runbook
|
||||||
|
|
||||||
|
> Audience: SRE on-call & platform engineers.
|
||||||
|
> Scope: How Sentry P1/P2 alerts are wired into Slack & email for the GoodGo platform, and how to verify routing end-to-end.
|
||||||
|
> Related ticket: [GOO-178](/GOO/issues/GOO-178) (gap surfaced by [GOO-117](/GOO/issues/GOO-117) audit).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Sentry projects & SDK wiring
|
||||||
|
|
||||||
|
The SDK is initialised in three Next.js entrypoints (server, client, edge):
|
||||||
|
|
||||||
|
| File | DSN env | Notes |
|
||||||
|
|------|---------|-------|
|
||||||
|
| `apps/web/sentry.server.config.ts` | `SENTRY_DSN` | Server-side errors, traces (`tracesSampleRate=0.2` in prod). |
|
||||||
|
| `apps/web/sentry.client.config.ts` | `NEXT_PUBLIC_SENTRY_DSN` | Browser errors + session replay on error. |
|
||||||
|
| `apps/web/sentry.edge.config.ts` | `SENTRY_DSN` | Edge runtime. |
|
||||||
|
|
||||||
|
Required env (see `.env.example`):
|
||||||
|
|
||||||
|
- `SENTRY_DSN`, `NEXT_PUBLIC_SENTRY_DSN`
|
||||||
|
- `SENTRY_AUTH_TOKEN` (CI source-map upload)
|
||||||
|
- `SENTRY_ORG`, `SENTRY_PROJECT`
|
||||||
|
- `SENTRY_RELEASE` / `NEXT_PUBLIC_SENTRY_RELEASE` (set per deploy by CI)
|
||||||
|
|
||||||
|
`environment` is set from `NODE_ENV` on every init call, so all events are tagged `production` / `staging` / `development` automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Severity model
|
||||||
|
|
||||||
|
We classify Sentry events into two routing tiers using event tags. Tag `severity` is set either by the SDK (`Sentry.setTag('severity', 'P1')`) at capture time, or via Sentry **Issue tagging rules** based on event.level / fingerprint.
|
||||||
|
|
||||||
|
| Tier | Trigger criteria | Routing target | SLA |
|
||||||
|
|------|------------------|----------------|-----|
|
||||||
|
| **P1** | `level:fatal` OR `tags[severity]=P1` OR fingerprint in critical group (payments, auth, data loss) | Slack `#alerts-p1-sentry` (immediate) + email to `oncall@goodgo.vn` | Page on-call within 5 min |
|
||||||
|
| **P2** | `level:error` AND `environment:production` AND not P1 | Slack `#alerts-p2-sentry` (digest, every 30 min) | Triage within 4 business hours |
|
||||||
|
| Below P2 (warn / info / staging-only) | Everything else | No paging — visible only in Sentry UI dashboards | Best effort |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Required Sentry integrations
|
||||||
|
|
||||||
|
Verify in **Sentry → Settings → Integrations**:
|
||||||
|
|
||||||
|
- [ ] **Slack** — workspace `goodgo`, channels `#alerts-p1-sentry` and `#alerts-p2-sentry` authorised.
|
||||||
|
- [ ] **Email** — built-in; `oncall@goodgo.vn` is a verified team mailing list and a member of the GoodGo team for the project.
|
||||||
|
|
||||||
|
If either integration is missing, install it before configuring rules below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Alert rule configuration
|
||||||
|
|
||||||
|
Configure under **Sentry → Alerts → Create Alert → Issues**.
|
||||||
|
|
||||||
|
### 4.1 P1 — immediate page
|
||||||
|
|
||||||
|
- **Name:** `P1 — immediate page`
|
||||||
|
- **Environment:** `production`
|
||||||
|
- **When:** "An issue is first seen" **OR** "An event is captured"
|
||||||
|
- **If (all):**
|
||||||
|
- `event.level` equals `fatal`
|
||||||
|
- **OR** `event.tags[severity]` equals `P1`
|
||||||
|
- **Then:**
|
||||||
|
- Send a Slack notification to `#alerts-p1-sentry`
|
||||||
|
- Send an email notification to Team `oncall`
|
||||||
|
- **Frequency:** every 1 minute (no batching)
|
||||||
|
- **Owner:** SRE team
|
||||||
|
|
||||||
|
### 4.2 P2 — production errors digest
|
||||||
|
|
||||||
|
- **Name:** `P2 — production errors`
|
||||||
|
- **Environment:** `production`
|
||||||
|
- **When:** "An issue changes state from resolved to unresolved" OR "A new issue is created"
|
||||||
|
- **If (all):**
|
||||||
|
- `event.level` equals `error`
|
||||||
|
- `event.tags[severity]` does not equal `P1`
|
||||||
|
- **Then:**
|
||||||
|
- Send a Slack notification to `#alerts-p2-sentry`
|
||||||
|
- **Frequency:** every 30 minutes (batched/digest)
|
||||||
|
|
||||||
|
### 4.3 Noise control
|
||||||
|
|
||||||
|
- Suppress alerts for environments other than `production` (a separate `staging` rule may post to `#alerts-staging` only).
|
||||||
|
- Use **Inbound filters** to drop browser extension errors, ResizeObserver loops, and known third-party noise.
|
||||||
|
- Per-issue **Mute → ignore until** for known-noisy fingerprints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Test fire procedure (`Sentry.captureException`)
|
||||||
|
|
||||||
|
Use this whenever rules change or a quarterly verification is due.
|
||||||
|
|
||||||
|
### 5.1 Staging fire
|
||||||
|
|
||||||
|
1. Deploy the staging branch with `SENTRY_DSN` populated.
|
||||||
|
2. Trigger the dedicated test endpoint (or via curl in staging shell):
|
||||||
|
```ts
|
||||||
|
// apps/web/app/api/_debug/sentry-test/route.ts (staging only — guard with env)
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
export async function GET() {
|
||||||
|
Sentry.captureException(new Error('GOO-178 routing test — P1'), {
|
||||||
|
tags: { severity: 'P1', source: 'manual_routing_test' },
|
||||||
|
});
|
||||||
|
return Response.json({ ok: true });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
curl -fsS https://staging.goodgo.vn/api/_debug/sentry-test
|
||||||
|
```
|
||||||
|
3. **Expected:**
|
||||||
|
- Event appears in Sentry within ~30s tagged `severity:P1`, `source:manual_routing_test`.
|
||||||
|
- Slack `#alerts-p1-sentry` receives the notification.
|
||||||
|
- Email lands at `oncall@goodgo.vn` within ~2 min.
|
||||||
|
4. Repeat without the `severity:P1` tag and with `level:error` to validate the P2 batched route (expect Slack message in next 30-min digest).
|
||||||
|
|
||||||
|
### 5.2 CLI alternative (no deploy)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SENTRY_DSN=$STAGING_SENTRY_DSN npx -y @sentry/cli send-event \
|
||||||
|
--message "GOO-178 routing test (P1)" \
|
||||||
|
--level fatal \
|
||||||
|
--tag severity:P1 \
|
||||||
|
--tag source:manual_routing_test \
|
||||||
|
--release "$(git rev-parse --short HEAD)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Cleanup
|
||||||
|
|
||||||
|
- Resolve the test issue in Sentry.
|
||||||
|
- Add `source:manual_routing_test` to the inbound filter ignore list if it generates additional noise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Verification checklist (run quarterly)
|
||||||
|
|
||||||
|
- [ ] Slack integration shows "Connected" in Sentry → Integrations.
|
||||||
|
- [ ] Email integration: open one P1 alert, confirm `oncall@goodgo.vn` received it.
|
||||||
|
- [ ] Both rules listed under Alerts with correct environment/scope.
|
||||||
|
- [ ] Test fire produces messages in `#alerts-p1-sentry` AND `#alerts-p2-sentry` per tier.
|
||||||
|
- [ ] On-call rotation in PagerDuty / Slack matches Sentry team membership.
|
||||||
|
- [ ] Inbound filters review — drop new noise sources surfaced since last review.
|
||||||
|
|
||||||
|
Record verification results as a comment on a tracking issue and update the **last verified** date in section 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Change log
|
||||||
|
|
||||||
|
| Date | Author | Change |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 2026-04-23 | SRE Engineer ([GOO-178](/GOO/issues/GOO-178)) | Initial runbook documenting P1/P2 routing model, alert rule definitions, and test fire procedure. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Outstanding
|
||||||
|
|
||||||
|
- **Sentry org/project access not yet provisioned for the agent.** Live verification of integrations + a real test fire is blocked until `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT` are provided to staging and to the SRE agent. See [GOO-178](/GOO/issues/GOO-178) for the open blocker.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- Migration: 20260423000000_add_vnbds_compliance_fields
|
||||||
|
-- Luật Kinh doanh BĐS 2023, Điều 6-7 + Nghị định 96/2024, Điều 62
|
||||||
|
|
||||||
|
CREATE TYPE "BrokerCertStatus" AS ENUM (
|
||||||
|
'UNVERIFIED', 'PENDING_REVIEW', 'VERIFIED', 'EXPIRED', 'REVOKED'
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "Property"
|
||||||
|
ADD COLUMN "certificateNumber" TEXT,
|
||||||
|
ADD COLUMN "certificateIssuedAt" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "certificateIssuedBy" TEXT,
|
||||||
|
ADD COLUMN "hasMortgage" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
ADD COLUMN "hasDispute" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
ADD COLUMN "legalEncumbrances" TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE "Agent"
|
||||||
|
ADD COLUMN "brokerCertNumber" TEXT,
|
||||||
|
ADD COLUMN "brokerCertIssuedAt" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "brokerCertExpiryAt" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "brokerCertStatus" "BrokerCertStatus" NOT NULL DEFAULT 'UNVERIFIED',
|
||||||
|
ADD COLUMN "brokerCertDocUrl" TEXT;
|
||||||
|
|
||||||
|
CREATE INDEX "Agent_brokerCertStatus_idx" ON "Agent"("brokerCertStatus");
|
||||||
|
CREATE INDEX "Agent_brokerCertExpiryAt_idx" ON "Agent"("brokerCertExpiryAt");
|
||||||
Reference in New Issue
Block a user