feat(web): add Privacy Policy and Terms of Service pages

- Create /chinh-sach-bao-mat (Privacy Policy) with 10 sections
- Create /dieu-khoan-su-dung (Terms of Service) with 11 sections
- Add legal link group to footer (vi + en i18n)
- Add legalLinks prop to Footer for bottom-bar display
- Wire links into public layout

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-24 00:50:55 +07:00
parent bafc3ddc2f
commit 579856795a
9 changed files with 588 additions and 8 deletions

View File

@@ -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,
) {}
}

View 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 của bạn. Chính
sách này tả cách chúng tôi thu thập, sử dụng bảo vệ thông tin 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 .
</li>
<li>
<strong className="text-foreground">Thông tin hồ :</strong> nh đi diện, thông
tin nghề nghiệp (nếu 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 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) ngăn chặn gian lận.</li>
<li>Xử thanh toán quản đăng dịch vụ.</li>
<li>Gửi thông báo về tin đăng, giá thị trường 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 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 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 :</strong> Khi yêu cầu từ
quan nhà nướ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 tổ chức phù hợp, bao gồm hóa
TLS/HTTPS, kiểm soát truy cập, 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. thông báo xác thực (JWT) thời hạn ngắn đư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 cookie phân tích đ
cải thiện trải nghiệm người dùng. Bạn thể điều chỉnh cài đt cookie trong trình
duyệt, tuy nhiên một số tính năng thể không hoạt đng nếu 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 nhân, bạn quyền:
</p>
<ul className="ml-4 list-disc space-y-2 text-foreground-muted">
<li>Truy cập xem thông tin 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 dữ liệu liên quan.</li>
<li>Phản đi việc xử 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 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 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 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>
);
}

View 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 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. tả dịch vụ</h2>
<p className="leading-relaxed text-foreground-muted">
GoodGo 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 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á 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 hồ môi giới đánh giá chất lượng.</li>
<li>Kết nối người mua, người bán nhà đu .</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 sự đng ý của người giám hộ hợp pháp.</li>
<li>Thông tin đăng phải chính xác, đy đ cập nhật.</li>
<li>
Bạn chịu trách nhiệm bảo mật thông tin đăng nhập 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 nhân.</li>
<li>
Tài khoản không đưc chuyển nhượng hoặc chia sẻ cho người khác không 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 chính xác, trung thực không gây hiểu lầm.</li>
<li>Bạn quyền hợp pháp đ đăng thông tin bất đng sản đó.</li>
<li>Hình nh 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 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 dịch vụ thanh toán
</h2>
<p className="leading-relaxed text-foreground-muted">
Một số tính năng yêu cầu đăng gói dịch vụ trả phí. Thanh toán đưc xử qua các
cổng thanh toán an toàn (VNPay, MoMo, ZaloPay). Phí dịch vụ đưc hiển thị 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ể 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, nguồn, logo, nhãn hiệu tài liệu trên nền tảng
GoodGo 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 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 không sự đng ý bằng văn bản 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 không chịu trách nhiệm về tính pháp 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á 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 .
</p>
</section>
<section>
<h2 className="mb-3 text-lg font-semibold text-foreground">
9. Đình chỉ chấm dứt tài khoản
</h2>
<p className="leading-relaxed text-foreground-muted">
GoodGo 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 quan thẩm quyền. Người dùng
thể tự xóa tài khoản bất cứ lúc nào từ trang cài đt hồ .
</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 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 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>
);
}

View File

@@ -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',

View File

@@ -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>
); );
} }

View File

@@ -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",

View File

@@ -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
View 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.

View File

@@ -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");