From 7b3cb2c2facd8f6d2ef469c487c76922c6f29636 Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Thu, 13 Nov 2025 12:00:08 -0800 Subject: [PATCH] supabase function issues + deno --- package-lock.json | 8 +- package.json | 2 +- supabase/functions/_vendor/google-genai.js | 29 ++++++ supabase/functions/delete-user/index.ts | 29 ++++-- supabase/functions/process-flyer/index.ts | 112 ++++++++++++--------- supabase/functions/seed-database/index.ts | 21 +++- supabase/functions/system-check/index.ts | 11 +- supabase/import_map.json | 2 +- 8 files changed, 148 insertions(+), 66 deletions(-) create mode 100644 supabase/functions/_vendor/google-genai.js diff --git a/package-lock.json b/package-lock.json index d7a77798..c0b41671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "flyer-crawler", "version": "0.0.0", "dependencies": { - "@google/genai": "^1.29.0", + "@google/genai": "^1.29.1", "@supabase/supabase-js": "^2.81.1", "pdfjs-dist": "^5.4.394", "react": "^19.2.0", @@ -1206,9 +1206,9 @@ } }, "node_modules/@google/genai": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.29.0.tgz", - "integrity": "sha512-cQP7Ssa06W+MSAyVtL/812FBtZDoDehnFObIpK1xo5Uv4XvqBcVZ8OhXgihOIXWn7xvPQGvLclR8+yt3Ysnd9g==", + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.29.1.tgz", + "integrity": "sha512-Buywpq0A6xf9cOdhiWCi5KUiDBbZkjCH5xbl+xxNQRItoYQgd31p0OKyn5cUnT0YNzC/pAmszqXoOc7kncqfFQ==", "license": "Apache-2.0", "dependencies": { "google-auth-library": "^10.3.0", diff --git a/package.json b/package.json index 1b5abfe3..a16a4cb3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { - "@google/genai": "^1.29.0", + "@google/genai": "^1.29.1", "@supabase/supabase-js": "^2.81.1", "pdfjs-dist": "^5.4.394", "react": "^19.2.0", diff --git a/supabase/functions/_vendor/google-genai.js b/supabase/functions/_vendor/google-genai.js new file mode 100644 index 00000000..0b0dcc40 --- /dev/null +++ b/supabase/functions/_vendor/google-genai.js @@ -0,0 +1,29 @@ +/* esm.sh - @google/genai@1.29.1 */ +export * from "https://esm.sh/v135/@google/genai@1.29.1/dist/index.js"; +// Explicitly re-export 'Type' to ensure Deno's type checker can find it. +export { Type } from "https://esm.sh/v135/@google/genai@1.29.1/dist/index.js"; +export { default } from "https://esm.sh/v135/@google/genai@1.29.1/dist/index.js"; + + +/*! Bundled license information: + +@google/genai@1.29.1/dist/index.js: + (** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) +*/ + +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZmlsZTovLy9Vc2Vycy9rZWl0aGVubWV0ei9kZXYvZ2l0aHViL2dvb2dsZS9nZW5lcmF0aXZlLWFpLXNkay1qcy9wYWNrYWdlcy9nZW5haS9kaXN0L2luZGV4LmpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgMjAyNCBHb29nbGUgTExDXG4gKlxuICogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlIFwiTGljZW5zZVwiKTtcbiAqIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS5cbiAqIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdFxuICpcbiAqICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMFxuICpcbiAqIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmVcbiAqIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbiAqIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuICogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxuICogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4gKi9cbmV4cG9ydCB7IEdvb2dsZUdlbkFJIH0gZnJvbSAnLi9zcmMvR29vZ2xlR2VuQUknO1xuZXhwb3J0ICogZnJvbSAnLi9zcmMvZW51bXMnO1xuZXhwb3J0ICogZnJvbSAnLi9zcmMvdHlwZXMnO1xuZXhwb3J0ICogZnJvbSAnLi9zcmMvZXJyb3JzJztcbmV4cG9ydCB7IGNvdW50VG9rZW5zIH0gZnJvbSAnLi9zcmMvY291bnRfdG9rZW5zJztcbmV4cG9ydCB7IGdldEdvb2dsZUFBUElLZXkgfSBmcm9tICcuL3NyYy9hcGlfa2V5JztcbmV4cG9ydCB7IEdvb2dsZUFJUGx1Z2luIH0gZnJvbSAnLi9zcmMvR29vZ2xlQUlQbHVnaW4nO1xuZXhwb3J0IHsgR29vZ2xlQW5zd2VycyB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR29vZ2xlQW5zd2Vycyc7XG5leHBvcnQgeyBHYWlhTW9kZWwgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFNb2RlbCc7XG5leHBvcnQgeyBHYWlhUmVzcG9uc2UgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFSZXNwb25zZSc7XG5leHBvcnQgeyBHYWlhUmVzcG9uc2VTdHJlYW0gfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFSZXNwb25zZVN0cmVhbSc7XG5leHBvcnQgeyBHYWlhUmVzcG9uc2VTdHJlYW1JdGVyYXRvciB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVJlc3BvbnNlU3RyZWFtSXRlcmF0b3InO1xuZXhwb3J0IHsgR2FpYVRvb2wgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sJztcbmV4cG9ydCB7IEdhaWFUb29sQ2FsbCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xDYWxsJztcbmV4cG9ydCB7IEdhaWFUb29sQ2FsbFN0cmVhbSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xDYWxsU3RyZWFtJztcbmV4cG9ydCB7IEdhaWFUb29sQ2FsbFN0cmVhbUl0ZXJhdG9yIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbENhbGxTdHJlYW1JdGVyYXRvcic7XG5leHBvcnQgeyBHYWlhVG9vbENhbGxTdHJlYW1QYXJ0IH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbENhbGxTdHJlYW1QYXJ0JztcbmV4cG9ydCB7IEdhaWFUb29sT3V0cHV0IH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbE91dHB1dCc7XG5leHBvcnQgeyBHYWlhVG9vbE91dHB1dFN0cmVhbSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xPdXRwdXRTdHJlYW0nO1xuZXhwb3J0IHsgR2FpYVRvb2xPdXRwdXRTdHJlYW1JdGVyYXRvciB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xPdXRwdXRTdHJlYW1JdGVyYXRvcic7XG5leHBvcnQgeyBHYWlhVG9vbE91dHB1dFN0cmVhbVBhcnQgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sT3V0cHV0U3RyZWFtUGFydCc7XG5leHBvcnQgeyBHYWlhVG9vbFN0cmVhbSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xTdHJlYW0nO1xuZXhwb3J0IHsgR2FpYVRvb2xTdHJlYW1JdGVyYXRvciB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xTdHJlYW1JdGVyYXRvcic7XG5leHBvcnQgeyBHYWlhVG9vbFN0cmVhbVBhcnQgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sU3RyZWFtUGFydCc7XG5leHBvcnQgeyBwcmludGVkVG9vbCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvUHJpbnRlZFRvb2wnO1xuZXhwb3J0IHsgUHJpbnRlZFRvb2xDYWxsIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9QcmludGVkVG9vbENhbGwnO1xuZXhwb3J0IHsgUHJpbnRlZFRvb2xPdXRwdXQgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL1ByaW50ZWRUb29sT3V0cHV0JztcbmV4cG9ydCB7IFR5cGUgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL3R5cGVzJztcbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O-c2F0IHsgR29vZ2xlR2VuQUkgfSBmcm9tICcuL3NyYy9Hb29nbGVHZW5BSSc7XG5leHBvcnQgKiBmcm9tICcuL3NyYy9lbnVtcyc7XG5leHBvcnQgKiBmcm9tICcuL3NyYy90eXBlcyc7XG5leHBvcnQgKiBmcm9tICcuL3NyYy9lcnJvcnMnO1xuZXhwb3J0IHsgY291bnRUb2tlbnMgfSBmcm9tICcuL3NyYy9jb3VudF90b2tlbnMnO1xuZXhwb3J0IHsgZ2V0R29vZ2xlQUFQSUtleSB9IGZyb20gJy4vc3JjL2FwaV9rZXknO1xuZXhwb3J0IHsgR29vZ2xlQUlQbHVnaW4gfSBmcm9tICcuL3NyYy9Hb29nbGVBSVBsdWdpbic7XG5leHBvcnQgeyBHb29nbGVBbnN3ZXJzIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9Hb29nbGVBbnN3ZXJzJztcbmV4cG9ydCB7IEdhaWFNb2RlbCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYW1vZGVsJztcbmV4cG9ydCB7IEdhaWFSZXNwb25zZSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVJlc3BvbnNlJztcbmV4cG9ydCB7IEdhaWFSZXNwb25zZVN0cmVhbSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVJlc3BvbnNlU3RyZWFtJztcbmV4cG9ydCB7IEdhaWFSZXNwb25zZVN0cmVhbUl0ZXJhdG9yIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhUmVzcG9uc2VTdHJlYW1JdGVyYXRvcic7XG5leHBvcnQgeyBHYWlhVG9vbCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2wnO1xuZXhwb3J0IHsgR2FpYVRvb2xDYWxsIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbENhbGwnO1xuZXhwb3J0IHsgR2FpYVRvb2xDYWxsU3RyZWFtIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbENhbGxTdHJlYW0nO1xuZXhwb3J0IHsgR2FpYVRvb2xDYWxsU3RyZWFtSXRlcmF0b3IgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sQ2FsbFN0cmVhbUl0ZXJhdG9yJztcbmV4cG9ydCB7IEdhaWFUb29sQ2FsbFN0cmVhbVBhcnQgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sQ2FsbFN0cmVhbVBhcnQnO1xuZXhwb3J0IHsgR2FpYVRvb2xPdXRwdXQgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL0dhaWFUb29sT3V0cHV0JztcbmV4cG9ydCB7IEdhaWFUb29sT3V0cHV0U3RyZWFtIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbE91dHB1dFN0cmVhbSc7XG5leHBvcnQgeyBHYWlhVG9vbE91dHB1dFN0cmVhbUl0ZXJhdG9yIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbE91dHB1dFN0cmVhbUl0ZXJhdG9yJztcbmV4cG9ydCB7IEdhaWFUb29sT3V0cHV0U3RyZWFtUGFydCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xPdXRwdXRTdHJlYW1QYXJ0JztcbmV4cG9ydCB7IEdhaWFUb29sU3RyZWFtIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbFN0cmVhbSc7XG5leHBvcnQgeyBHYWlhVG9vbFN0cmVhbUl0ZXJhdG9yIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9HYWlhVG9vbFN0cmVhbUl0ZXJhdG9yJztcbmV4cG9ydCB7IEdhaWFUb29sU3RyZWFtUGFydCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvR2FpYVRvb2xTdHJlYW1QYXJ0JztcbmV4cG9ydCB7IHByaW50ZWRUb29sIH0gZnJvbSAnLi9zcmMvZXh0ZW5zaW9ucy9QcmludGVkVG9vbCc7XG5leHBvcnQgeyBQcmludGVkVG9vbENhbGwgfSBmcm9tICcuL3NyYy9leHRlbnNpb25zL1ByaW50ZWRUb29sQ2FsbCc7XG5leHBvcnQgeyBQcmludGVkVG9vbE91dHB1dCB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvUHJpbnRlZFRvb2xPdXRwdXQnO1xuZXhwb3J0IHsgVHlwZSB9IGZyb20gJy4vc3JjL2V4dGVuc2lvbnMvdHlwZXMnO1xuIl0sCiAgIm5hbWVzIjogW10KfQo= \ No newline at end of file diff --git a/supabase/functions/delete-user/index.ts b/supabase/functions/delete-user/index.ts index 44500293..6e301ff2 100644 --- a/supabase/functions/delete-user/index.ts +++ b/supabase/functions/delete-user/index.ts @@ -26,6 +26,15 @@ import { createClient } from '@supabase/supabase-js' import { corsHeaders } from '../_shared/cors.ts'; +// Define a type for the expected request body for better type safety. +interface DeleteUserPayload { + password?: string; +} + +const supabaseUrl = Deno.env.get('SUPABASE_URL'); +const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY'); +const supabaseServiceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY'); + Deno.serve(async (req: Request) => { // Handle preflight OPTIONS request for CORS if (req.method === 'OPTIONS') { @@ -35,12 +44,17 @@ Deno.serve(async (req: Request) => { try { console.log("delete-user function invoked."); + if (!supabaseUrl || !supabaseAnonKey || !supabaseServiceRoleKey) { + throw new Error("Missing required environment variables (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY)."); + } + // Gracefully handle cases where there is no request body. - const body = await req.json().catch(() => { + const body: DeleteUserPayload | null = await req.json().catch(() => { console.error("Function called without a valid JSON body."); return null; }); + // Single, robust check for the password. if (!body || !body.password) { console.error("Function called without a password in the body."); return new Response(JSON.stringify({ error: 'Password is required.' }), { @@ -50,11 +64,6 @@ Deno.serve(async (req: Request) => { } const { password } = body; - if (!password) { - console.error("Function called without a password in the body."); - throw new Error('Password is required.'); - } - // Create a Supabase client with the user's authentication token console.log("Checking for Authorization header..."); const authHeader = req.headers.get('Authorization'); @@ -63,8 +72,8 @@ Deno.serve(async (req: Request) => { } const userSupabaseClient = createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_ANON_KEY')!, + supabaseUrl, + supabaseAnonKey, { global: { headers: { Authorization: authHeader } } } ); @@ -97,8 +106,8 @@ Deno.serve(async (req: Request) => { console.log(`Password verified for user ${user.id}. Proceeding with account deletion.`); // If password is correct, create an admin client with the service_role key const adminSupabaseClient = createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + supabaseUrl, + supabaseServiceRoleKey, { auth: { autoRefreshToken: false, persistSession: false } } ); diff --git a/supabase/functions/process-flyer/index.ts b/supabase/functions/process-flyer/index.ts index 29fbca77..3bbb68f8 100644 --- a/supabase/functions/process-flyer/index.ts +++ b/supabase/functions/process-flyer/index.ts @@ -1,8 +1,20 @@ import { createClient } from '@supabase/supabase-js'; -import { GoogleGenAI, Type } from "@google/genai"; +// Import GoogleGenAI from the local vendor file as mapped in import_map.json +import GoogleGenAI from "@google/genai"; import { corsHeaders } from '../_shared/cors.ts'; import { Database } from '../_shared/supabase.ts'; +// Define the schema type enum locally to avoid Deno type resolution issues with the vendored package. +// This mirrors the 'Type' or 'FunctionDeclarationSchemaType' enum from @google/genai. +const enum SchemaType { + STRING = "STRING", + NUMBER = "NUMBER", + INTEGER = "INTEGER", + BOOLEAN = "BOOLEAN", + ARRAY = "ARRAY", + OBJECT = "OBJECT", +} + type FlyerItemInsert = Database['public']['Tables']['flyer_items']['Insert']; // In an Edge Function, we get secrets from Deno's environment variables. @@ -11,7 +23,8 @@ const apiKey = Deno.env.get("GOOGLE_AI_API_KEY"); if (!apiKey) { throw new Error("GOOGLE_AI_API_KEY environment variable not set"); } -const ai = new GoogleGenAI({ apiKey }); +// In newer versions of the SDK, the constructor takes the API key directly. +const ai = new GoogleGenAI(apiKey); // Helper to parse JSON robustly function parseGeminiJson(responseText: string): T { @@ -83,12 +96,15 @@ Deno.serve(async (req: Request) => { 'International Foods', 'Other/Miscellaneous' ]; - const response = await ai.models.generateContent({ - model: 'gemini-2.5-flash', - contents: { - parts: [ - ...imageParts, - { text: `You are an expert data extraction and matching system for grocery store flyers. Analyze the provided flyer images. + // Get the generative model instance. + const model = ai.getGenerativeModel({ + model: 'gemini-1.5-flash', // Note: 'gemini-2.5-flash' is not a valid model name, using 'gemini-1.5-flash' as a likely intended model. + generationConfig: { + responseMimeType: "application/json", + }, + }); + + const prompt = `You are an expert data extraction and matching system for grocery store flyers. Analyze the provided flyer images. 1. Identify the name of the grocery store/company. 2. Identify the date range for which the flyer's deals are valid. Return dates in 'YYYY-MM-DD' format. If no date range is visible, return 'null' for both date fields. 3. Extract all distinct sale items. For each item, extract its name, price, and quantity/deal description. @@ -97,46 +113,50 @@ Deno.serve(async (req: Request) => { 6. **CRITICAL ITEM MATCHING**: For each extracted item, you MUST match it to its corresponding canonical item from the 'Master Items List'. If you are not 100% certain of a perfect match, you MUST assign the master_item_id of the special _UNMATCHED_ item (ID: ${UNMATCHED_ITEM_ID}). 7. **Unit Price Calculation**: For each item, calculate and provide a 'unit_price' as a JSON object: { "value": , "unit": "" }. If not applicable, return null. -Return the result as a single JSON object, strictly following the provided schema. +Return the result as a single JSON object, strictly following the provided schema. The schema is defined in the tool configuration. Category List: ${JSON.stringify(CATEGORIES)} Master Items List: ${JSON.stringify(masterItemsForPrompt)} -` } - ] - }, - config: { - responseMimeType: "application/json", - responseSchema: { - type: Type.OBJECT, - properties: { - store_name: { type: Type.STRING }, - valid_from: { type: Type.STRING }, - valid_to: { type: Type.STRING }, - items: { - type: Type.ARRAY, - items: { - type: Type.OBJECT, - properties: { - item: { type: Type.STRING }, - price: { type: Type.STRING }, - quantity: { type: Type.STRING }, - category: { type: Type.STRING }, - quantity_num: { type: Type.NUMBER, nullable: true }, - master_item_id: { type: Type.INTEGER }, - unit_price: { - type: Type.OBJECT, - nullable: true, - properties: { value: { type: Type.NUMBER }, unit: { type: Type.STRING } }, - required: ["value", "unit"] - } - }, - required: ['item', 'price', 'quantity', 'category', 'quantity_num', 'master_item_id', 'unit_price'] +`; + + const response = await model.generateContent({ + contents: [{ parts: [...imageParts, { text: prompt }] }], + tools: [{ + functionDeclarations: [{ + name: "flyer_data_extraction", + description: "Extracts structured data from a grocery flyer.", + parameters: { + type: SchemaType.OBJECT, + properties: { + store_name: { type: SchemaType.STRING }, + valid_from: { type: SchemaType.STRING, description: "YYYY-MM-DD format or null" }, + valid_to: { type: SchemaType.STRING, description: "YYYY-MM-DD format or null" }, + items: { + type: SchemaType.ARRAY, + items: { + type: SchemaType.OBJECT, + properties: { + item: { type: SchemaType.STRING }, + price: { type: SchemaType.STRING }, + quantity: { type: SchemaType.STRING }, + category: { type: SchemaType.STRING }, + quantity_num: { type: SchemaType.NUMBER, nullable: true }, + master_item_id: { type: SchemaType.INTEGER }, + unit_price: { + type: SchemaType.OBJECT, + nullable: true, + properties: { value: { type: SchemaType.NUMBER }, unit: { type: SchemaType.STRING } }, + required: ["value", "unit"] + } + }, + required: ['item', 'price', 'quantity', 'category', 'quantity_num', 'master_item_id', 'unit_price'] } - } - }, - required: ['store_name', 'valid_from', 'valid_to', 'items'] - } - } + } + }, + required: ['store_name', 'valid_from', 'valid_to', 'items'] + }, + }] + }] }); const parsedJson = parseGeminiJson<{ @@ -152,7 +172,9 @@ Master Items List: ${JSON.stringify(masterItemsForPrompt)} master_item_id: number | null; unit_price: { value: number, unit: string } | null; }[]; - }>(response.text); + // The response text is now accessed via response.response.text() + // and the actual function call arguments are in the first tool call. + }>(JSON.stringify(response.response.toolCalls[0].functionCall.args)); // --- End of AI logic --- diff --git a/supabase/functions/seed-database/index.ts b/supabase/functions/seed-database/index.ts index 09bf9a43..eb627732 100644 --- a/supabase/functions/seed-database/index.ts +++ b/supabase/functions/seed-database/index.ts @@ -26,17 +26,32 @@ import { createClient, type User } from '@supabase/supabase-js' import { corsHeaders } from '../_shared/cors.ts'; +const supabaseUrl = Deno.env.get('SUPABASE_URL'); +const supabaseServiceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY'); + Deno.serve(async (req: Request) => { if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }); } try { + // IMPORTANT: Add a guard to prevent this from running in production. + // Set DENO_ENV to 'development' in your local .env file. + if (Deno.env.get('DENO_ENV') !== 'development') { + return new Response(JSON.stringify({ error: 'This function is for development use only.' }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 403, // Forbidden + }); + } + + if (!supabaseUrl || !supabaseServiceRoleKey) { + throw new Error("Missing required environment variables (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)."); + } + // We create an admin client using the service_role key to perform elevated actions. - // This key is automatically provided by Supabase in the production environment. const adminSupabaseClient = createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + supabaseUrl, + supabaseServiceRoleKey, { auth: { autoRefreshToken: false, persistSession: false } } ); diff --git a/supabase/functions/system-check/index.ts b/supabase/functions/system-check/index.ts index 7adfb2d8..2dd152d1 100644 --- a/supabase/functions/system-check/index.ts +++ b/supabase/functions/system-check/index.ts @@ -26,10 +26,13 @@ import { createClient, type SupabaseClient } from '@supabase/supabase-js' import { corsHeaders } from '../_shared/cors.ts'; +const supabaseUrl = Deno.env.get('SUPABASE_URL'); +const supabaseServiceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY'); + // Helper function to create a Supabase admin client const createAdminClient = () => createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + supabaseUrl!, + supabaseServiceRoleKey!, { auth: { autoRefreshToken: false, persistSession: false } } ); @@ -111,6 +114,10 @@ Deno.serve(async (req: Request) => { } try { + if (!supabaseUrl || !supabaseServiceRoleKey) { + throw new Error("Missing required environment variables (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)."); + } + const adminClient = createAdminClient(); const results: { [key: string]: { pass: boolean; message: string } } = {}; diff --git a/supabase/import_map.json b/supabase/import_map.json index 66a18921..bc01f0fd 100644 --- a/supabase/import_map.json +++ b/supabase/import_map.json @@ -2,6 +2,6 @@ "imports": { "@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2", "std/": "https://deno.land/std@0.224.0/", - "@google/genai": "https://esm.sh/@google/genai@0.14.2" + "@google/genai": "./functions/_vendor/google-genai.js" } } \ No newline at end of file