diff --git a/apps/docs/components/MDX/storage_management.mdx b/apps/docs/components/MDX/storage_management.mdx new file mode 100644 index 00000000000..d9df719486d --- /dev/null +++ b/apps/docs/components/MDX/storage_management.mdx @@ -0,0 +1,108 @@ +If you upload additional profile photos, they'll accumulate +in the `avatars` bucket because of their random names with only the latest being referenced +from `public.profiles` and the older versions getting orphaned. + +To automatically remove obsolete storage objects, extend the database +triggers. Note that it is not sufficient to delete the objects from the +`storage.objects` table because that would orphan and leak the actual storage objects in +the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension. + +Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard. +Then, define the following SQL functions in the SQL Editor to delete +storage objects via the API: + +```SQL +create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar) +returns record +language 'plpgsql' +security definer +as $$ +declare + project_url varchar := ''; + service_role_key varchar := ''; -- full access needed + url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object; +begin + select + into status, content + result.status::int, result.content::varchar + FROM extensions.http(( + 'DELETE', + url, + ARRAY[extensions.http_header('authorization','Bearer '||service_role_key)], + NULL, + NULL)::extensions.http_request) as result; +end; +$$; + +create or replace function delete_avatar(avatar_url text, out status int, out content varchar) +returns record +language 'plpgsql' +security definer +as $$ +begin + select + into status, content + result.status, result.content + from public.delete_storage_object('avatars', avatar_url) as result; +end; +$$; + +``` + +Next, add a trigger that removes any obsolete avatar whenever the +profile is updated or deleted: + +```SQL +create or replace function delete_old_avatar() +returns trigger +language 'plpgsql' +security definer +as $$ +declare + status int; + content varchar; +begin + if coalesce(old.avatar_url, '') <> '' + and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then + select + into status, content + result.status, result.content + from public.delete_avatar(old.avatar_url) as result; + if status <> 200 then + raise warning 'Could not delete avatar: % %', status, content; + end if; + end if; + if tg_op = 'DELETE' then + return old; + end if; + return new; +end; +$$; + +create trigger before_profile_changes + before update of avatar_url or delete on public.profiles + for each row execute function public.delete_old_avatar(); + +``` + +Finally, delete the `public.profile` row before a user is deleted. +If this step is omitted, you won't be able to delete users without +first manually deleting their avatar image. + +```SQL +create or replace function delete_old_profile() +returns trigger +language 'plpgsql' +security definer +as $$ +begin + delete from public.profiles where id = old.id; + return old; +end; +$$; + +create trigger before_delete_user + before delete on auth.users + for each row execute function public.delete_old_profile(); + +``` diff --git a/apps/docs/components/index.tsx b/apps/docs/components/index.tsx index 587a5cfee32..0097630387b 100644 --- a/apps/docs/components/index.tsx +++ b/apps/docs/components/index.tsx @@ -20,6 +20,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx' import ProjectSetup from './MDX/project_setup.mdx' import SocialProviderSetup from './MDX/social_provider_setup.mdx' import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx' +import StorageManagement from './MDX/storage_management.mdx' import { Mermaid } from 'mdx-mermaid/lib/Mermaid' import InlineCodeTag from './CustomHTMLElements/InlineCode' import React from 'react' @@ -49,6 +50,7 @@ const components = { ProjectSetup, SocialProviderSetup, SocialProviderSettingsSupabase, + StorageManagement, Mermaid, Extensions, Alert: (props: any) => ( diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-angular.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-angular.mdx index 8f2754453f9..b76748ad8aa 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-angular.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-angular.mdx @@ -530,6 +530,10 @@ export class AccountComponent implements OnInit { } ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index b60db313a51..192caa40725 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -500,6 +500,10 @@ Now you will need to run the prebuild command to get the application working on expo prebuild ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-flutter.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-flutter.mdx index 6a73c7aa8ab..478390cf7c0 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-flutter.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-flutter.mdx @@ -793,6 +793,10 @@ class _AccountPageState extends State { } ``` +### Storage management + + + Congratulations, that is it! You have now built a fully functional user management app using Flutter and Supabase! ## See also diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-angular.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-angular.mdx index 88c1d9e6433..4616bb796b0 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-angular.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-angular.mdx @@ -522,6 +522,10 @@ template: ` ` ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-react.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-react.mdx index c14bb343ed3..3c7c9847b96 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-react.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-react.mdx @@ -495,6 +495,10 @@ return ( ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-vue.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-vue.mdx index 2f368771b78..b5599980212 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-ionic-vue.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-ionic-vue.mdx @@ -555,6 +555,10 @@ export default defineComponent({ ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index 81b86e96b2f..78c6c444d1e 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -718,6 +718,10 @@ return ( ) ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nuxt-3.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nuxt-3.mdx index b4f5af0a1a3..844c10dc2a4 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nuxt-3.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nuxt-3.mdx @@ -420,6 +420,10 @@ And then we can add the widget to the Account page: That is it! You should now be able to upload a profile photo to Supabase Storage. +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx index 7d1418b7146..981c7b33d9b 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-react.mdx @@ -397,6 +397,10 @@ return ( ) ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-redwoodjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-redwoodjs.mdx index cae8504a010..6fd481ea9cc 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-redwoodjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-redwoodjs.mdx @@ -771,6 +771,10 @@ return ( ) ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-solidjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-solidjs.mdx index 7de71d63910..eaf47892298 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-solidjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-solidjs.mdx @@ -415,6 +415,10 @@ return ( ) ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-svelte.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-svelte.mdx index 69cdea17b87..5195fa8b135 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-svelte.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-svelte.mdx @@ -368,6 +368,10 @@ And then we can add the widget to the Account page: ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx index bbac06e6019..78f236a99d9 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-sveltekit.mdx @@ -434,6 +434,10 @@ And then we can add the widget to the Account page: ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application! diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx index a1ec84a8c92..a09c7e7c1ec 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-vue-3.mdx @@ -379,6 +379,10 @@ And then we can add the widget to the Account page: ``` +### Storage management + + + ## Next steps At this stage you have a fully functional application!