diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3ec39d0..ba22a9b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,7 +9,7 @@ on: jobs: test: # The type of runner that the job will run on - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -27,13 +27,13 @@ jobs: - run: yarn test name: 🔬 Testing the repo - name: Archive code coverage results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: code-coverage-report path: testres.json - name: Uploading failing screenshots if: ${{ !success() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: failing-screenshots path: __screenshot-tests__/__image_snapshots__/__diff_output__ \ No newline at end of file diff --git a/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-clicking-the-pic-the-gallery-popup-is-opened-1-snap.png b/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-clicking-the-pic-the-gallery-popup-is-opened-1-snap.png index 8dc9a05..e41e140 100644 Binary files a/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-clicking-the-pic-the-gallery-popup-is-opened-1-snap.png and b/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-clicking-the-pic-the-gallery-popup-is-opened-1-snap.png differ diff --git a/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-should-be-shown-according-to-the-mobile-design-1-snap.png b/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-should-be-shown-according-to-the-mobile-design-1-snap.png index 5f1b6ee..b666e82 100644 Binary files a/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-should-be-shown-according-to-the-mobile-design-1-snap.png and b/__screenshot-tests__/__image_snapshots__/index-screentest-ts-the-home-page-should-be-shown-according-to-the-mobile-design-1-snap.png differ diff --git a/next.config.js b/next.config.js index a47e545..670a4a0 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,10 @@ const nextConfig = { reactStrictMode: true, webpack(config) { + config.module.rules.push({ + test: /supabase\/.*/, + use: 'ignore-loader', + }); config.module.rules.push({ test: /\.svg$/i, issuer: /\.[jt]sx?$/, @@ -12,6 +16,12 @@ const nextConfig = { }, images: { remotePatterns: [ + { + protocol: 'https', + hostname: 'fpbswrebvsmjdwekyznx.supabase.co', + port: '', + pathname: '**', + }, { protocol: 'https', hostname: 'content-eu.drive.amazonaws.com', diff --git a/package.json b/package.json index b425d6f..56f4eb7 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ }, "dependencies": { "@emotion/react": "^11.8.2", + "@supabase/supabase-js": "^2.45.4", "@types/express-useragent": "^1.0.2", "@types/jwk-to-pem": "^2.0.1", "@types/node-jose": "^1.1.10", "@types/uuid": "^8.3.4", "axios": "^0.27.2", + "buffer-image-size": "^0.6.4", "express-useragent": "^1.0.15", "jest-environment-jsdom": "^28.1.1", "jwk-to-pem": "^2.0.5", @@ -55,6 +57,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.3.0", + "ignore-loader": "^0.1.2", "jest": "^28.1.1", "jest-image-snapshot": "^5.1.0", "jest-puppeteer-docker": "https://github.com/bertuz/jest-puppeteer-docker.git#specify-docker-build", diff --git a/pages/api/galleryPhotos/index.ts b/pages/api/galleryPhotos/index.ts new file mode 100644 index 0000000..0e7e8d9 --- /dev/null +++ b/pages/api/galleryPhotos/index.ts @@ -0,0 +1,74 @@ +import sizeOf from 'buffer-image-size'; + +import type { NextApiRequest, NextApiResponse } from 'next'; + +type SupabaseStorageQueryResponse = ReadonlyArray<{ + name: string; +}>; + +const SUPABASE_PUBLIC_KEY = process.env.SUPABASE_PUBLIC_KEY || ' '; +const SUPABASE_HEADERS = new Headers({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${SUPABASE_PUBLIC_KEY}`, + apikey: SUPABASE_PUBLIC_KEY, +}); + +type ResponseData = ReadonlyArray<{ + contentProperties: { image: { height: number; width: number } }; + url: string; + name: string; +}>; + +export async function getImageData(): Promise { + const requestOptions = { + method: 'POST', + headers: SUPABASE_HEADERS, + body: JSON.stringify({ + prefix: '', + limit: 20, + offset: 0, + sortBy: { + column: 'created_at', + order: 'desc', + }, + search: '', + }), + }; + + const SUPABASE_DOMAIN = process.env.SUPABASE_DOMAIN || ''; + const IMAGES_OBJECTS_URL = `${SUPABASE_DOMAIN}/storage/v1/object/list/photos`; + const IMAGES_URL = `${SUPABASE_DOMAIN}/storage/v1/object/public/photos`; + const images = (await fetch(IMAGES_OBJECTS_URL, requestOptions).then( + (response) => response.json() + )) as SupabaseStorageQueryResponse; + + const imagesToReturn = []; + for (const image of images) { + const { name: nameWithExtension } = image; + const name = nameWithExtension.replace(/\.[a-zA-Z0-9]{1,3}$/, ''); + const url = `${IMAGES_URL}/${nameWithExtension}`; + const contentImage = await fetch(url, { + method: 'GET', + }); + const imageBuffer = Buffer.from(await contentImage.arrayBuffer()); + const imageSize = sizeOf(imageBuffer); + + imagesToReturn.push({ + url, + contentProperties: { + image: { height: imageSize.height, width: imageSize.width }, + }, + name, + }); + } + + return imagesToReturn; +} + +export default async function handler( + _: NextApiRequest, + res: NextApiResponse +) { + const imagesToReturn = await getImageData(); + res.status(200).json(imagesToReturn); +} diff --git a/pages/index.tsx b/pages/index.tsx index f554e6d..05cfe40 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -33,6 +33,8 @@ import useScreenSize from '../utils/useScreenSize'; import toBase64 from '../utils/toBase64'; import { isRunningAcceptanceTest } from '../utils/testUtils'; +import { createClient } from '@supabase/supabase-js'; + import { Transition } from 'react-transition-group'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -742,6 +744,17 @@ const Home: NextPage = ({ galleryPics }) => { With that in mind, this is what I currently have in my toolbox 🧑🏽‍💻:

    +
  • + Mobile +
      +
    • + + Flutter + +
    • +
    • Supabase
    • +
    +
  • Frontend
      @@ -764,8 +777,11 @@ const Home: NextPage = ({ galleryPics }) => {
      • Agile: kanban and scrum
      • Kubernetes
      • -
      • Hexagonal architecture
      • -
      • DDD
      • +
      • Clean architectures: hexagonal, DDD, even on front
      • +
      • + Foster async - yet effective - communication + within a team +
    @@ -994,51 +1010,55 @@ export async function getServerSideProps() { }; } - const response = await fetch( - 'https://www.amazon.it/drive/v1/nodes/mmVUOJzUS_KqKRykQrzFPA/children?asset=ALL&filters=kind%3A(FILE*+OR+FOLDER*)+AND+contentProperties.contentType%3A(image*)+AND+status%3A(AVAILABLE*)&limit=15&lowResThumbnail=true&searchOnFamily=true&sort=%5B%27contentProperties.contentDate+DESC%27%5D&tempLink=true&shareId=qFervNlenYwkjdQ1o26YOsWhld5fnsJ0t89xbcv2Vep&offset=0&resourceVersion=V2&ContentType=JSON&_=1660508015523' - ); + const supabaseUrl = process.env.SUPABASE_NEXT_PUBLIC_SUPABASE_URL || ''; + const supabaseAnonKey = + process.env.SUPABASE_NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; - if (!response?.body) { - return []; + const supabase = createClient(supabaseUrl, supabaseAnonKey, { + global: { + headers: { + Authorization: `Bearer ${process.env.SUPABASE_SUPABASE_SERVICE_ROLE_KEY}`, + }, + }, + }); + const { data, error } = await supabase + .from('gallery-images') + .select('url, name, height, width') + .limit(30) + .order('created_at', { ascending: false }); + + if (data === null) { + throw new Error(error?.message); } - const data = await response.json(); - - const images = data.data.map( - (photo: { - contentProperties: { image: { height: number; width: number } }; - tempLink: string; - name: string; - }) => { - const { height: originalHeight, width: originalWidth } = - photo.contentProperties.image; - const dimensionsRatio = originalHeight / originalWidth; - const thumbnailFitWidth = - originalHeight > 200 - ? [200, 200 / dimensionsRatio] - : [originalHeight, originalWidth]; - const thumbnailFit = - originalWidth > 150 - ? [thumbnailFitWidth[0] * dimensionsRatio, 150] - : thumbnailFitWidth; - - return { - src: photo.tempLink, - name: photo.name, - dimensions: { - ratio: dimensionsRatio, - thumbnail: { - height: thumbnailFit[0], - width: thumbnailFit[1], - }, - original: { - height: originalHeight, - width: originalWidth, - }, + const images = data.map((photo) => { + const { height: originalHeight, width: originalWidth } = photo; + const dimensionsRatio = originalHeight / originalWidth; + const thumbnailFitWidth = + originalHeight > 200 + ? [200, 200 / dimensionsRatio] + : [originalHeight, originalWidth]; + const thumbnailFit = + originalWidth > 150 + ? [thumbnailFitWidth[0] * dimensionsRatio, 150] + : thumbnailFitWidth; + + return { + src: photo.url, + name: photo.name, + dimensions: { + ratio: dimensionsRatio, + thumbnail: { + height: thumbnailFit[0], + width: thumbnailFit[1], }, - }; - } - ); + original: { + height: originalHeight, + width: originalWidth, + }, + }, + }; + }); return { props: { diff --git a/supabase/.gitignore b/supabase/.gitignore new file mode 100644 index 0000000..a3ad880 --- /dev/null +++ b/supabase/.gitignore @@ -0,0 +1,4 @@ +# Supabase +.branches +.temp +.env diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..1744970 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,161 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "bertuz.github.io" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. public and storage are always included. +schemas = ["public", "storage", "graphql_public"] +# Extra schemas to add to the search_path of every request. public is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv6) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = true +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }} ." + +# Use pre-defined map of phone number to OTP for testing. +[auth.sms.test_otp] +# 4152127777 = "123456" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +[auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" + +[analytics] +enabled = false +port = 54327 +vector_port = 54328 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" diff --git a/supabase/functions/after_insert_image/index.ts b/supabase/functions/after_insert_image/index.ts new file mode 100644 index 0000000..f24f8d6 --- /dev/null +++ b/supabase/functions/after_insert_image/index.ts @@ -0,0 +1,50 @@ +import { createClient } from 'jsr:@supabase/supabase-js@2'; +import sizeOf from 'npm:buffer-image-size'; + +import { Buffer } from 'node:buffer'; + +const SUPABASE_DOMAIN = Deno.env.get('SUPABASE_URL'); +const IMAGES_URL = `${SUPABASE_DOMAIN}/storage/v1/object/public/photos`; + +Deno.serve(async (req) => { + const { 'new-image': image } = await req.json(); + + const { name: nameWithExtension, id } = image; + console.log('id', id); + const name = nameWithExtension.replace(/\.[a-zA-Z0-9]{1,3}$/, ''); + const url = `${IMAGES_URL}/${nameWithExtension}`; + const contentImage = await fetch(url, { + method: 'GET', + }); + const imageBuffer = Buffer.from(await contentImage.arrayBuffer()); + const imageSize = sizeOf(imageBuffer); + + const dataToSend = { + id, + url, + height: imageSize.height, + width: imageSize.width, + name, + }; + console.log(dataToSend); + console.log(req.headers.get('Authorization')); + + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL') ?? '', + Deno.env.get('SUPABASE_ANON_KEY') ?? '', + { + global: { headers: { Authorization: req.headers.get('Authorization')! } }, + } + ); + + const { data, error } = await supabaseClient + .from('gallery-images') + .insert(dataToSend); + console.log(data); + console.log(error); + console.log(` inserted image data ${data}, error (if any): ${error}`); + + return new Response(JSON.stringify('Gotcha'), { + headers: { 'Content-Type': 'application/json' }, + }); +}); diff --git a/supabase/migrations/20240914225051_remote_schema.sql b/supabase/migrations/20240914225051_remote_schema.sql new file mode 100644 index 0000000..179ee71 --- /dev/null +++ b/supabase/migrations/20240914225051_remote_schema.sql @@ -0,0 +1,109 @@ + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium"; + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + +CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql"; + +CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions"; + +CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions"; + +CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions"; + +CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault"; + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions"; + +CREATE OR REPLACE FUNCTION "public"."after_insert_image"() RETURNS "text" + LANGUAGE "plpgsql" + AS $$ +declare + project_id text; + private_key text; +BEGIN +select decrypted_secret into project_id +from vault.decrypted_secrets +where name = 'PROJECT_ID'; +select decrypted_secret into private_key +from vault.decrypted_secrets +where name = 'PRIVATE_KEY'; + perform "net"."http_post"( + 'https://'||project_id||'.supabase.co/functions/v1/insert-image-metadata'::text, + jsonb_build_object( + 'new-image', to_jsonb(new.*) + ), + headers:='{"Content-Type": "application/json", "Authorization": "Bearer "||private_key||""}'::jsonb + ) as request_id; + + RETURN 'https://'||project_id; +END; +$$; + +ALTER FUNCTION "public"."after_insert_image"() OWNER TO "postgres"; + +SET default_tablespace = ''; + +SET default_table_access_method = "heap"; + +CREATE TABLE IF NOT EXISTS "public"."gallery-images" ( + "id" "uuid" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "url" "text" NOT NULL, + "name" "text" NOT NULL, + "height" bigint NOT NULL, + "width" bigint NOT NULL +); + +ALTER TABLE "public"."gallery-images" OWNER TO "postgres"; + +ALTER TABLE ONLY "public"."gallery-images" + ADD CONSTRAINT "gallery-images_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."gallery-images" + ADD CONSTRAINT "gallery-images_id_fkey" FOREIGN KEY ("id") REFERENCES "storage"."objects"("id") ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE "public"."gallery-images" ENABLE ROW LEVEL SECURITY; + +ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres"; + +GRANT USAGE ON SCHEMA "public" TO "postgres"; +GRANT USAGE ON SCHEMA "public" TO "anon"; +GRANT USAGE ON SCHEMA "public" TO "authenticated"; +GRANT USAGE ON SCHEMA "public" TO "service_role"; + +GRANT ALL ON FUNCTION "public"."after_insert_image"() TO "anon"; +GRANT ALL ON FUNCTION "public"."after_insert_image"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."after_insert_image"() TO "service_role"; + +GRANT ALL ON TABLE "public"."gallery-images" TO "anon"; +GRANT ALL ON TABLE "public"."gallery-images" TO "authenticated"; +GRANT ALL ON TABLE "public"."gallery-images" TO "service_role"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role"; + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role"; + +RESET ALL; diff --git a/supabase/migrations/20240915114926_remote_schema.sql b/supabase/migrations/20240915114926_remote_schema.sql new file mode 100644 index 0000000..dec2aa6 --- /dev/null +++ b/supabase/migrations/20240915114926_remote_schema.sql @@ -0,0 +1,89 @@ +drop function if exists "public"."after_insert_image"(); + +create table "public"."ress" ( + "content" text +); + + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.trigger_update_image_metadata() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +declare + project_id text; + private_key text; + net_req_id bigint; +BEGIN +select decrypted_secret into project_id +from vault.decrypted_secrets +where name = 'PROJECT_ID'; + +select decrypted_secret into private_key +from vault.decrypted_secrets +where name = 'PRIVATE_KEY'; + +select + net.http_post( + url:='https://'||project_id||'.supabase.co/functions/v1/after_insert_image', + body:=jsonb_build_object( + 'new-image', to_jsonb(new.*) + ), + headers:=json_build_object('Authorization', 'Bearer '||private_key)::jsonb + ) as request_id into net_req_id; + RETURN new; +exception + when others then + raise exception 'An error occurred: %', SQLERRM; +end; +$function$ +; + +grant delete on table "public"."ress" to "anon"; + +grant insert on table "public"."ress" to "anon"; + +grant references on table "public"."ress" to "anon"; + +grant select on table "public"."ress" to "anon"; + +grant trigger on table "public"."ress" to "anon"; + +grant truncate on table "public"."ress" to "anon"; + +grant update on table "public"."ress" to "anon"; + +grant delete on table "public"."ress" to "authenticated"; + +grant insert on table "public"."ress" to "authenticated"; + +grant references on table "public"."ress" to "authenticated"; + +grant select on table "public"."ress" to "authenticated"; + +grant trigger on table "public"."ress" to "authenticated"; + +grant truncate on table "public"."ress" to "authenticated"; + +grant update on table "public"."ress" to "authenticated"; + +grant delete on table "public"."ress" to "service_role"; + +grant insert on table "public"."ress" to "service_role"; + +grant references on table "public"."ress" to "service_role"; + +grant select on table "public"."ress" to "service_role"; + +grant trigger on table "public"."ress" to "service_role"; + +grant truncate on table "public"."ress" to "service_role"; + +grant update on table "public"."ress" to "service_role"; + + +CREATE TRIGGER update_image_metadata + AFTER INSERT ON storage.objects FOR EACH row + EXECUTE FUNCTION public.trigger_update_image_metadata(); diff --git a/supabase/seed.sql b/supabase/seed.sql new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json index 756c800..e97a164 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,7 @@ "**/*.tsx" ], "exclude": [ - "node_modules" + "node_modules", + "supabase" ] } diff --git a/yarn.lock b/yarn.lock index 6f493c8..48dfbb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1917,6 +1917,63 @@ dependencies: "@babel/core" "^7.17.9" +"@supabase/auth-js@2.65.0": + version "2.65.0" + resolved "https://registry.yarnpkg.com/@supabase/auth-js/-/auth-js-2.65.0.tgz#e345c492f8cbc31cd6289968eae0e349ff0f39e9" + integrity sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/functions-js@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@supabase/functions-js/-/functions-js-2.4.1.tgz#373e75f8d3453bacd71fb64f88d7a341d7b53ad7" + integrity sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/node-fetch@2.6.15", "@supabase/node-fetch@^2.6.14": + version "2.6.15" + resolved "https://registry.yarnpkg.com/@supabase/node-fetch/-/node-fetch-2.6.15.tgz#731271430e276983191930816303c44159e7226c" + integrity sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ== + dependencies: + whatwg-url "^5.0.0" + +"@supabase/postgrest-js@1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@supabase/postgrest-js/-/postgrest-js-1.16.1.tgz#68dfa0581d8ae4296378cb8815bbde3f4602aef5" + integrity sha512-EOSEZFm5pPuCPGCmLF1VOCS78DfkSz600PBuvBND/IZmMciJ1pmsS3ss6TkB6UkuvTybYiBh7gKOYyxoEO3USA== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/realtime-js@2.10.2": + version "2.10.2" + resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.10.2.tgz#c2b42d17d723d2d2a9146cfad61dc3df1ce3127e" + integrity sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA== + dependencies: + "@supabase/node-fetch" "^2.6.14" + "@types/phoenix" "^1.5.4" + "@types/ws" "^8.5.10" + ws "^8.14.2" + +"@supabase/storage-js@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@supabase/storage-js/-/storage-js-2.7.0.tgz#9ff322d2c3b141087aa34115cf14205e4980ce75" + integrity sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig== + dependencies: + "@supabase/node-fetch" "^2.6.14" + +"@supabase/supabase-js@^2.45.4": + version "2.45.4" + resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.45.4.tgz#0bcf8722f1732dfe3e4c5190d23e3938dcc689c3" + integrity sha512-E5p8/zOLaQ3a462MZnmnz03CrduA5ySH9hZyL03Y+QZLIOO4/Gs8Rdy4ZCKDHsN7x0xdanVEWWFN3pJFQr9/hg== + dependencies: + "@supabase/auth-js" "2.65.0" + "@supabase/functions-js" "2.4.1" + "@supabase/node-fetch" "2.6.15" + "@supabase/postgrest-js" "1.16.1" + "@supabase/realtime-js" "2.10.2" + "@supabase/storage-js" "2.7.0" + "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18" @@ -2300,6 +2357,11 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== +"@types/phoenix@^1.5.4": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.5.tgz#5654e14ec7ad25334a157a20015996b6d7d2075e" + integrity sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w== + "@types/pixelmatch@*": version "5.2.4" resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" @@ -2405,6 +2467,13 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/ws@^8.5.10": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -3111,6 +3180,13 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-image-size@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/buffer-image-size/-/buffer-image-size-0.6.4.tgz#357e8173e951ced3b5a2785c695993aa29dbcac4" + integrity sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ== + dependencies: + "@types/node" "*" + buffer@^5.2.1, buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -3685,9 +3761,9 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -"docker-chromium@github:bertuz/docker-chromium#specify-docker-build": +"docker-chromium@github:bertuz/docker-chromium#explicit-platform": version "1.4.2" - resolved "https://codeload.github.com/bertuz/docker-chromium/tar.gz/6f237a5576458731310b162695a4068a6ca03a0c" + resolved "https://codeload.github.com/bertuz/docker-chromium/tar.gz/484c52d91e102d88e37d4ce7d753da42e9a21384" dependencies: colors "^1.4.0" request "^2.88.2" @@ -5030,6 +5106,11 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +ignore-loader@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ignore-loader/-/ignore-loader-0.1.2.tgz#d81f240376d0ba4f0d778972c3ad25874117a463" + integrity sha512-yOJQEKrNwoYqrWLS4DcnzM7SEQhRKis5mB+LdKKh4cPmGYlLPR0ozRzHV5jmEk2IxptqJNQA5Cc0gw8Fj12bXA== + ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -5719,9 +5800,9 @@ jest-pnp-resolver@^1.2.2: "jest-puppeteer-docker@https://github.com/bertuz/jest-puppeteer-docker.git#specify-docker-build": version "1.4.2" - resolved "https://github.com/bertuz/jest-puppeteer-docker.git#f814cb2761337281a10dfb294a7d58025551ddc6" + resolved "https://github.com/bertuz/jest-puppeteer-docker.git#30ca45d8b7dfd077ab09233aceac20cb4571b7fc" dependencies: - docker-chromium "github:bertuz/docker-chromium#specify-docker-build" + docker-chromium "github:bertuz/docker-chromium#explicit-platform" find-node-modules "^2.0.0" jest-puppeteer "^4.4.0" @@ -8574,6 +8655,11 @@ ws@8.8.0, ws@^8.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== +ws@^8.14.2: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xml-name-validator@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"