[{"data":1,"prerenderedAt":7966},["ShallowReactive",2],{"navigation_docs":3,"-tu-hoc-database-supabase-functions-ai":420,"-tu-hoc-database-supabase-functions-ai-surround":7961},[4,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":26},"Phỏng vấn .NET","i-lucide-briefcase","\u002Fphong-van","1.phong-van",[10,27,66,111,128],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":26},"Tổng quan","i-lucide-compass","\u002Fphong-van\u002Ftong-quan","1.phong-van\u002F1.tong-quan",[16,21],{"title":17,"path":18,"stem":19,"icon":20},"Thị trường HCM","\u002Fphong-van\u002Ftong-quan\u002Fthi-truong","1.phong-van\u002F1.tong-quan\u002F1.thi-truong","i-lucide-trending-up",{"title":22,"path":23,"stem":24,"icon":25},"Lộ trình 7 ngày","\u002Fphong-van\u002Ftong-quan\u002Flo-trinh","1.phong-van\u002F1.tong-quan\u002F2.lo-trinh","i-lucide-calendar-days",false,{"title":22,"icon":25,"path":28,"stem":29,"children":30,"page":26},"\u002Fphong-van\u002Flo-trinh","1.phong-van\u002F2.lo-trinh",[31,36,41,46,51,56,61],{"title":32,"path":33,"stem":34,"icon":35},"Ngày 1 — C# cơ bản","\u002Fphong-van\u002Flo-trinh\u002Fngay-1-csharp-co-ban","1.phong-van\u002F2.lo-trinh\u002F1.ngay-1-csharp-co-ban","i-lucide-1",{"title":37,"path":38,"stem":39,"icon":40},"Ngày 2 — OOP và SOLID","\u002Fphong-van\u002Flo-trinh\u002Fngay-2-oop-solid","1.phong-van\u002F2.lo-trinh\u002F2.ngay-2-oop-solid","i-lucide-2",{"title":42,"path":43,"stem":44,"icon":45},"Ngày 3 — C# nâng cao","\u002Fphong-van\u002Flo-trinh\u002Fngay-3-csharp-nang-cao","1.phong-van\u002F2.lo-trinh\u002F3.ngay-3-csharp-nang-cao","i-lucide-3",{"title":47,"path":48,"stem":49,"icon":50},"Ngày 4 — Database và SQL","\u002Fphong-van\u002Flo-trinh\u002Fngay-4-database-sql","1.phong-van\u002F2.lo-trinh\u002F4.ngay-4-database-sql","i-lucide-4",{"title":52,"path":53,"stem":54,"icon":55},"Ngày 5 — ASP.NET Core và EF Core","\u002Fphong-van\u002Flo-trinh\u002Fngay-5-aspnet-efcore","1.phong-van\u002F2.lo-trinh\u002F5.ngay-5-aspnet-efcore","i-lucide-5",{"title":57,"path":58,"stem":59,"icon":60},"Ngày 6 — DSA coding","\u002Fphong-van\u002Flo-trinh\u002Fngay-6-dsa","1.phong-van\u002F2.lo-trinh\u002F6.ngay-6-dsa","i-lucide-6",{"title":62,"path":63,"stem":64,"icon":65},"Ngày 7 — Mock và HR","\u002Fphong-van\u002Flo-trinh\u002Fngay-7-mock-interview","1.phong-van\u002F2.lo-trinh\u002F7.ngay-7-mock-interview","i-lucide-7",{"title":67,"icon":68,"path":69,"stem":70,"children":71,"page":26},"Ngân hàng câu hỏi","i-lucide-database","\u002Fphong-van\u002Fngan-hang-cau-hoi","1.phong-van\u002F3.ngan-hang-cau-hoi",[72,77,82,87,92,97,102,107],{"title":73,"path":74,"stem":75,"icon":76},"C# (60+ câu)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fcsharp","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F1.csharp","i-lucide-hash",{"title":78,"path":79,"stem":80,"icon":81},"OOP và SOLID (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Foop","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F2.oop","i-lucide-shapes",{"title":83,"path":84,"stem":85,"icon":86},"SQL (40+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fsql","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F3.sql","i-lucide-database-zap",{"title":88,"path":89,"stem":90,"icon":91},"ASP.NET và EF (50+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-ef","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F4.aspnet-ef","i-lucide-server",{"title":93,"path":94,"stem":95,"icon":96},"DSA và Coding (30+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdsa-coding","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F5.dsa-coding","i-lucide-binary",{"title":98,"path":99,"stem":100,"icon":101},"HR và Soft skill (25+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fhr-behavior","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F6.hr-behavior","i-lucide-users",{"title":103,"path":104,"stem":105,"icon":106},"Design Pattern (55+)","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Fdesign-pattern","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F7.design-pattern","i-lucide-puzzle",{"title":108,"path":109,"stem":110,"icon":91},"ASP.NET MVC và Web API","\u002Fphong-van\u002Fngan-hang-cau-hoi\u002Faspnet-mvc-webapi","1.phong-van\u002F3.ngan-hang-cau-hoi\u002F8.aspnet-mvc-webapi",{"title":112,"itle":113,"icon":114,"path":115,"stem":116,"children":117,"page":26},"Tai Lieu","Tài liệu và Cheat sheet","i-lucide-book-marked","\u002Fphong-van\u002Ftai-lieu","1.phong-van\u002F4.tai-lieu",[118,123],{"title":119,"path":120,"stem":121,"icon":122},"Cheat Sheet","\u002Fphong-van\u002Ftai-lieu\u002Fcheatsheet","1.phong-van\u002F4.tai-lieu\u002F1.cheatsheet","i-lucide-clipboard-list",{"title":124,"path":125,"stem":126,"icon":127},"Nguồn tham khảo","\u002Fphong-van\u002Ftai-lieu\u002Fnguon","1.phong-van\u002F4.tai-lieu\u002F2.nguon","i-lucide-link",{"title":129,"icon":130,"path":131,"stem":132,"children":133,"page":26},"Phỏng vấn English","i-lucide-globe","\u002Fphong-van\u002Fenglish-interview","1.phong-van\u002F5.english-interview",[134,138,143,148],{"title":135,"path":136,"stem":137,"icon":130},"Tổng quan English","\u002Fphong-van\u002Fenglish-interview\u002Ftong-quan","1.phong-van\u002F5.english-interview\u002F1.tong-quan",{"title":139,"path":140,"stem":141,"icon":142},"Vocabulary","\u002Fphong-van\u002Fenglish-interview\u002Fvocabulary","1.phong-van\u002F5.english-interview\u002F2.vocabulary","i-lucide-book-a",{"title":144,"path":145,"stem":146,"icon":147},"Technical Q và A (EN)","\u002Fphong-van\u002Fenglish-interview\u002Ftechnical-en","1.phong-van\u002F5.english-interview\u002F3.technical-en","i-lucide-code",{"title":149,"path":150,"stem":151,"icon":152},"Behavioral (EN)","\u002Fphong-van\u002Fenglish-interview\u002Fbehavioral-en","1.phong-van\u002F5.english-interview\u002F4.behavioral-en","i-lucide-message-square-text",{"title":154,"icon":155,"path":156,"stem":157,"children":158},"Tự học","i-lucide-graduation-cap","\u002Ftu-hoc","2.tu-hoc",[159,161,287,310,333,357,384],{"title":154,"path":156,"stem":160,"icon":155},"2.tu-hoc\u002Findex",{"title":162,"icon":68,"path":163,"stem":164,"children":165},"Database","\u002Ftu-hoc\u002Fdatabase","2.tu-hoc\u002F1.database\u002Findex",[166,167,197,222,243,248],{"title":162,"path":163,"stem":164,"icon":68},{"title":168,"icon":91,"path":169,"stem":170,"children":171,"page":26},"SQL Server","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server","2.tu-hoc\u002F1.database\u002F2.sql-server",[172,177,182,187,192],{"title":173,"path":174,"stem":175,"icon":176},"1. Giới thiệu và Cài đặt","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F1.gioi-thieu-cai-dat","i-lucide-play-circle",{"title":178,"path":179,"stem":180,"icon":181},"2. Cú pháp cơ bản","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F2.cu-phap-co-ban","i-lucide-code-2",{"title":183,"path":184,"stem":185,"icon":186},"3. Truy vấn nâng cao","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F3.truy-van-nang-cao","i-lucide-git-merge",{"title":188,"path":189,"stem":190,"icon":191},"4. Đặc thù và Performance","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F4.dac-thu-performance","i-lucide-gauge",{"title":193,"path":194,"stem":195,"icon":196},"5. Q và A phỏng vấn","\u002Ftu-hoc\u002Fdatabase\u002Fsql-server\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F2.sql-server\u002F5.cau-hoi-phong-van","i-lucide-message-circle-question",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":26},"PostgreSQL","i-simple-icons-postgresql","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql","2.tu-hoc\u002F1.database\u002F3.postgresql",[203,206,209,212,215,218],{"title":173,"path":204,"stem":205,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":207,"stem":208,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F2.cu-phap-co-ban",{"title":183,"path":210,"stem":211,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F3.truy-van-nang-cao",{"title":188,"path":213,"stem":214,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F4.dac-thu-performance",{"title":193,"path":216,"stem":217,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F5.cau-hoi-phong-van",{"title":219,"path":220,"stem":221,"icon":96},"6. Tích hợp C# và Postgres","\u002Ftu-hoc\u002Fdatabase\u002Fpostgresql\u002Ftich-hop-csharp","2.tu-hoc\u002F1.database\u002F3.postgresql\u002F6.tich-hop-csharp",{"title":223,"icon":224,"path":225,"stem":226,"children":227,"page":26},"MySQL","i-simple-icons-mysql","\u002Ftu-hoc\u002Fdatabase\u002Fmysql","2.tu-hoc\u002F1.database\u002F4.mysql",[228,231,234,237,240],{"title":173,"path":229,"stem":230,"icon":176},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F1.database\u002F4.mysql\u002F1.gioi-thieu-cai-dat",{"title":178,"path":232,"stem":233,"icon":181},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcu-phap-co-ban","2.tu-hoc\u002F1.database\u002F4.mysql\u002F2.cu-phap-co-ban",{"title":183,"path":235,"stem":236,"icon":186},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Ftruy-van-nang-cao","2.tu-hoc\u002F1.database\u002F4.mysql\u002F3.truy-van-nang-cao",{"title":188,"path":238,"stem":239,"icon":191},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fdac-thu-performance","2.tu-hoc\u002F1.database\u002F4.mysql\u002F4.dac-thu-performance",{"title":193,"path":241,"stem":242,"icon":196},"\u002Ftu-hoc\u002Fdatabase\u002Fmysql\u002Fcau-hoi-phong-van","2.tu-hoc\u002F1.database\u002F4.mysql\u002F5.cau-hoi-phong-van",{"title":244,"path":245,"stem":246,"icon":247},"So sánh 3 RDBMS","\u002Ftu-hoc\u002Fdatabase\u002Fso-sanh","2.tu-hoc\u002F1.database\u002F5.so-sanh","i-lucide-columns-3",{"title":249,"path":250,"stem":251,"children":252,"icon":254},"Supabase (BaaS)","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002Findex",[253,255,259,264,269,274,278,283],{"title":249,"path":250,"stem":251,"icon":254},"i-simple-icons-supabase",{"title":256,"path":257,"stem":258,"icon":68},"1. Database","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fdatabase","2.tu-hoc\u002F1.database\u002F6.supabase\u002F1.database",{"title":260,"path":261,"stem":262,"icon":263},"2. Authentication","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fauthentication","2.tu-hoc\u002F1.database\u002F6.supabase\u002F2.authentication","i-lucide-key",{"title":265,"path":266,"stem":267,"icon":268},"3. Storage & Realtime","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fstorage-realtime","2.tu-hoc\u002F1.database\u002F6.supabase\u002F3.storage-realtime","i-lucide-zap",{"title":270,"path":271,"stem":272,"icon":273},"4. Edge Functions & AI","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Ffunctions-ai","2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai","i-lucide-brain",{"title":275,"path":276,"stem":277,"icon":181},"5. API & SDKs","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fapi-sdk","2.tu-hoc\u002F1.database\u002F6.supabase\u002F5.api-sdk",{"title":279,"path":280,"stem":281,"icon":282},"6. Platform & Deploy","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Fplatform-deploy","2.tu-hoc\u002F1.database\u002F6.supabase\u002F6.platform-deploy","i-lucide-cloud",{"title":284,"path":285,"stem":286,"icon":196},"7. Interview & Q&A","\u002Ftu-hoc\u002Fdatabase\u002Fsupabase\u002Finterview","2.tu-hoc\u002F1.database\u002F6.supabase\u002F7.interview",{"title":288,"icon":289,"path":290,"stem":291,"children":292},"TypeScript","i-simple-icons-typescript","\u002Ftu-hoc\u002Ftypescript","2.tu-hoc\u002F2.typescript\u002Findex",[293,294,297,301,306],{"title":288,"path":290,"stem":291,"icon":289},{"title":173,"path":295,"stem":296,"icon":176},"\u002Ftu-hoc\u002Ftypescript\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F2.typescript\u002F1.gioi-thieu-cai-dat",{"title":298,"path":299,"stem":300,"icon":81},"2. Type System","\u002Ftu-hoc\u002Ftypescript\u002Ftype-system","2.tu-hoc\u002F2.typescript\u002F2.type-system",{"title":302,"path":303,"stem":304,"icon":305},"3. Advanced types","\u002Ftu-hoc\u002Ftypescript\u002Fadvanced-types","2.tu-hoc\u002F2.typescript\u002F3.advanced-types","i-lucide-braces",{"title":307,"path":308,"stem":309,"icon":196},"4. Q và A phỏng vấn","\u002Ftu-hoc\u002Ftypescript\u002Fcau-hoi-phong-van","2.tu-hoc\u002F2.typescript\u002F4.cau-hoi-phong-van",{"title":311,"icon":81,"path":312,"stem":313,"children":314},"OOP","\u002Ftu-hoc\u002Foop","2.tu-hoc\u002F3.oop\u002Findex",[315,316,321,326,330],{"title":311,"path":312,"stem":313,"icon":81},{"title":317,"path":318,"stem":319,"icon":320},"1. Bốn tính chất","\u002Ftu-hoc\u002Foop\u002F4-tinh-chat","2.tu-hoc\u002F3.oop\u002F1.4-tinh-chat","i-lucide-circle-dot",{"title":322,"path":323,"stem":324,"icon":325},"2. SOLID","\u002Ftu-hoc\u002Foop\u002Fsolid","2.tu-hoc\u002F3.oop\u002F2.solid","i-lucide-shield",{"title":327,"path":328,"stem":329,"icon":106},"3. Design Pattern","\u002Ftu-hoc\u002Foop\u002Fdesign-pattern","2.tu-hoc\u002F3.oop\u002F3.design-pattern",{"title":307,"path":331,"stem":332,"icon":196},"\u002Ftu-hoc\u002Foop\u002Fcau-hoi-phong-van","2.tu-hoc\u002F3.oop\u002F4.cau-hoi-phong-van",{"title":334,"icon":305,"path":335,"stem":336,"children":337},"Generic","\u002Ftu-hoc\u002Fgeneric","2.tu-hoc\u002F4.generic\u002Findex",[338,339,344,349,354],{"title":334,"path":335,"stem":336,"icon":305},{"title":340,"path":341,"stem":342,"icon":343},"1. Khái niệm và cú pháp","\u002Ftu-hoc\u002Fgeneric\u002Fkhai-niem","2.tu-hoc\u002F4.generic\u002F1.khai-niem","i-lucide-book",{"title":345,"path":346,"stem":347,"icon":348},"2. Constraint và Variance","\u002Ftu-hoc\u002Fgeneric\u002Fconstraint-variance","2.tu-hoc\u002F4.generic\u002F2.constraint-variance","i-lucide-filter",{"title":350,"path":351,"stem":352,"icon":353},"3. C# vs TS","\u002Ftu-hoc\u002Fgeneric\u002Fso-sanh","2.tu-hoc\u002F4.generic\u002F3.so-sanh","i-lucide-git-compare",{"title":307,"path":355,"stem":356,"icon":196},"\u002Ftu-hoc\u002Fgeneric\u002Fcau-hoi-phong-van","2.tu-hoc\u002F4.generic\u002F4.cau-hoi-phong-van",{"title":358,"icon":359,"path":360,"stem":361,"children":362},"Nuxt 4","i-simple-icons-nuxt","\u002Ftu-hoc\u002Fnuxt-4","2.tu-hoc\u002F5.nuxt-4\u002Findex",[363,364,367,372,376,379],{"title":358,"path":360,"stem":361,"icon":359},{"title":173,"path":365,"stem":366,"icon":176},"\u002Ftu-hoc\u002Fnuxt-4\u002Fgioi-thieu-cai-dat","2.tu-hoc\u002F5.nuxt-4\u002F1.gioi-thieu-cai-dat",{"title":368,"path":369,"stem":370,"icon":371},"2. Routing và Rendering","\u002Ftu-hoc\u002Fnuxt-4\u002Frouting-rendering","2.tu-hoc\u002F5.nuxt-4\u002F2.routing-rendering","i-lucide-route",{"title":373,"path":374,"stem":375,"icon":91},"3. Data và Server","\u002Ftu-hoc\u002Fnuxt-4\u002Fdata-server","2.tu-hoc\u002F5.nuxt-4\u002F3.data-server",{"title":307,"path":377,"stem":378,"icon":196},"\u002Ftu-hoc\u002Fnuxt-4\u002Fcau-hoi-phong-van","2.tu-hoc\u002F5.nuxt-4\u002F4.cau-hoi-phong-van",{"title":380,"path":381,"stem":382,"icon":383},"5. Practical Todo App","\u002Ftu-hoc\u002Fnuxt-4\u002Fpractical-todo-app","2.tu-hoc\u002F5.nuxt-4\u002F5.practical-todo-app","i-lucide-check-square",{"title":385,"path":386,"stem":387,"children":388,"icon":390},"Tailwind CSS","\u002Ftu-hoc\u002Ftailwindcss","2.tu-hoc\u002F6.tailwindcss\u002Findex",[389,391,396,401,406,411,416],{"title":385,"path":386,"stem":387,"icon":390},"i-simple-icons-tailwindcss",{"title":392,"path":393,"stem":394,"icon":395},"1. Layout & Spacing","\u002Ftu-hoc\u002Ftailwindcss\u002Flayout-spacing","2.tu-hoc\u002F6.tailwindcss\u002F1.layout-spacing","i-lucide-layout",{"title":397,"path":398,"stem":399,"icon":400},"2. Typography & Colors","\u002Ftu-hoc\u002Ftailwindcss\u002Ftypography-colors","2.tu-hoc\u002F6.tailwindcss\u002F2.typography-colors","i-lucide-type",{"title":402,"path":403,"stem":404,"icon":405},"3. Interactive & Effects","\u002Ftu-hoc\u002Ftailwindcss\u002Finteractive-effects","2.tu-hoc\u002F6.tailwindcss\u002F3.interactive-effects","i-lucide-sparkles",{"title":407,"path":408,"stem":409,"icon":410},"4. Responsive & Dark Mode","\u002Ftu-hoc\u002Ftailwindcss\u002Fresponsive-darkmode","2.tu-hoc\u002F6.tailwindcss\u002F4.responsive-darkmode","i-lucide-monitor-smartphone",{"title":412,"path":413,"stem":414,"icon":415},"5. Customization & Theme","\u002Ftu-hoc\u002Ftailwindcss\u002Fcustomization","2.tu-hoc\u002F6.tailwindcss\u002F5.customization","i-lucide-settings-2",{"title":417,"path":418,"stem":419,"icon":181},"6. Practical Projects","\u002Ftu-hoc\u002Ftailwindcss\u002Fpractical-projects","2.tu-hoc\u002F6.tailwindcss\u002F6.practical-projects",{"id":421,"title":422,"body":423,"description":7954,"extension":7955,"links":7956,"meta":7957,"navigation":7958,"path":271,"seo":7959,"stem":272,"__hash__":7960},"docs\u002F2.tu-hoc\u002F1.database\u002F6.supabase\u002F4.functions-ai.md","Supabase Edge Functions & AI\u002FVector",{"type":424,"value":425,"toc":7920},"minimark",[426,431,435,448,466,470,553,557,560,685,690,1022,1027,1270,1274,1277,1577,1580,1584,1587,1693,1835,1839,1842,2324,2328,2331,2425,2429,2434,3215,3219,3604,3608,3993,3997,4371,4375,4382,4528,4531,4535,4539,4545,4645,4651,4655,4661,4776,4781,4788,4790,4794,4797,4804,4810,4814,4929,4934,4986,4990,4993,5049,5211,5428,5432,5435,5846,5850,5853,5859,6875,6879,6886,6965,6972,7098,7102,7105,7189,7193,7196,7224,7228,7231,7251,7253,7257,7261,7266,7295,7300,7501,7505,7509,7532,7536,7540,7565,7569,7870,7874,7878,7898,7903,7909,7911,7916],[427,428,430],"h2",{"id":429},"edge-functions-serverless-tại-edge","Edge Functions — Serverless tại Edge",[432,433,11],"h3",{"id":434},"tổng-quan",[436,437,438,439,443,444,447],"p",{},"Supabase Edge Functions là các ",[440,441,442],"strong",{},"server-side TypeScript functions"," chạy trên ",[440,445,446],{},"Deno runtime",", được phân phối toàn cầu tại edge -- gần với người dùng nhất để giảm latency. Mỗi function được deploy độc lập và có thể gọi từ client SDK hoặc HTTP request.",[449,450,453,456,457,461,462,465],"callout",{"color":451,"icon":452},"primary","i-lucide-star",[440,454,455],{},"Deno, không phải Node.js:"," Edge Functions dùng Deno runtime, hỗ trợ TypeScript native và WASM. Import từ ",[458,459,460],"code",{},"jsr:"," hoặc ",[458,463,464],{},"npm:"," specifiers. Cold starts có thể xảy ra -- thiết kế function ngắn, idempotent. Service role key thường được dùng trong Edge Function để bypass RLS khi cần truy cập toàn bộ dữ liệu.",[432,467,469],{"id":468},"đặc-điểm-chính","Đặc điểm chính",[471,472,473,486],"table",{},[474,475,476],"thead",{},[477,478,479,483],"tr",{},[480,481,482],"th",{},"Đặc điểm",[480,484,485],{},"Mô tả",[487,488,489,500,510,520,530,543],"tbody",{},[477,490,491,497],{},[492,493,494],"td",{},[440,495,496],{},"Runtime",[492,498,499],{},"Supabase Edge Runtime (Deno-compatible, TypeScript first)",[477,501,502,507],{},[492,503,504],{},[440,505,506],{},"Global distribution",[492,508,509],{},"Triển khai trên mạng lưới edge toàn cầu, gần user nhất",[477,511,512,517],{},[492,513,514],{},[440,515,516],{},"WASM support",[492,518,519],{},"Hỗ trợ WebAssembly modules",[477,521,522,527],{},[492,523,524],{},[440,525,526],{},"Local dev parity",[492,528,529],{},"Supabase CLI cung cấp runtime giống production",[477,531,532,537],{},[492,533,534],{},[440,535,536],{},"Secrets management",[492,538,539,540],{},"Lưu credentials trong project secrets, truy cập qua ",[458,541,542],{},"Deno.env.get()",[477,544,545,550],{},[492,546,547],{},[440,548,549],{},"Time limit",[492,551,552],{},"Hàm được thiết kế cho tác vụ ngắn (thường dưới vài phút)",[432,554,556],{"id":555},"quickstart","Quickstart",[436,558,559],{},"Tạo, deploy và gọi Edge Function đơn giản:",[561,562,567],"pre",{"className":563,"code":564,"language":565,"meta":566,"style":566},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Tạo function mới\nsupabase functions new hello-world\n\n# Phát triển và test local\nsupabase functions serve\n\n# Deploy lên production\nsupabase functions deploy hello-world\n\n# Xóa function\nsupabase functions delete hello-world\n\n# Danh sách functions\nsupabase functions list\n","bash","",[458,568,569,578,595,602,608,618,623,629,641,646,652,664,669,675],{"__ignoreMap":566},[570,571,574],"span",{"class":572,"line":573},"line",1,[570,575,577],{"class":576},"sHwdD","# Tạo function mới\n",[570,579,581,585,589,592],{"class":572,"line":580},2,[570,582,584],{"class":583},"sBMFI","supabase",[570,586,588],{"class":587},"sfazB"," functions",[570,590,591],{"class":587}," new",[570,593,594],{"class":587}," hello-world\n",[570,596,598],{"class":572,"line":597},3,[570,599,601],{"emptyLinePlaceholder":600},true,"\n",[570,603,605],{"class":572,"line":604},4,[570,606,607],{"class":576},"# Phát triển và test local\n",[570,609,611,613,615],{"class":572,"line":610},5,[570,612,584],{"class":583},[570,614,588],{"class":587},[570,616,617],{"class":587}," serve\n",[570,619,621],{"class":572,"line":620},6,[570,622,601],{"emptyLinePlaceholder":600},[570,624,626],{"class":572,"line":625},7,[570,627,628],{"class":576},"# Deploy lên production\n",[570,630,632,634,636,639],{"class":572,"line":631},8,[570,633,584],{"class":583},[570,635,588],{"class":587},[570,637,638],{"class":587}," deploy",[570,640,594],{"class":587},[570,642,644],{"class":572,"line":643},9,[570,645,601],{"emptyLinePlaceholder":600},[570,647,649],{"class":572,"line":648},10,[570,650,651],{"class":576},"# Xóa function\n",[570,653,655,657,659,662],{"class":572,"line":654},11,[570,656,584],{"class":583},[570,658,588],{"class":587},[570,660,661],{"class":587}," delete",[570,663,594],{"class":587},[570,665,667],{"class":572,"line":666},12,[570,668,601],{"emptyLinePlaceholder":600},[570,670,672],{"class":572,"line":671},13,[570,673,674],{"class":576},"# Danh sách functions\n",[570,676,678,680,682],{"class":572,"line":677},14,[570,679,584],{"class":583},[570,681,588],{"class":587},[570,683,684],{"class":587}," list\n",[436,686,687],{},[440,688,689],{},"Cấu trúc function cơ bản:",[561,691,695],{"className":692,"code":693,"language":694,"meta":566,"style":566},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F supabase\u002Ffunctions\u002Fhello-world\u002Findex.ts\n\ninterface RequestBody {\n  name: string\n}\n\nDeno.serve(async (req: Request) => {\n  \u002F\u002F Đọc body\n  const { name } = await req.json() as RequestBody\n\n  \u002F\u002F Xử lý logic\n  const data = {\n    message: `Xin chào ${name}!`,\n    timestamp: new Date().toISOString()\n  }\n\n  \u002F\u002F Trả về response\n  return new Response(JSON.stringify(data), {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Access-Control-Allow-Origin': '*'\n    }\n  })\n})\n","typescript",[458,696,697,702,706,719,731,736,740,779,784,822,826,831,842,873,896,902,907,913,946,956,980,1000,1006,1015],{"__ignoreMap":566},[570,698,699],{"class":572,"line":573},[570,700,701],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fhello-world\u002Findex.ts\n",[570,703,704],{"class":572,"line":580},[570,705,601],{"emptyLinePlaceholder":600},[570,707,708,712,715],{"class":572,"line":597},[570,709,711],{"class":710},"spNyl","interface",[570,713,714],{"class":583}," RequestBody",[570,716,718],{"class":717},"sMK4o"," {\n",[570,720,721,725,728],{"class":572,"line":604},[570,722,724],{"class":723},"swJcz","  name",[570,726,727],{"class":717},":",[570,729,730],{"class":583}," string\n",[570,732,733],{"class":572,"line":610},[570,734,735],{"class":717},"}\n",[570,737,738],{"class":572,"line":620},[570,739,601],{"emptyLinePlaceholder":600},[570,741,742,746,749,753,756,759,762,766,768,771,774,777],{"class":572,"line":625},[570,743,745],{"class":744},"sTEyZ","Deno",[570,747,748],{"class":717},".",[570,750,752],{"class":751},"s2Zo4","serve",[570,754,755],{"class":744},"(",[570,757,758],{"class":710},"async",[570,760,761],{"class":717}," (",[570,763,765],{"class":764},"sHdIc","req",[570,767,727],{"class":717},[570,769,770],{"class":583}," Request",[570,772,773],{"class":717},")",[570,775,776],{"class":710}," =>",[570,778,718],{"class":717},[570,780,781],{"class":572,"line":631},[570,782,783],{"class":576},"  \u002F\u002F Đọc body\n",[570,785,786,789,792,795,798,801,805,808,810,813,816,819],{"class":572,"line":643},[570,787,788],{"class":710},"  const",[570,790,791],{"class":717}," {",[570,793,794],{"class":744}," name",[570,796,797],{"class":717}," }",[570,799,800],{"class":717}," =",[570,802,804],{"class":803},"s7zQu"," await",[570,806,807],{"class":744}," req",[570,809,748],{"class":717},[570,811,812],{"class":751},"json",[570,814,815],{"class":723},"() ",[570,817,818],{"class":803},"as",[570,820,821],{"class":583}," RequestBody\n",[570,823,824],{"class":572,"line":648},[570,825,601],{"emptyLinePlaceholder":600},[570,827,828],{"class":572,"line":654},[570,829,830],{"class":576},"  \u002F\u002F Xử lý logic\n",[570,832,833,835,838,840],{"class":572,"line":666},[570,834,788],{"class":710},[570,836,837],{"class":744}," data",[570,839,800],{"class":717},[570,841,718],{"class":717},[570,843,844,847,849,852,855,858,861,864,867,870],{"class":572,"line":671},[570,845,846],{"class":723},"    message",[570,848,727],{"class":717},[570,850,851],{"class":717}," `",[570,853,854],{"class":587},"Xin chào ",[570,856,857],{"class":717},"${",[570,859,860],{"class":744},"name",[570,862,863],{"class":717},"}",[570,865,866],{"class":587},"!",[570,868,869],{"class":717},"`",[570,871,872],{"class":717},",\n",[570,874,875,878,880,882,885,888,890,893],{"class":572,"line":677},[570,876,877],{"class":723},"    timestamp",[570,879,727],{"class":717},[570,881,591],{"class":717},[570,883,884],{"class":751}," Date",[570,886,887],{"class":723},"()",[570,889,748],{"class":717},[570,891,892],{"class":751},"toISOString",[570,894,895],{"class":723},"()\n",[570,897,899],{"class":572,"line":898},15,[570,900,901],{"class":717},"  }\n",[570,903,905],{"class":572,"line":904},16,[570,906,601],{"emptyLinePlaceholder":600},[570,908,910],{"class":572,"line":909},17,[570,911,912],{"class":576},"  \u002F\u002F Trả về response\n",[570,914,916,919,921,924,926,929,931,934,936,939,941,944],{"class":572,"line":915},18,[570,917,918],{"class":803},"  return",[570,920,591],{"class":717},[570,922,923],{"class":751}," Response",[570,925,755],{"class":723},[570,927,928],{"class":744},"JSON",[570,930,748],{"class":717},[570,932,933],{"class":751},"stringify",[570,935,755],{"class":723},[570,937,938],{"class":744},"data",[570,940,773],{"class":723},[570,942,943],{"class":717},",",[570,945,718],{"class":717},[570,947,949,952,954],{"class":572,"line":948},19,[570,950,951],{"class":723},"    headers",[570,953,727],{"class":717},[570,955,718],{"class":717},[570,957,959,962,965,968,970,973,976,978],{"class":572,"line":958},20,[570,960,961],{"class":717},"      '",[570,963,964],{"class":723},"Content-Type",[570,966,967],{"class":717},"'",[570,969,727],{"class":717},[570,971,972],{"class":717}," '",[570,974,975],{"class":587},"application\u002Fjson",[570,977,967],{"class":717},[570,979,872],{"class":717},[570,981,983,985,988,990,992,994,997],{"class":572,"line":982},21,[570,984,961],{"class":717},[570,986,987],{"class":723},"Access-Control-Allow-Origin",[570,989,967],{"class":717},[570,991,727],{"class":717},[570,993,972],{"class":717},[570,995,996],{"class":587},"*",[570,998,999],{"class":717},"'\n",[570,1001,1003],{"class":572,"line":1002},22,[570,1004,1005],{"class":717},"    }\n",[570,1007,1009,1012],{"class":572,"line":1008},23,[570,1010,1011],{"class":717},"  }",[570,1013,1014],{"class":723},")\n",[570,1016,1018,1020],{"class":572,"line":1017},24,[570,1019,863],{"class":717},[570,1021,1014],{"class":744},[436,1023,1024],{},[440,1025,1026],{},"Gọi function từ client:",[561,1028,1032],{"className":1029,"code":1030,"language":1031,"meta":566,"style":566},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Gọi từ supabase-js\nconst { data, error } = await supabase.functions.invoke('hello-world', {\n  body: { name: 'Vinh' }\n})\n\n\u002F\u002F Gọi trực tiếp qua HTTP\nconst response = await fetch(\n  'https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fhello-world',\n  {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${ANON_KEY}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({ name: 'Vinh' })\n  }\n)\n","javascript",[458,1033,1034,1039,1085,1108,1114,1118,1123,1141,1153,1158,1174,1182,1208,1224,1229,1262,1266],{"__ignoreMap":566},[570,1035,1036],{"class":572,"line":573},[570,1037,1038],{"class":576},"\u002F\u002F Gọi từ supabase-js\n",[570,1040,1041,1044,1046,1048,1050,1053,1055,1057,1059,1062,1064,1067,1069,1072,1074,1076,1079,1081,1083],{"class":572,"line":580},[570,1042,1043],{"class":710},"const",[570,1045,791],{"class":717},[570,1047,837],{"class":744},[570,1049,943],{"class":717},[570,1051,1052],{"class":744}," error ",[570,1054,863],{"class":717},[570,1056,800],{"class":717},[570,1058,804],{"class":803},[570,1060,1061],{"class":744}," supabase",[570,1063,748],{"class":717},[570,1065,1066],{"class":744},"functions",[570,1068,748],{"class":717},[570,1070,1071],{"class":751},"invoke",[570,1073,755],{"class":744},[570,1075,967],{"class":717},[570,1077,1078],{"class":587},"hello-world",[570,1080,967],{"class":717},[570,1082,943],{"class":717},[570,1084,718],{"class":717},[570,1086,1087,1090,1092,1094,1096,1098,1100,1103,1105],{"class":572,"line":597},[570,1088,1089],{"class":723},"  body",[570,1091,727],{"class":717},[570,1093,791],{"class":717},[570,1095,794],{"class":723},[570,1097,727],{"class":717},[570,1099,972],{"class":717},[570,1101,1102],{"class":587},"Vinh",[570,1104,967],{"class":717},[570,1106,1107],{"class":717}," }\n",[570,1109,1110,1112],{"class":572,"line":604},[570,1111,863],{"class":717},[570,1113,1014],{"class":744},[570,1115,1116],{"class":572,"line":610},[570,1117,601],{"emptyLinePlaceholder":600},[570,1119,1120],{"class":572,"line":620},[570,1121,1122],{"class":576},"\u002F\u002F Gọi trực tiếp qua HTTP\n",[570,1124,1125,1127,1130,1133,1135,1138],{"class":572,"line":625},[570,1126,1043],{"class":710},[570,1128,1129],{"class":744}," response ",[570,1131,1132],{"class":717},"=",[570,1134,804],{"class":803},[570,1136,1137],{"class":751}," fetch",[570,1139,1140],{"class":744},"(\n",[570,1142,1143,1146,1149,1151],{"class":572,"line":631},[570,1144,1145],{"class":717},"  '",[570,1147,1148],{"class":587},"https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fhello-world",[570,1150,967],{"class":717},[570,1152,872],{"class":717},[570,1154,1155],{"class":572,"line":643},[570,1156,1157],{"class":717},"  {\n",[570,1159,1160,1163,1165,1167,1170,1172],{"class":572,"line":648},[570,1161,1162],{"class":723},"    method",[570,1164,727],{"class":717},[570,1166,972],{"class":717},[570,1168,1169],{"class":587},"POST",[570,1171,967],{"class":717},[570,1173,872],{"class":717},[570,1175,1176,1178,1180],{"class":572,"line":654},[570,1177,951],{"class":723},[570,1179,727],{"class":717},[570,1181,718],{"class":717},[570,1183,1184,1186,1189,1191,1193,1195,1198,1200,1203,1206],{"class":572,"line":666},[570,1185,961],{"class":717},[570,1187,1188],{"class":723},"Authorization",[570,1190,967],{"class":717},[570,1192,727],{"class":717},[570,1194,851],{"class":717},[570,1196,1197],{"class":587},"Bearer ",[570,1199,857],{"class":717},[570,1201,1202],{"class":744},"ANON_KEY",[570,1204,1205],{"class":717},"}`",[570,1207,872],{"class":717},[570,1209,1210,1212,1214,1216,1218,1220,1222],{"class":572,"line":671},[570,1211,961],{"class":717},[570,1213,964],{"class":723},[570,1215,967],{"class":717},[570,1217,727],{"class":717},[570,1219,972],{"class":717},[570,1221,975],{"class":587},[570,1223,999],{"class":717},[570,1225,1226],{"class":572,"line":677},[570,1227,1228],{"class":717},"    },\n",[570,1230,1231,1234,1236,1239,1241,1243,1245,1248,1250,1252,1254,1256,1258,1260],{"class":572,"line":898},[570,1232,1233],{"class":723},"    body",[570,1235,727],{"class":717},[570,1237,1238],{"class":744}," JSON",[570,1240,748],{"class":717},[570,1242,933],{"class":751},[570,1244,755],{"class":744},[570,1246,1247],{"class":717},"{",[570,1249,794],{"class":723},[570,1251,727],{"class":717},[570,1253,972],{"class":717},[570,1255,1102],{"class":587},[570,1257,967],{"class":717},[570,1259,797],{"class":717},[570,1261,1014],{"class":744},[570,1263,1264],{"class":572,"line":904},[570,1265,901],{"class":717},[570,1267,1268],{"class":572,"line":909},[570,1269,1014],{"class":744},[432,1271,1273],{"id":1272},"cors-handling","CORS Handling",[436,1275,1276],{},"Khi Edge Function được gọi từ browser (frontend), cần xử lý CORS:",[561,1278,1280],{"className":692,"code":1279,"language":694,"meta":566,"style":566},"Deno.serve(async (req: Request) => {\n  \u002F\u002F Handle preflight OPTIONS request\n  if (req.method === 'OPTIONS') {\n    return new Response('ok', {\n      headers: {\n        'Access-Control-Allow-Origin': '*',\n        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',\n        'Access-Control-Allow-Headers': 'Authorization, Content-Type, apikey'\n      }\n    })\n  }\n\n  \u002F\u002F Handle actual request\n  const data = await req.json()\n\n  return new Response(JSON.stringify({ success: true, data }), {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Access-Control-Allow-Origin': '*'\n    }\n  })\n})\n",[458,1281,1282,1308,1313,1343,1365,1374,1393,1413,1431,1436,1443,1447,1451,1456,1474,1478,1519,1527,1545,1561,1565,1571],{"__ignoreMap":566},[570,1283,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306],{"class":572,"line":573},[570,1285,745],{"class":744},[570,1287,748],{"class":717},[570,1289,752],{"class":751},[570,1291,755],{"class":744},[570,1293,758],{"class":710},[570,1295,761],{"class":717},[570,1297,765],{"class":764},[570,1299,727],{"class":717},[570,1301,770],{"class":583},[570,1303,773],{"class":717},[570,1305,776],{"class":710},[570,1307,718],{"class":717},[570,1309,1310],{"class":572,"line":580},[570,1311,1312],{"class":576},"  \u002F\u002F Handle preflight OPTIONS request\n",[570,1314,1315,1318,1320,1322,1324,1327,1330,1332,1335,1337,1340],{"class":572,"line":597},[570,1316,1317],{"class":803},"  if",[570,1319,761],{"class":723},[570,1321,765],{"class":744},[570,1323,748],{"class":717},[570,1325,1326],{"class":744},"method",[570,1328,1329],{"class":717}," ===",[570,1331,972],{"class":717},[570,1333,1334],{"class":587},"OPTIONS",[570,1336,967],{"class":717},[570,1338,1339],{"class":723},") ",[570,1341,1342],{"class":717},"{\n",[570,1344,1345,1348,1350,1352,1354,1356,1359,1361,1363],{"class":572,"line":604},[570,1346,1347],{"class":803},"    return",[570,1349,591],{"class":717},[570,1351,923],{"class":751},[570,1353,755],{"class":723},[570,1355,967],{"class":717},[570,1357,1358],{"class":587},"ok",[570,1360,967],{"class":717},[570,1362,943],{"class":717},[570,1364,718],{"class":717},[570,1366,1367,1370,1372],{"class":572,"line":610},[570,1368,1369],{"class":723},"      headers",[570,1371,727],{"class":717},[570,1373,718],{"class":717},[570,1375,1376,1379,1381,1383,1385,1387,1389,1391],{"class":572,"line":620},[570,1377,1378],{"class":717},"        '",[570,1380,987],{"class":723},[570,1382,967],{"class":717},[570,1384,727],{"class":717},[570,1386,972],{"class":717},[570,1388,996],{"class":587},[570,1390,967],{"class":717},[570,1392,872],{"class":717},[570,1394,1395,1397,1400,1402,1404,1406,1409,1411],{"class":572,"line":625},[570,1396,1378],{"class":717},[570,1398,1399],{"class":723},"Access-Control-Allow-Methods",[570,1401,967],{"class":717},[570,1403,727],{"class":717},[570,1405,972],{"class":717},[570,1407,1408],{"class":587},"POST, GET, OPTIONS",[570,1410,967],{"class":717},[570,1412,872],{"class":717},[570,1414,1415,1417,1420,1422,1424,1426,1429],{"class":572,"line":631},[570,1416,1378],{"class":717},[570,1418,1419],{"class":723},"Access-Control-Allow-Headers",[570,1421,967],{"class":717},[570,1423,727],{"class":717},[570,1425,972],{"class":717},[570,1427,1428],{"class":587},"Authorization, Content-Type, apikey",[570,1430,999],{"class":717},[570,1432,1433],{"class":572,"line":643},[570,1434,1435],{"class":717},"      }\n",[570,1437,1438,1441],{"class":572,"line":648},[570,1439,1440],{"class":717},"    }",[570,1442,1014],{"class":723},[570,1444,1445],{"class":572,"line":654},[570,1446,901],{"class":717},[570,1448,1449],{"class":572,"line":666},[570,1450,601],{"emptyLinePlaceholder":600},[570,1452,1453],{"class":572,"line":671},[570,1454,1455],{"class":576},"  \u002F\u002F Handle actual request\n",[570,1457,1458,1460,1462,1464,1466,1468,1470,1472],{"class":572,"line":677},[570,1459,788],{"class":710},[570,1461,837],{"class":744},[570,1463,800],{"class":717},[570,1465,804],{"class":803},[570,1467,807],{"class":744},[570,1469,748],{"class":717},[570,1471,812],{"class":751},[570,1473,895],{"class":723},[570,1475,1476],{"class":572,"line":898},[570,1477,601],{"emptyLinePlaceholder":600},[570,1479,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498,1501,1503,1507,1509,1511,1513,1515,1517],{"class":572,"line":904},[570,1481,918],{"class":803},[570,1483,591],{"class":717},[570,1485,923],{"class":751},[570,1487,755],{"class":723},[570,1489,928],{"class":744},[570,1491,748],{"class":717},[570,1493,933],{"class":751},[570,1495,755],{"class":723},[570,1497,1247],{"class":717},[570,1499,1500],{"class":723}," success",[570,1502,727],{"class":717},[570,1504,1506],{"class":1505},"sfNiH"," true",[570,1508,943],{"class":717},[570,1510,837],{"class":744},[570,1512,797],{"class":717},[570,1514,773],{"class":723},[570,1516,943],{"class":717},[570,1518,718],{"class":717},[570,1520,1521,1523,1525],{"class":572,"line":909},[570,1522,951],{"class":723},[570,1524,727],{"class":717},[570,1526,718],{"class":717},[570,1528,1529,1531,1533,1535,1537,1539,1541,1543],{"class":572,"line":915},[570,1530,961],{"class":717},[570,1532,964],{"class":723},[570,1534,967],{"class":717},[570,1536,727],{"class":717},[570,1538,972],{"class":717},[570,1540,975],{"class":587},[570,1542,967],{"class":717},[570,1544,872],{"class":717},[570,1546,1547,1549,1551,1553,1555,1557,1559],{"class":572,"line":948},[570,1548,961],{"class":717},[570,1550,987],{"class":723},[570,1552,967],{"class":717},[570,1554,727],{"class":717},[570,1556,972],{"class":717},[570,1558,996],{"class":587},[570,1560,999],{"class":717},[570,1562,1563],{"class":572,"line":958},[570,1564,1005],{"class":717},[570,1566,1567,1569],{"class":572,"line":982},[570,1568,1011],{"class":717},[570,1570,1014],{"class":723},[570,1572,1573,1575],{"class":572,"line":1002},[570,1574,863],{"class":717},[570,1576,1014],{"class":744},[436,1578,1579],{},"Luôn handle OPTIONS method cùng với các CORS headers trong mọi function được gọi từ browser.",[432,1581,1583],{"id":1582},"environment-variables-secrets","Environment Variables & Secrets",[436,1585,1586],{},"Tuyệt đối không hardcode API keys trong code. Dùng Supabase Secrets:",[561,1588,1590],{"className":563,"code":1589,"language":565,"meta":566,"style":566},"# Set secrets qua CLI\nsupabase secrets set OPENAI_API_KEY=sk-xxx\nsupabase secrets set STRIPE_SECRET_KEY=sk_live_xxx\nsupabase secrets set RESEND_API_KEY=re_xxx\n\n# Set từ file .env\nsupabase secrets set --env-file .env.production\n\n# Liệt kê secrets (chỉ hiển thị tên, không hiển thị giá trị)\nsupabase secrets list\n\n# Xóa secret\nsupabase secrets unset MY_SECRET\n",[458,1591,1592,1597,1610,1621,1632,1636,1641,1655,1659,1664,1672,1676,1681],{"__ignoreMap":566},[570,1593,1594],{"class":572,"line":573},[570,1595,1596],{"class":576},"# Set secrets qua CLI\n",[570,1598,1599,1601,1604,1607],{"class":572,"line":580},[570,1600,584],{"class":583},[570,1602,1603],{"class":587}," secrets",[570,1605,1606],{"class":587}," set",[570,1608,1609],{"class":587}," OPENAI_API_KEY=sk-xxx\n",[570,1611,1612,1614,1616,1618],{"class":572,"line":597},[570,1613,584],{"class":583},[570,1615,1603],{"class":587},[570,1617,1606],{"class":587},[570,1619,1620],{"class":587}," STRIPE_SECRET_KEY=sk_live_xxx\n",[570,1622,1623,1625,1627,1629],{"class":572,"line":604},[570,1624,584],{"class":583},[570,1626,1603],{"class":587},[570,1628,1606],{"class":587},[570,1630,1631],{"class":587}," RESEND_API_KEY=re_xxx\n",[570,1633,1634],{"class":572,"line":610},[570,1635,601],{"emptyLinePlaceholder":600},[570,1637,1638],{"class":572,"line":620},[570,1639,1640],{"class":576},"# Set từ file .env\n",[570,1642,1643,1645,1647,1649,1652],{"class":572,"line":625},[570,1644,584],{"class":583},[570,1646,1603],{"class":587},[570,1648,1606],{"class":587},[570,1650,1651],{"class":587}," --env-file",[570,1653,1654],{"class":587}," .env.production\n",[570,1656,1657],{"class":572,"line":631},[570,1658,601],{"emptyLinePlaceholder":600},[570,1660,1661],{"class":572,"line":643},[570,1662,1663],{"class":576},"# Liệt kê secrets (chỉ hiển thị tên, không hiển thị giá trị)\n",[570,1665,1666,1668,1670],{"class":572,"line":648},[570,1667,584],{"class":583},[570,1669,1603],{"class":587},[570,1671,684],{"class":587},[570,1673,1674],{"class":572,"line":654},[570,1675,601],{"emptyLinePlaceholder":600},[570,1677,1678],{"class":572,"line":666},[570,1679,1680],{"class":576},"# Xóa secret\n",[570,1682,1683,1685,1687,1690],{"class":572,"line":671},[570,1684,584],{"class":583},[570,1686,1603],{"class":587},[570,1688,1689],{"class":587}," unset",[570,1691,1692],{"class":587}," MY_SECRET\n",[561,1694,1696],{"className":692,"code":1695,"language":694,"meta":566,"style":566},"\u002F\u002F Truy cập secrets trong Edge Function\nconst OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!\nconst STRIPE_SECRET = Deno.env.get('STRIPE_SECRET_KEY')!\nconst SUPABASE_URL = Deno.env.get('SUPABASE_URL')!\nconst SUPABASE_SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n",[458,1697,1698,1703,1739,1771,1803],{"__ignoreMap":566},[570,1699,1700],{"class":572,"line":573},[570,1701,1702],{"class":576},"\u002F\u002F Truy cập secrets trong Edge Function\n",[570,1704,1705,1707,1710,1712,1715,1717,1720,1722,1725,1727,1729,1732,1734,1736],{"class":572,"line":580},[570,1706,1043],{"class":710},[570,1708,1709],{"class":744}," OPENAI_KEY ",[570,1711,1132],{"class":717},[570,1713,1714],{"class":744}," Deno",[570,1716,748],{"class":717},[570,1718,1719],{"class":744},"env",[570,1721,748],{"class":717},[570,1723,1724],{"class":751},"get",[570,1726,755],{"class":744},[570,1728,967],{"class":717},[570,1730,1731],{"class":587},"OPENAI_API_KEY",[570,1733,967],{"class":717},[570,1735,773],{"class":744},[570,1737,1738],{"class":717},"!\n",[570,1740,1741,1743,1746,1748,1750,1752,1754,1756,1758,1760,1762,1765,1767,1769],{"class":572,"line":597},[570,1742,1043],{"class":710},[570,1744,1745],{"class":744}," STRIPE_SECRET ",[570,1747,1132],{"class":717},[570,1749,1714],{"class":744},[570,1751,748],{"class":717},[570,1753,1719],{"class":744},[570,1755,748],{"class":717},[570,1757,1724],{"class":751},[570,1759,755],{"class":744},[570,1761,967],{"class":717},[570,1763,1764],{"class":587},"STRIPE_SECRET_KEY",[570,1766,967],{"class":717},[570,1768,773],{"class":744},[570,1770,1738],{"class":717},[570,1772,1773,1775,1778,1780,1782,1784,1786,1788,1790,1792,1794,1797,1799,1801],{"class":572,"line":604},[570,1774,1043],{"class":710},[570,1776,1777],{"class":744}," SUPABASE_URL ",[570,1779,1132],{"class":717},[570,1781,1714],{"class":744},[570,1783,748],{"class":717},[570,1785,1719],{"class":744},[570,1787,748],{"class":717},[570,1789,1724],{"class":751},[570,1791,755],{"class":744},[570,1793,967],{"class":717},[570,1795,1796],{"class":587},"SUPABASE_URL",[570,1798,967],{"class":717},[570,1800,773],{"class":744},[570,1802,1738],{"class":717},[570,1804,1805,1807,1810,1812,1814,1816,1818,1820,1822,1824,1826,1829,1831,1833],{"class":572,"line":610},[570,1806,1043],{"class":710},[570,1808,1809],{"class":744}," SUPABASE_SERVICE_ROLE_KEY ",[570,1811,1132],{"class":717},[570,1813,1714],{"class":744},[570,1815,748],{"class":717},[570,1817,1719],{"class":744},[570,1819,748],{"class":717},[570,1821,1724],{"class":751},[570,1823,755],{"class":744},[570,1825,967],{"class":717},[570,1827,1828],{"class":587},"SUPABASE_SERVICE_ROLE_KEY",[570,1830,967],{"class":717},[570,1832,773],{"class":744},[570,1834,1738],{"class":717},[432,1836,1838],{"id":1837},"tích-hợp-với-supabase-services","Tích hợp với Supabase Services",[436,1840,1841],{},"Edge Function có thể tương tác với tất cả dịch vụ Supabase thông qua service role key:",[561,1843,1845],{"className":692,"code":1844,"language":694,"meta":566,"style":566},"import { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\n\nDeno.serve(async (req: Request) => {\n  \u002F\u002F Tạo Supabase client với service role để bypass RLS\n  const supabase = createClient(\n    Deno.env.get('SUPABASE_URL')!,\n    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n  )\n\n  \u002F\u002F Query database\n  const { data: users } = await supabase\n    .from('profiles')\n    .select('id, email, full_name')\n    .order('created_at', { ascending: false })\n\n  \u002F\u002F Upload file lên Storage\n  const { data: upload } = await supabase.storage\n    .from('exports')\n    .upload('report.csv', csvContent)\n\n  \u002F\u002F Gọi Auth admin API (tạo user, xóa user...)\n  const { data: newUser } = await supabase.auth.admin.createUser({\n    email: 'new@example.com',\n    password: 'secure-password',\n    email_confirm: true\n  })\n\n  return new Response(JSON.stringify({ users, upload }), {\n    headers: { 'Content-Type': 'application\u002Fjson' }\n  })\n})\n",[458,1846,1847,1869,1873,1899,1904,1916,1942,1966,1971,1975,1980,2002,2021,2039,2071,2075,2080,2106,2123,2146,2150,2155,2195,2211,2227,2238,2245,2250,2285,2310,2317],{"__ignoreMap":566},[570,1848,1849,1852,1854,1857,1859,1862,1864,1867],{"class":572,"line":573},[570,1850,1851],{"class":803},"import",[570,1853,791],{"class":717},[570,1855,1856],{"class":744}," createClient",[570,1858,797],{"class":717},[570,1860,1861],{"class":803}," from",[570,1863,972],{"class":717},[570,1865,1866],{"class":587},"jsr:@supabase\u002Fsupabase-js@2",[570,1868,999],{"class":717},[570,1870,1871],{"class":572,"line":580},[570,1872,601],{"emptyLinePlaceholder":600},[570,1874,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895,1897],{"class":572,"line":597},[570,1876,745],{"class":744},[570,1878,748],{"class":717},[570,1880,752],{"class":751},[570,1882,755],{"class":744},[570,1884,758],{"class":710},[570,1886,761],{"class":717},[570,1888,765],{"class":764},[570,1890,727],{"class":717},[570,1892,770],{"class":583},[570,1894,773],{"class":717},[570,1896,776],{"class":710},[570,1898,718],{"class":717},[570,1900,1901],{"class":572,"line":604},[570,1902,1903],{"class":576},"  \u002F\u002F Tạo Supabase client với service role để bypass RLS\n",[570,1905,1906,1908,1910,1912,1914],{"class":572,"line":610},[570,1907,788],{"class":710},[570,1909,1061],{"class":744},[570,1911,800],{"class":717},[570,1913,1856],{"class":751},[570,1915,1140],{"class":723},[570,1917,1918,1921,1923,1925,1927,1929,1931,1933,1935,1937,1939],{"class":572,"line":620},[570,1919,1920],{"class":744},"    Deno",[570,1922,748],{"class":717},[570,1924,1719],{"class":744},[570,1926,748],{"class":717},[570,1928,1724],{"class":751},[570,1930,755],{"class":723},[570,1932,967],{"class":717},[570,1934,1796],{"class":587},[570,1936,967],{"class":717},[570,1938,773],{"class":723},[570,1940,1941],{"class":717},"!,\n",[570,1943,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964],{"class":572,"line":625},[570,1945,1920],{"class":744},[570,1947,748],{"class":717},[570,1949,1719],{"class":744},[570,1951,748],{"class":717},[570,1953,1724],{"class":751},[570,1955,755],{"class":723},[570,1957,967],{"class":717},[570,1959,1828],{"class":587},[570,1961,967],{"class":717},[570,1963,773],{"class":723},[570,1965,1738],{"class":717},[570,1967,1968],{"class":572,"line":631},[570,1969,1970],{"class":723},"  )\n",[570,1972,1973],{"class":572,"line":643},[570,1974,601],{"emptyLinePlaceholder":600},[570,1976,1977],{"class":572,"line":648},[570,1978,1979],{"class":576},"  \u002F\u002F Query database\n",[570,1981,1982,1984,1986,1988,1990,1993,1995,1997,1999],{"class":572,"line":654},[570,1983,788],{"class":710},[570,1985,791],{"class":717},[570,1987,837],{"class":723},[570,1989,727],{"class":717},[570,1991,1992],{"class":744}," users",[570,1994,797],{"class":717},[570,1996,800],{"class":717},[570,1998,804],{"class":803},[570,2000,2001],{"class":744}," supabase\n",[570,2003,2004,2007,2010,2012,2014,2017,2019],{"class":572,"line":666},[570,2005,2006],{"class":717},"    .",[570,2008,2009],{"class":751},"from",[570,2011,755],{"class":723},[570,2013,967],{"class":717},[570,2015,2016],{"class":587},"profiles",[570,2018,967],{"class":717},[570,2020,1014],{"class":723},[570,2022,2023,2025,2028,2030,2032,2035,2037],{"class":572,"line":671},[570,2024,2006],{"class":717},[570,2026,2027],{"class":751},"select",[570,2029,755],{"class":723},[570,2031,967],{"class":717},[570,2033,2034],{"class":587},"id, email, full_name",[570,2036,967],{"class":717},[570,2038,1014],{"class":723},[570,2040,2041,2043,2046,2048,2050,2053,2055,2057,2059,2062,2064,2067,2069],{"class":572,"line":677},[570,2042,2006],{"class":717},[570,2044,2045],{"class":751},"order",[570,2047,755],{"class":723},[570,2049,967],{"class":717},[570,2051,2052],{"class":587},"created_at",[570,2054,967],{"class":717},[570,2056,943],{"class":717},[570,2058,791],{"class":717},[570,2060,2061],{"class":723}," ascending",[570,2063,727],{"class":717},[570,2065,2066],{"class":1505}," false",[570,2068,797],{"class":717},[570,2070,1014],{"class":723},[570,2072,2073],{"class":572,"line":898},[570,2074,601],{"emptyLinePlaceholder":600},[570,2076,2077],{"class":572,"line":904},[570,2078,2079],{"class":576},"  \u002F\u002F Upload file lên Storage\n",[570,2081,2082,2084,2086,2088,2090,2093,2095,2097,2099,2101,2103],{"class":572,"line":909},[570,2083,788],{"class":710},[570,2085,791],{"class":717},[570,2087,837],{"class":723},[570,2089,727],{"class":717},[570,2091,2092],{"class":744}," upload",[570,2094,797],{"class":717},[570,2096,800],{"class":717},[570,2098,804],{"class":803},[570,2100,1061],{"class":744},[570,2102,748],{"class":717},[570,2104,2105],{"class":744},"storage\n",[570,2107,2108,2110,2112,2114,2116,2119,2121],{"class":572,"line":915},[570,2109,2006],{"class":717},[570,2111,2009],{"class":751},[570,2113,755],{"class":723},[570,2115,967],{"class":717},[570,2117,2118],{"class":587},"exports",[570,2120,967],{"class":717},[570,2122,1014],{"class":723},[570,2124,2125,2127,2130,2132,2134,2137,2139,2141,2144],{"class":572,"line":948},[570,2126,2006],{"class":717},[570,2128,2129],{"class":751},"upload",[570,2131,755],{"class":723},[570,2133,967],{"class":717},[570,2135,2136],{"class":587},"report.csv",[570,2138,967],{"class":717},[570,2140,943],{"class":717},[570,2142,2143],{"class":744}," csvContent",[570,2145,1014],{"class":723},[570,2147,2148],{"class":572,"line":958},[570,2149,601],{"emptyLinePlaceholder":600},[570,2151,2152],{"class":572,"line":982},[570,2153,2154],{"class":576},"  \u002F\u002F Gọi Auth admin API (tạo user, xóa user...)\n",[570,2156,2157,2159,2161,2163,2165,2168,2170,2172,2174,2176,2178,2181,2183,2186,2188,2191,2193],{"class":572,"line":1002},[570,2158,788],{"class":710},[570,2160,791],{"class":717},[570,2162,837],{"class":723},[570,2164,727],{"class":717},[570,2166,2167],{"class":744}," newUser",[570,2169,797],{"class":717},[570,2171,800],{"class":717},[570,2173,804],{"class":803},[570,2175,1061],{"class":744},[570,2177,748],{"class":717},[570,2179,2180],{"class":744},"auth",[570,2182,748],{"class":717},[570,2184,2185],{"class":744},"admin",[570,2187,748],{"class":717},[570,2189,2190],{"class":751},"createUser",[570,2192,755],{"class":723},[570,2194,1342],{"class":717},[570,2196,2197,2200,2202,2204,2207,2209],{"class":572,"line":1008},[570,2198,2199],{"class":723},"    email",[570,2201,727],{"class":717},[570,2203,972],{"class":717},[570,2205,2206],{"class":587},"new@example.com",[570,2208,967],{"class":717},[570,2210,872],{"class":717},[570,2212,2213,2216,2218,2220,2223,2225],{"class":572,"line":1017},[570,2214,2215],{"class":723},"    password",[570,2217,727],{"class":717},[570,2219,972],{"class":717},[570,2221,2222],{"class":587},"secure-password",[570,2224,967],{"class":717},[570,2226,872],{"class":717},[570,2228,2230,2233,2235],{"class":572,"line":2229},25,[570,2231,2232],{"class":723},"    email_confirm",[570,2234,727],{"class":717},[570,2236,2237],{"class":1505}," true\n",[570,2239,2241,2243],{"class":572,"line":2240},26,[570,2242,1011],{"class":717},[570,2244,1014],{"class":723},[570,2246,2248],{"class":572,"line":2247},27,[570,2249,601],{"emptyLinePlaceholder":600},[570,2251,2253,2255,2257,2259,2261,2263,2265,2267,2269,2271,2273,2275,2277,2279,2281,2283],{"class":572,"line":2252},28,[570,2254,918],{"class":803},[570,2256,591],{"class":717},[570,2258,923],{"class":751},[570,2260,755],{"class":723},[570,2262,928],{"class":744},[570,2264,748],{"class":717},[570,2266,933],{"class":751},[570,2268,755],{"class":723},[570,2270,1247],{"class":717},[570,2272,1992],{"class":744},[570,2274,943],{"class":717},[570,2276,2092],{"class":744},[570,2278,797],{"class":717},[570,2280,773],{"class":723},[570,2282,943],{"class":717},[570,2284,718],{"class":717},[570,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2306,2308],{"class":572,"line":2287},29,[570,2289,951],{"class":723},[570,2291,727],{"class":717},[570,2293,791],{"class":717},[570,2295,972],{"class":717},[570,2297,964],{"class":723},[570,2299,967],{"class":717},[570,2301,727],{"class":717},[570,2303,972],{"class":717},[570,2305,975],{"class":587},[570,2307,967],{"class":717},[570,2309,1107],{"class":717},[570,2311,2313,2315],{"class":572,"line":2312},30,[570,2314,1011],{"class":717},[570,2316,1014],{"class":723},[570,2318,2320,2322],{"class":572,"line":2319},31,[570,2321,863],{"class":717},[570,2323,1014],{"class":744},[432,2325,2327],{"id":2326},"khi-nào-dùng-edge-functions","Khi nào dùng Edge Functions?",[436,2329,2330],{},"Edge Functions phù hợp cho nhiều use case:",[471,2332,2333,2343],{},[474,2334,2335],{},[477,2336,2337,2340],{},[480,2338,2339],{},"Use case",[480,2341,2342],{},"Ví dụ cụ thể",[487,2344,2345,2355,2365,2375,2385,2395,2405,2415],{},[477,2346,2347,2352],{},[492,2348,2349],{},[440,2350,2351],{},"Webhook receivers",[492,2353,2354],{},"Stripe payment webhook, GitHub push events, Slack slash commands",[477,2356,2357,2362],{},[492,2358,2359],{},[440,2360,2361],{},"API proxies",[492,2363,2364],{},"Gọi OpenAI API (giấu API key), Resend transactional email",[477,2366,2367,2372],{},[492,2368,2369],{},[440,2370,2371],{},"Custom business logic",[492,2373,2374],{},"Logic validation phức tạp, tính toán không nên chạy trên client",[477,2376,2377,2382],{},[492,2378,2379],{},[440,2380,2381],{},"Image\u002FFile processing",[492,2383,2384],{},"Resize ảnh server-side, generate PDF, extract metadata",[477,2386,2387,2392],{},[492,2388,2389],{},[440,2390,2391],{},"Sending emails",[492,2393,2394],{},"Gửi email xác nhận đơn hàng, welcome email, password reset",[477,2396,2397,2402],{},[492,2398,2399],{},[440,2400,2401],{},"AI inference",[492,2403,2404],{},"Gọi OpenAI, HuggingFace, Claude từ edge",[477,2406,2407,2412],{},[492,2408,2409],{},[440,2410,2411],{},"Bots",[492,2413,2414],{},"Discord Bot, Telegram Bot, Slack Bot",[477,2416,2417,2422],{},[492,2418,2419],{},[440,2420,2421],{},"Scheduled tasks",[492,2423,2424],{},"Cron jobs gọi Edge Function định kỳ",[432,2426,2428],{"id":2427},"use-case-chi-tiết","Use Case Chi Tiết",[2430,2431,2433],"h4",{"id":2432},"_1-stripe-webhook-handler","1. Stripe Webhook Handler",[561,2435,2437],{"className":692,"code":2436,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fstripe-webhook\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\nimport Stripe from 'npm:stripe'\n\nconst stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)\nconst supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n)\n\nDeno.serve(async (req: Request) => {\n  const signature = req.headers.get('stripe-signature')!\n  const body = await req.text()\n\n  \u002F\u002F Verify webhook signature\n  const event = stripe.webhooks.constructEvent(\n    body,\n    signature,\n    Deno.env.get('STRIPE_WEBHOOK_SECRET')!\n  )\n\n  switch (event.type) {\n    case 'checkout.session.completed': {\n      const session = event.data.object\n      const userId = session.client_reference_id\n\n      \u002F\u002F Cập nhật subscription trong database\n      await supabase.from('subscriptions').upsert({\n        user_id: userId,\n        stripe_customer_id: session.customer,\n        status: 'active',\n        plan: session.metadata.plan,\n        current_period_end: new Date(session.expires_at! * 1000).toISOString()\n      })\n\n      break\n    }\n\n    case 'customer.subscription.deleted': {\n      const subscription = event.data.object\n      await supabase.from('subscriptions')\n        .update({ status: 'canceled' })\n        .eq('stripe_customer_id', subscription.customer)\n      break\n    }\n  }\n\n  return new Response(JSON.stringify({ received: true }), {\n    status: 200,\n    headers: { 'Content-Type': 'application\u002Fjson' }\n  })\n})\n",[458,2438,2439,2444,2462,2478,2482,2521,2534,2559,2583,2587,2591,2617,2650,2670,2674,2679,2703,2709,2716,2741,2745,2749,2768,2784,2805,2821,2825,2830,2861,2872,2888,2904,2926,2965,2973,2978,2984,2989,2994,3010,3030,3051,3080,3107,3112,3117,3122,3127,3163,3176,3201,3208],{"__ignoreMap":566},[570,2440,2441],{"class":572,"line":573},[570,2442,2443],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fstripe-webhook\u002Findex.ts\n",[570,2445,2446,2448,2450,2452,2454,2456,2458,2460],{"class":572,"line":580},[570,2447,1851],{"class":803},[570,2449,791],{"class":717},[570,2451,1856],{"class":744},[570,2453,797],{"class":717},[570,2455,1861],{"class":803},[570,2457,972],{"class":717},[570,2459,1866],{"class":587},[570,2461,999],{"class":717},[570,2463,2464,2466,2469,2471,2473,2476],{"class":572,"line":597},[570,2465,1851],{"class":803},[570,2467,2468],{"class":744}," Stripe ",[570,2470,2009],{"class":803},[570,2472,972],{"class":717},[570,2474,2475],{"class":587},"npm:stripe",[570,2477,999],{"class":717},[570,2479,2480],{"class":572,"line":604},[570,2481,601],{"emptyLinePlaceholder":600},[570,2483,2484,2486,2489,2491,2493,2496,2499,2501,2503,2505,2507,2509,2511,2513,2515,2517,2519],{"class":572,"line":610},[570,2485,1043],{"class":710},[570,2487,2488],{"class":744}," stripe ",[570,2490,1132],{"class":717},[570,2492,591],{"class":717},[570,2494,2495],{"class":751}," Stripe",[570,2497,2498],{"class":744},"(Deno",[570,2500,748],{"class":717},[570,2502,1719],{"class":744},[570,2504,748],{"class":717},[570,2506,1724],{"class":751},[570,2508,755],{"class":744},[570,2510,967],{"class":717},[570,2512,1764],{"class":587},[570,2514,967],{"class":717},[570,2516,773],{"class":744},[570,2518,866],{"class":717},[570,2520,1014],{"class":744},[570,2522,2523,2525,2528,2530,2532],{"class":572,"line":620},[570,2524,1043],{"class":710},[570,2526,2527],{"class":744}," supabase ",[570,2529,1132],{"class":717},[570,2531,1856],{"class":751},[570,2533,1140],{"class":744},[570,2535,2536,2539,2541,2543,2545,2547,2549,2551,2553,2555,2557],{"class":572,"line":625},[570,2537,2538],{"class":744},"  Deno",[570,2540,748],{"class":717},[570,2542,1719],{"class":744},[570,2544,748],{"class":717},[570,2546,1724],{"class":751},[570,2548,755],{"class":744},[570,2550,967],{"class":717},[570,2552,1796],{"class":587},[570,2554,967],{"class":717},[570,2556,773],{"class":744},[570,2558,1941],{"class":717},[570,2560,2561,2563,2565,2567,2569,2571,2573,2575,2577,2579,2581],{"class":572,"line":631},[570,2562,2538],{"class":744},[570,2564,748],{"class":717},[570,2566,1719],{"class":744},[570,2568,748],{"class":717},[570,2570,1724],{"class":751},[570,2572,755],{"class":744},[570,2574,967],{"class":717},[570,2576,1828],{"class":587},[570,2578,967],{"class":717},[570,2580,773],{"class":744},[570,2582,1738],{"class":717},[570,2584,2585],{"class":572,"line":643},[570,2586,1014],{"class":744},[570,2588,2589],{"class":572,"line":648},[570,2590,601],{"emptyLinePlaceholder":600},[570,2592,2593,2595,2597,2599,2601,2603,2605,2607,2609,2611,2613,2615],{"class":572,"line":654},[570,2594,745],{"class":744},[570,2596,748],{"class":717},[570,2598,752],{"class":751},[570,2600,755],{"class":744},[570,2602,758],{"class":710},[570,2604,761],{"class":717},[570,2606,765],{"class":764},[570,2608,727],{"class":717},[570,2610,770],{"class":583},[570,2612,773],{"class":717},[570,2614,776],{"class":710},[570,2616,718],{"class":717},[570,2618,2619,2621,2624,2626,2628,2630,2633,2635,2637,2639,2641,2644,2646,2648],{"class":572,"line":666},[570,2620,788],{"class":710},[570,2622,2623],{"class":744}," signature",[570,2625,800],{"class":717},[570,2627,807],{"class":744},[570,2629,748],{"class":717},[570,2631,2632],{"class":744},"headers",[570,2634,748],{"class":717},[570,2636,1724],{"class":751},[570,2638,755],{"class":723},[570,2640,967],{"class":717},[570,2642,2643],{"class":587},"stripe-signature",[570,2645,967],{"class":717},[570,2647,773],{"class":723},[570,2649,1738],{"class":717},[570,2651,2652,2654,2657,2659,2661,2663,2665,2668],{"class":572,"line":671},[570,2653,788],{"class":710},[570,2655,2656],{"class":744}," body",[570,2658,800],{"class":717},[570,2660,804],{"class":803},[570,2662,807],{"class":744},[570,2664,748],{"class":717},[570,2666,2667],{"class":751},"text",[570,2669,895],{"class":723},[570,2671,2672],{"class":572,"line":677},[570,2673,601],{"emptyLinePlaceholder":600},[570,2675,2676],{"class":572,"line":898},[570,2677,2678],{"class":576},"  \u002F\u002F Verify webhook signature\n",[570,2680,2681,2683,2686,2688,2691,2693,2696,2698,2701],{"class":572,"line":904},[570,2682,788],{"class":710},[570,2684,2685],{"class":744}," event",[570,2687,800],{"class":717},[570,2689,2690],{"class":744}," stripe",[570,2692,748],{"class":717},[570,2694,2695],{"class":744},"webhooks",[570,2697,748],{"class":717},[570,2699,2700],{"class":751},"constructEvent",[570,2702,1140],{"class":723},[570,2704,2705,2707],{"class":572,"line":909},[570,2706,1233],{"class":744},[570,2708,872],{"class":717},[570,2710,2711,2714],{"class":572,"line":915},[570,2712,2713],{"class":744},"    signature",[570,2715,872],{"class":717},[570,2717,2718,2720,2722,2724,2726,2728,2730,2732,2735,2737,2739],{"class":572,"line":948},[570,2719,1920],{"class":744},[570,2721,748],{"class":717},[570,2723,1719],{"class":744},[570,2725,748],{"class":717},[570,2727,1724],{"class":751},[570,2729,755],{"class":723},[570,2731,967],{"class":717},[570,2733,2734],{"class":587},"STRIPE_WEBHOOK_SECRET",[570,2736,967],{"class":717},[570,2738,773],{"class":723},[570,2740,1738],{"class":717},[570,2742,2743],{"class":572,"line":958},[570,2744,1970],{"class":723},[570,2746,2747],{"class":572,"line":982},[570,2748,601],{"emptyLinePlaceholder":600},[570,2750,2751,2754,2756,2759,2761,2764,2766],{"class":572,"line":1002},[570,2752,2753],{"class":803},"  switch",[570,2755,761],{"class":723},[570,2757,2758],{"class":744},"event",[570,2760,748],{"class":717},[570,2762,2763],{"class":744},"type",[570,2765,1339],{"class":723},[570,2767,1342],{"class":717},[570,2769,2770,2773,2775,2778,2780,2782],{"class":572,"line":1008},[570,2771,2772],{"class":803},"    case",[570,2774,972],{"class":717},[570,2776,2777],{"class":587},"checkout.session.completed",[570,2779,967],{"class":717},[570,2781,727],{"class":717},[570,2783,718],{"class":717},[570,2785,2786,2789,2792,2794,2796,2798,2800,2802],{"class":572,"line":1017},[570,2787,2788],{"class":710},"      const",[570,2790,2791],{"class":744}," session",[570,2793,800],{"class":717},[570,2795,2685],{"class":744},[570,2797,748],{"class":717},[570,2799,938],{"class":744},[570,2801,748],{"class":717},[570,2803,2804],{"class":744},"object\n",[570,2806,2807,2809,2812,2814,2816,2818],{"class":572,"line":2229},[570,2808,2788],{"class":710},[570,2810,2811],{"class":744}," userId",[570,2813,800],{"class":717},[570,2815,2791],{"class":744},[570,2817,748],{"class":717},[570,2819,2820],{"class":744},"client_reference_id\n",[570,2822,2823],{"class":572,"line":2240},[570,2824,601],{"emptyLinePlaceholder":600},[570,2826,2827],{"class":572,"line":2247},[570,2828,2829],{"class":576},"      \u002F\u002F Cập nhật subscription trong database\n",[570,2831,2832,2835,2837,2839,2841,2843,2845,2848,2850,2852,2854,2857,2859],{"class":572,"line":2252},[570,2833,2834],{"class":803},"      await",[570,2836,1061],{"class":744},[570,2838,748],{"class":717},[570,2840,2009],{"class":751},[570,2842,755],{"class":723},[570,2844,967],{"class":717},[570,2846,2847],{"class":587},"subscriptions",[570,2849,967],{"class":717},[570,2851,773],{"class":723},[570,2853,748],{"class":717},[570,2855,2856],{"class":751},"upsert",[570,2858,755],{"class":723},[570,2860,1342],{"class":717},[570,2862,2863,2866,2868,2870],{"class":572,"line":2287},[570,2864,2865],{"class":723},"        user_id",[570,2867,727],{"class":717},[570,2869,2811],{"class":744},[570,2871,872],{"class":717},[570,2873,2874,2877,2879,2881,2883,2886],{"class":572,"line":2312},[570,2875,2876],{"class":723},"        stripe_customer_id",[570,2878,727],{"class":717},[570,2880,2791],{"class":744},[570,2882,748],{"class":717},[570,2884,2885],{"class":744},"customer",[570,2887,872],{"class":717},[570,2889,2890,2893,2895,2897,2900,2902],{"class":572,"line":2319},[570,2891,2892],{"class":723},"        status",[570,2894,727],{"class":717},[570,2896,972],{"class":717},[570,2898,2899],{"class":587},"active",[570,2901,967],{"class":717},[570,2903,872],{"class":717},[570,2905,2907,2910,2912,2914,2916,2919,2921,2924],{"class":572,"line":2906},32,[570,2908,2909],{"class":723},"        plan",[570,2911,727],{"class":717},[570,2913,2791],{"class":744},[570,2915,748],{"class":717},[570,2917,2918],{"class":744},"metadata",[570,2920,748],{"class":717},[570,2922,2923],{"class":744},"plan",[570,2925,872],{"class":717},[570,2927,2929,2932,2934,2936,2938,2940,2943,2945,2948,2950,2953,2957,2959,2961,2963],{"class":572,"line":2928},33,[570,2930,2931],{"class":723},"        current_period_end",[570,2933,727],{"class":717},[570,2935,591],{"class":717},[570,2937,884],{"class":751},[570,2939,755],{"class":723},[570,2941,2942],{"class":744},"session",[570,2944,748],{"class":717},[570,2946,2947],{"class":744},"expires_at",[570,2949,866],{"class":717},[570,2951,2952],{"class":717}," *",[570,2954,2956],{"class":2955},"sbssI"," 1000",[570,2958,773],{"class":723},[570,2960,748],{"class":717},[570,2962,892],{"class":751},[570,2964,895],{"class":723},[570,2966,2968,2971],{"class":572,"line":2967},34,[570,2969,2970],{"class":717},"      }",[570,2972,1014],{"class":723},[570,2974,2976],{"class":572,"line":2975},35,[570,2977,601],{"emptyLinePlaceholder":600},[570,2979,2981],{"class":572,"line":2980},36,[570,2982,2983],{"class":803},"      break\n",[570,2985,2987],{"class":572,"line":2986},37,[570,2988,1005],{"class":717},[570,2990,2992],{"class":572,"line":2991},38,[570,2993,601],{"emptyLinePlaceholder":600},[570,2995,2997,2999,3001,3004,3006,3008],{"class":572,"line":2996},39,[570,2998,2772],{"class":803},[570,3000,972],{"class":717},[570,3002,3003],{"class":587},"customer.subscription.deleted",[570,3005,967],{"class":717},[570,3007,727],{"class":717},[570,3009,718],{"class":717},[570,3011,3013,3015,3018,3020,3022,3024,3026,3028],{"class":572,"line":3012},40,[570,3014,2788],{"class":710},[570,3016,3017],{"class":744}," subscription",[570,3019,800],{"class":717},[570,3021,2685],{"class":744},[570,3023,748],{"class":717},[570,3025,938],{"class":744},[570,3027,748],{"class":717},[570,3029,2804],{"class":744},[570,3031,3033,3035,3037,3039,3041,3043,3045,3047,3049],{"class":572,"line":3032},41,[570,3034,2834],{"class":803},[570,3036,1061],{"class":744},[570,3038,748],{"class":717},[570,3040,2009],{"class":751},[570,3042,755],{"class":723},[570,3044,967],{"class":717},[570,3046,2847],{"class":587},[570,3048,967],{"class":717},[570,3050,1014],{"class":723},[570,3052,3054,3057,3060,3062,3064,3067,3069,3071,3074,3076,3078],{"class":572,"line":3053},42,[570,3055,3056],{"class":717},"        .",[570,3058,3059],{"class":751},"update",[570,3061,755],{"class":723},[570,3063,1247],{"class":717},[570,3065,3066],{"class":723}," status",[570,3068,727],{"class":717},[570,3070,972],{"class":717},[570,3072,3073],{"class":587},"canceled",[570,3075,967],{"class":717},[570,3077,797],{"class":717},[570,3079,1014],{"class":723},[570,3081,3083,3085,3088,3090,3092,3095,3097,3099,3101,3103,3105],{"class":572,"line":3082},43,[570,3084,3056],{"class":717},[570,3086,3087],{"class":751},"eq",[570,3089,755],{"class":723},[570,3091,967],{"class":717},[570,3093,3094],{"class":587},"stripe_customer_id",[570,3096,967],{"class":717},[570,3098,943],{"class":717},[570,3100,3017],{"class":744},[570,3102,748],{"class":717},[570,3104,2885],{"class":744},[570,3106,1014],{"class":723},[570,3108,3110],{"class":572,"line":3109},44,[570,3111,2983],{"class":803},[570,3113,3115],{"class":572,"line":3114},45,[570,3116,1005],{"class":717},[570,3118,3120],{"class":572,"line":3119},46,[570,3121,901],{"class":717},[570,3123,3125],{"class":572,"line":3124},47,[570,3126,601],{"emptyLinePlaceholder":600},[570,3128,3130,3132,3134,3136,3138,3140,3142,3144,3146,3148,3151,3153,3155,3157,3159,3161],{"class":572,"line":3129},48,[570,3131,918],{"class":803},[570,3133,591],{"class":717},[570,3135,923],{"class":751},[570,3137,755],{"class":723},[570,3139,928],{"class":744},[570,3141,748],{"class":717},[570,3143,933],{"class":751},[570,3145,755],{"class":723},[570,3147,1247],{"class":717},[570,3149,3150],{"class":723}," received",[570,3152,727],{"class":717},[570,3154,1506],{"class":1505},[570,3156,797],{"class":717},[570,3158,773],{"class":723},[570,3160,943],{"class":717},[570,3162,718],{"class":717},[570,3164,3166,3169,3171,3174],{"class":572,"line":3165},49,[570,3167,3168],{"class":723},"    status",[570,3170,727],{"class":717},[570,3172,3173],{"class":2955}," 200",[570,3175,872],{"class":717},[570,3177,3179,3181,3183,3185,3187,3189,3191,3193,3195,3197,3199],{"class":572,"line":3178},50,[570,3180,951],{"class":723},[570,3182,727],{"class":717},[570,3184,791],{"class":717},[570,3186,972],{"class":717},[570,3188,964],{"class":723},[570,3190,967],{"class":717},[570,3192,727],{"class":717},[570,3194,972],{"class":717},[570,3196,975],{"class":587},[570,3198,967],{"class":717},[570,3200,1107],{"class":717},[570,3202,3204,3206],{"class":572,"line":3203},51,[570,3205,1011],{"class":717},[570,3207,1014],{"class":723},[570,3209,3211,3213],{"class":572,"line":3210},52,[570,3212,863],{"class":717},[570,3214,1014],{"class":744},[2430,3216,3218],{"id":3217},"_2-openai-api-proxy","2. OpenAI API Proxy",[561,3220,3222],{"className":692,"code":3221,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fopenai-proxy\u002Findex.ts\n\nDeno.serve(async (req: Request) => {\n  const { prompt, max_tokens } = await req.json()\n\n  const response = await fetch('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({\n      model: 'gpt-4o',\n      messages: [{ role: 'user', content: prompt }],\n      max_tokens: max_tokens || 500\n    })\n  })\n\n  const data = await response.json()\n  return new Response(JSON.stringify(data), {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Access-Control-Allow-Origin': '*'\n    }\n  })\n})\n",[458,3223,3224,3229,3233,3259,3287,3291,3317,3331,3339,3379,3395,3399,3415,3431,3471,3486,3492,3498,3502,3520,3546,3554,3572,3588,3592,3598],{"__ignoreMap":566},[570,3225,3226],{"class":572,"line":573},[570,3227,3228],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fopenai-proxy\u002Findex.ts\n",[570,3230,3231],{"class":572,"line":580},[570,3232,601],{"emptyLinePlaceholder":600},[570,3234,3235,3237,3239,3241,3243,3245,3247,3249,3251,3253,3255,3257],{"class":572,"line":597},[570,3236,745],{"class":744},[570,3238,748],{"class":717},[570,3240,752],{"class":751},[570,3242,755],{"class":744},[570,3244,758],{"class":710},[570,3246,761],{"class":717},[570,3248,765],{"class":764},[570,3250,727],{"class":717},[570,3252,770],{"class":583},[570,3254,773],{"class":717},[570,3256,776],{"class":710},[570,3258,718],{"class":717},[570,3260,3261,3263,3265,3268,3270,3273,3275,3277,3279,3281,3283,3285],{"class":572,"line":604},[570,3262,788],{"class":710},[570,3264,791],{"class":717},[570,3266,3267],{"class":744}," prompt",[570,3269,943],{"class":717},[570,3271,3272],{"class":744}," max_tokens",[570,3274,797],{"class":717},[570,3276,800],{"class":717},[570,3278,804],{"class":803},[570,3280,807],{"class":744},[570,3282,748],{"class":717},[570,3284,812],{"class":751},[570,3286,895],{"class":723},[570,3288,3289],{"class":572,"line":610},[570,3290,601],{"emptyLinePlaceholder":600},[570,3292,3293,3295,3298,3300,3302,3304,3306,3308,3311,3313,3315],{"class":572,"line":620},[570,3294,788],{"class":710},[570,3296,3297],{"class":744}," response",[570,3299,800],{"class":717},[570,3301,804],{"class":803},[570,3303,1137],{"class":751},[570,3305,755],{"class":723},[570,3307,967],{"class":717},[570,3309,3310],{"class":587},"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions",[570,3312,967],{"class":717},[570,3314,943],{"class":717},[570,3316,718],{"class":717},[570,3318,3319,3321,3323,3325,3327,3329],{"class":572,"line":625},[570,3320,1162],{"class":723},[570,3322,727],{"class":717},[570,3324,972],{"class":717},[570,3326,1169],{"class":587},[570,3328,967],{"class":717},[570,3330,872],{"class":717},[570,3332,3333,3335,3337],{"class":572,"line":631},[570,3334,951],{"class":723},[570,3336,727],{"class":717},[570,3338,718],{"class":717},[570,3340,3341,3343,3345,3347,3349,3351,3353,3355,3357,3359,3361,3363,3365,3367,3369,3371,3373,3375,3377],{"class":572,"line":643},[570,3342,961],{"class":717},[570,3344,1188],{"class":723},[570,3346,967],{"class":717},[570,3348,727],{"class":717},[570,3350,851],{"class":717},[570,3352,1197],{"class":587},[570,3354,857],{"class":717},[570,3356,745],{"class":744},[570,3358,748],{"class":717},[570,3360,1719],{"class":744},[570,3362,748],{"class":717},[570,3364,1724],{"class":751},[570,3366,755],{"class":744},[570,3368,967],{"class":717},[570,3370,1731],{"class":587},[570,3372,967],{"class":717},[570,3374,773],{"class":744},[570,3376,1205],{"class":717},[570,3378,872],{"class":717},[570,3380,3381,3383,3385,3387,3389,3391,3393],{"class":572,"line":648},[570,3382,961],{"class":717},[570,3384,964],{"class":723},[570,3386,967],{"class":717},[570,3388,727],{"class":717},[570,3390,972],{"class":717},[570,3392,975],{"class":587},[570,3394,999],{"class":717},[570,3396,3397],{"class":572,"line":654},[570,3398,1228],{"class":717},[570,3400,3401,3403,3405,3407,3409,3411,3413],{"class":572,"line":666},[570,3402,1233],{"class":723},[570,3404,727],{"class":717},[570,3406,1238],{"class":744},[570,3408,748],{"class":717},[570,3410,933],{"class":751},[570,3412,755],{"class":723},[570,3414,1342],{"class":717},[570,3416,3417,3420,3422,3424,3427,3429],{"class":572,"line":671},[570,3418,3419],{"class":723},"      model",[570,3421,727],{"class":717},[570,3423,972],{"class":717},[570,3425,3426],{"class":587},"gpt-4o",[570,3428,967],{"class":717},[570,3430,872],{"class":717},[570,3432,3433,3436,3438,3441,3443,3446,3448,3450,3453,3455,3457,3460,3462,3464,3466,3469],{"class":572,"line":677},[570,3434,3435],{"class":723},"      messages",[570,3437,727],{"class":717},[570,3439,3440],{"class":723}," [",[570,3442,1247],{"class":717},[570,3444,3445],{"class":723}," role",[570,3447,727],{"class":717},[570,3449,972],{"class":717},[570,3451,3452],{"class":587},"user",[570,3454,967],{"class":717},[570,3456,943],{"class":717},[570,3458,3459],{"class":723}," content",[570,3461,727],{"class":717},[570,3463,3267],{"class":744},[570,3465,797],{"class":717},[570,3467,3468],{"class":723},"]",[570,3470,872],{"class":717},[570,3472,3473,3476,3478,3480,3483],{"class":572,"line":898},[570,3474,3475],{"class":723},"      max_tokens",[570,3477,727],{"class":717},[570,3479,3272],{"class":744},[570,3481,3482],{"class":717}," ||",[570,3484,3485],{"class":2955}," 500\n",[570,3487,3488,3490],{"class":572,"line":904},[570,3489,1440],{"class":717},[570,3491,1014],{"class":723},[570,3493,3494,3496],{"class":572,"line":909},[570,3495,1011],{"class":717},[570,3497,1014],{"class":723},[570,3499,3500],{"class":572,"line":915},[570,3501,601],{"emptyLinePlaceholder":600},[570,3503,3504,3506,3508,3510,3512,3514,3516,3518],{"class":572,"line":948},[570,3505,788],{"class":710},[570,3507,837],{"class":744},[570,3509,800],{"class":717},[570,3511,804],{"class":803},[570,3513,3297],{"class":744},[570,3515,748],{"class":717},[570,3517,812],{"class":751},[570,3519,895],{"class":723},[570,3521,3522,3524,3526,3528,3530,3532,3534,3536,3538,3540,3542,3544],{"class":572,"line":958},[570,3523,918],{"class":803},[570,3525,591],{"class":717},[570,3527,923],{"class":751},[570,3529,755],{"class":723},[570,3531,928],{"class":744},[570,3533,748],{"class":717},[570,3535,933],{"class":751},[570,3537,755],{"class":723},[570,3539,938],{"class":744},[570,3541,773],{"class":723},[570,3543,943],{"class":717},[570,3545,718],{"class":717},[570,3547,3548,3550,3552],{"class":572,"line":982},[570,3549,951],{"class":723},[570,3551,727],{"class":717},[570,3553,718],{"class":717},[570,3555,3556,3558,3560,3562,3564,3566,3568,3570],{"class":572,"line":1002},[570,3557,961],{"class":717},[570,3559,964],{"class":723},[570,3561,967],{"class":717},[570,3563,727],{"class":717},[570,3565,972],{"class":717},[570,3567,975],{"class":587},[570,3569,967],{"class":717},[570,3571,872],{"class":717},[570,3573,3574,3576,3578,3580,3582,3584,3586],{"class":572,"line":1008},[570,3575,961],{"class":717},[570,3577,987],{"class":723},[570,3579,967],{"class":717},[570,3581,727],{"class":717},[570,3583,972],{"class":717},[570,3585,996],{"class":587},[570,3587,999],{"class":717},[570,3589,3590],{"class":572,"line":1017},[570,3591,1005],{"class":717},[570,3593,3594,3596],{"class":572,"line":2229},[570,3595,1011],{"class":717},[570,3597,1014],{"class":723},[570,3599,3600,3602],{"class":572,"line":2240},[570,3601,863],{"class":717},[570,3603,1014],{"class":744},[2430,3605,3607],{"id":3606},"_3-transactional-email-với-resend","3. Transactional Email với Resend",[561,3609,3611],{"className":692,"code":3610,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fsend-email\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\n\nconst RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')!\n\nDeno.serve(async (req: Request) => {\n  const { to, subject, html } = await req.json()\n\n  const response = await fetch('https:\u002F\u002Fapi.resend.com\u002Femails', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${RESEND_API_KEY}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({\n      from: 'My App \u003Cnoreply@myapp.com>',\n      to: [to],\n      subject: subject,\n      html: html\n    })\n  })\n\n  const data = await response.json()\n  return new Response(JSON.stringify(data), {\n    headers: { 'Content-Type': 'application\u002Fjson' }\n  })\n})\n",[458,3612,3613,3618,3636,3640,3672,3676,3702,3735,3739,3764,3778,3786,3808,3824,3828,3844,3860,3876,3887,3897,3903,3909,3913,3931,3957,3981,3987],{"__ignoreMap":566},[570,3614,3615],{"class":572,"line":573},[570,3616,3617],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fsend-email\u002Findex.ts\n",[570,3619,3620,3622,3624,3626,3628,3630,3632,3634],{"class":572,"line":580},[570,3621,1851],{"class":803},[570,3623,791],{"class":717},[570,3625,1856],{"class":744},[570,3627,797],{"class":717},[570,3629,1861],{"class":803},[570,3631,972],{"class":717},[570,3633,1866],{"class":587},[570,3635,999],{"class":717},[570,3637,3638],{"class":572,"line":597},[570,3639,601],{"emptyLinePlaceholder":600},[570,3641,3642,3644,3647,3649,3651,3653,3655,3657,3659,3661,3663,3666,3668,3670],{"class":572,"line":604},[570,3643,1043],{"class":710},[570,3645,3646],{"class":744}," RESEND_API_KEY ",[570,3648,1132],{"class":717},[570,3650,1714],{"class":744},[570,3652,748],{"class":717},[570,3654,1719],{"class":744},[570,3656,748],{"class":717},[570,3658,1724],{"class":751},[570,3660,755],{"class":744},[570,3662,967],{"class":717},[570,3664,3665],{"class":587},"RESEND_API_KEY",[570,3667,967],{"class":717},[570,3669,773],{"class":744},[570,3671,1738],{"class":717},[570,3673,3674],{"class":572,"line":610},[570,3675,601],{"emptyLinePlaceholder":600},[570,3677,3678,3680,3682,3684,3686,3688,3690,3692,3694,3696,3698,3700],{"class":572,"line":620},[570,3679,745],{"class":744},[570,3681,748],{"class":717},[570,3683,752],{"class":751},[570,3685,755],{"class":744},[570,3687,758],{"class":710},[570,3689,761],{"class":717},[570,3691,765],{"class":764},[570,3693,727],{"class":717},[570,3695,770],{"class":583},[570,3697,773],{"class":717},[570,3699,776],{"class":710},[570,3701,718],{"class":717},[570,3703,3704,3706,3708,3711,3713,3716,3718,3721,3723,3725,3727,3729,3731,3733],{"class":572,"line":625},[570,3705,788],{"class":710},[570,3707,791],{"class":717},[570,3709,3710],{"class":744}," to",[570,3712,943],{"class":717},[570,3714,3715],{"class":744}," subject",[570,3717,943],{"class":717},[570,3719,3720],{"class":744}," html",[570,3722,797],{"class":717},[570,3724,800],{"class":717},[570,3726,804],{"class":803},[570,3728,807],{"class":744},[570,3730,748],{"class":717},[570,3732,812],{"class":751},[570,3734,895],{"class":723},[570,3736,3737],{"class":572,"line":631},[570,3738,601],{"emptyLinePlaceholder":600},[570,3740,3741,3743,3745,3747,3749,3751,3753,3755,3758,3760,3762],{"class":572,"line":643},[570,3742,788],{"class":710},[570,3744,3297],{"class":744},[570,3746,800],{"class":717},[570,3748,804],{"class":803},[570,3750,1137],{"class":751},[570,3752,755],{"class":723},[570,3754,967],{"class":717},[570,3756,3757],{"class":587},"https:\u002F\u002Fapi.resend.com\u002Femails",[570,3759,967],{"class":717},[570,3761,943],{"class":717},[570,3763,718],{"class":717},[570,3765,3766,3768,3770,3772,3774,3776],{"class":572,"line":648},[570,3767,1162],{"class":723},[570,3769,727],{"class":717},[570,3771,972],{"class":717},[570,3773,1169],{"class":587},[570,3775,967],{"class":717},[570,3777,872],{"class":717},[570,3779,3780,3782,3784],{"class":572,"line":654},[570,3781,951],{"class":723},[570,3783,727],{"class":717},[570,3785,718],{"class":717},[570,3787,3788,3790,3792,3794,3796,3798,3800,3802,3804,3806],{"class":572,"line":666},[570,3789,961],{"class":717},[570,3791,1188],{"class":723},[570,3793,967],{"class":717},[570,3795,727],{"class":717},[570,3797,851],{"class":717},[570,3799,1197],{"class":587},[570,3801,857],{"class":717},[570,3803,3665],{"class":744},[570,3805,1205],{"class":717},[570,3807,872],{"class":717},[570,3809,3810,3812,3814,3816,3818,3820,3822],{"class":572,"line":671},[570,3811,961],{"class":717},[570,3813,964],{"class":723},[570,3815,967],{"class":717},[570,3817,727],{"class":717},[570,3819,972],{"class":717},[570,3821,975],{"class":587},[570,3823,999],{"class":717},[570,3825,3826],{"class":572,"line":677},[570,3827,1228],{"class":717},[570,3829,3830,3832,3834,3836,3838,3840,3842],{"class":572,"line":898},[570,3831,1233],{"class":723},[570,3833,727],{"class":717},[570,3835,1238],{"class":744},[570,3837,748],{"class":717},[570,3839,933],{"class":751},[570,3841,755],{"class":723},[570,3843,1342],{"class":717},[570,3845,3846,3849,3851,3853,3856,3858],{"class":572,"line":904},[570,3847,3848],{"class":723},"      from",[570,3850,727],{"class":717},[570,3852,972],{"class":717},[570,3854,3855],{"class":587},"My App \u003Cnoreply@myapp.com>",[570,3857,967],{"class":717},[570,3859,872],{"class":717},[570,3861,3862,3865,3867,3869,3872,3874],{"class":572,"line":909},[570,3863,3864],{"class":723},"      to",[570,3866,727],{"class":717},[570,3868,3440],{"class":723},[570,3870,3871],{"class":744},"to",[570,3873,3468],{"class":723},[570,3875,872],{"class":717},[570,3877,3878,3881,3883,3885],{"class":572,"line":915},[570,3879,3880],{"class":723},"      subject",[570,3882,727],{"class":717},[570,3884,3715],{"class":744},[570,3886,872],{"class":717},[570,3888,3889,3892,3894],{"class":572,"line":948},[570,3890,3891],{"class":723},"      html",[570,3893,727],{"class":717},[570,3895,3896],{"class":744}," html\n",[570,3898,3899,3901],{"class":572,"line":958},[570,3900,1440],{"class":717},[570,3902,1014],{"class":723},[570,3904,3905,3907],{"class":572,"line":982},[570,3906,1011],{"class":717},[570,3908,1014],{"class":723},[570,3910,3911],{"class":572,"line":1002},[570,3912,601],{"emptyLinePlaceholder":600},[570,3914,3915,3917,3919,3921,3923,3925,3927,3929],{"class":572,"line":1008},[570,3916,788],{"class":710},[570,3918,837],{"class":744},[570,3920,800],{"class":717},[570,3922,804],{"class":803},[570,3924,3297],{"class":744},[570,3926,748],{"class":717},[570,3928,812],{"class":751},[570,3930,895],{"class":723},[570,3932,3933,3935,3937,3939,3941,3943,3945,3947,3949,3951,3953,3955],{"class":572,"line":1017},[570,3934,918],{"class":803},[570,3936,591],{"class":717},[570,3938,923],{"class":751},[570,3940,755],{"class":723},[570,3942,928],{"class":744},[570,3944,748],{"class":717},[570,3946,933],{"class":751},[570,3948,755],{"class":723},[570,3950,938],{"class":744},[570,3952,773],{"class":723},[570,3954,943],{"class":717},[570,3956,718],{"class":717},[570,3958,3959,3961,3963,3965,3967,3969,3971,3973,3975,3977,3979],{"class":572,"line":2229},[570,3960,951],{"class":723},[570,3962,727],{"class":717},[570,3964,791],{"class":717},[570,3966,972],{"class":717},[570,3968,964],{"class":723},[570,3970,967],{"class":717},[570,3972,727],{"class":717},[570,3974,972],{"class":717},[570,3976,975],{"class":587},[570,3978,967],{"class":717},[570,3980,1107],{"class":717},[570,3982,3983,3985],{"class":572,"line":2240},[570,3984,1011],{"class":717},[570,3986,1014],{"class":723},[570,3988,3989,3991],{"class":572,"line":2247},[570,3990,863],{"class":717},[570,3992,1014],{"class":744},[2430,3994,3996],{"id":3995},"_4-image-processing-pipeline","4. Image Processing Pipeline",[561,3998,4000],{"className":692,"code":3999,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fprocess-image\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\n\nDeno.serve(async (req: Request) => {\n  const { imagePath, operations } = await req.json()\n\n  const supabase = createClient(\n    Deno.env.get('SUPABASE_URL')!,\n    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n  )\n\n  \u002F\u002F Download ảnh gốc\n  const { data: imageBlob } = await supabase.storage\n    .from('uploads')\n    .download(imagePath)\n\n  \u002F\u002F Xử lý ảnh (ví dụ: thêm watermark, resize, compress)\n  \u002F\u002F ... processing logic ...\n\n  \u002F\u002F Upload ảnh đã xử lý\n  await supabase.storage\n    .from('processed')\n    .upload(imagePath, processedBlob, { upsert: true })\n\n  return new Response(JSON.stringify({ success: true }), {\n    headers: { 'Content-Type': 'application\u002Fjson' }\n  })\n})\n",[458,4001,4002,4007,4025,4029,4055,4083,4087,4099,4123,4147,4151,4155,4160,4185,4202,4216,4220,4225,4230,4234,4239,4250,4267,4297,4301,4335,4359,4365],{"__ignoreMap":566},[570,4003,4004],{"class":572,"line":573},[570,4005,4006],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fprocess-image\u002Findex.ts\n",[570,4008,4009,4011,4013,4015,4017,4019,4021,4023],{"class":572,"line":580},[570,4010,1851],{"class":803},[570,4012,791],{"class":717},[570,4014,1856],{"class":744},[570,4016,797],{"class":717},[570,4018,1861],{"class":803},[570,4020,972],{"class":717},[570,4022,1866],{"class":587},[570,4024,999],{"class":717},[570,4026,4027],{"class":572,"line":597},[570,4028,601],{"emptyLinePlaceholder":600},[570,4030,4031,4033,4035,4037,4039,4041,4043,4045,4047,4049,4051,4053],{"class":572,"line":604},[570,4032,745],{"class":744},[570,4034,748],{"class":717},[570,4036,752],{"class":751},[570,4038,755],{"class":744},[570,4040,758],{"class":710},[570,4042,761],{"class":717},[570,4044,765],{"class":764},[570,4046,727],{"class":717},[570,4048,770],{"class":583},[570,4050,773],{"class":717},[570,4052,776],{"class":710},[570,4054,718],{"class":717},[570,4056,4057,4059,4061,4064,4066,4069,4071,4073,4075,4077,4079,4081],{"class":572,"line":610},[570,4058,788],{"class":710},[570,4060,791],{"class":717},[570,4062,4063],{"class":744}," imagePath",[570,4065,943],{"class":717},[570,4067,4068],{"class":744}," operations",[570,4070,797],{"class":717},[570,4072,800],{"class":717},[570,4074,804],{"class":803},[570,4076,807],{"class":744},[570,4078,748],{"class":717},[570,4080,812],{"class":751},[570,4082,895],{"class":723},[570,4084,4085],{"class":572,"line":620},[570,4086,601],{"emptyLinePlaceholder":600},[570,4088,4089,4091,4093,4095,4097],{"class":572,"line":625},[570,4090,788],{"class":710},[570,4092,1061],{"class":744},[570,4094,800],{"class":717},[570,4096,1856],{"class":751},[570,4098,1140],{"class":723},[570,4100,4101,4103,4105,4107,4109,4111,4113,4115,4117,4119,4121],{"class":572,"line":631},[570,4102,1920],{"class":744},[570,4104,748],{"class":717},[570,4106,1719],{"class":744},[570,4108,748],{"class":717},[570,4110,1724],{"class":751},[570,4112,755],{"class":723},[570,4114,967],{"class":717},[570,4116,1796],{"class":587},[570,4118,967],{"class":717},[570,4120,773],{"class":723},[570,4122,1941],{"class":717},[570,4124,4125,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145],{"class":572,"line":643},[570,4126,1920],{"class":744},[570,4128,748],{"class":717},[570,4130,1719],{"class":744},[570,4132,748],{"class":717},[570,4134,1724],{"class":751},[570,4136,755],{"class":723},[570,4138,967],{"class":717},[570,4140,1828],{"class":587},[570,4142,967],{"class":717},[570,4144,773],{"class":723},[570,4146,1738],{"class":717},[570,4148,4149],{"class":572,"line":648},[570,4150,1970],{"class":723},[570,4152,4153],{"class":572,"line":654},[570,4154,601],{"emptyLinePlaceholder":600},[570,4156,4157],{"class":572,"line":666},[570,4158,4159],{"class":576},"  \u002F\u002F Download ảnh gốc\n",[570,4161,4162,4164,4166,4168,4170,4173,4175,4177,4179,4181,4183],{"class":572,"line":671},[570,4163,788],{"class":710},[570,4165,791],{"class":717},[570,4167,837],{"class":723},[570,4169,727],{"class":717},[570,4171,4172],{"class":744}," imageBlob",[570,4174,797],{"class":717},[570,4176,800],{"class":717},[570,4178,804],{"class":803},[570,4180,1061],{"class":744},[570,4182,748],{"class":717},[570,4184,2105],{"class":744},[570,4186,4187,4189,4191,4193,4195,4198,4200],{"class":572,"line":677},[570,4188,2006],{"class":717},[570,4190,2009],{"class":751},[570,4192,755],{"class":723},[570,4194,967],{"class":717},[570,4196,4197],{"class":587},"uploads",[570,4199,967],{"class":717},[570,4201,1014],{"class":723},[570,4203,4204,4206,4209,4211,4214],{"class":572,"line":898},[570,4205,2006],{"class":717},[570,4207,4208],{"class":751},"download",[570,4210,755],{"class":723},[570,4212,4213],{"class":744},"imagePath",[570,4215,1014],{"class":723},[570,4217,4218],{"class":572,"line":904},[570,4219,601],{"emptyLinePlaceholder":600},[570,4221,4222],{"class":572,"line":909},[570,4223,4224],{"class":576},"  \u002F\u002F Xử lý ảnh (ví dụ: thêm watermark, resize, compress)\n",[570,4226,4227],{"class":572,"line":915},[570,4228,4229],{"class":576},"  \u002F\u002F ... processing logic ...\n",[570,4231,4232],{"class":572,"line":948},[570,4233,601],{"emptyLinePlaceholder":600},[570,4235,4236],{"class":572,"line":958},[570,4237,4238],{"class":576},"  \u002F\u002F Upload ảnh đã xử lý\n",[570,4240,4241,4244,4246,4248],{"class":572,"line":982},[570,4242,4243],{"class":803},"  await",[570,4245,1061],{"class":744},[570,4247,748],{"class":717},[570,4249,2105],{"class":744},[570,4251,4252,4254,4256,4258,4260,4263,4265],{"class":572,"line":1002},[570,4253,2006],{"class":717},[570,4255,2009],{"class":751},[570,4257,755],{"class":723},[570,4259,967],{"class":717},[570,4261,4262],{"class":587},"processed",[570,4264,967],{"class":717},[570,4266,1014],{"class":723},[570,4268,4269,4271,4273,4275,4277,4279,4282,4284,4286,4289,4291,4293,4295],{"class":572,"line":1008},[570,4270,2006],{"class":717},[570,4272,2129],{"class":751},[570,4274,755],{"class":723},[570,4276,4213],{"class":744},[570,4278,943],{"class":717},[570,4280,4281],{"class":744}," processedBlob",[570,4283,943],{"class":717},[570,4285,791],{"class":717},[570,4287,4288],{"class":723}," upsert",[570,4290,727],{"class":717},[570,4292,1506],{"class":1505},[570,4294,797],{"class":717},[570,4296,1014],{"class":723},[570,4298,4299],{"class":572,"line":1017},[570,4300,601],{"emptyLinePlaceholder":600},[570,4302,4303,4305,4307,4309,4311,4313,4315,4317,4319,4321,4323,4325,4327,4329,4331,4333],{"class":572,"line":2229},[570,4304,918],{"class":803},[570,4306,591],{"class":717},[570,4308,923],{"class":751},[570,4310,755],{"class":723},[570,4312,928],{"class":744},[570,4314,748],{"class":717},[570,4316,933],{"class":751},[570,4318,755],{"class":723},[570,4320,1247],{"class":717},[570,4322,1500],{"class":723},[570,4324,727],{"class":717},[570,4326,1506],{"class":1505},[570,4328,797],{"class":717},[570,4330,773],{"class":723},[570,4332,943],{"class":717},[570,4334,718],{"class":717},[570,4336,4337,4339,4341,4343,4345,4347,4349,4351,4353,4355,4357],{"class":572,"line":2240},[570,4338,951],{"class":723},[570,4340,727],{"class":717},[570,4342,791],{"class":717},[570,4344,972],{"class":717},[570,4346,964],{"class":723},[570,4348,967],{"class":717},[570,4350,727],{"class":717},[570,4352,972],{"class":717},[570,4354,975],{"class":587},[570,4356,967],{"class":717},[570,4358,1107],{"class":717},[570,4360,4361,4363],{"class":572,"line":2247},[570,4362,1011],{"class":717},[570,4364,1014],{"class":723},[570,4366,4367,4369],{"class":572,"line":2252},[570,4368,863],{"class":717},[570,4370,1014],{"class":744},[432,4372,4374],{"id":4373},"background-tasks-cron","Background Tasks & Cron",[436,4376,4377,4378,4381],{},"Edge Functions phù hợp cho tác vụ ngắn (vài giây đến vài phút). Với tác vụ dài hạn, kết hợp với ",[440,4379,4380],{},"Cron"," để chạy định kỳ:",[561,4383,4387],{"className":4384,"code":4385,"language":4386,"meta":566,"style":566},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Gọi Edge Function mỗi giờ để đồng bộ dữ liệu\nSELECT cron.schedule(\n  'hourly-sync',\n  '0 * * * *',\n  $$\n  SELECT net.http_post(\n    url := 'https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fsync-data',\n    headers := '{\"Authorization\": \"Bearer [SERVICE_ROLE_KEY]\"}'::jsonb,\n    body := '{\"source\": \"external-api\"}'::jsonb\n  )\n  $$\n);\n\n-- Dọn dẹp dữ liệu cũ mỗi ngày lúc 3:00 AM\nSELECT cron.schedule(\n  'daily-cleanup',\n  '0 3 * * *',\n  $$\n  SELECT net.http_post(\n    url := 'https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fcleanup',\n    headers := '{\"Authorization\": \"Bearer [SERVICE_ROLE_KEY]\"}'::jsonb\n  )\n  $$\n);\n\n-- Xem danh sách cron jobs\nSELECT * FROM cron.job;\n\n-- Hủy cron job\nSELECT cron.unschedule('hourly-sync');\n","sql",[458,4388,4389,4394,4399,4404,4409,4414,4419,4424,4429,4434,4438,4442,4447,4451,4456,4460,4465,4470,4474,4478,4483,4488,4492,4496,4500,4504,4509,4514,4518,4523],{"__ignoreMap":566},[570,4390,4391],{"class":572,"line":573},[570,4392,4393],{},"-- Gọi Edge Function mỗi giờ để đồng bộ dữ liệu\n",[570,4395,4396],{"class":572,"line":580},[570,4397,4398],{},"SELECT cron.schedule(\n",[570,4400,4401],{"class":572,"line":597},[570,4402,4403],{},"  'hourly-sync',\n",[570,4405,4406],{"class":572,"line":604},[570,4407,4408],{},"  '0 * * * *',\n",[570,4410,4411],{"class":572,"line":610},[570,4412,4413],{},"  $$\n",[570,4415,4416],{"class":572,"line":620},[570,4417,4418],{},"  SELECT net.http_post(\n",[570,4420,4421],{"class":572,"line":625},[570,4422,4423],{},"    url := 'https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fsync-data',\n",[570,4425,4426],{"class":572,"line":631},[570,4427,4428],{},"    headers := '{\"Authorization\": \"Bearer [SERVICE_ROLE_KEY]\"}'::jsonb,\n",[570,4430,4431],{"class":572,"line":643},[570,4432,4433],{},"    body := '{\"source\": \"external-api\"}'::jsonb\n",[570,4435,4436],{"class":572,"line":648},[570,4437,1970],{},[570,4439,4440],{"class":572,"line":654},[570,4441,4413],{},[570,4443,4444],{"class":572,"line":666},[570,4445,4446],{},");\n",[570,4448,4449],{"class":572,"line":671},[570,4450,601],{"emptyLinePlaceholder":600},[570,4452,4453],{"class":572,"line":677},[570,4454,4455],{},"-- Dọn dẹp dữ liệu cũ mỗi ngày lúc 3:00 AM\n",[570,4457,4458],{"class":572,"line":898},[570,4459,4398],{},[570,4461,4462],{"class":572,"line":904},[570,4463,4464],{},"  'daily-cleanup',\n",[570,4466,4467],{"class":572,"line":909},[570,4468,4469],{},"  '0 3 * * *',\n",[570,4471,4472],{"class":572,"line":915},[570,4473,4413],{},[570,4475,4476],{"class":572,"line":948},[570,4477,4418],{},[570,4479,4480],{"class":572,"line":958},[570,4481,4482],{},"    url := 'https:\u002F\u002F[PROJECT_REF].supabase.co\u002Ffunctions\u002Fv1\u002Fcleanup',\n",[570,4484,4485],{"class":572,"line":982},[570,4486,4487],{},"    headers := '{\"Authorization\": \"Bearer [SERVICE_ROLE_KEY]\"}'::jsonb\n",[570,4489,4490],{"class":572,"line":1002},[570,4491,1970],{},[570,4493,4494],{"class":572,"line":1008},[570,4495,4413],{},[570,4497,4498],{"class":572,"line":1017},[570,4499,4446],{},[570,4501,4502],{"class":572,"line":2229},[570,4503,601],{"emptyLinePlaceholder":600},[570,4505,4506],{"class":572,"line":2240},[570,4507,4508],{},"-- Xem danh sách cron jobs\n",[570,4510,4511],{"class":572,"line":2247},[570,4512,4513],{},"SELECT * FROM cron.job;\n",[570,4515,4516],{"class":572,"line":2252},[570,4517,601],{"emptyLinePlaceholder":600},[570,4519,4520],{"class":572,"line":2287},[570,4521,4522],{},"-- Hủy cron job\n",[570,4524,4525],{"class":572,"line":2312},[570,4526,4527],{},"SELECT cron.unschedule('hourly-sync');\n",[4529,4530],"hr",{},[427,4532,4534],{"id":4533},"cron-queues-lập-lịch-và-hàng-đợi","Cron & Queues — Lập lịch và Hàng đợi",[432,4536,4538],{"id":4537},"pg_cron-scheduled-jobs-trong-postgres","pg_cron — Scheduled Jobs trong Postgres",[436,4540,4541,4544],{},[458,4542,4543],{},"pg_cron"," cho phép lập lịch thực thi SQL hoặc gọi HTTP request định kỳ ngay trong database:",[561,4546,4548],{"className":4384,"code":4547,"language":4386,"meta":566,"style":566},"-- Enable extension\nCREATE EXTENSION IF NOT EXISTS pg_cron;\n\n-- Tạo job chạy SQL mỗi phút\nSELECT cron.schedule(\n  'cleanup-temp-tables',\n  '* * * * *',\n  'DELETE FROM temp_data WHERE created_at \u003C now() - interval ''1 hour'''\n);\n\n-- Tạo job chạy vào 2:00 AM mỗi Chủ Nhật\nSELECT cron.schedule(\n  'weekly-report',\n  '0 2 * * 0',\n  $$\n  SELECT net.http_post(\n    url := 'https:\u002F\u002F[REF].supabase.co\u002Ffunctions\u002Fv1\u002Fgenerate-report',\n    headers := '{\"Authorization\": \"Bearer [KEY]\"}'::jsonb\n  )\n  $$\n);\n",[458,4549,4550,4555,4560,4564,4569,4573,4578,4583,4588,4592,4596,4601,4605,4610,4615,4619,4623,4628,4633,4637,4641],{"__ignoreMap":566},[570,4551,4552],{"class":572,"line":573},[570,4553,4554],{},"-- Enable extension\n",[570,4556,4557],{"class":572,"line":580},[570,4558,4559],{},"CREATE EXTENSION IF NOT EXISTS pg_cron;\n",[570,4561,4562],{"class":572,"line":597},[570,4563,601],{"emptyLinePlaceholder":600},[570,4565,4566],{"class":572,"line":604},[570,4567,4568],{},"-- Tạo job chạy SQL mỗi phút\n",[570,4570,4571],{"class":572,"line":610},[570,4572,4398],{},[570,4574,4575],{"class":572,"line":620},[570,4576,4577],{},"  'cleanup-temp-tables',\n",[570,4579,4580],{"class":572,"line":625},[570,4581,4582],{},"  '* * * * *',\n",[570,4584,4585],{"class":572,"line":631},[570,4586,4587],{},"  'DELETE FROM temp_data WHERE created_at \u003C now() - interval ''1 hour'''\n",[570,4589,4590],{"class":572,"line":643},[570,4591,4446],{},[570,4593,4594],{"class":572,"line":648},[570,4595,601],{"emptyLinePlaceholder":600},[570,4597,4598],{"class":572,"line":654},[570,4599,4600],{},"-- Tạo job chạy vào 2:00 AM mỗi Chủ Nhật\n",[570,4602,4603],{"class":572,"line":666},[570,4604,4398],{},[570,4606,4607],{"class":572,"line":671},[570,4608,4609],{},"  'weekly-report',\n",[570,4611,4612],{"class":572,"line":677},[570,4613,4614],{},"  '0 2 * * 0',\n",[570,4616,4617],{"class":572,"line":898},[570,4618,4413],{},[570,4620,4621],{"class":572,"line":904},[570,4622,4418],{},[570,4624,4625],{"class":572,"line":909},[570,4626,4627],{},"    url := 'https:\u002F\u002F[REF].supabase.co\u002Ffunctions\u002Fv1\u002Fgenerate-report',\n",[570,4629,4630],{"class":572,"line":915},[570,4631,4632],{},"    headers := '{\"Authorization\": \"Bearer [KEY]\"}'::jsonb\n",[570,4634,4635],{"class":572,"line":948},[570,4636,1970],{},[570,4638,4639],{"class":572,"line":958},[570,4640,4413],{},[570,4642,4643],{"class":572,"line":982},[570,4644,4446],{},[436,4646,4647,4650],{},[440,4648,4649],{},"Giới hạn:"," Khuyến nghị không quá 8 jobs chạy đồng thời, mỗi job không quá 10 phút.",[432,4652,4654],{"id":4653},"pgmq-message-queue-trong-postgres","pgmq — Message Queue trong Postgres",[436,4656,4657,4660],{},[458,4658,4659],{},"pgmq"," cung cấp message queue bền vững (persistent) ngay trong Postgres, không cần Redis hay RabbitMQ:",[561,4662,4664],{"className":4384,"code":4663,"language":4386,"meta":566,"style":566},"-- Enable extension và tạo queue\nCREATE EXTENSION IF NOT EXISTS pgmq;\nSELECT pgmq.create('email_queue');\n\n-- Gửi message vào queue\nSELECT pgmq.send('email_queue', '{\n  \"to\": \"user@example.com\",\n  \"subject\": \"Welcome!\",\n  \"template\": \"welcome_email\"\n}');\n\n-- Consumer: đọc messages (visibility timeout 60 giây)\nSELECT msg_id, read_ct, enqueued_at, vt, message\nFROM pgmq.read('email_queue', 10, 60);\n\n-- Xóa message sau khi xử lý thành công\nSELECT pgmq.delete('email_queue', 42); -- msg_id = 42\n\n-- Archive message (lưu lại để debug)\nSELECT pgmq.archive('email_queue', 42);\n\n-- Xóa queue\nSELECT pgmq.drop_queue('email_queue');\n",[458,4665,4666,4671,4676,4681,4685,4690,4695,4700,4705,4710,4715,4719,4724,4729,4734,4738,4743,4748,4752,4757,4762,4766,4771],{"__ignoreMap":566},[570,4667,4668],{"class":572,"line":573},[570,4669,4670],{},"-- Enable extension và tạo queue\n",[570,4672,4673],{"class":572,"line":580},[570,4674,4675],{},"CREATE EXTENSION IF NOT EXISTS pgmq;\n",[570,4677,4678],{"class":572,"line":597},[570,4679,4680],{},"SELECT pgmq.create('email_queue');\n",[570,4682,4683],{"class":572,"line":604},[570,4684,601],{"emptyLinePlaceholder":600},[570,4686,4687],{"class":572,"line":610},[570,4688,4689],{},"-- Gửi message vào queue\n",[570,4691,4692],{"class":572,"line":620},[570,4693,4694],{},"SELECT pgmq.send('email_queue', '{\n",[570,4696,4697],{"class":572,"line":625},[570,4698,4699],{},"  \"to\": \"user@example.com\",\n",[570,4701,4702],{"class":572,"line":631},[570,4703,4704],{},"  \"subject\": \"Welcome!\",\n",[570,4706,4707],{"class":572,"line":643},[570,4708,4709],{},"  \"template\": \"welcome_email\"\n",[570,4711,4712],{"class":572,"line":648},[570,4713,4714],{},"}');\n",[570,4716,4717],{"class":572,"line":654},[570,4718,601],{"emptyLinePlaceholder":600},[570,4720,4721],{"class":572,"line":666},[570,4722,4723],{},"-- Consumer: đọc messages (visibility timeout 60 giây)\n",[570,4725,4726],{"class":572,"line":671},[570,4727,4728],{},"SELECT msg_id, read_ct, enqueued_at, vt, message\n",[570,4730,4731],{"class":572,"line":677},[570,4732,4733],{},"FROM pgmq.read('email_queue', 10, 60);\n",[570,4735,4736],{"class":572,"line":898},[570,4737,601],{"emptyLinePlaceholder":600},[570,4739,4740],{"class":572,"line":904},[570,4741,4742],{},"-- Xóa message sau khi xử lý thành công\n",[570,4744,4745],{"class":572,"line":909},[570,4746,4747],{},"SELECT pgmq.delete('email_queue', 42); -- msg_id = 42\n",[570,4749,4750],{"class":572,"line":915},[570,4751,601],{"emptyLinePlaceholder":600},[570,4753,4754],{"class":572,"line":948},[570,4755,4756],{},"-- Archive message (lưu lại để debug)\n",[570,4758,4759],{"class":572,"line":958},[570,4760,4761],{},"SELECT pgmq.archive('email_queue', 42);\n",[570,4763,4764],{"class":572,"line":982},[570,4765,601],{"emptyLinePlaceholder":600},[570,4767,4768],{"class":572,"line":1002},[570,4769,4770],{},"-- Xóa queue\n",[570,4772,4773],{"class":572,"line":1008},[570,4774,4775],{},"SELECT pgmq.drop_queue('email_queue');\n",[436,4777,4778],{},[440,4779,4780],{},"Kiến trúc bất đồng bộ với Edge Functions + Queue:",[561,4782,4786],{"className":4783,"code":4785,"language":2667},[4784],"language-text","Client gửi request -> Edge Function nhận và gửi message vào queue\n                              |\n                     Cron job đọc queue mỗi phút -> xử lý từng message\n                              |\n                     Kết quả lưu vào database -> Client poll hoặc subscribe\n",[458,4787,4785],{"__ignoreMap":566},[4529,4789],{},[427,4791,4793],{"id":4792},"ai-vectors-ứng-dụng-trí-tuệ-nhân-tạo","AI & Vectors — Ứng dụng Trí tuệ Nhân tạo",[432,4795,11],{"id":4796},"tổng-quan-1",[436,4798,4799,4800,4803],{},"Supabase cung cấp ",[440,4801,4802],{},"open source toolkit"," để phát triển ứng dụng AI trên Postgres và pgvector. Triết lý: \"Vector database tốt nhất là database bạn đã có sẵn.\"",[449,4805,4806,4809],{"color":451,"icon":452},[440,4807,4808],{},"pgvector là lý do chính nhiều team chọn Supabase cho AI apps."," Bạn không cần tách biệt vector DB -- embeddings được lưu trong cùng PostgreSQL với data gốc. RLS áp dụng cho cả vector search, đảm bảo bảo mật. Hybrid search (semantic + keyword) thường cho kết quả tốt hơn chỉ dùng một loại.",[432,4811,4813],{"id":4812},"pgvector-thiết-lập-và-index","pgvector — Thiết lập và Index",[561,4815,4817],{"className":4384,"code":4816,"language":4386,"meta":566,"style":566},"-- Enable extension\nCREATE EXTENSION IF NOT EXISTS vector;\n\n-- Tạo bảng lưu documents với embedding\nCREATE TABLE documents (\n  id BIGSERIAL PRIMARY KEY,\n  title TEXT NOT NULL,\n  content TEXT NOT NULL,\n  embedding VECTOR(1536), -- 1536 dimensions (OpenAI text-embedding-3-small)\n  metadata JSONB DEFAULT '{}',\n  owner_id UUID REFERENCES auth.users(id),\n  created_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- HNSW index — nhanh nhưng tốn RAM (khuyên dùng cho production)\nCREATE INDEX ON documents\nUSING hnsw (embedding vector_cosine_ops)\nWITH (m = 16, ef_construction = 200);\n\n-- IVFFlat index — ít tốn RAM hơn, chậm hơn HNSW\n-- CREATE INDEX ON documents\n-- USING ivfflat (embedding vector_cosine_ops)\n-- WITH (lists = 100);\n",[458,4818,4819,4823,4828,4832,4837,4842,4847,4852,4857,4862,4867,4872,4877,4881,4885,4890,4895,4900,4905,4909,4914,4919,4924],{"__ignoreMap":566},[570,4820,4821],{"class":572,"line":573},[570,4822,4554],{},[570,4824,4825],{"class":572,"line":580},[570,4826,4827],{},"CREATE EXTENSION IF NOT EXISTS vector;\n",[570,4829,4830],{"class":572,"line":597},[570,4831,601],{"emptyLinePlaceholder":600},[570,4833,4834],{"class":572,"line":604},[570,4835,4836],{},"-- Tạo bảng lưu documents với embedding\n",[570,4838,4839],{"class":572,"line":610},[570,4840,4841],{},"CREATE TABLE documents (\n",[570,4843,4844],{"class":572,"line":620},[570,4845,4846],{},"  id BIGSERIAL PRIMARY KEY,\n",[570,4848,4849],{"class":572,"line":625},[570,4850,4851],{},"  title TEXT NOT NULL,\n",[570,4853,4854],{"class":572,"line":631},[570,4855,4856],{},"  content TEXT NOT NULL,\n",[570,4858,4859],{"class":572,"line":643},[570,4860,4861],{},"  embedding VECTOR(1536), -- 1536 dimensions (OpenAI text-embedding-3-small)\n",[570,4863,4864],{"class":572,"line":648},[570,4865,4866],{},"  metadata JSONB DEFAULT '{}',\n",[570,4868,4869],{"class":572,"line":654},[570,4870,4871],{},"  owner_id UUID REFERENCES auth.users(id),\n",[570,4873,4874],{"class":572,"line":666},[570,4875,4876],{},"  created_at TIMESTAMPTZ DEFAULT now()\n",[570,4878,4879],{"class":572,"line":671},[570,4880,4446],{},[570,4882,4883],{"class":572,"line":677},[570,4884,601],{"emptyLinePlaceholder":600},[570,4886,4887],{"class":572,"line":898},[570,4888,4889],{},"-- HNSW index — nhanh nhưng tốn RAM (khuyên dùng cho production)\n",[570,4891,4892],{"class":572,"line":904},[570,4893,4894],{},"CREATE INDEX ON documents\n",[570,4896,4897],{"class":572,"line":909},[570,4898,4899],{},"USING hnsw (embedding vector_cosine_ops)\n",[570,4901,4902],{"class":572,"line":915},[570,4903,4904],{},"WITH (m = 16, ef_construction = 200);\n",[570,4906,4907],{"class":572,"line":948},[570,4908,601],{"emptyLinePlaceholder":600},[570,4910,4911],{"class":572,"line":958},[570,4912,4913],{},"-- IVFFlat index — ít tốn RAM hơn, chậm hơn HNSW\n",[570,4915,4916],{"class":572,"line":982},[570,4917,4918],{},"-- CREATE INDEX ON documents\n",[570,4920,4921],{"class":572,"line":1002},[570,4922,4923],{},"-- USING ivfflat (embedding vector_cosine_ops)\n",[570,4925,4926],{"class":572,"line":1008},[570,4927,4928],{},"-- WITH (lists = 100);\n",[436,4930,4931],{},[440,4932,4933],{},"So sánh index types:",[471,4935,4936,4952],{},[474,4937,4938],{},[477,4939,4940,4943,4946,4949],{},[480,4941,4942],{},"Index",[480,4944,4945],{},"Tốc độ",[480,4947,4948],{},"RAM",[480,4950,4951],{},"Phù hợp",[487,4953,4954,4970],{},[477,4955,4956,4961,4964,4967],{},[492,4957,4958],{},[440,4959,4960],{},"HNSW",[492,4962,4963],{},"Rất nhanh",[492,4965,4966],{},"Cao",[492,4968,4969],{},"Production, dataset lớn",[477,4971,4972,4977,4980,4983],{},[492,4973,4974],{},[440,4975,4976],{},"IVFFlat",[492,4978,4979],{},"Khá nhanh",[492,4981,4982],{},"Thấp",[492,4984,4985],{},"Dataset vừa, tiết kiệm RAM",[432,4987,4989],{"id":4988},"_3-loại-tìm-kiếm","3 Loại Tìm kiếm",[436,4991,4992],{},"Supabase hỗ trợ 3 loại tìm kiếm, có thể dùng riêng hoặc kết hợp:",[471,4994,4995,5008],{},[474,4996,4997],{},[477,4998,4999,5002,5005],{},[480,5000,5001],{},"Loại Search",[480,5003,5004],{},"Cơ chế",[480,5006,5007],{},"Dùng khi",[487,5009,5010,5023,5036],{},[477,5011,5012,5017,5020],{},[492,5013,5014],{},[440,5015,5016],{},"Semantic Search",[492,5018,5019],{},"Tìm theo ý nghĩa (vector similarity)",[492,5021,5022],{},"FAQ, documentation, chatbot knowledge base",[477,5024,5025,5030,5033],{},[492,5026,5027],{},[440,5028,5029],{},"Keyword Search",[492,5031,5032],{},"Tìm theo từ khóa chính xác (Full Text Search)",[492,5034,5035],{},"Product catalog, exact matching",[477,5037,5038,5043,5046],{},[492,5039,5040],{},[440,5041,5042],{},"Hybrid Search",[492,5044,5045],{},"Kết hợp semantic + keyword",[492,5047,5048],{},"Cho kết quả toàn diện và chính xác nhất",[561,5050,5052],{"className":4384,"code":5051,"language":4386,"meta":566,"style":566},"-- Semantic Search: tìm documents tương tự với query embedding\nCREATE OR REPLACE FUNCTION match_documents(\n  query_embedding VECTOR(1536),\n  match_threshold FLOAT DEFAULT 0.78,\n  match_count INT DEFAULT 10\n)\nRETURNS TABLE (\n  id BIGINT,\n  title TEXT,\n  content TEXT,\n  similarity FLOAT\n)\nLANGUAGE SQL STABLE\nAS $$\n  SELECT\n    id,\n    title,\n    content,\n    1 - (embedding \u003C=> query_embedding) AS similarity\n  FROM documents\n  WHERE 1 - (embedding \u003C=> query_embedding) > match_threshold\n  ORDER BY embedding \u003C=> query_embedding\n  LIMIT match_count;\n$$;\n\n-- Keyword Search (Full Text Search)\nSELECT id, title, ts_rank(to_tsvector('english', content), query) AS rank\nFROM documents,\n     websearch_to_tsquery('english', 'supabase authentication') AS query\nWHERE to_tsvector('english', content) @@ query\nORDER BY rank DESC\nLIMIT 10;\n",[458,5053,5054,5059,5064,5069,5074,5079,5083,5088,5093,5098,5103,5108,5112,5117,5122,5127,5132,5137,5142,5147,5152,5157,5162,5167,5172,5176,5181,5186,5191,5196,5201,5206],{"__ignoreMap":566},[570,5055,5056],{"class":572,"line":573},[570,5057,5058],{},"-- Semantic Search: tìm documents tương tự với query embedding\n",[570,5060,5061],{"class":572,"line":580},[570,5062,5063],{},"CREATE OR REPLACE FUNCTION match_documents(\n",[570,5065,5066],{"class":572,"line":597},[570,5067,5068],{},"  query_embedding VECTOR(1536),\n",[570,5070,5071],{"class":572,"line":604},[570,5072,5073],{},"  match_threshold FLOAT DEFAULT 0.78,\n",[570,5075,5076],{"class":572,"line":610},[570,5077,5078],{},"  match_count INT DEFAULT 10\n",[570,5080,5081],{"class":572,"line":620},[570,5082,1014],{},[570,5084,5085],{"class":572,"line":625},[570,5086,5087],{},"RETURNS TABLE (\n",[570,5089,5090],{"class":572,"line":631},[570,5091,5092],{},"  id BIGINT,\n",[570,5094,5095],{"class":572,"line":643},[570,5096,5097],{},"  title TEXT,\n",[570,5099,5100],{"class":572,"line":648},[570,5101,5102],{},"  content TEXT,\n",[570,5104,5105],{"class":572,"line":654},[570,5106,5107],{},"  similarity FLOAT\n",[570,5109,5110],{"class":572,"line":666},[570,5111,1014],{},[570,5113,5114],{"class":572,"line":671},[570,5115,5116],{},"LANGUAGE SQL STABLE\n",[570,5118,5119],{"class":572,"line":677},[570,5120,5121],{},"AS $$\n",[570,5123,5124],{"class":572,"line":898},[570,5125,5126],{},"  SELECT\n",[570,5128,5129],{"class":572,"line":904},[570,5130,5131],{},"    id,\n",[570,5133,5134],{"class":572,"line":909},[570,5135,5136],{},"    title,\n",[570,5138,5139],{"class":572,"line":915},[570,5140,5141],{},"    content,\n",[570,5143,5144],{"class":572,"line":948},[570,5145,5146],{},"    1 - (embedding \u003C=> query_embedding) AS similarity\n",[570,5148,5149],{"class":572,"line":958},[570,5150,5151],{},"  FROM documents\n",[570,5153,5154],{"class":572,"line":982},[570,5155,5156],{},"  WHERE 1 - (embedding \u003C=> query_embedding) > match_threshold\n",[570,5158,5159],{"class":572,"line":1002},[570,5160,5161],{},"  ORDER BY embedding \u003C=> query_embedding\n",[570,5163,5164],{"class":572,"line":1008},[570,5165,5166],{},"  LIMIT match_count;\n",[570,5168,5169],{"class":572,"line":1017},[570,5170,5171],{},"$$;\n",[570,5173,5174],{"class":572,"line":2229},[570,5175,601],{"emptyLinePlaceholder":600},[570,5177,5178],{"class":572,"line":2240},[570,5179,5180],{},"-- Keyword Search (Full Text Search)\n",[570,5182,5183],{"class":572,"line":2247},[570,5184,5185],{},"SELECT id, title, ts_rank(to_tsvector('english', content), query) AS rank\n",[570,5187,5188],{"class":572,"line":2252},[570,5189,5190],{},"FROM documents,\n",[570,5192,5193],{"class":572,"line":2287},[570,5194,5195],{},"     websearch_to_tsquery('english', 'supabase authentication') AS query\n",[570,5197,5198],{"class":572,"line":2312},[570,5199,5200],{},"WHERE to_tsvector('english', content) @@ query\n",[570,5202,5203],{"class":572,"line":2319},[570,5204,5205],{},"ORDER BY rank DESC\n",[570,5207,5208],{"class":572,"line":2906},[570,5209,5210],{},"LIMIT 10;\n",[561,5212,5214],{"className":1029,"code":5213,"language":1031,"meta":566,"style":566},"\u002F\u002F Semantic search từ JavaScript client\nconst { data, error } = await supabase.rpc('match_documents', {\n  query_embedding: embedding,\n  match_threshold: 0.78,\n  match_count: 10\n})\n\n\u002F\u002F Keyword search từ JavaScript client\nconst { data, error } = await supabase\n  .from('documents')\n  .select('id, title, content')\n  .textSearch('content', 'supabase authentication', {\n    type: 'websearch',\n    config: 'english'\n  })\n",[458,5215,5216,5221,5259,5271,5283,5293,5299,5303,5308,5328,5346,5363,5392,5408,5422],{"__ignoreMap":566},[570,5217,5218],{"class":572,"line":573},[570,5219,5220],{"class":576},"\u002F\u002F Semantic search từ JavaScript client\n",[570,5222,5223,5225,5227,5229,5231,5233,5235,5237,5239,5241,5243,5246,5248,5250,5253,5255,5257],{"class":572,"line":580},[570,5224,1043],{"class":710},[570,5226,791],{"class":717},[570,5228,837],{"class":744},[570,5230,943],{"class":717},[570,5232,1052],{"class":744},[570,5234,863],{"class":717},[570,5236,800],{"class":717},[570,5238,804],{"class":803},[570,5240,1061],{"class":744},[570,5242,748],{"class":717},[570,5244,5245],{"class":751},"rpc",[570,5247,755],{"class":744},[570,5249,967],{"class":717},[570,5251,5252],{"class":587},"match_documents",[570,5254,967],{"class":717},[570,5256,943],{"class":717},[570,5258,718],{"class":717},[570,5260,5261,5264,5266,5269],{"class":572,"line":597},[570,5262,5263],{"class":723},"  query_embedding",[570,5265,727],{"class":717},[570,5267,5268],{"class":744}," embedding",[570,5270,872],{"class":717},[570,5272,5273,5276,5278,5281],{"class":572,"line":604},[570,5274,5275],{"class":723},"  match_threshold",[570,5277,727],{"class":717},[570,5279,5280],{"class":2955}," 0.78",[570,5282,872],{"class":717},[570,5284,5285,5288,5290],{"class":572,"line":610},[570,5286,5287],{"class":723},"  match_count",[570,5289,727],{"class":717},[570,5291,5292],{"class":2955}," 10\n",[570,5294,5295,5297],{"class":572,"line":620},[570,5296,863],{"class":717},[570,5298,1014],{"class":744},[570,5300,5301],{"class":572,"line":625},[570,5302,601],{"emptyLinePlaceholder":600},[570,5304,5305],{"class":572,"line":631},[570,5306,5307],{"class":576},"\u002F\u002F Keyword search từ JavaScript client\n",[570,5309,5310,5312,5314,5316,5318,5320,5322,5324,5326],{"class":572,"line":643},[570,5311,1043],{"class":710},[570,5313,791],{"class":717},[570,5315,837],{"class":744},[570,5317,943],{"class":717},[570,5319,1052],{"class":744},[570,5321,863],{"class":717},[570,5323,800],{"class":717},[570,5325,804],{"class":803},[570,5327,2001],{"class":744},[570,5329,5330,5333,5335,5337,5339,5342,5344],{"class":572,"line":648},[570,5331,5332],{"class":717},"  .",[570,5334,2009],{"class":751},[570,5336,755],{"class":744},[570,5338,967],{"class":717},[570,5340,5341],{"class":587},"documents",[570,5343,967],{"class":717},[570,5345,1014],{"class":744},[570,5347,5348,5350,5352,5354,5356,5359,5361],{"class":572,"line":654},[570,5349,5332],{"class":717},[570,5351,2027],{"class":751},[570,5353,755],{"class":744},[570,5355,967],{"class":717},[570,5357,5358],{"class":587},"id, title, content",[570,5360,967],{"class":717},[570,5362,1014],{"class":744},[570,5364,5365,5367,5370,5372,5374,5377,5379,5381,5383,5386,5388,5390],{"class":572,"line":666},[570,5366,5332],{"class":717},[570,5368,5369],{"class":751},"textSearch",[570,5371,755],{"class":744},[570,5373,967],{"class":717},[570,5375,5376],{"class":587},"content",[570,5378,967],{"class":717},[570,5380,943],{"class":717},[570,5382,972],{"class":717},[570,5384,5385],{"class":587},"supabase authentication",[570,5387,967],{"class":717},[570,5389,943],{"class":717},[570,5391,718],{"class":717},[570,5393,5394,5397,5399,5401,5404,5406],{"class":572,"line":671},[570,5395,5396],{"class":723},"    type",[570,5398,727],{"class":717},[570,5400,972],{"class":717},[570,5402,5403],{"class":587},"websearch",[570,5405,967],{"class":717},[570,5407,872],{"class":717},[570,5409,5410,5413,5415,5417,5420],{"class":572,"line":677},[570,5411,5412],{"class":723},"    config",[570,5414,727],{"class":717},[570,5416,972],{"class":717},[570,5418,5419],{"class":587},"english",[570,5421,999],{"class":717},[570,5423,5424,5426],{"class":572,"line":898},[570,5425,1011],{"class":717},[570,5427,1014],{"class":744},[432,5429,5431],{"id":5430},"generate-embeddings-với-edge-functions","Generate Embeddings với Edge Functions",[436,5433,5434],{},"Sử dụng Edge Function để tạo embeddings an toàn (không expose API key):",[561,5436,5438],{"className":692,"code":5437,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fgenerate-embedding\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\n\nconst OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!\n\nDeno.serve(async (req: Request) => {\n  const { text } = await req.json()\n\n  \u002F\u002F Gọi OpenAI Embeddings API\n  const response = await fetch('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${OPENAI_KEY}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({\n      model: 'text-embedding-3-small',\n      input: text\n    })\n  })\n\n  const { data } = await response.json()\n  const embedding = data[0].embedding \u002F\u002F [1536 số float]\n\n  return new Response(JSON.stringify({ embedding }), {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Access-Control-Allow-Origin': '*'\n    }\n  })\n})\n",[458,5439,5440,5445,5463,5467,5497,5501,5527,5550,5554,5559,5584,5598,5606,5629,5645,5649,5665,5680,5690,5696,5702,5706,5728,5754,5758,5788,5796,5814,5830,5834,5840],{"__ignoreMap":566},[570,5441,5442],{"class":572,"line":573},[570,5443,5444],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Fgenerate-embedding\u002Findex.ts\n",[570,5446,5447,5449,5451,5453,5455,5457,5459,5461],{"class":572,"line":580},[570,5448,1851],{"class":803},[570,5450,791],{"class":717},[570,5452,1856],{"class":744},[570,5454,797],{"class":717},[570,5456,1861],{"class":803},[570,5458,972],{"class":717},[570,5460,1866],{"class":587},[570,5462,999],{"class":717},[570,5464,5465],{"class":572,"line":597},[570,5466,601],{"emptyLinePlaceholder":600},[570,5468,5469,5471,5473,5475,5477,5479,5481,5483,5485,5487,5489,5491,5493,5495],{"class":572,"line":604},[570,5470,1043],{"class":710},[570,5472,1709],{"class":744},[570,5474,1132],{"class":717},[570,5476,1714],{"class":744},[570,5478,748],{"class":717},[570,5480,1719],{"class":744},[570,5482,748],{"class":717},[570,5484,1724],{"class":751},[570,5486,755],{"class":744},[570,5488,967],{"class":717},[570,5490,1731],{"class":587},[570,5492,967],{"class":717},[570,5494,773],{"class":744},[570,5496,1738],{"class":717},[570,5498,5499],{"class":572,"line":610},[570,5500,601],{"emptyLinePlaceholder":600},[570,5502,5503,5505,5507,5509,5511,5513,5515,5517,5519,5521,5523,5525],{"class":572,"line":620},[570,5504,745],{"class":744},[570,5506,748],{"class":717},[570,5508,752],{"class":751},[570,5510,755],{"class":744},[570,5512,758],{"class":710},[570,5514,761],{"class":717},[570,5516,765],{"class":764},[570,5518,727],{"class":717},[570,5520,770],{"class":583},[570,5522,773],{"class":717},[570,5524,776],{"class":710},[570,5526,718],{"class":717},[570,5528,5529,5531,5533,5536,5538,5540,5542,5544,5546,5548],{"class":572,"line":625},[570,5530,788],{"class":710},[570,5532,791],{"class":717},[570,5534,5535],{"class":744}," text",[570,5537,797],{"class":717},[570,5539,800],{"class":717},[570,5541,804],{"class":803},[570,5543,807],{"class":744},[570,5545,748],{"class":717},[570,5547,812],{"class":751},[570,5549,895],{"class":723},[570,5551,5552],{"class":572,"line":631},[570,5553,601],{"emptyLinePlaceholder":600},[570,5555,5556],{"class":572,"line":643},[570,5557,5558],{"class":576},"  \u002F\u002F Gọi OpenAI Embeddings API\n",[570,5560,5561,5563,5565,5567,5569,5571,5573,5575,5578,5580,5582],{"class":572,"line":648},[570,5562,788],{"class":710},[570,5564,3297],{"class":744},[570,5566,800],{"class":717},[570,5568,804],{"class":803},[570,5570,1137],{"class":751},[570,5572,755],{"class":723},[570,5574,967],{"class":717},[570,5576,5577],{"class":587},"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings",[570,5579,967],{"class":717},[570,5581,943],{"class":717},[570,5583,718],{"class":717},[570,5585,5586,5588,5590,5592,5594,5596],{"class":572,"line":654},[570,5587,1162],{"class":723},[570,5589,727],{"class":717},[570,5591,972],{"class":717},[570,5593,1169],{"class":587},[570,5595,967],{"class":717},[570,5597,872],{"class":717},[570,5599,5600,5602,5604],{"class":572,"line":666},[570,5601,951],{"class":723},[570,5603,727],{"class":717},[570,5605,718],{"class":717},[570,5607,5608,5610,5612,5614,5616,5618,5620,5622,5625,5627],{"class":572,"line":671},[570,5609,961],{"class":717},[570,5611,1188],{"class":723},[570,5613,967],{"class":717},[570,5615,727],{"class":717},[570,5617,851],{"class":717},[570,5619,1197],{"class":587},[570,5621,857],{"class":717},[570,5623,5624],{"class":744},"OPENAI_KEY",[570,5626,1205],{"class":717},[570,5628,872],{"class":717},[570,5630,5631,5633,5635,5637,5639,5641,5643],{"class":572,"line":677},[570,5632,961],{"class":717},[570,5634,964],{"class":723},[570,5636,967],{"class":717},[570,5638,727],{"class":717},[570,5640,972],{"class":717},[570,5642,975],{"class":587},[570,5644,999],{"class":717},[570,5646,5647],{"class":572,"line":898},[570,5648,1228],{"class":717},[570,5650,5651,5653,5655,5657,5659,5661,5663],{"class":572,"line":904},[570,5652,1233],{"class":723},[570,5654,727],{"class":717},[570,5656,1238],{"class":744},[570,5658,748],{"class":717},[570,5660,933],{"class":751},[570,5662,755],{"class":723},[570,5664,1342],{"class":717},[570,5666,5667,5669,5671,5673,5676,5678],{"class":572,"line":909},[570,5668,3419],{"class":723},[570,5670,727],{"class":717},[570,5672,972],{"class":717},[570,5674,5675],{"class":587},"text-embedding-3-small",[570,5677,967],{"class":717},[570,5679,872],{"class":717},[570,5681,5682,5685,5687],{"class":572,"line":915},[570,5683,5684],{"class":723},"      input",[570,5686,727],{"class":717},[570,5688,5689],{"class":744}," text\n",[570,5691,5692,5694],{"class":572,"line":948},[570,5693,1440],{"class":717},[570,5695,1014],{"class":723},[570,5697,5698,5700],{"class":572,"line":958},[570,5699,1011],{"class":717},[570,5701,1014],{"class":723},[570,5703,5704],{"class":572,"line":982},[570,5705,601],{"emptyLinePlaceholder":600},[570,5707,5708,5710,5712,5714,5716,5718,5720,5722,5724,5726],{"class":572,"line":1002},[570,5709,788],{"class":710},[570,5711,791],{"class":717},[570,5713,837],{"class":744},[570,5715,797],{"class":717},[570,5717,800],{"class":717},[570,5719,804],{"class":803},[570,5721,3297],{"class":744},[570,5723,748],{"class":717},[570,5725,812],{"class":751},[570,5727,895],{"class":723},[570,5729,5730,5732,5734,5736,5738,5741,5744,5746,5748,5751],{"class":572,"line":1008},[570,5731,788],{"class":710},[570,5733,5268],{"class":744},[570,5735,800],{"class":717},[570,5737,837],{"class":744},[570,5739,5740],{"class":723},"[",[570,5742,5743],{"class":2955},"0",[570,5745,3468],{"class":723},[570,5747,748],{"class":717},[570,5749,5750],{"class":744},"embedding",[570,5752,5753],{"class":576}," \u002F\u002F [1536 số float]\n",[570,5755,5756],{"class":572,"line":1017},[570,5757,601],{"emptyLinePlaceholder":600},[570,5759,5760,5762,5764,5766,5768,5770,5772,5774,5776,5778,5780,5782,5784,5786],{"class":572,"line":2229},[570,5761,918],{"class":803},[570,5763,591],{"class":717},[570,5765,923],{"class":751},[570,5767,755],{"class":723},[570,5769,928],{"class":744},[570,5771,748],{"class":717},[570,5773,933],{"class":751},[570,5775,755],{"class":723},[570,5777,1247],{"class":717},[570,5779,5268],{"class":744},[570,5781,797],{"class":717},[570,5783,773],{"class":723},[570,5785,943],{"class":717},[570,5787,718],{"class":717},[570,5789,5790,5792,5794],{"class":572,"line":2240},[570,5791,951],{"class":723},[570,5793,727],{"class":717},[570,5795,718],{"class":717},[570,5797,5798,5800,5802,5804,5806,5808,5810,5812],{"class":572,"line":2247},[570,5799,961],{"class":717},[570,5801,964],{"class":723},[570,5803,967],{"class":717},[570,5805,727],{"class":717},[570,5807,972],{"class":717},[570,5809,975],{"class":587},[570,5811,967],{"class":717},[570,5813,872],{"class":717},[570,5815,5816,5818,5820,5822,5824,5826,5828],{"class":572,"line":2252},[570,5817,961],{"class":717},[570,5819,987],{"class":723},[570,5821,967],{"class":717},[570,5823,727],{"class":717},[570,5825,972],{"class":717},[570,5827,996],{"class":587},[570,5829,999],{"class":717},[570,5831,5832],{"class":572,"line":2287},[570,5833,1005],{"class":717},[570,5835,5836,5838],{"class":572,"line":2312},[570,5837,1011],{"class":717},[570,5839,1014],{"class":723},[570,5841,5842,5844],{"class":572,"line":2319},[570,5843,863],{"class":717},[570,5845,1014],{"class":744},[432,5847,5849],{"id":5848},"rag-retrieval-augmented-generation","RAG (Retrieval Augmented Generation)",[436,5851,5852],{},"Kiến trúc RAG cho phép LLM trả lời dựa trên dữ liệu của bạn:",[561,5854,5857],{"className":5855,"code":5856,"language":2667},[4784],"1. EMBED    2. RETRIEVE          3. AUGMENT              4. GENERATE\nDocument  ─> Query embedding ─> Tìm top-k docs ─> Context + Query ─> LLM ─> Answer\n  │              │                  │                    │\n  │         user query          pgvector             prompt:\n  │         -> embedding        similarity        \"Answer based\n  │         (OpenAI)            search               on context:\"\n  └─> Vector DB\n      (pgvector)\n",[458,5858,5856],{"__ignoreMap":566},[561,5860,5862],{"className":692,"code":5861,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Frag-query\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\n\nconst OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!\nconst supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n)\n\nDeno.serve(async (req: Request) => {\n  const { query, userId } = await req.json()\n\n  \u002F\u002F Step 1: Tạo embedding cho câu hỏi\n  const embedRes = await fetch('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${OPENAI_KEY}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({\n      model: 'text-embedding-3-small',\n      input: query\n    })\n  })\n  const { data: [{ embedding }] } = await embedRes.json()\n\n  \u002F\u002F Step 2: Tìm documents liên quan (RLS-aware)\n  const { data: documents } = await supabase.rpc('match_documents', {\n    query_embedding: embedding,\n    match_count: 5\n  })\n\n  \u002F\u002F Step 3 & 4: Gửi context + query cho LLM\n  const context = documents\n    .map(d => `[${d.title}]\\n${d.content}`)\n    .join('\\n\\n---\\n\\n')\n\n  const chatRes = await fetch('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${OPENAI_KEY}`,\n      'Content-Type': 'application\u002Fjson'\n    },\n    body: JSON.stringify({\n      model: 'gpt-4o',\n      messages: [\n        {\n          role: 'system',\n          content: `Bạn là trợ lý AI. Chỉ trả lời dựa trên context được cung cấp.\n          Nếu context không đủ thông tin, hãy nói \"Tôi không có đủ thông tin để trả lời.\"\n          Luôn trích dẫn nguồn từ title của document.`\n        },\n        {\n          role: 'user',\n          content: `Context:\\n${context}\\n\\nCâu hỏi: ${query}`\n        }\n      ],\n      temperature: 0.3\n    })\n  })\n\n  const answer = await chatRes.json()\n\n  return new Response(JSON.stringify({\n    answer: answer.choices[0].message.content,\n    sources: documents.map(d => ({ title: d.title, similarity: d.similarity }))\n  }), {\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Access-Control-Allow-Origin': '*'\n    }\n  })\n})\n",[458,5863,5864,5869,5887,5891,5921,5933,5957,5981,5985,5989,6015,6042,6046,6051,6076,6090,6098,6120,6136,6140,6156,6170,6179,6185,6191,6223,6227,6232,6269,6280,6290,6296,6300,6305,6317,6363,6386,6390,6415,6429,6437,6459,6475,6479,6495,6509,6518,6523,6539,6551,6556,6564,6569,6574,6589,6623,6629,6637,6648,6655,6662,6667,6687,6692,6713,6745,6800,6811,6820,6839,6856,6861,6868],{"__ignoreMap":566},[570,5865,5866],{"class":572,"line":573},[570,5867,5868],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Frag-query\u002Findex.ts\n",[570,5870,5871,5873,5875,5877,5879,5881,5883,5885],{"class":572,"line":580},[570,5872,1851],{"class":803},[570,5874,791],{"class":717},[570,5876,1856],{"class":744},[570,5878,797],{"class":717},[570,5880,1861],{"class":803},[570,5882,972],{"class":717},[570,5884,1866],{"class":587},[570,5886,999],{"class":717},[570,5888,5889],{"class":572,"line":597},[570,5890,601],{"emptyLinePlaceholder":600},[570,5892,5893,5895,5897,5899,5901,5903,5905,5907,5909,5911,5913,5915,5917,5919],{"class":572,"line":604},[570,5894,1043],{"class":710},[570,5896,1709],{"class":744},[570,5898,1132],{"class":717},[570,5900,1714],{"class":744},[570,5902,748],{"class":717},[570,5904,1719],{"class":744},[570,5906,748],{"class":717},[570,5908,1724],{"class":751},[570,5910,755],{"class":744},[570,5912,967],{"class":717},[570,5914,1731],{"class":587},[570,5916,967],{"class":717},[570,5918,773],{"class":744},[570,5920,1738],{"class":717},[570,5922,5923,5925,5927,5929,5931],{"class":572,"line":610},[570,5924,1043],{"class":710},[570,5926,2527],{"class":744},[570,5928,1132],{"class":717},[570,5930,1856],{"class":751},[570,5932,1140],{"class":744},[570,5934,5935,5937,5939,5941,5943,5945,5947,5949,5951,5953,5955],{"class":572,"line":620},[570,5936,2538],{"class":744},[570,5938,748],{"class":717},[570,5940,1719],{"class":744},[570,5942,748],{"class":717},[570,5944,1724],{"class":751},[570,5946,755],{"class":744},[570,5948,967],{"class":717},[570,5950,1796],{"class":587},[570,5952,967],{"class":717},[570,5954,773],{"class":744},[570,5956,1941],{"class":717},[570,5958,5959,5961,5963,5965,5967,5969,5971,5973,5975,5977,5979],{"class":572,"line":625},[570,5960,2538],{"class":744},[570,5962,748],{"class":717},[570,5964,1719],{"class":744},[570,5966,748],{"class":717},[570,5968,1724],{"class":751},[570,5970,755],{"class":744},[570,5972,967],{"class":717},[570,5974,1828],{"class":587},[570,5976,967],{"class":717},[570,5978,773],{"class":744},[570,5980,1738],{"class":717},[570,5982,5983],{"class":572,"line":631},[570,5984,1014],{"class":744},[570,5986,5987],{"class":572,"line":643},[570,5988,601],{"emptyLinePlaceholder":600},[570,5990,5991,5993,5995,5997,5999,6001,6003,6005,6007,6009,6011,6013],{"class":572,"line":648},[570,5992,745],{"class":744},[570,5994,748],{"class":717},[570,5996,752],{"class":751},[570,5998,755],{"class":744},[570,6000,758],{"class":710},[570,6002,761],{"class":717},[570,6004,765],{"class":764},[570,6006,727],{"class":717},[570,6008,770],{"class":583},[570,6010,773],{"class":717},[570,6012,776],{"class":710},[570,6014,718],{"class":717},[570,6016,6017,6019,6021,6024,6026,6028,6030,6032,6034,6036,6038,6040],{"class":572,"line":654},[570,6018,788],{"class":710},[570,6020,791],{"class":717},[570,6022,6023],{"class":744}," query",[570,6025,943],{"class":717},[570,6027,2811],{"class":744},[570,6029,797],{"class":717},[570,6031,800],{"class":717},[570,6033,804],{"class":803},[570,6035,807],{"class":744},[570,6037,748],{"class":717},[570,6039,812],{"class":751},[570,6041,895],{"class":723},[570,6043,6044],{"class":572,"line":666},[570,6045,601],{"emptyLinePlaceholder":600},[570,6047,6048],{"class":572,"line":671},[570,6049,6050],{"class":576},"  \u002F\u002F Step 1: Tạo embedding cho câu hỏi\n",[570,6052,6053,6055,6058,6060,6062,6064,6066,6068,6070,6072,6074],{"class":572,"line":677},[570,6054,788],{"class":710},[570,6056,6057],{"class":744}," embedRes",[570,6059,800],{"class":717},[570,6061,804],{"class":803},[570,6063,1137],{"class":751},[570,6065,755],{"class":723},[570,6067,967],{"class":717},[570,6069,5577],{"class":587},[570,6071,967],{"class":717},[570,6073,943],{"class":717},[570,6075,718],{"class":717},[570,6077,6078,6080,6082,6084,6086,6088],{"class":572,"line":898},[570,6079,1162],{"class":723},[570,6081,727],{"class":717},[570,6083,972],{"class":717},[570,6085,1169],{"class":587},[570,6087,967],{"class":717},[570,6089,872],{"class":717},[570,6091,6092,6094,6096],{"class":572,"line":904},[570,6093,951],{"class":723},[570,6095,727],{"class":717},[570,6097,718],{"class":717},[570,6099,6100,6102,6104,6106,6108,6110,6112,6114,6116,6118],{"class":572,"line":909},[570,6101,961],{"class":717},[570,6103,1188],{"class":723},[570,6105,967],{"class":717},[570,6107,727],{"class":717},[570,6109,851],{"class":717},[570,6111,1197],{"class":587},[570,6113,857],{"class":717},[570,6115,5624],{"class":744},[570,6117,1205],{"class":717},[570,6119,872],{"class":717},[570,6121,6122,6124,6126,6128,6130,6132,6134],{"class":572,"line":915},[570,6123,961],{"class":717},[570,6125,964],{"class":723},[570,6127,967],{"class":717},[570,6129,727],{"class":717},[570,6131,972],{"class":717},[570,6133,975],{"class":587},[570,6135,999],{"class":717},[570,6137,6138],{"class":572,"line":948},[570,6139,1228],{"class":717},[570,6141,6142,6144,6146,6148,6150,6152,6154],{"class":572,"line":958},[570,6143,1233],{"class":723},[570,6145,727],{"class":717},[570,6147,1238],{"class":744},[570,6149,748],{"class":717},[570,6151,933],{"class":751},[570,6153,755],{"class":723},[570,6155,1342],{"class":717},[570,6157,6158,6160,6162,6164,6166,6168],{"class":572,"line":982},[570,6159,3419],{"class":723},[570,6161,727],{"class":717},[570,6163,972],{"class":717},[570,6165,5675],{"class":587},[570,6167,967],{"class":717},[570,6169,872],{"class":717},[570,6171,6172,6174,6176],{"class":572,"line":1002},[570,6173,5684],{"class":723},[570,6175,727],{"class":717},[570,6177,6178],{"class":744}," query\n",[570,6180,6181,6183],{"class":572,"line":1008},[570,6182,1440],{"class":717},[570,6184,1014],{"class":723},[570,6186,6187,6189],{"class":572,"line":1017},[570,6188,1011],{"class":717},[570,6190,1014],{"class":723},[570,6192,6193,6195,6197,6199,6201,6204,6206,6209,6211,6213,6215,6217,6219,6221],{"class":572,"line":2229},[570,6194,788],{"class":710},[570,6196,791],{"class":717},[570,6198,837],{"class":723},[570,6200,727],{"class":717},[570,6202,6203],{"class":717}," [{",[570,6205,5268],{"class":744},[570,6207,6208],{"class":717}," }]",[570,6210,797],{"class":717},[570,6212,800],{"class":717},[570,6214,804],{"class":803},[570,6216,6057],{"class":744},[570,6218,748],{"class":717},[570,6220,812],{"class":751},[570,6222,895],{"class":723},[570,6224,6225],{"class":572,"line":2240},[570,6226,601],{"emptyLinePlaceholder":600},[570,6228,6229],{"class":572,"line":2247},[570,6230,6231],{"class":576},"  \u002F\u002F Step 2: Tìm documents liên quan (RLS-aware)\n",[570,6233,6234,6236,6238,6240,6242,6245,6247,6249,6251,6253,6255,6257,6259,6261,6263,6265,6267],{"class":572,"line":2252},[570,6235,788],{"class":710},[570,6237,791],{"class":717},[570,6239,837],{"class":723},[570,6241,727],{"class":717},[570,6243,6244],{"class":744}," documents",[570,6246,797],{"class":717},[570,6248,800],{"class":717},[570,6250,804],{"class":803},[570,6252,1061],{"class":744},[570,6254,748],{"class":717},[570,6256,5245],{"class":751},[570,6258,755],{"class":723},[570,6260,967],{"class":717},[570,6262,5252],{"class":587},[570,6264,967],{"class":717},[570,6266,943],{"class":717},[570,6268,718],{"class":717},[570,6270,6271,6274,6276,6278],{"class":572,"line":2287},[570,6272,6273],{"class":723},"    query_embedding",[570,6275,727],{"class":717},[570,6277,5268],{"class":744},[570,6279,872],{"class":717},[570,6281,6282,6285,6287],{"class":572,"line":2312},[570,6283,6284],{"class":723},"    match_count",[570,6286,727],{"class":717},[570,6288,6289],{"class":2955}," 5\n",[570,6291,6292,6294],{"class":572,"line":2319},[570,6293,1011],{"class":717},[570,6295,1014],{"class":723},[570,6297,6298],{"class":572,"line":2906},[570,6299,601],{"emptyLinePlaceholder":600},[570,6301,6302],{"class":572,"line":2928},[570,6303,6304],{"class":576},"  \u002F\u002F Step 3 & 4: Gửi context + query cho LLM\n",[570,6306,6307,6309,6312,6314],{"class":572,"line":2967},[570,6308,788],{"class":710},[570,6310,6311],{"class":744}," context",[570,6313,800],{"class":717},[570,6315,6316],{"class":744}," documents\n",[570,6318,6319,6321,6324,6326,6329,6331,6333,6335,6337,6339,6341,6344,6346,6348,6351,6353,6355,6357,6359,6361],{"class":572,"line":2975},[570,6320,2006],{"class":717},[570,6322,6323],{"class":751},"map",[570,6325,755],{"class":723},[570,6327,6328],{"class":764},"d",[570,6330,776],{"class":710},[570,6332,851],{"class":717},[570,6334,5740],{"class":587},[570,6336,857],{"class":717},[570,6338,6328],{"class":744},[570,6340,748],{"class":717},[570,6342,6343],{"class":744},"title",[570,6345,863],{"class":717},[570,6347,3468],{"class":587},[570,6349,6350],{"class":744},"\\n",[570,6352,857],{"class":717},[570,6354,6328],{"class":744},[570,6356,748],{"class":717},[570,6358,5376],{"class":744},[570,6360,1205],{"class":717},[570,6362,1014],{"class":723},[570,6364,6365,6367,6370,6372,6374,6377,6380,6382,6384],{"class":572,"line":2980},[570,6366,2006],{"class":717},[570,6368,6369],{"class":751},"join",[570,6371,755],{"class":723},[570,6373,967],{"class":717},[570,6375,6376],{"class":744},"\\n\\n",[570,6378,6379],{"class":587},"---",[570,6381,6376],{"class":744},[570,6383,967],{"class":717},[570,6385,1014],{"class":723},[570,6387,6388],{"class":572,"line":2986},[570,6389,601],{"emptyLinePlaceholder":600},[570,6391,6392,6394,6397,6399,6401,6403,6405,6407,6409,6411,6413],{"class":572,"line":2991},[570,6393,788],{"class":710},[570,6395,6396],{"class":744}," chatRes",[570,6398,800],{"class":717},[570,6400,804],{"class":803},[570,6402,1137],{"class":751},[570,6404,755],{"class":723},[570,6406,967],{"class":717},[570,6408,3310],{"class":587},[570,6410,967],{"class":717},[570,6412,943],{"class":717},[570,6414,718],{"class":717},[570,6416,6417,6419,6421,6423,6425,6427],{"class":572,"line":2996},[570,6418,1162],{"class":723},[570,6420,727],{"class":717},[570,6422,972],{"class":717},[570,6424,1169],{"class":587},[570,6426,967],{"class":717},[570,6428,872],{"class":717},[570,6430,6431,6433,6435],{"class":572,"line":3012},[570,6432,951],{"class":723},[570,6434,727],{"class":717},[570,6436,718],{"class":717},[570,6438,6439,6441,6443,6445,6447,6449,6451,6453,6455,6457],{"class":572,"line":3032},[570,6440,961],{"class":717},[570,6442,1188],{"class":723},[570,6444,967],{"class":717},[570,6446,727],{"class":717},[570,6448,851],{"class":717},[570,6450,1197],{"class":587},[570,6452,857],{"class":717},[570,6454,5624],{"class":744},[570,6456,1205],{"class":717},[570,6458,872],{"class":717},[570,6460,6461,6463,6465,6467,6469,6471,6473],{"class":572,"line":3053},[570,6462,961],{"class":717},[570,6464,964],{"class":723},[570,6466,967],{"class":717},[570,6468,727],{"class":717},[570,6470,972],{"class":717},[570,6472,975],{"class":587},[570,6474,999],{"class":717},[570,6476,6477],{"class":572,"line":3082},[570,6478,1228],{"class":717},[570,6480,6481,6483,6485,6487,6489,6491,6493],{"class":572,"line":3109},[570,6482,1233],{"class":723},[570,6484,727],{"class":717},[570,6486,1238],{"class":744},[570,6488,748],{"class":717},[570,6490,933],{"class":751},[570,6492,755],{"class":723},[570,6494,1342],{"class":717},[570,6496,6497,6499,6501,6503,6505,6507],{"class":572,"line":3114},[570,6498,3419],{"class":723},[570,6500,727],{"class":717},[570,6502,972],{"class":717},[570,6504,3426],{"class":587},[570,6506,967],{"class":717},[570,6508,872],{"class":717},[570,6510,6511,6513,6515],{"class":572,"line":3119},[570,6512,3435],{"class":723},[570,6514,727],{"class":717},[570,6516,6517],{"class":723}," [\n",[570,6519,6520],{"class":572,"line":3124},[570,6521,6522],{"class":717},"        {\n",[570,6524,6525,6528,6530,6532,6535,6537],{"class":572,"line":3129},[570,6526,6527],{"class":723},"          role",[570,6529,727],{"class":717},[570,6531,972],{"class":717},[570,6533,6534],{"class":587},"system",[570,6536,967],{"class":717},[570,6538,872],{"class":717},[570,6540,6541,6544,6546,6548],{"class":572,"line":3165},[570,6542,6543],{"class":723},"          content",[570,6545,727],{"class":717},[570,6547,851],{"class":717},[570,6549,6550],{"class":587},"Bạn là trợ lý AI. Chỉ trả lời dựa trên context được cung cấp.\n",[570,6552,6553],{"class":572,"line":3178},[570,6554,6555],{"class":587},"          Nếu context không đủ thông tin, hãy nói \"Tôi không có đủ thông tin để trả lời.\"\n",[570,6557,6558,6561],{"class":572,"line":3203},[570,6559,6560],{"class":587},"          Luôn trích dẫn nguồn từ title của document.",[570,6562,6563],{"class":717},"`\n",[570,6565,6566],{"class":572,"line":3210},[570,6567,6568],{"class":717},"        },\n",[570,6570,6572],{"class":572,"line":6571},53,[570,6573,6522],{"class":717},[570,6575,6577,6579,6581,6583,6585,6587],{"class":572,"line":6576},54,[570,6578,6527],{"class":723},[570,6580,727],{"class":717},[570,6582,972],{"class":717},[570,6584,3452],{"class":587},[570,6586,967],{"class":717},[570,6588,872],{"class":717},[570,6590,6592,6594,6596,6598,6601,6603,6605,6608,6610,6612,6615,6617,6620],{"class":572,"line":6591},55,[570,6593,6543],{"class":723},[570,6595,727],{"class":717},[570,6597,851],{"class":717},[570,6599,6600],{"class":587},"Context:",[570,6602,6350],{"class":744},[570,6604,857],{"class":717},[570,6606,6607],{"class":744},"context",[570,6609,863],{"class":717},[570,6611,6376],{"class":744},[570,6613,6614],{"class":587},"Câu hỏi: ",[570,6616,857],{"class":717},[570,6618,6619],{"class":744},"query",[570,6621,6622],{"class":717},"}`\n",[570,6624,6626],{"class":572,"line":6625},56,[570,6627,6628],{"class":717},"        }\n",[570,6630,6632,6635],{"class":572,"line":6631},57,[570,6633,6634],{"class":723},"      ]",[570,6636,872],{"class":717},[570,6638,6640,6643,6645],{"class":572,"line":6639},58,[570,6641,6642],{"class":723},"      temperature",[570,6644,727],{"class":717},[570,6646,6647],{"class":2955}," 0.3\n",[570,6649,6651,6653],{"class":572,"line":6650},59,[570,6652,1440],{"class":717},[570,6654,1014],{"class":723},[570,6656,6658,6660],{"class":572,"line":6657},60,[570,6659,1011],{"class":717},[570,6661,1014],{"class":723},[570,6663,6665],{"class":572,"line":6664},61,[570,6666,601],{"emptyLinePlaceholder":600},[570,6668,6670,6672,6675,6677,6679,6681,6683,6685],{"class":572,"line":6669},62,[570,6671,788],{"class":710},[570,6673,6674],{"class":744}," answer",[570,6676,800],{"class":717},[570,6678,804],{"class":803},[570,6680,6396],{"class":744},[570,6682,748],{"class":717},[570,6684,812],{"class":751},[570,6686,895],{"class":723},[570,6688,6690],{"class":572,"line":6689},63,[570,6691,601],{"emptyLinePlaceholder":600},[570,6693,6695,6697,6699,6701,6703,6705,6707,6709,6711],{"class":572,"line":6694},64,[570,6696,918],{"class":803},[570,6698,591],{"class":717},[570,6700,923],{"class":751},[570,6702,755],{"class":723},[570,6704,928],{"class":744},[570,6706,748],{"class":717},[570,6708,933],{"class":751},[570,6710,755],{"class":723},[570,6712,1342],{"class":717},[570,6714,6716,6719,6721,6723,6725,6728,6730,6732,6734,6736,6739,6741,6743],{"class":572,"line":6715},65,[570,6717,6718],{"class":723},"    answer",[570,6720,727],{"class":717},[570,6722,6674],{"class":744},[570,6724,748],{"class":717},[570,6726,6727],{"class":744},"choices",[570,6729,5740],{"class":723},[570,6731,5743],{"class":2955},[570,6733,3468],{"class":723},[570,6735,748],{"class":717},[570,6737,6738],{"class":744},"message",[570,6740,748],{"class":717},[570,6742,5376],{"class":744},[570,6744,872],{"class":717},[570,6746,6748,6751,6753,6755,6757,6759,6761,6763,6765,6767,6769,6772,6774,6777,6779,6781,6783,6786,6788,6790,6792,6795,6797],{"class":572,"line":6747},66,[570,6749,6750],{"class":723},"    sources",[570,6752,727],{"class":717},[570,6754,6244],{"class":744},[570,6756,748],{"class":717},[570,6758,6323],{"class":751},[570,6760,755],{"class":723},[570,6762,6328],{"class":764},[570,6764,776],{"class":710},[570,6766,761],{"class":723},[570,6768,1247],{"class":717},[570,6770,6771],{"class":723}," title",[570,6773,727],{"class":717},[570,6775,6776],{"class":744}," d",[570,6778,748],{"class":717},[570,6780,6343],{"class":744},[570,6782,943],{"class":717},[570,6784,6785],{"class":723}," similarity",[570,6787,727],{"class":717},[570,6789,6776],{"class":744},[570,6791,748],{"class":717},[570,6793,6794],{"class":744},"similarity",[570,6796,797],{"class":717},[570,6798,6799],{"class":723},"))\n",[570,6801,6803,6805,6807,6809],{"class":572,"line":6802},67,[570,6804,1011],{"class":717},[570,6806,773],{"class":723},[570,6808,943],{"class":717},[570,6810,718],{"class":717},[570,6812,6814,6816,6818],{"class":572,"line":6813},68,[570,6815,951],{"class":723},[570,6817,727],{"class":717},[570,6819,718],{"class":717},[570,6821,6823,6825,6827,6829,6831,6833,6835,6837],{"class":572,"line":6822},69,[570,6824,961],{"class":717},[570,6826,964],{"class":723},[570,6828,967],{"class":717},[570,6830,727],{"class":717},[570,6832,972],{"class":717},[570,6834,975],{"class":587},[570,6836,967],{"class":717},[570,6838,872],{"class":717},[570,6840,6842,6844,6846,6848,6850,6852,6854],{"class":572,"line":6841},70,[570,6843,961],{"class":717},[570,6845,987],{"class":723},[570,6847,967],{"class":717},[570,6849,727],{"class":717},[570,6851,972],{"class":717},[570,6853,996],{"class":587},[570,6855,999],{"class":717},[570,6857,6859],{"class":572,"line":6858},71,[570,6860,1005],{"class":717},[570,6862,6864,6866],{"class":572,"line":6863},72,[570,6865,1011],{"class":717},[570,6867,1014],{"class":723},[570,6869,6871,6873],{"class":572,"line":6870},73,[570,6872,863],{"class":717},[570,6874,1014],{"class":744},[432,6876,6878],{"id":6877},"rag-với-row-level-security","RAG với Row Level Security",[436,6880,6881,6882,6885],{},"Supabase hỗ trợ ",[440,6883,6884],{},"RAG with RLS"," -- đảm bảo mỗi user chỉ search được documents họ có quyền truy cập:",[561,6887,6889],{"className":4384,"code":6888,"language":4386,"meta":566,"style":566},"-- Policy: user chỉ search được documents của chính họ hoặc public\nCREATE POLICY \"Users can only search own docs\"\nON documents FOR SELECT\nUSING (\n  auth.uid() = owner_id\n  OR is_public = true\n);\n\n-- Policy cho tổ chức: team members có thể search documents của team\nCREATE POLICY \"Team members can search team docs\"\nON documents FOR SELECT\nUSING (\n  team_id IN (\n    SELECT team_id FROM team_members WHERE user_id = auth.uid()\n  )\n);\n",[458,6890,6891,6896,6901,6906,6911,6916,6921,6925,6929,6934,6939,6943,6947,6952,6957,6961],{"__ignoreMap":566},[570,6892,6893],{"class":572,"line":573},[570,6894,6895],{},"-- Policy: user chỉ search được documents của chính họ hoặc public\n",[570,6897,6898],{"class":572,"line":580},[570,6899,6900],{},"CREATE POLICY \"Users can only search own docs\"\n",[570,6902,6903],{"class":572,"line":597},[570,6904,6905],{},"ON documents FOR SELECT\n",[570,6907,6908],{"class":572,"line":604},[570,6909,6910],{},"USING (\n",[570,6912,6913],{"class":572,"line":610},[570,6914,6915],{},"  auth.uid() = owner_id\n",[570,6917,6918],{"class":572,"line":620},[570,6919,6920],{},"  OR is_public = true\n",[570,6922,6923],{"class":572,"line":625},[570,6924,4446],{},[570,6926,6927],{"class":572,"line":631},[570,6928,601],{"emptyLinePlaceholder":600},[570,6930,6931],{"class":572,"line":643},[570,6932,6933],{},"-- Policy cho tổ chức: team members có thể search documents của team\n",[570,6935,6936],{"class":572,"line":648},[570,6937,6938],{},"CREATE POLICY \"Team members can search team docs\"\n",[570,6940,6941],{"class":572,"line":654},[570,6942,6905],{},[570,6944,6945],{"class":572,"line":666},[570,6946,6910],{},[570,6948,6949],{"class":572,"line":671},[570,6950,6951],{},"  team_id IN (\n",[570,6953,6954],{"class":572,"line":677},[570,6955,6956],{},"    SELECT team_id FROM team_members WHERE user_id = auth.uid()\n",[570,6958,6959],{"class":572,"line":898},[570,6960,1970],{},[570,6962,6963],{"class":572,"line":904},[570,6964,4446],{},[436,6966,6967,6968,6971],{},"Khi gọi ",[458,6969,6970],{},"match_documents()"," từ Edge Function với service role key, RLS bị bypass. Để RLS hoạt động, gọi function với user JWT:",[561,6973,6975],{"className":692,"code":6974,"language":694,"meta":566,"style":566},"\u002F\u002F Dùng user's JWT thay vì service role key\nconst supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_ANON_KEY')!,\n  {\n    global: {\n      headers: {\n        Authorization: `Bearer ${userJWT}`\n      }\n    }\n  }\n)\n",[458,6976,6977,6982,6994,7018,7043,7047,7056,7064,7082,7086,7090,7094],{"__ignoreMap":566},[570,6978,6979],{"class":572,"line":573},[570,6980,6981],{"class":576},"\u002F\u002F Dùng user's JWT thay vì service role key\n",[570,6983,6984,6986,6988,6990,6992],{"class":572,"line":580},[570,6985,1043],{"class":710},[570,6987,2527],{"class":744},[570,6989,1132],{"class":717},[570,6991,1856],{"class":751},[570,6993,1140],{"class":744},[570,6995,6996,6998,7000,7002,7004,7006,7008,7010,7012,7014,7016],{"class":572,"line":597},[570,6997,2538],{"class":744},[570,6999,748],{"class":717},[570,7001,1719],{"class":744},[570,7003,748],{"class":717},[570,7005,1724],{"class":751},[570,7007,755],{"class":744},[570,7009,967],{"class":717},[570,7011,1796],{"class":587},[570,7013,967],{"class":717},[570,7015,773],{"class":744},[570,7017,1941],{"class":717},[570,7019,7020,7022,7024,7026,7028,7030,7032,7034,7037,7039,7041],{"class":572,"line":604},[570,7021,2538],{"class":744},[570,7023,748],{"class":717},[570,7025,1719],{"class":744},[570,7027,748],{"class":717},[570,7029,1724],{"class":751},[570,7031,755],{"class":744},[570,7033,967],{"class":717},[570,7035,7036],{"class":587},"SUPABASE_ANON_KEY",[570,7038,967],{"class":717},[570,7040,773],{"class":744},[570,7042,1941],{"class":717},[570,7044,7045],{"class":572,"line":610},[570,7046,1157],{"class":717},[570,7048,7049,7052,7054],{"class":572,"line":620},[570,7050,7051],{"class":723},"    global",[570,7053,727],{"class":717},[570,7055,718],{"class":717},[570,7057,7058,7060,7062],{"class":572,"line":625},[570,7059,1369],{"class":723},[570,7061,727],{"class":717},[570,7063,718],{"class":717},[570,7065,7066,7069,7071,7073,7075,7077,7080],{"class":572,"line":631},[570,7067,7068],{"class":723},"        Authorization",[570,7070,727],{"class":717},[570,7072,851],{"class":717},[570,7074,1197],{"class":587},[570,7076,857],{"class":717},[570,7078,7079],{"class":744},"userJWT",[570,7081,6622],{"class":717},[570,7083,7084],{"class":572,"line":643},[570,7085,1435],{"class":717},[570,7087,7088],{"class":572,"line":648},[570,7089,1005],{"class":717},[570,7091,7092],{"class":572,"line":654},[570,7093,901],{"class":717},[570,7095,7096],{"class":572,"line":666},[570,7097,1014],{"class":744},[432,7099,7101],{"id":7100},"integrations","Integrations",[436,7103,7104],{},"Supabase tích hợp với nhiều nền tảng AI\u002FML:",[471,7106,7107,7117],{},[474,7108,7109],{},[477,7110,7111,7114],{},[480,7112,7113],{},"Integration",[480,7115,7116],{},"Công dụng",[487,7118,7119,7129,7139,7149,7159,7169,7179],{},[477,7120,7121,7126],{},[492,7122,7123],{},[440,7124,7125],{},"OpenAI",[492,7127,7128],{},"Embeddings (text-embedding-3-small\u002Flarge), ChatGPT\u002FGPT-4o completions",[477,7130,7131,7136],{},[492,7132,7133],{},[440,7134,7135],{},"HuggingFace",[492,7137,7138],{},"100,000+ ML models, Inference API, open-source embeddings",[477,7140,7141,7146],{},[492,7142,7143],{},[440,7144,7145],{},"LangChain",[492,7147,7148],{},"Framework phát triển LLM apps với vector stores, chains, agents",[477,7150,7151,7156],{},[492,7152,7153],{},[440,7154,7155],{},"LlamaIndex",[492,7157,7158],{},"Data framework cho LLM, indexing, querying",[477,7160,7161,7166],{},[492,7162,7163],{},[440,7164,7165],{},"Amazon Bedrock",[492,7167,7168],{},"Foundation models từ AWS (Claude, Llama, Titan)",[477,7170,7171,7176],{},[492,7172,7173],{},[440,7174,7175],{},"Google Colab",[492,7177,7178],{},"Notebooks cho AI development, prototyping",[477,7180,7181,7186],{},[492,7182,7183],{},[440,7184,7185],{},"Roboflow",[492,7187,7188],{},"Computer vision, object detection integration",[432,7190,7192],{"id":7191},"vector-buckets","Vector Buckets",[436,7194,7195],{},"Loại bucket mới trong Supabase Storage dành riêng cho vector operations:",[7197,7198,7199,7206,7212,7218],"ul",{},[7200,7201,7202,7205],"li",{},[440,7203,7204],{},"HNSW indexing",": Fast approximate nearest neighbor search",[7200,7207,7208,7211],{},[440,7209,7210],{},"Distance metrics",": Cosine, Euclidean, Inner Product",[7200,7213,7214,7217],{},[440,7215,7216],{},"Metadata filtering",": Lọc kết quả theo metadata kèm vector",[7200,7219,7220,7223],{},[440,7221,7222],{},"Local development",": Hỗ trợ dev local với Supabase CLI",[432,7225,7227],{"id":7226},"compute-cho-ai-workloads","Compute cho AI Workloads",[436,7229,7230],{},"Vector search production cần compute cao:",[7197,7232,7233,7242,7245,7248],{},[7200,7234,7235,461,7238,7241],{},[440,7236,7237],{},"Large",[440,7239,7240],{},"XL"," compute cho vector search production",[7200,7243,7244],{},"Cần RAM cao để HNSW index nạp vào memory",[7200,7246,7247],{},"IOPS cao cho parallel similarity queries",[7200,7249,7250],{},"Nếu dataset > 1M vectors, cân nhắc 2XL hoặc cao hơn",[4529,7252],{},[427,7254,7256],{"id":7255},"bài-tập","Bài tập",[432,7258,7260],{"id":7259},"bài-tập-5-stripe-checkout-webhook-handler","Bài tập 5: Stripe Checkout Webhook Handler",[436,7262,7263],{},[440,7264,7265],{},"Yêu cầu:",[7197,7267,7268,7271,7274,7280,7285,7292],{},[7200,7269,7270],{},"Tạo Edge Function nhận webhook từ Stripe khi thanh toán hoàn tất",[7200,7272,7273],{},"Verify webhook signature để đảm bảo request từ Stripe",[7200,7275,7276,7277,7279],{},"Khi ",[458,7278,2777],{},": cập nhật subscription status trong database",[7200,7281,7276,7282,7284],{},[458,7283,3003],{},": đánh dấu subscription canceled",[7200,7286,7287,7288,7291],{},"Log tất cả events vào bảng ",[458,7289,7290],{},"webhook_logs"," để debug",[7200,7293,7294],{},"Xử lý idempotency (tránh xử lý trùng lặp)",[436,7296,7297],{},[440,7298,7299],{},"Gợi ý triển khai:",[561,7301,7303],{"className":692,"code":7302,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Fstripe-webhook\u002Findex.ts\nimport { createClient } from 'jsr:@supabase\u002Fsupabase-js@2'\nimport Stripe from 'npm:stripe'\n\nconst stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)\nconst supabase = createClient(\n  Deno.env.get('SUPABASE_URL')!,\n  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n)\n\nDeno.serve(async (req: Request) => {\n  \u002F\u002F 1. Verify signature\n  \u002F\u002F 2. Handle event types\n  \u002F\u002F 3. Log to webhook_logs table\n  \u002F\u002F 4. Return success\n})\n",[458,7304,7305,7309,7327,7341,7345,7381,7393,7417,7441,7445,7449,7475,7480,7485,7490,7495],{"__ignoreMap":566},[570,7306,7307],{"class":572,"line":573},[570,7308,2443],{"class":576},[570,7310,7311,7313,7315,7317,7319,7321,7323,7325],{"class":572,"line":580},[570,7312,1851],{"class":803},[570,7314,791],{"class":717},[570,7316,1856],{"class":744},[570,7318,797],{"class":717},[570,7320,1861],{"class":803},[570,7322,972],{"class":717},[570,7324,1866],{"class":587},[570,7326,999],{"class":717},[570,7328,7329,7331,7333,7335,7337,7339],{"class":572,"line":597},[570,7330,1851],{"class":803},[570,7332,2468],{"class":744},[570,7334,2009],{"class":803},[570,7336,972],{"class":717},[570,7338,2475],{"class":587},[570,7340,999],{"class":717},[570,7342,7343],{"class":572,"line":604},[570,7344,601],{"emptyLinePlaceholder":600},[570,7346,7347,7349,7351,7353,7355,7357,7359,7361,7363,7365,7367,7369,7371,7373,7375,7377,7379],{"class":572,"line":610},[570,7348,1043],{"class":710},[570,7350,2488],{"class":744},[570,7352,1132],{"class":717},[570,7354,591],{"class":717},[570,7356,2495],{"class":751},[570,7358,2498],{"class":744},[570,7360,748],{"class":717},[570,7362,1719],{"class":744},[570,7364,748],{"class":717},[570,7366,1724],{"class":751},[570,7368,755],{"class":744},[570,7370,967],{"class":717},[570,7372,1764],{"class":587},[570,7374,967],{"class":717},[570,7376,773],{"class":744},[570,7378,866],{"class":717},[570,7380,1014],{"class":744},[570,7382,7383,7385,7387,7389,7391],{"class":572,"line":620},[570,7384,1043],{"class":710},[570,7386,2527],{"class":744},[570,7388,1132],{"class":717},[570,7390,1856],{"class":751},[570,7392,1140],{"class":744},[570,7394,7395,7397,7399,7401,7403,7405,7407,7409,7411,7413,7415],{"class":572,"line":625},[570,7396,2538],{"class":744},[570,7398,748],{"class":717},[570,7400,1719],{"class":744},[570,7402,748],{"class":717},[570,7404,1724],{"class":751},[570,7406,755],{"class":744},[570,7408,967],{"class":717},[570,7410,1796],{"class":587},[570,7412,967],{"class":717},[570,7414,773],{"class":744},[570,7416,1941],{"class":717},[570,7418,7419,7421,7423,7425,7427,7429,7431,7433,7435,7437,7439],{"class":572,"line":631},[570,7420,2538],{"class":744},[570,7422,748],{"class":717},[570,7424,1719],{"class":744},[570,7426,748],{"class":717},[570,7428,1724],{"class":751},[570,7430,755],{"class":744},[570,7432,967],{"class":717},[570,7434,1828],{"class":587},[570,7436,967],{"class":717},[570,7438,773],{"class":744},[570,7440,1738],{"class":717},[570,7442,7443],{"class":572,"line":643},[570,7444,1014],{"class":744},[570,7446,7447],{"class":572,"line":648},[570,7448,601],{"emptyLinePlaceholder":600},[570,7450,7451,7453,7455,7457,7459,7461,7463,7465,7467,7469,7471,7473],{"class":572,"line":654},[570,7452,745],{"class":744},[570,7454,748],{"class":717},[570,7456,752],{"class":751},[570,7458,755],{"class":744},[570,7460,758],{"class":710},[570,7462,761],{"class":717},[570,7464,765],{"class":764},[570,7466,727],{"class":717},[570,7468,770],{"class":583},[570,7470,773],{"class":717},[570,7472,776],{"class":710},[570,7474,718],{"class":717},[570,7476,7477],{"class":572,"line":666},[570,7478,7479],{"class":576},"  \u002F\u002F 1. Verify signature\n",[570,7481,7482],{"class":572,"line":671},[570,7483,7484],{"class":576},"  \u002F\u002F 2. Handle event types\n",[570,7486,7487],{"class":572,"line":677},[570,7488,7489],{"class":576},"  \u002F\u002F 3. Log to webhook_logs table\n",[570,7491,7492],{"class":572,"line":898},[570,7493,7494],{"class":576},"  \u002F\u002F 4. Return success\n",[570,7496,7497,7499],{"class":572,"line":904},[570,7498,863],{"class":717},[570,7500,1014],{"class":744},[432,7502,7504],{"id":7503},"bài-tập-6-contact-form-api-với-email-notification","Bài tập 6: Contact Form API với Email Notification",[436,7506,7507],{},[440,7508,7265],{},[7197,7510,7511,7514,7517,7520,7523,7526,7529],{},[7200,7512,7513],{},"Tạo Edge Function xử lý form liên hệ từ website",[7200,7515,7516],{},"Validate input (tên, email, nội dung không được trống)",[7200,7518,7519],{},"Lưu submission vào database",[7200,7521,7522],{},"Gửi email thông báo cho admin qua Resend",[7200,7524,7525],{},"Gửi email xác nhận cho người gửi",[7200,7527,7528],{},"Rate limiting: mỗi IP chỉ được gửi 3 form\u002Fgiờ",[7200,7530,7531],{},"Trả về CAPTCHA token verification (Turnstile)",[432,7533,7535],{"id":7534},"bài-tập-7-semantic-search-cho-knowledge-base","Bài tập 7: Semantic Search cho Knowledge Base",[436,7537,7538],{},[440,7539,7265],{},[7197,7541,7542,7549,7552,7559,7562],{},[7200,7543,7544,7545,7548],{},"Tạo bảng ",[458,7546,7547],{},"articles"," với title, content, embedding (VECTOR)",[7200,7550,7551],{},"Viết Edge Function để tạo và lưu embedding khi thêm\u002Fsửa article",[7200,7553,7554,7555,7558],{},"Viết hàm ",[458,7556,7557],{},"match_articles"," trong Postgres để semantic search",[7200,7560,7561],{},"Hiển thị kết quả với similarity score, highlight đoạn text liên quan",[7200,7563,7564],{},"Hỗ trợ kết hợp keyword search + semantic search (hybrid)",[436,7566,7567],{},[440,7568,7299],{},[561,7570,7572],{"className":692,"code":7571,"language":694,"meta":566,"style":566},"\u002F\u002F supabase\u002Ffunctions\u002Findex-article\u002Findex.ts\n\u002F\u002F Tự động tạo embedding khi article được tạo\u002Fcập nhật\n\nDeno.serve(async (req: Request) => {\n  const { article_id, title, content } = await req.json()\n\n  \u002F\u002F Tạo embedding từ title + content\n  const text = `${title}\\n\\n${content}`\n  const embedding = await generateEmbedding(text)\n\n  \u002F\u002F Lưu embedding vào article\n  const supabase = createClient(\n    Deno.env.get('SUPABASE_URL')!,\n    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!\n  )\n\n  await supabase.from('articles')\n    .update({ embedding })\n    .eq('id', article_id)\n\n  return new Response(JSON.stringify({ success: true }))\n})\n",[458,7573,7574,7579,7584,7588,7614,7645,7649,7654,7677,7696,7700,7705,7717,7741,7765,7769,7773,7793,7809,7830,7834,7864],{"__ignoreMap":566},[570,7575,7576],{"class":572,"line":573},[570,7577,7578],{"class":576},"\u002F\u002F supabase\u002Ffunctions\u002Findex-article\u002Findex.ts\n",[570,7580,7581],{"class":572,"line":580},[570,7582,7583],{"class":576},"\u002F\u002F Tự động tạo embedding khi article được tạo\u002Fcập nhật\n",[570,7585,7586],{"class":572,"line":597},[570,7587,601],{"emptyLinePlaceholder":600},[570,7589,7590,7592,7594,7596,7598,7600,7602,7604,7606,7608,7610,7612],{"class":572,"line":604},[570,7591,745],{"class":744},[570,7593,748],{"class":717},[570,7595,752],{"class":751},[570,7597,755],{"class":744},[570,7599,758],{"class":710},[570,7601,761],{"class":717},[570,7603,765],{"class":764},[570,7605,727],{"class":717},[570,7607,770],{"class":583},[570,7609,773],{"class":717},[570,7611,776],{"class":710},[570,7613,718],{"class":717},[570,7615,7616,7618,7620,7623,7625,7627,7629,7631,7633,7635,7637,7639,7641,7643],{"class":572,"line":610},[570,7617,788],{"class":710},[570,7619,791],{"class":717},[570,7621,7622],{"class":744}," article_id",[570,7624,943],{"class":717},[570,7626,6771],{"class":744},[570,7628,943],{"class":717},[570,7630,3459],{"class":744},[570,7632,797],{"class":717},[570,7634,800],{"class":717},[570,7636,804],{"class":803},[570,7638,807],{"class":744},[570,7640,748],{"class":717},[570,7642,812],{"class":751},[570,7644,895],{"class":723},[570,7646,7647],{"class":572,"line":620},[570,7648,601],{"emptyLinePlaceholder":600},[570,7650,7651],{"class":572,"line":625},[570,7652,7653],{"class":576},"  \u002F\u002F Tạo embedding từ title + content\n",[570,7655,7656,7658,7660,7662,7665,7667,7669,7671,7673,7675],{"class":572,"line":631},[570,7657,788],{"class":710},[570,7659,5535],{"class":744},[570,7661,800],{"class":717},[570,7663,7664],{"class":717}," `${",[570,7666,6343],{"class":744},[570,7668,863],{"class":717},[570,7670,6376],{"class":744},[570,7672,857],{"class":717},[570,7674,5376],{"class":744},[570,7676,6622],{"class":717},[570,7678,7679,7681,7683,7685,7687,7690,7692,7694],{"class":572,"line":643},[570,7680,788],{"class":710},[570,7682,5268],{"class":744},[570,7684,800],{"class":717},[570,7686,804],{"class":803},[570,7688,7689],{"class":751}," generateEmbedding",[570,7691,755],{"class":723},[570,7693,2667],{"class":744},[570,7695,1014],{"class":723},[570,7697,7698],{"class":572,"line":648},[570,7699,601],{"emptyLinePlaceholder":600},[570,7701,7702],{"class":572,"line":654},[570,7703,7704],{"class":576},"  \u002F\u002F Lưu embedding vào article\n",[570,7706,7707,7709,7711,7713,7715],{"class":572,"line":666},[570,7708,788],{"class":710},[570,7710,1061],{"class":744},[570,7712,800],{"class":717},[570,7714,1856],{"class":751},[570,7716,1140],{"class":723},[570,7718,7719,7721,7723,7725,7727,7729,7731,7733,7735,7737,7739],{"class":572,"line":671},[570,7720,1920],{"class":744},[570,7722,748],{"class":717},[570,7724,1719],{"class":744},[570,7726,748],{"class":717},[570,7728,1724],{"class":751},[570,7730,755],{"class":723},[570,7732,967],{"class":717},[570,7734,1796],{"class":587},[570,7736,967],{"class":717},[570,7738,773],{"class":723},[570,7740,1941],{"class":717},[570,7742,7743,7745,7747,7749,7751,7753,7755,7757,7759,7761,7763],{"class":572,"line":677},[570,7744,1920],{"class":744},[570,7746,748],{"class":717},[570,7748,1719],{"class":744},[570,7750,748],{"class":717},[570,7752,1724],{"class":751},[570,7754,755],{"class":723},[570,7756,967],{"class":717},[570,7758,1828],{"class":587},[570,7760,967],{"class":717},[570,7762,773],{"class":723},[570,7764,1738],{"class":717},[570,7766,7767],{"class":572,"line":898},[570,7768,1970],{"class":723},[570,7770,7771],{"class":572,"line":904},[570,7772,601],{"emptyLinePlaceholder":600},[570,7774,7775,7777,7779,7781,7783,7785,7787,7789,7791],{"class":572,"line":909},[570,7776,4243],{"class":803},[570,7778,1061],{"class":744},[570,7780,748],{"class":717},[570,7782,2009],{"class":751},[570,7784,755],{"class":723},[570,7786,967],{"class":717},[570,7788,7547],{"class":587},[570,7790,967],{"class":717},[570,7792,1014],{"class":723},[570,7794,7795,7797,7799,7801,7803,7805,7807],{"class":572,"line":915},[570,7796,2006],{"class":717},[570,7798,3059],{"class":751},[570,7800,755],{"class":723},[570,7802,1247],{"class":717},[570,7804,5268],{"class":744},[570,7806,797],{"class":717},[570,7808,1014],{"class":723},[570,7810,7811,7813,7815,7817,7819,7822,7824,7826,7828],{"class":572,"line":948},[570,7812,2006],{"class":717},[570,7814,3087],{"class":751},[570,7816,755],{"class":723},[570,7818,967],{"class":717},[570,7820,7821],{"class":587},"id",[570,7823,967],{"class":717},[570,7825,943],{"class":717},[570,7827,7622],{"class":744},[570,7829,1014],{"class":723},[570,7831,7832],{"class":572,"line":958},[570,7833,601],{"emptyLinePlaceholder":600},[570,7835,7836,7838,7840,7842,7844,7846,7848,7850,7852,7854,7856,7858,7860,7862],{"class":572,"line":982},[570,7837,918],{"class":803},[570,7839,591],{"class":717},[570,7841,923],{"class":751},[570,7843,755],{"class":723},[570,7845,928],{"class":744},[570,7847,748],{"class":717},[570,7849,933],{"class":751},[570,7851,755],{"class":723},[570,7853,1247],{"class":717},[570,7855,1500],{"class":723},[570,7857,727],{"class":717},[570,7859,1506],{"class":1505},[570,7861,797],{"class":717},[570,7863,6799],{"class":723},[570,7865,7866,7868],{"class":572,"line":1002},[570,7867,863],{"class":717},[570,7869,1014],{"class":744},[432,7871,7873],{"id":7872},"bài-tập-8-rag-chatbot-với-document-context","Bài tập 8: RAG Chatbot với Document Context",[436,7875,7876],{},[440,7877,7265],{},[7197,7879,7880,7883,7886,7889,7892,7895],{},[7200,7881,7882],{},"Upload PDF\u002Ftext documents, chunk thành các đoạn nhỏ (500-1000 tokens)",[7200,7884,7885],{},"Tạo embedding cho từng chunk, lưu vào pgvector",[7200,7887,7888],{},"Xây dựng chat UI: user gửi câu hỏi, system tìm context + gọi LLM",[7200,7890,7891],{},"Hiển thị sources (trích dẫn document + chunk) kèm câu trả lời",[7200,7893,7894],{},"Xử lý streaming response từ LLM (hiển thị từng chữ)",[7200,7896,7897],{},"Hỗ trợ follow-up questions (giữ conversation history)",[436,7899,7900],{},[440,7901,7902],{},"Kiến trúc gợi ý:",[561,7904,7907],{"className":7905,"code":7906,"language":2667},[4784],"Upload Doc -> Chunking -> Embedding -> pgvector\n                                         |\nUser Query -> Embedding -> Vector Search -> Top-K Chunks\n                                               |\n                                    Context + Query -> OpenAI -> Answer + Sources\n",[458,7908,7906],{"__ignoreMap":566},[4529,7910],{},[7912,7913],"navigation",{":next":7914,"prev":7915},"false","3.storage-realtime.md",[7917,7918,7919],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":566,"searchDepth":580,"depth":580,"links":7921},[7922,7933,7937,7948],{"id":429,"depth":580,"text":430,"children":7923},[7924,7925,7926,7927,7928,7929,7930,7931,7932],{"id":434,"depth":597,"text":11},{"id":468,"depth":597,"text":469},{"id":555,"depth":597,"text":556},{"id":1272,"depth":597,"text":1273},{"id":1582,"depth":597,"text":1583},{"id":1837,"depth":597,"text":1838},{"id":2326,"depth":597,"text":2327},{"id":2427,"depth":597,"text":2428},{"id":4373,"depth":597,"text":4374},{"id":4533,"depth":580,"text":4534,"children":7934},[7935,7936],{"id":4537,"depth":597,"text":4538},{"id":4653,"depth":597,"text":4654},{"id":4792,"depth":580,"text":4793,"children":7938},[7939,7940,7941,7942,7943,7944,7945,7946,7947],{"id":4796,"depth":597,"text":11},{"id":4812,"depth":597,"text":4813},{"id":4988,"depth":597,"text":4989},{"id":5430,"depth":597,"text":5431},{"id":5848,"depth":597,"text":5849},{"id":6877,"depth":597,"text":6878},{"id":7100,"depth":597,"text":7101},{"id":7191,"depth":597,"text":7192},{"id":7226,"depth":597,"text":7227},{"id":7255,"depth":580,"text":7256,"children":7949},[7950,7951,7952,7953],{"id":7259,"depth":597,"text":7260},{"id":7503,"depth":597,"text":7504},{"id":7534,"depth":597,"text":7535},{"id":7872,"depth":597,"text":7873},"Supabase Edge Functions (Deno, TypeScript, global) và AI\u002FVector (pgvector, RAG, semantic search, embeddings).","md",null,{},{"title":270,"icon":273},{"title":422,"description":7954},"5RtZDprfsU4B4mdHRlUVveVqObq4KN42rLBG3Hp-AGo",[7962,7964],{"title":265,"path":266,"stem":267,"description":7963,"icon":268,"children":-1},"Supabase Storage (S3-compatible, CDN, image transform) và Realtime (Broadcast, Presence, Postgres Changes CDC).",{"title":275,"path":276,"stem":277,"description":7965,"icon":181,"children":-1},"Supabase API reference — REST API (PostgREST), GraphQL (pg_graphql), Client SDKs (JS, Flutter, Python, C#, Swift, Kotlin), CLI.",1783490368503]