Compare commits
2 Commits
master
...
579856795a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
579856795a | ||
|
|
bafc3ddc2f |
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -195,8 +195,8 @@ jobs:
|
||||
ports:
|
||||
- 9000:9000
|
||||
env:
|
||||
MINIO_ROOT_USER: ci_minio_user
|
||||
MINIO_ROOT_PASSWORD: ci_minio_secret_key_32chars!!
|
||||
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
|
||||
@@ -206,22 +206,41 @@ jobs:
|
||||
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: ci_minio_user
|
||||
MINIO_SECRET_KEY: ci_minio_secret_key_32chars!!
|
||||
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
|
||||
JWT_SECRET: e2e-test-jwt-secret-key
|
||||
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key
|
||||
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: TESTHASHSECRET
|
||||
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
|
||||
|
||||
@@ -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 mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: ReturnType<typeof vi.fn> };
|
||||
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||
let mockSbvCompliance: { checkPaymentCompliance: ReturnType<typeof vi.fn> };
|
||||
|
||||
beforeEach(() => {
|
||||
mockPaymentRepo = {
|
||||
@@ -35,11 +36,16 @@ describe('CreatePaymentHandler', () => {
|
||||
|
||||
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() };
|
||||
|
||||
handler = new CreatePaymentHandler(
|
||||
mockPaymentRepo as any,
|
||||
mockGatewayFactory as any,
|
||||
mockSbvCompliance as any,
|
||||
mockEventBus 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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('footer.legal'),
|
||||
links: [
|
||||
{ label: t('footer.privacyPolicy'), href: '/chinh-sach-bao-mat' },
|
||||
{ label: t('footer.termsOfService'), href: '/dieu-khoan-su-dung' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -163,6 +170,10 @@ export default function PublicLayout({ children }: { children: React.ReactNode }
|
||||
description={t('footer.description')}
|
||||
linkGroups={footerLinkGroups}
|
||||
copyright={t('common.allRightsReserved')}
|
||||
legalLinks={[
|
||||
{ label: t('footer.privacyPolicy'), href: '/chinh-sach-bao-mat' },
|
||||
{ label: t('footer.termsOfService'), href: '/dieu-khoan-su-dung' },
|
||||
]}
|
||||
contact={{
|
||||
address: 'TP. Hồ Chí Minh, Việt Nam',
|
||||
phone: '1900 xxxx',
|
||||
|
||||
@@ -34,6 +34,8 @@ export interface FooterProps {
|
||||
};
|
||||
/** Social links. */
|
||||
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. */
|
||||
renderLink: (props: {
|
||||
href: string;
|
||||
@@ -63,6 +65,7 @@ export function Footer({
|
||||
copyright,
|
||||
contact,
|
||||
socials,
|
||||
legalLinks,
|
||||
renderLink,
|
||||
}: FooterProps) {
|
||||
return (
|
||||
@@ -156,13 +159,27 @@ export function Footer({
|
||||
|
||||
{/* Bottom bar */}
|
||||
<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>
|
||||
<div className="flex items-center gap-1">
|
||||
<Building2 className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Sàn giao dịch bất động sản
|
||||
</span>
|
||||
<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">
|
||||
<Building2 className="h-3 w-3 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Sàn giao dịch bất động sản
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -118,7 +118,10 @@
|
||||
"description": "Smart real estate platform in Vietnam",
|
||||
"propertyTypes": "Property types",
|
||||
"areas": "Areas",
|
||||
"support": "Support"
|
||||
"support": "Support",
|
||||
"legal": "Legal",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"termsOfService": "Terms of Service"
|
||||
},
|
||||
"propertyTypes": {
|
||||
"APARTMENT": "Apartment",
|
||||
|
||||
@@ -118,7 +118,10 @@
|
||||
"description": "Nền tảng bất động sản thông minh tại Việt Nam",
|
||||
"propertyTypes": "Loại BĐS",
|
||||
"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": {
|
||||
"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