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..aa9213f --- /dev/null +++ b/_supabase/config.toml @@ -0,0 +1,149 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "zenblog" + +[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" + +[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" + +# 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 + +[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 = true + +# 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" + +# 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`, `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-accelerate.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/migrations/20240328172813_v1.sql b/_supabase/migrations/20240328172813_v1.sql similarity index 100% rename from supabase/migrations/20240328172813_v1.sql rename to _supabase/migrations/20240328172813_v1.sql diff --git a/supabase/migrations/20240331095233_posts_with_blog_and_subscription_status_v2.sql b/_supabase/migrations/20240331095233_posts_with_blog_and_subscription_status_v2.sql similarity index 100% rename from supabase/migrations/20240331095233_posts_with_blog_and_subscription_status_v2.sql rename to _supabase/migrations/20240331095233_posts_with_blog_and_subscription_status_v2.sql diff --git a/supabase/migrations/20240331095320_remote_schema.sql b/_supabase/migrations/20240331095320_remote_schema.sql similarity index 100% rename from supabase/migrations/20240331095320_remote_schema.sql rename to _supabase/migrations/20240331095320_remote_schema.sql diff --git a/supabase/migrations/20240331095515_testfix.sql b/_supabase/migrations/20240331095515_testfix.sql similarity index 100% rename from supabase/migrations/20240331095515_testfix.sql rename to _supabase/migrations/20240331095515_testfix.sql diff --git a/supabase/migrations/20240331150057_post_with_tags_v2.sql b/_supabase/migrations/20240331150057_post_with_tags_v2.sql similarity index 100% rename from supabase/migrations/20240331150057_post_with_tags_v2.sql rename to _supabase/migrations/20240331150057_post_with_tags_v2.sql diff --git a/supabase/migrations/20240402225940_add_public_posts_v1.sql b/_supabase/migrations/20240402225940_add_public_posts_v1.sql similarity index 100% rename from supabase/migrations/20240402225940_add_public_posts_v1.sql rename to _supabase/migrations/20240402225940_add_public_posts_v1.sql diff --git a/supabase/rls-templates/owner-of-blog-id.sql b/_supabase/rls-templates/owner-of-blog-id.sql similarity index 100% rename from supabase/rls-templates/owner-of-blog-id.sql rename to _supabase/rls-templates/owner-of-blog-id.sql diff --git a/_supabase/seed.sql b/_supabase/seed.sql new file mode 100644 index 0000000..e69de29 diff --git a/supabase/config.toml b/supabase/config.toml index aa9213f..1dca6c3 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -40,6 +40,8 @@ max_client_conn = 100 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 @@ -79,6 +81,8 @@ 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. @@ -87,7 +91,7 @@ enable_signup = true # 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 = true +enable_confirmations = false # Uncomment to customize email template # [auth.email.template.invite] @@ -106,6 +110,12 @@ template = "Your code is {{ .Code }} ." [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 @@ -115,7 +125,7 @@ message_service_sid = "" 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`, `notion`, `twitch`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, # `twitter`, `slack`, `spotify`, `workos`, `zoom`. [auth.external.apple] enabled = false @@ -139,7 +149,7 @@ backend = "postgres" [experimental] # Configures Postgres storage engine to use OrioleDB (S3) orioledb_version = "" -# Configures S3 bucket URL, eg. .s3-accelerate.amazonaws.com +# 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)" diff --git a/supabase/migrations/20240503011135_remote_schema.sql b/supabase/migrations/20240503011135_remote_schema.sql new file mode 100644 index 0000000..ca9e343 --- /dev/null +++ b/supabase/migrations/20240503011135_remote_schema.sql @@ -0,0 +1,732 @@ + +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 "pg_net" WITH SCHEMA "extensions"; + +CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium"; + +CREATE SCHEMA IF NOT EXISTS "stripe"; + +ALTER SCHEMA "stripe" OWNER TO "postgres"; + +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"."generate_random_string"("length" integer) RETURNS "text" + LANGUAGE "plpgsql" + AS $$ +DECLARE + chars text := 'abcdefghijklmnopqrstuvwxyz'; + result text := ''; + i int := 1; +BEGIN + FOR i IN 1..length LOOP + result := result || substr(chars, floor(random() * length(chars) + 1)::int, 1); + END LOOP; + RETURN result; +END; +$$; + +ALTER FUNCTION "public"."generate_random_string"("length" integer) OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."generate_slug"("title" "text") RETURNS "text" + LANGUAGE "plpgsql" + AS $$ +DECLARE + slug_text text; + normalized_title text; +BEGIN + -- Normalize the title by removing special characters and converting to lowercase + normalized_title := lower(regexp_replace(title, '[^\w\s]', '', 'g')); + + -- Replace spaces with dashes to create the slug + slug_text := regexp_replace(normalized_title, '\s+', '-', 'g'); + + -- Ensure the slug is unique in the articles table + WHILE EXISTS(SELECT 1 FROM articles WHERE slug = slug_text) LOOP + slug_text := slug_text || '-' || substring(md5(random()::text), 1, 4); + END LOOP; + + RETURN slug_text; +END; +$$; + +ALTER FUNCTION "public"."generate_slug"("title" "text") OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."is_blog_owner"("blog_id" "uuid", "given_user_id" "uuid") RETURNS boolean + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN EXISTS ( + SELECT 1 + FROM public.blogs + WHERE id = blog_id AND user_id = given_user_id + ); +END; +$$; + +ALTER FUNCTION "public"."is_blog_owner"("blog_id" "uuid", "given_user_id" "uuid") OWNER TO "postgres"; + +CREATE OR REPLACE FUNCTION "public"."requesting_user_id"() RETURNS "text" + LANGUAGE "sql" STABLE + AS $$ + select nullif(current_setting('request.jwt.claims', true)::json->>'sub', '')::text; +$$; + +ALTER FUNCTION "public"."requesting_user_id"() OWNER TO "postgres"; + +SET default_tablespace = ''; + +SET default_table_access_method = "heap"; + +CREATE TABLE IF NOT EXISTS "public"."blog_tags" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "blog_id" "uuid" NOT NULL, + "name" "text" NOT NULL, + "slug" "text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "description" "text" +); + +ALTER TABLE "public"."blog_tags" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."blogs" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "title" "text" NOT NULL, + "emoji" "text" NOT NULL, + "description" "text", + "user_id" "uuid" DEFAULT "auth"."uid"() NOT NULL, + "slug" "text" NOT NULL, + CONSTRAINT "blogs_slug_check" CHECK (("length"("slug") < 50)) +); + +ALTER TABLE "public"."blogs" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."feedback" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "feedback" "text", + "user_email" "text" +); + +ALTER TABLE "public"."feedback" OWNER TO "postgres"; + +ALTER TABLE "public"."feedback" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."feedback_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE TABLE IF NOT EXISTS "public"."homepage_signup" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"(), + "name" "text" NOT NULL, + "email" "text" NOT NULL +); + +ALTER TABLE "public"."homepage_signup" OWNER TO "postgres"; + +ALTER TABLE "public"."homepage_signup" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."homepage_signup_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE TABLE IF NOT EXISTS "public"."post_tags" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "post_id" "uuid" NOT NULL, + "tag_id" "uuid" NOT NULL, + "blog_id" "uuid" NOT NULL +); + +ALTER TABLE "public"."post_tags" OWNER TO "postgres"; + +ALTER TABLE "public"."post_tags" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."post_tags_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE OR REPLACE VIEW "public"."post_tags_with_tags" AS + SELECT "pt"."id", + "pt"."created_at", + "pt"."post_id", + "pt"."tag_id", + "bt"."name" AS "tag_name", + "bt"."slug" AS "tag_slug", + "bt"."description" AS "tag_description" + FROM ("public"."post_tags" "pt" + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))); + +ALTER TABLE "public"."post_tags_with_tags" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."posts" ( + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "user_id" "uuid" DEFAULT "auth"."uid"() NOT NULL, + "blog_id" "uuid" NOT NULL, + "title" "text" NOT NULL, + "published" boolean DEFAULT false NOT NULL, + "content" "jsonb" DEFAULT '{}'::"jsonb" NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "slug" "text" NOT NULL, + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "cover_image" "text", + "metadata" "jsonb"[], + "deleted" boolean DEFAULT false NOT NULL, + "published_at" timestamp with time zone DEFAULT "now"() +); + +ALTER TABLE "public"."posts" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."subscriptions" ( + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "user_id" "uuid" NOT NULL, + "status" "text" NOT NULL, + "stripe_subscription_id" "text" NOT NULL, + "subscription" "jsonb" NOT NULL +); + +ALTER TABLE "public"."subscriptions" OWNER TO "postgres"; + +CREATE OR REPLACE VIEW "public"."posts_with_blog_and_subscription_status" AS + SELECT "p"."created_at", + "p"."blog_id", + "p"."title", + "p"."published", + "p"."content", + "p"."updated_at", + "p"."slug", + "p"."id" AS "post_id", + "p"."cover_image", + "p"."metadata", + "p"."deleted", + COALESCE("array_agg"("bt"."name") FILTER (WHERE ("bt"."id" IS NOT NULL)), '{}'::"text"[]) AS "tags", + "s"."status" AS "subscription_status" + FROM (((("public"."posts" "p" + LEFT JOIN "public"."blogs" "b" ON (("p"."blog_id" = "b"."id"))) + LEFT JOIN "public"."subscriptions" "s" ON (("b"."user_id" = "s"."user_id"))) + LEFT JOIN "public"."post_tags" "pt" ON (("p"."id" = "pt"."post_id"))) + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))) + GROUP BY "p"."created_at", "p"."blog_id", "p"."title", "p"."published", "p"."content", "p"."updated_at", "p"."slug", "p"."id", "p"."cover_image", "p"."metadata", "p"."deleted", "p"."user_id", "s"."status" + ORDER BY "p"."created_at" DESC; + +ALTER TABLE "public"."posts_with_blog_and_subscription_status" OWNER TO "postgres"; + +CREATE OR REPLACE VIEW "public"."posts_with_blog_and_subscription_status_v2" AS + SELECT "p"."created_at", + "p"."blog_id", + "p"."title", + "p"."published", + "p"."published_at", + "p"."content", + "p"."updated_at", + "p"."slug", + "p"."id" AS "post_id", + "p"."cover_image", + "p"."metadata", + "p"."deleted", + COALESCE("array_agg"("bt"."name") FILTER (WHERE ("bt"."id" IS NOT NULL)), '{}'::"text"[]) AS "tags", + "s"."status" AS "subscription_status" + FROM (((("public"."posts" "p" + LEFT JOIN "public"."blogs" "b" ON (("p"."blog_id" = "b"."id"))) + LEFT JOIN "public"."subscriptions" "s" ON (("b"."user_id" = "s"."user_id"))) + LEFT JOIN "public"."post_tags" "pt" ON (("p"."id" = "pt"."post_id"))) + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))) + GROUP BY "p"."created_at", "p"."blog_id", "p"."title", "p"."published", "p"."published_at", "p"."content", "p"."updated_at", "p"."slug", "p"."id", "p"."cover_image", "p"."metadata", "p"."deleted", "p"."user_id", "s"."status" + ORDER BY "p"."created_at" DESC; + +ALTER TABLE "public"."posts_with_blog_and_subscription_status_v2" OWNER TO "postgres"; + +CREATE OR REPLACE VIEW "public"."posts_with_tags" AS + SELECT "p"."created_at", + "p"."blog_id", + "p"."title", + "p"."published", + "p"."content", + "p"."updated_at", + "p"."slug", + "p"."id" AS "post_id", + "p"."cover_image", + "p"."metadata", + "p"."deleted", + COALESCE("array_agg"("bt"."name") FILTER (WHERE ("bt"."id" IS NOT NULL)), '{}'::"text"[]) AS "tags" + FROM (("public"."posts" "p" + LEFT JOIN "public"."post_tags" "pt" ON (("p"."id" = "pt"."post_id"))) + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))) + GROUP BY "p"."created_at", "p"."blog_id", "p"."title", "p"."published", "p"."content", "p"."updated_at", "p"."slug", "p"."id", "p"."cover_image", "p"."metadata", "p"."deleted", "p"."user_id" + ORDER BY "p"."created_at" DESC; + +ALTER TABLE "public"."posts_with_tags" OWNER TO "postgres"; + +CREATE OR REPLACE VIEW "public"."posts_with_tags_v2" AS + SELECT "p"."created_at", + "p"."blog_id", + "p"."title", + "p"."published", + "p"."content", + "p"."updated_at", + "p"."slug", + "p"."id" AS "post_id", + "p"."cover_image", + "p"."metadata", + "p"."deleted", + "p"."published_at", + COALESCE("array_agg"("bt"."name") FILTER (WHERE ("bt"."id" IS NOT NULL)), '{}'::"text"[]) AS "tags" + FROM (("public"."posts" "p" + LEFT JOIN "public"."post_tags" "pt" ON (("p"."id" = "pt"."post_id"))) + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))) + GROUP BY "p"."created_at", "p"."blog_id", "p"."title", "p"."published", "p"."content", "p"."updated_at", "p"."slug", "p"."id", "p"."cover_image", "p"."metadata", "p"."deleted", "p"."user_id", "p"."published_at" + ORDER BY "p"."created_at" DESC; + +ALTER TABLE "public"."posts_with_tags_v2" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."prices" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "price" "jsonb" NOT NULL, + "stripe_price_id" "text" NOT NULL +); + +ALTER TABLE "public"."prices" OWNER TO "postgres"; + +ALTER TABLE "public"."prices" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."prices_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE TABLE IF NOT EXISTS "public"."products" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "product" "jsonb" NOT NULL, + "stripe_product_id" "text" NOT NULL +); + +ALTER TABLE "public"."products" OWNER TO "postgres"; + +ALTER TABLE "public"."products" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."products_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE OR REPLACE VIEW "public"."public_posts_v1" AS + SELECT "p"."created_at", + "p"."blog_id", + "p"."title", + "p"."published", + "p"."published_at", + "p"."content", + "p"."updated_at", + "p"."slug", + "p"."id" AS "post_id", + "p"."cover_image", + "p"."metadata", + "p"."deleted", + COALESCE("array_agg"("bt"."name") FILTER (WHERE ("bt"."id" IS NOT NULL)), '{}'::"text"[]) AS "tags", + "s"."status" AS "subscription_status", + "b"."slug" AS "blog_slug" + FROM (((("public"."posts" "p" + LEFT JOIN "public"."blogs" "b" ON (("p"."blog_id" = "b"."id"))) + LEFT JOIN "public"."subscriptions" "s" ON (("b"."user_id" = "s"."user_id"))) + LEFT JOIN "public"."post_tags" "pt" ON (("p"."id" = "pt"."post_id"))) + LEFT JOIN "public"."blog_tags" "bt" ON (("pt"."tag_id" = "bt"."id"))) + GROUP BY "p"."created_at", "p"."blog_id", "p"."title", "p"."published", "p"."published_at", "p"."content", "p"."updated_at", "p"."slug", "p"."id", "p"."cover_image", "p"."metadata", "p"."deleted", "p"."user_id", "s"."status", "b"."slug" + ORDER BY "p"."created_at" DESC; + +ALTER TABLE "public"."public_posts_v1" OWNER TO "postgres"; + +CREATE OR REPLACE VIEW "public"."tag_usage_count_v2" AS + SELECT "bt"."id" AS "tag_id", + "bt"."blog_id", + "bt"."name" AS "tag_name", + "bt"."slug", + "bt"."created_at", + "bt"."updated_at", + "count"("pt"."post_id") AS "post_count" + FROM ("public"."blog_tags" "bt" + LEFT JOIN "public"."post_tags" "pt" ON (("bt"."id" = "pt"."tag_id"))) + GROUP BY "bt"."id", "bt"."blog_id", "bt"."name", "bt"."slug", "bt"."created_at", "bt"."updated_at"; + +ALTER TABLE "public"."tag_usage_count_v2" OWNER TO "postgres"; + +CREATE TABLE IF NOT EXISTS "public"."teams" ( + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "owner_id" "uuid", + "name" "text" NOT NULL, + "ref" "text" DEFAULT "public"."generate_random_string"(20), + "id" bigint NOT NULL +); + +ALTER TABLE "public"."teams" OWNER TO "postgres"; + +ALTER TABLE "public"."teams" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."teams_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + +CREATE OR REPLACE VIEW "public"."users" AS + SELECT "users"."instance_id", + "users"."id", + "users"."aud", + "users"."role", + "users"."email", + "users"."encrypted_password", + "users"."email_confirmed_at", + "users"."invited_at", + "users"."confirmation_token", + "users"."confirmation_sent_at", + "users"."recovery_token", + "users"."recovery_sent_at", + "users"."email_change_token_new", + "users"."email_change", + "users"."email_change_sent_at", + "users"."last_sign_in_at", + "users"."raw_app_meta_data", + "users"."raw_user_meta_data", + "users"."is_super_admin", + "users"."created_at", + "users"."updated_at", + "users"."phone", + "users"."phone_confirmed_at", + "users"."phone_change", + "users"."phone_change_token", + "users"."phone_change_sent_at", + "users"."confirmed_at", + "users"."email_change_token_current", + "users"."email_change_confirm_status", + "users"."banned_until", + "users"."reauthentication_token", + "users"."reauthentication_sent_at", + "users"."is_sso_user", + "users"."deleted_at" + FROM "auth"."users"; + +ALTER TABLE "public"."users" OWNER TO "postgres"; + +ALTER TABLE ONLY "public"."blogs" + ADD CONSTRAINT "blogs_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."blogs" + ADD CONSTRAINT "blogs_slug_key" UNIQUE ("slug"); + +ALTER TABLE ONLY "public"."blog_tags" + ADD CONSTRAINT "categories_blog_id_slug_key" UNIQUE ("blog_id", "slug"); + +ALTER TABLE ONLY "public"."blog_tags" + ADD CONSTRAINT "categories_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."feedback" + ADD CONSTRAINT "feedback_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."homepage_signup" + ADD CONSTRAINT "homepage_signup_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."post_tags" + ADD CONSTRAINT "post_categories_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."posts" + ADD CONSTRAINT "posts_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."prices" + ADD CONSTRAINT "prices_pkey" PRIMARY KEY ("id", "stripe_price_id"); + +ALTER TABLE ONLY "public"."prices" + ADD CONSTRAINT "prices_stripe_price_id_key" UNIQUE ("stripe_price_id"); + +ALTER TABLE ONLY "public"."products" + ADD CONSTRAINT "products_pkey" PRIMARY KEY ("id", "stripe_product_id"); + +ALTER TABLE ONLY "public"."products" + ADD CONSTRAINT "products_stripe_product_id_key" UNIQUE ("stripe_product_id"); + +ALTER TABLE ONLY "public"."subscriptions" + ADD CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("stripe_subscription_id"); + +ALTER TABLE ONLY "public"."subscriptions" + ADD CONSTRAINT "subscriptions_stripe_subscription_id_key" UNIQUE ("stripe_subscription_id"); + +ALTER TABLE ONLY "public"."subscriptions" + ADD CONSTRAINT "subscriptions_user_id_key" UNIQUE ("user_id"); + +ALTER TABLE ONLY "public"."teams" + ADD CONSTRAINT "teams_id_key" UNIQUE ("id"); + +ALTER TABLE ONLY "public"."teams" + ADD CONSTRAINT "teams_pkey" PRIMARY KEY ("id"); + +ALTER TABLE ONLY "public"."teams" + ADD CONSTRAINT "teams_ref_key" UNIQUE ("ref"); + +ALTER TABLE ONLY "public"."post_tags" + ADD CONSTRAINT "unique_post_tag_blog_combination" UNIQUE ("post_id", "tag_id", "blog_id"); + +CREATE INDEX "posts_slug_blog_id_idx" ON "public"."posts" USING "btree" ("slug", "blog_id"); + +CREATE INDEX "posts_slug_idx" ON "public"."posts" USING "btree" ("slug"); + +CREATE UNIQUE INDEX "unique_slug_per_user_blog_constraint" ON "public"."posts" USING "btree" ("slug", "user_id", "blog_id"); + +ALTER TABLE ONLY "public"."blog_tags" + ADD CONSTRAINT "blog_tags_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs"("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."blogs" + ADD CONSTRAINT "blogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"); + +ALTER TABLE ONLY "public"."post_tags" + ADD CONSTRAINT "post_tags_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs"("id"); + +ALTER TABLE ONLY "public"."post_tags" + ADD CONSTRAINT "post_tags_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id"); + +ALTER TABLE ONLY "public"."post_tags" + ADD CONSTRAINT "post_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "public"."blog_tags"("id"); + +ALTER TABLE ONLY "public"."posts" + ADD CONSTRAINT "posts_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs"("id") ON DELETE CASCADE; + +ALTER TABLE ONLY "public"."posts" + ADD CONSTRAINT "public_posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"); + +ALTER TABLE ONLY "public"."teams" + ADD CONSTRAINT "public_teams_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "auth"."users"("id"); + +ALTER TABLE ONLY "public"."subscriptions" + ADD CONSTRAINT "subscriptions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"); + +CREATE POLICY "Enable INSERT access for all users" ON "public"."homepage_signup" FOR INSERT TO "anon" WITH CHECK (true); + +CREATE POLICY "Enable insert for authenticated users only" ON "public"."feedback" FOR INSERT TO "authenticated" WITH CHECK (true); + +CREATE POLICY "Enable insert for owners" ON "public"."teams" FOR INSERT TO "authenticated" WITH CHECK (("auth"."uid"() = "owner_id")); + +CREATE POLICY "Enable read access for all users" ON "public"."blogs" FOR SELECT TO "anon" USING (true); + +CREATE POLICY "Enable read access for all users" ON "public"."prices" FOR SELECT USING (true); + +CREATE POLICY "Enable read access for all users" ON "public"."products" FOR SELECT USING (true); + +CREATE POLICY "Enable read access for all users" ON "public"."teams" FOR SELECT USING (true); + +CREATE POLICY "authed_read_posts" ON "public"."posts" FOR SELECT TO "authenticated" USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +ALTER TABLE "public"."blog_tags" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."blogs" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "delete_post_tag" ON "public"."post_tags" FOR DELETE TO "authenticated" USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +CREATE POLICY "delete_tags" ON "public"."blog_tags" FOR DELETE USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +ALTER TABLE "public"."feedback" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."homepage_signup" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "insert_post_tag" ON "public"."post_tags" FOR INSERT TO "authenticated" WITH CHECK ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +CREATE POLICY "insert_posts" ON "public"."posts" FOR INSERT TO "authenticated" WITH CHECK ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +CREATE POLICY "insert_tags" ON "public"."blog_tags" FOR INSERT WITH CHECK ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +ALTER TABLE "public"."post_tags" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."posts" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."prices" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."products" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "read_post_tags" ON "public"."post_tags" FOR SELECT USING (true); + +CREATE POLICY "read_posts" ON "public"."posts" FOR SELECT TO "anon" USING ((("published" = true) AND ("deleted" = false))); + +CREATE POLICY "select subscription" ON "public"."subscriptions" FOR SELECT TO "authenticated" USING (("auth"."uid"() = "user_id")); + +CREATE POLICY "select_categories_policy" ON "public"."blog_tags" FOR SELECT USING (true); + +ALTER TABLE "public"."subscriptions" ENABLE ROW LEVEL SECURITY; + +ALTER TABLE "public"."teams" ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "update_post_tags" ON "public"."post_tags" FOR UPDATE TO "authenticated" USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())) WITH CHECK (true); + +CREATE POLICY "update_posts" ON "public"."posts" FOR UPDATE TO "authenticated" USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +CREATE POLICY "update_tags" ON "public"."blog_tags" FOR UPDATE USING ("public"."is_blog_owner"("blog_id", "auth"."uid"())); + +CREATE POLICY "users can crud their blogs" ON "public"."blogs" TO "authenticated" USING (("auth"."uid"() = "user_id")); + +ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres"; + +REVOKE USAGE ON SCHEMA "public" FROM PUBLIC; +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"."generate_random_string"("length" integer) TO "anon"; +GRANT ALL ON FUNCTION "public"."generate_random_string"("length" integer) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."generate_random_string"("length" integer) TO "service_role"; + +GRANT ALL ON FUNCTION "public"."generate_slug"("title" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."generate_slug"("title" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."generate_slug"("title" "text") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."is_blog_owner"("blog_id" "uuid", "given_user_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."is_blog_owner"("blog_id" "uuid", "given_user_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."is_blog_owner"("blog_id" "uuid", "given_user_id" "uuid") TO "service_role"; + +GRANT ALL ON FUNCTION "public"."requesting_user_id"() TO "anon"; +GRANT ALL ON FUNCTION "public"."requesting_user_id"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."requesting_user_id"() TO "service_role"; + +GRANT ALL ON TABLE "public"."blog_tags" TO "anon"; +GRANT ALL ON TABLE "public"."blog_tags" TO "authenticated"; +GRANT ALL ON TABLE "public"."blog_tags" TO "service_role"; + +GRANT ALL ON TABLE "public"."blogs" TO "anon"; +GRANT ALL ON TABLE "public"."blogs" TO "authenticated"; +GRANT ALL ON TABLE "public"."blogs" TO "service_role"; + +GRANT ALL ON TABLE "public"."feedback" TO "anon"; +GRANT ALL ON TABLE "public"."feedback" TO "authenticated"; +GRANT ALL ON TABLE "public"."feedback" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."feedback_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."feedback_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."feedback_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."homepage_signup" TO "anon"; +GRANT ALL ON TABLE "public"."homepage_signup" TO "authenticated"; +GRANT ALL ON TABLE "public"."homepage_signup" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."homepage_signup_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."homepage_signup_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."homepage_signup_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."post_tags" TO "anon"; +GRANT ALL ON TABLE "public"."post_tags" TO "authenticated"; +GRANT ALL ON TABLE "public"."post_tags" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."post_tags_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."post_tags_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."post_tags_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."post_tags_with_tags" TO "anon"; +GRANT ALL ON TABLE "public"."post_tags_with_tags" TO "authenticated"; +GRANT ALL ON TABLE "public"."post_tags_with_tags" TO "service_role"; + +GRANT ALL ON TABLE "public"."posts" TO "anon"; +GRANT ALL ON TABLE "public"."posts" TO "authenticated"; +GRANT ALL ON TABLE "public"."posts" TO "service_role"; + +GRANT ALL ON TABLE "public"."subscriptions" TO "anon"; +GRANT ALL ON TABLE "public"."subscriptions" TO "authenticated"; +GRANT ALL ON TABLE "public"."subscriptions" TO "service_role"; + +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status" TO "anon"; +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status" TO "authenticated"; +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status" TO "service_role"; + +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status_v2" TO "anon"; +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status_v2" TO "authenticated"; +GRANT ALL ON TABLE "public"."posts_with_blog_and_subscription_status_v2" TO "service_role"; + +GRANT ALL ON TABLE "public"."posts_with_tags" TO "anon"; +GRANT ALL ON TABLE "public"."posts_with_tags" TO "authenticated"; +GRANT ALL ON TABLE "public"."posts_with_tags" TO "service_role"; + +GRANT ALL ON TABLE "public"."posts_with_tags_v2" TO "anon"; +GRANT ALL ON TABLE "public"."posts_with_tags_v2" TO "authenticated"; +GRANT ALL ON TABLE "public"."posts_with_tags_v2" TO "service_role"; + +GRANT ALL ON TABLE "public"."prices" TO "anon"; +GRANT ALL ON TABLE "public"."prices" TO "authenticated"; +GRANT ALL ON TABLE "public"."prices" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."prices_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."prices_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."prices_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."products" TO "anon"; +GRANT ALL ON TABLE "public"."products" TO "authenticated"; +GRANT ALL ON TABLE "public"."products" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."products_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."products_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."products_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."public_posts_v1" TO "anon"; +GRANT ALL ON TABLE "public"."public_posts_v1" TO "authenticated"; +GRANT ALL ON TABLE "public"."public_posts_v1" TO "service_role"; + +GRANT ALL ON TABLE "public"."tag_usage_count_v2" TO "anon"; +GRANT ALL ON TABLE "public"."tag_usage_count_v2" TO "authenticated"; +GRANT ALL ON TABLE "public"."tag_usage_count_v2" TO "service_role"; + +GRANT ALL ON TABLE "public"."teams" TO "anon"; +GRANT ALL ON TABLE "public"."teams" TO "authenticated"; +GRANT ALL ON TABLE "public"."teams" TO "service_role"; + +GRANT ALL ON SEQUENCE "public"."teams_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."teams_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."teams_id_seq" TO "service_role"; + +GRANT ALL ON TABLE "public"."users" TO "anon"; +GRANT ALL ON TABLE "public"."users" TO "authenticated"; +GRANT ALL ON TABLE "public"."users" 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/20240503011230_remote_schema.sql b/supabase/migrations/20240503011230_remote_schema.sql new file mode 100644 index 0000000..d31a974 --- /dev/null +++ b/supabase/migrations/20240503011230_remote_schema.sql @@ -0,0 +1,81 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION storage.extension(name text) + RETURNS text + LANGUAGE plpgsql +AS $function$ +DECLARE +_parts text[]; +_filename text; +BEGIN + select string_to_array(name, '/') into _parts; + select _parts[array_length(_parts,1)] into _filename; + -- @todo return the last part instead of 2 + return split_part(_filename, '.', 2); +END +$function$ +; + +CREATE OR REPLACE FUNCTION storage.filename(name text) + RETURNS text + LANGUAGE plpgsql +AS $function$ +DECLARE +_parts text[]; +BEGIN + select string_to_array(name, '/') into _parts; + return _parts[array_length(_parts,1)]; +END +$function$ +; + +CREATE OR REPLACE FUNCTION storage.foldername(name text) + RETURNS text[] + LANGUAGE plpgsql +AS $function$ +DECLARE +_parts text[]; +BEGIN + select string_to_array(name, '/') into _parts; + return _parts[1:array_length(_parts,1)-1]; +END +$function$ +; + +create policy "ALLOW ALL IN public 1ffg0oo_0" +on "storage"."objects" +as permissive +for select +to public +using (true); + + +create policy "ALLOW ALL IN public 1ffg0oo_1" +on "storage"."objects" +as permissive +for insert +to public +with check (true); + + +create policy "Allow owners to delete" +on "storage"."objects" +as permissive +for delete +to authenticated +using ((EXISTS ( SELECT blogs.id + FROM blogs + WHERE ((blogs.user_id = ( SELECT auth.uid() AS uid)) AND (split_part(objects.name, '/'::text, 1) = (blogs.id)::text))))); + + +create policy "Allow owners to insert" +on "storage"."objects" +as permissive +for insert +to authenticated +with check ((EXISTS ( SELECT blogs.id + FROM blogs + WHERE ((blogs.user_id = ( SELECT auth.uid() AS uid)) AND (split_part(objects.name, '/'::text, 1) = (blogs.id)::text))))); + + +