Closed rphlmr closed 1 year ago
@barbinbrad this is for you ;)
@smartsense-as realtime is back ;)
EDIT
It should work now, I found a way ⚡️
🥶 Realtime is not working fine. It only works with RLS set to anon
.
Other than that, RLS works server-side.
With this implementation, the real-time client has an access_token = null
.
I'm trying to debug but I think it's not a bug.
Oh wow! I'm kind of at a loss for words of gratitude without sounding cheesy. How can I help?
Oh wow! I'm kind of at a loss for words of gratitude without sounding cheesy. How can I help?
You are welcome ;)
To help, just try It and tell me how It feels if you can 😅
If you can't pull and try this branch, I'll try to merge asap
Hm... I get the following error running npm run setup
:
Invalid `prisma.note.create()` invocation in
/Users/bbarbin/Code/supa-fly-stack/app/database/seed.server.ts:53:21
50 },
51 });
52
→ 53 await prisma.note.create(
The column `user_id` does not exist in the current database.
at RequestHandler.handleRequestError (/Users/bbarbin/Code/supa-fly-stack/node_modules/@prisma/client/runtime/index.js:34310:13)
at RequestHandler.request (/Users/bbarbin/Code/supa-fly-stack/node_modules/@prisma/client/runtime/index.js:34293:12)
Not too familiar with prisma, but noticed that the rls_notes
table had a user_id
column, while the Notes
table has userId
column.
There is a trick to make It works.
First, It's always refreshed server-side.
It's requireAuthSession that is responsible of refreshing accessToken
Then, we pass down this token in root.tsx
(In reality It could be where you want. What matter is to do that in a "parent" route).
We use this access token through a Remix hook (useMatches).
Maybe I'll rewrite that to pass accessToken
directly by props to SupabaseProvider
(a traditional React way) , because if accessToken
no more comes from root
we have to change useMatchesData
target 😅
Then come the fake refresh
Here, we don't refresh the token. What we do is create a setInterval (useInterval hook) based on expiresIn
of the token to send a post
request to a Remix resource route (refresh-session).
This route calls our requireAuthSession
and the token is refreshed.
Next, every loader is re-run (root included, this is the power of Remix), SupabaseProvider shows the change and re-create a client with the refreshed accessToken.
This is mostly to prevent users who would stay on the page without ever refreshing anything. Every time the user navigates, there is a chance that a loader/action refreshes the token (and then, SupabaseProvider handles It).
Even If you have a race condition on loader/action, you'll have the same token (Supabase option: Reuse interval of 10s, on the dashboard)
Hm... I get the following error running
npm run setup
:Invalid `prisma.note.create()` invocation in /Users/bbarbin/Code/supa-fly-stack/app/database/seed.server.ts:53:21 50 }, 51 }); 52 → 53 await prisma.note.create( The column `user_id` does not exist in the current database. at RequestHandler.handleRequestError (/Users/bbarbin/Code/supa-fly-stack/node_modules/@prisma/client/runtime/index.js:34310:13) at RequestHandler.request (/Users/bbarbin/Code/supa-fly-stack/node_modules/@prisma/client/runtime/index.js:34293:12)
Not too familiar with prisma, but noticed that the
rls_notes
table had auser_id
column, while theNotes
table hasuserId
column.
Ah yes, you have to run prisma migrate deploy
to apply migrations : npm run db:deploy-migration
Or just run this sql on your Supabase Dashboard SQL editor :)
Even If you have a race condition on loader/action, you'll have the same token (Supabase option: Reuse interval of 10s, on the dashboard)
That's perfect! Thanks for the explanation!
I have pushed a change
Now we pass accessToken
to SupabaseProvider
props.
I have removed the magic thing (useMacthes)
Sorry for not merging this PR. I need to review It again to be sure to merge and maintain this part in time.
I have worked on a 100% supabase stack here: https://github.com/rphlmr/supa-remix-stack I'm not sure I will maintain It, but you can take some inspiration ;)
Sorry for not merging this PR. I need to review It again to be sure to merge and maintain this part in time.
I have worked on a 100% supabase stack here: https://github.com/rphlmr/supa-remix-stack I'm not sure I will maintain It, but you can take some inspiration ;)
No worries at all man! This is open-source work, so again, my deepest gratitude! I've been able to use this MR in my project successfully -- although I haven't got to a need to use realtime yet. I do use useSupabase
quite a bit for uploading images to storage with RLS and it works perfectly.
I think this client-side approach is much better than the send bytes through remix server:
const ProfilePhotoForm = ({ user }: ProfilePhotoFormProps) => {
const { supabase } = useSupabase();
const fileInputRef = useRef<HTMLInputElement>(null);
const notification = useNotification();
const submit = useSubmit();
const uploadImage = async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && supabase) {
const avatarFile = e.target.files[0];
const fileExtension = avatarFile.name.substring(
avatarFile.name.lastIndexOf(".") + 1
);
const imageUpload = await supabase.storage
.from("avatars")
.upload(`${user.id}.${fileExtension}`, avatarFile, {
cacheControl: `${12 * 60 * 60}`,
upsert: true,
});
if (imageUpload.error) {
notification.copyableError(imageUpload.error, "Failed to upload image");
}
if (imageUpload.data?.path) {
submitAvatarUrl(imageUpload.data.path);
}
}
};
const deleteImage = async () => {
if (supabase) {
const imageDelete = await supabase.storage
.from("avatars")
.remove([`${user.id}.png`]);
if (imageDelete.error) {
notification.copyableError(imageDelete.error, "Failed to remove image");
}
submitAvatarUrl(null);
}
};
const submitAvatarUrl = (path: string | null) => {
const formData = new FormData();
formData.append("intent", "photo");
if (path) formData.append("path", path);
submit(formData, {
method: "post",
action: "/app/account/profile",
});
};
return (
<VStack w="full" spacing={2} px={8}>
<Avatar size="2xl" path={user?.avatarUrl} title={user?.fullName ?? ""} />
<InputGroup w="auto">
<Input
ref={fileInputRef}
id="avatar-upload"
type="file"
hidden
accept="image/*"
onChange={uploadImage}
/>
<Button
variant="solid"
colorScheme="brand"
onClick={() => {
if (fileInputRef.current) fileInputRef.current.click();
}}
>
{user.avatarUrl ? "Change" : "Upload"}
</Button>
</InputGroup>
{user.avatarUrl && (
<Button variant="outline" onClick={deleteImage}>
Remove
</Button>
)}
</VStack>
);
};
export default ProfilePhotoForm;
I have worked on a 100% supabase stack here: https://github.com/rphlmr/supa-remix-stack I'm not sure I will maintain It, but you can take some inspiration ;)
What would you say are the major benefits/differences with the 100% supabase stack?
Oh yes, this is a good approach!
It's a matter of choice to choose the front or backend way to upload images.
What would you say are the major benefits/differences with the 100% supabase stack?
The major benefit is that it's officially maintained by supabase (thru their auth package). The drawback is that using supabase SDK to talk to your database adds an extra layer between your backend and your DB. The SDK points to a gotrue API and (on my projects) it adds a 200ms delay per request 😬.
With SDK : front > back > gotrue > db > gotrue > back > front Without : front > back > db > back > front
Depending on everyone's needs it can be fine. My client wants the more speed he can have, so, this is why I still use Prisma and this stack.
Depending on everyone's needs it can be fine. My client wants the more speed he can have, so, this is why I still use Prisma and this stack.
Thanks for the heads up. I think I'm going to eliminate prisma from my project too. Currently, I use the supabase server-side for authz, and, I get Lighthouse performance scores of 100 on just about every page. But the caveat is that I'm running supabase locally from the CLI. I think that this kind of speed could be replicated by having the supabase containers and the remix app in the same cluster, but that'd mean self-hosting supabase (I think), which isn't something I'd love to do.
My ERP project is getting pretty large and stable (much thanks to you), I'm going to open-source it when I get to an MVP type status, but if you ever want see any of the code, I'd be happy to give you access.
I'm glad It helps you 🤩
I'm working on a new stack mixing Remix, Supabase (with Prisma), and Stripe. Using Stripe as a CMS for subscription tiers and handling all of this with a Remix/Supabase project.
I'm closing this pull request in favor of https://github.com/rphlmr/supa-remix-stack. I keep the branch in case of ;)
Add Supabase Provider to use Supabase client (with RLS support) in the browser. Add a
/rls
route example to illustrate. Add/refresh-session
route to enable session refresh (triggered by the Provider on expires). Add doc in a code comment to explain how It works. Add a migration to create a RLS table, for demo purposes. Add a typeDatabase
to have typing completion (It's only to illustrate how it works with supabase cli)