From 802cfbbf4aab76fcdaf72f3408ad243b8075b2fe Mon Sep 17 00:00:00 2001 From: John Rallis Date: Sat, 11 May 2024 22:48:14 +0300 Subject: [PATCH 1/5] add existing users as collaborators and new OWNER role --- package-lock.json | 4 +- .../components/Modals/ServerInvitation.svelte | 17 ++--- .../components/Modals/SiteInvitation.svelte | 26 ++++---- src/lib/index.d.ts | 2 +- src/routes/+page.svelte | 15 +++-- src/routes/api/invitations/+server.js | 64 +++++++++++++------ 6 files changed, 81 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82428c63d..dd63b4cc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "primo-server", - "version": "2.0.0--beta.41", + "version": "2.0.0--beta.48", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "primo-server", - "version": "2.0.0--beta.41", + "version": "2.0.0--beta.48", "dependencies": { "@fontsource/fira-code": "^5.0.5", "@iconify/svelte": "^2.2.1", diff --git a/src/lib/components/Modals/ServerInvitation.svelte b/src/lib/components/Modals/ServerInvitation.svelte index 0ddd507ff..e59084c26 100644 --- a/src/lib/components/Modals/ServerInvitation.svelte +++ b/src/lib/components/Modals/ServerInvitation.svelte @@ -12,20 +12,19 @@ async function invite_editor() { loading = true - await supabase.from('invitations').insert({ - email, - inviter_email: $page.data.user.email, - role, - server_invitation: true, - }) const { data } = await axios.post('/api/invitations', { url: $page.url.origin, email, role, server_invitation: true, }) - if (data.success) { + await supabase.from('invitations').insert({ + email, + inviter_email: $page.data.user.email, + role, + server_invitation: true, + }) invitations = await get_invitations() } else { alert(data.error) @@ -66,7 +65,8 @@ ({ DEV: 'Developer', EDITOR: 'Content Editor', - }[role]) + ADMIN: 'Admin', + })[role]
@@ -91,6 +91,7 @@
@@ -116,11 +120,11 @@ {owner.email} Owner - {#each editors as { email }} + {#each editors as { role, email }}
  • {email[0]} - Editor + {role}
  • {/each} diff --git a/src/lib/index.d.ts b/src/lib/index.d.ts index 86a269afb..f918fbcce 100644 --- a/src/lib/index.d.ts +++ b/src/lib/index.d.ts @@ -78,7 +78,7 @@ export type User = { email: string; server_member: boolean; admin: boolean; - role: 'DEV' | 'EDITOR'; + role: 'DEV' | 'EDITOR' | 'OWNER'; created_at: string; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ba3312491..6d29b64f3 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -69,7 +69,8 @@
    {#if siteBeingEdited.id === site.id}
    (siteBeingEdited = { id: null, element: null })} + on:submit|preventDefault={() => + (siteBeingEdited = { id: null, element: null })} > {/if} - {#if $page.data.user.admin} + {#if $page.data.user.admin || site.collaborators.some((c) => c.site === $page.data.user.id && c.role === 'OWNER')}
    - @@ -115,7 +119,10 @@ Rename - diff --git a/src/routes/api/invitations/+server.js b/src/routes/api/invitations/+server.js index 1c6654880..bbf73c5b3 100644 --- a/src/routes/api/invitations/+server.js +++ b/src/routes/api/invitations/+server.js @@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit' import supabase_admin from '$lib/supabase/admin' export async function POST({ request }) { + let user = null const { url, site = null, @@ -10,30 +11,51 @@ export async function POST({ request }) { email, } = await request.json() - const { data, error } = await supabase_admin.auth.admin.inviteUserByEmail( - email, - { redirectTo: `${url}/auth/set-password?email=${email}` } - ) + const { data: existing_user, error: err } = await supabase_admin + .from('users') + .select('*, server_members (admin, role), collaborators (role)') + .eq('email', email) + .single() - if (!error) { - await supabase_admin.from('users').insert({ - id: data.user.id, - email: data.user.email, - }) + if (!err && existing_user) { + const [server_member] = existing_user.server_members + const [collaborator] = existing_user.collaborators - // Add to 'server_members' or 'collaborators' - const { error } = server_invitation - ? await supabase_admin - .from('server_members') - .insert({ user: data.user.id, role }) - : await supabase_admin - .from('collaborators') - .insert({ site, user: data.user.id, role }) + if (server_member || (!server_invitation && collaborator.site === site)) { + return json({ success: false, error: 'User already has access' }) + } - console.error(error) - return json({ success: !error, error: error?.message }) + user = existing_user } else { - console.error(error) - return json({ success: false, error: error.message }) + const { data, error } = await supabase_admin.auth.admin.inviteUserByEmail( + email, + { redirectTo: `${url}/auth/set-password?email=${email}` } + ) + + if (!error) { + await supabase_admin.from('users').insert({ + id: data.user.id, + email: data.user.email, + }) + } else { + console.error(error) + return json({ success: false, error: error.message }) + } + user = data.user } + // Add to 'server_members' or 'collaborators' + const { error } = server_invitation + ? role === 'ADMIN' ? + await supabase_admin + .from('server_members') + .insert({ user: user.id, role: 'DEV', admin: true }) + : await supabase_admin + .from('server_members') + .insert({ user: user.id, role }) + : await supabase_admin + .from('collaborators') + .insert({ site, user: user.id, role }) + + console.error(error) + return json({ success: !error, error: error?.message }) } From 2d52d5fb1afc104e4c1118d2137151f9974b09fb Mon Sep 17 00:00:00 2001 From: John Rallis Date: Sat, 11 May 2024 23:38:25 +0300 Subject: [PATCH 2/5] ability to remove editors and invites --- .../components/Modals/ServerInvitation.svelte | 49 ++++++++++++++++++- .../components/Modals/SiteInvitation.svelte | 45 ++++++++++++++++- src/routes/api/invitations/+server.js | 36 ++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Modals/ServerInvitation.svelte b/src/lib/components/Modals/ServerInvitation.svelte index e59084c26..c38d77402 100644 --- a/src/lib/components/Modals/ServerInvitation.svelte +++ b/src/lib/components/Modals/ServerInvitation.svelte @@ -4,7 +4,9 @@ import * as timeago from 'timeago.js' import { page } from '$app/stores' - const { supabase } = $page.data + const { supabase, session } = $page.data + + const owner = session?.user let loading = false let email = '' @@ -61,6 +63,35 @@ } else return data } + async function remove_editor(user_id) { + loading = true + const { data: success } = await axios.delete('/api/invitations', { + params: { + user: user_id, + server_invitation: true, + }, + }) + if (success) { + get_collaborators().then((res) => { + editors = res + }) + } + loading = false + } + + async function cancel_invitation(email) { + loading = true + const { data: success } = await axios.put('/api/invitations', { + email, + }) + if (success) { + get_invitations().then((res) => { + invitations = res + }) + } + loading = false + } + const Role = (role) => ({ DEV: 'Developer', @@ -112,6 +143,13 @@ {email[0]} Sent {timeago.format(created_at)} + {/each} @@ -125,6 +163,15 @@ {user.email[0]} {Role(role)} + {#if user.id !== owner.id} + + {/if} {/each} diff --git a/src/lib/components/Modals/SiteInvitation.svelte b/src/lib/components/Modals/SiteInvitation.svelte index 496868f09..75df9208a 100644 --- a/src/lib/components/Modals/SiteInvitation.svelte +++ b/src/lib/components/Modals/SiteInvitation.svelte @@ -2,6 +2,7 @@ import axios from 'axios' import * as timeago from 'timeago.js' import { page } from '$app/stores' + import Icon from '@iconify/svelte' export let site @@ -74,6 +75,37 @@ return { role: item.role, ...item.user } }) } + + async function remove_editor(user_id) { + loading = true + const { data: success } = await axios.delete('/api/invitations', { + params: { + site: site.id, + user: user_id, + server_invitation: false, + }, + }) + if (success) { + get_collaborators(site.id).then((res) => { + editors = res + }) + } + loading = false + } + + async function cancel_invitation(email) { + loading = true + const { data: success } = await axios.put('/api/invitations', { + site: site.id, + email, + }) + if (success) { + get_invitations(site.id).then((res) => { + invitations = res + }) + } + loading = false + }
    @@ -107,6 +139,13 @@ {email[0]} Sent {timeago.format(created_at)} + {/each} @@ -120,11 +159,15 @@ Owner - {#each editors as { role, email }} + {#each editors as { id, role, email }}
  • {email[0]} {role} +
  • {/each} diff --git a/src/routes/api/invitations/+server.js b/src/routes/api/invitations/+server.js index bbf73c5b3..1768878b5 100644 --- a/src/routes/api/invitations/+server.js +++ b/src/routes/api/invitations/+server.js @@ -59,3 +59,39 @@ export async function POST({ request }) { console.error(error) return json({ success: !error, error: error?.message }) } + +export async function DELETE({ url }) { + const user = url.searchParams.get('user') + const site = url.searchParams.get('site') + const server_invitation = url.searchParams.get('server_invitation') === 'true' + + // Remove from 'server_members' or 'collaborators' + const { error } = server_invitation ? + await supabase_admin + .from('server_members') + .delete() + .eq('user', user) + : await supabase_admin + .from('collaborators') + .delete() + .match({ user, site }) + + console.error(error) + return json({ success: !error, error: error?.message }) +} + +export async function PUT({ request }) { + const { + email, + site = null + } = await request.json() + + // Remove from 'invitations' + const { error } = await supabase_admin + .from('collaborators') + .delete() + .match({ email, site }) + + console.error(error) + return json({ success: !error, error: error?.message }) +} \ No newline at end of file From a439f426c6b55407cb16eebfb45308e1d6b5f29d Mon Sep 17 00:00:00 2001 From: John Rallis Date: Sat, 11 May 2024 23:56:02 +0300 Subject: [PATCH 3/5] delete user when cancelling an invite --- src/routes/api/invitations/+server.js | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/routes/api/invitations/+server.js b/src/routes/api/invitations/+server.js index 1768878b5..7778d1206 100644 --- a/src/routes/api/invitations/+server.js +++ b/src/routes/api/invitations/+server.js @@ -86,9 +86,43 @@ export async function PUT({ request }) { site = null } = await request.json() + const { data: user, error: err } = await supabase_admin + .from('users') + .select('id') + .eq('email', email) + .single() + + if (err) return json({ success: !err, error: err?.message }) + + if (site) { + const { error } = await supabase_admin + .from('collaborators') + .delete() + .match({ user: user.id, site }) + + if (error) return json({ success: !error, error: error?.message }) + } else { + const { error } = await supabase_admin + .from('server_members') + .delete() + .match({ user: user.id }) + + if (error) return json({ success: !error, error: error?.message }) + } + + // Remove from 'users' + const { data, error: err2 } = await supabase_admin.auth.admin.deleteUser(user.id) + if (err2) return json({ success: !err2, error: err2?.message }) + const { error: err3 } = await supabase_admin + .from('users') + .delete() + .match({ id: user.id }) + + if (err3) return json({ success: !err3, error: err3?.message }) + // Remove from 'invitations' const { error } = await supabase_admin - .from('collaborators') + .from('invitations') .delete() .match({ email, site }) From bfda772a8e91224f15ddbd1ca93502d28f0e12e9 Mon Sep 17 00:00:00 2001 From: John Rallis Date: Sun, 12 May 2024 10:26:43 +0300 Subject: [PATCH 4/5] only invite new users --- .../components/Modals/ServerInvitation.svelte | 15 ++++++---- .../components/Modals/SiteInvitation.svelte | 25 ++++++++--------- src/routes/api/invitations/+server.js | 28 +++++++++++++++++-- 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/lib/components/Modals/ServerInvitation.svelte b/src/lib/components/Modals/ServerInvitation.svelte index c38d77402..77249f32a 100644 --- a/src/lib/components/Modals/ServerInvitation.svelte +++ b/src/lib/components/Modals/ServerInvitation.svelte @@ -21,13 +21,16 @@ server_invitation: true, }) if (data.success) { - await supabase.from('invitations').insert({ - email, - inviter_email: $page.data.user.email, - role, - server_invitation: true, - }) + if (data.isNew) { + await supabase.from('invitations').insert({ + email, + inviter_email: $page.data.user.email, + role, + server_invitation: true, + }) + } invitations = await get_invitations() + editors = await get_collaborators() } else { alert(data.error) } diff --git a/src/lib/components/Modals/SiteInvitation.svelte b/src/lib/components/Modals/SiteInvitation.svelte index 75df9208a..d52958946 100644 --- a/src/lib/components/Modals/SiteInvitation.svelte +++ b/src/lib/components/Modals/SiteInvitation.svelte @@ -16,27 +16,24 @@ async function invite_editor() { loading = true - const { data: success } = await axios.post('/api/invitations', { + const { data: res } = await axios.post('/api/invitations', { site: site.id, email, role, server_invitation: false, url: $page.url.origin, }) - if (success) { - await supabase.from('invitations').insert({ - email, - inviter_email: owner.email, - site: site.id, - role, - }) - const { data, error } = await supabase - .from('invitations') - .select('*') - .eq('site', site.id) - if (data) { - invitations = data + if (res.success) { + if (res.isNew) { + await supabase.from('invitations').insert({ + email, + inviter_email: owner.email, + site: site.id, + role, + }) } + invitations = await get_invitations(site.id) + editors = await get_collaborators(site.id) } else { alert('Could not send invitation. Please try again.') } diff --git a/src/routes/api/invitations/+server.js b/src/routes/api/invitations/+server.js index 7778d1206..25fadfd95 100644 --- a/src/routes/api/invitations/+server.js +++ b/src/routes/api/invitations/+server.js @@ -57,7 +57,7 @@ export async function POST({ request }) { .insert({ site, user: user.id, role }) console.error(error) - return json({ success: !error, error: error?.message }) + return json({ success: !error, error: error?.message, isNew: existing_user === null }) } export async function DELETE({ url }) { @@ -80,7 +80,7 @@ export async function DELETE({ url }) { return json({ success: !error, error: error?.message }) } -export async function PUT({ request }) { +export async function PUT({ request }) { // cancel an invitation const { email, site = null @@ -110,6 +110,30 @@ export async function PUT({ request }) { if (error) return json({ success: !error, error: error?.message }) } + const { data: user, error: err } = await supabase_admin + .from('users') + .select('id') + .eq('email', email) + .single() + + if (err) return json({ success: !err, error: err?.message }) + + if (site) { + const { error } = await supabase_admin + .from('collaborators') + .delete() + .match({ user: user.id, site }) + + if (error) return json({ success: !error, error: error?.message }) + } else { + const { error } = await supabase_admin + .from('server_members') + .delete() + .match({ user: user.id }) + + if (error) return json({ success: !error, error: error?.message }) + } + // Remove from 'users' const { data, error: err2 } = await supabase_admin.auth.admin.deleteUser(user.id) if (err2) return json({ success: !err2, error: err2?.message }) From 1a9391cabb9f1f0d9ebe5a8ce93228a475206697 Mon Sep 17 00:00:00 2001 From: John Rallis Date: Sun, 12 May 2024 22:37:03 +0300 Subject: [PATCH 5/5] damn copy-paste --- src/routes/api/invitations/+server.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/routes/api/invitations/+server.js b/src/routes/api/invitations/+server.js index 25fadfd95..4bcf8423c 100644 --- a/src/routes/api/invitations/+server.js +++ b/src/routes/api/invitations/+server.js @@ -110,30 +110,6 @@ export async function PUT({ request }) { // cancel an invitation if (error) return json({ success: !error, error: error?.message }) } - const { data: user, error: err } = await supabase_admin - .from('users') - .select('id') - .eq('email', email) - .single() - - if (err) return json({ success: !err, error: err?.message }) - - if (site) { - const { error } = await supabase_admin - .from('collaborators') - .delete() - .match({ user: user.id, site }) - - if (error) return json({ success: !error, error: error?.message }) - } else { - const { error } = await supabase_admin - .from('server_members') - .delete() - .match({ user: user.id }) - - if (error) return json({ success: !error, error: error?.message }) - } - // Remove from 'users' const { data, error: err2 } = await supabase_admin.auth.admin.deleteUser(user.id) if (err2) return json({ success: !err2, error: err2?.message })