Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3d2d158
chore: update package-lock ver.2
phamhieu Mar 17, 2021
88e3977
chore: create StorageClient
phamhieu Mar 17, 2021
f0f2262
fix: delete bucket
phamhieu Mar 17, 2021
1190013
chore: add empty bucket
phamhieu Mar 17, 2021
261b19c
chore: add search api
phamhieu Mar 17, 2021
fec0302
chore: add storage unit tests
phamhieu Mar 17, 2021
737ad0c
chore: add more storage api
phamhieu Mar 17, 2021
c5c8cfb
chore: create supabaseStorageClient
phamhieu Mar 18, 2021
ad7e0b1
chore: init next-storage example app
phamhieu Mar 18, 2021
5436dda
chore: implement authentication flow for next-storage
phamhieu Mar 18, 2021
6bb8611
chore: support upload avatar
phamhieu Mar 18, 2021
c3d2fd3
chore: download avatar and show
phamhieu Mar 18, 2021
ce0c153
fix: storage download api
phamhieu Mar 18, 2021
a4c09ca
chore: disable storage-api unit test
phamhieu Mar 18, 2021
471f8b6
chore: clean up
phamhieu Mar 18, 2021
65d2409
fix: replace avatar
phamhieu Mar 18, 2021
91d0906
fix: unit test
phamhieu Mar 18, 2021
564b206
fix: bump gotrue to get user data
kiwicopple Mar 18, 2021
db692a4
Put some error logic around the avatar download
kiwicopple Mar 18, 2021
a0e5677
Adds some styles, starting to think that we should separate the table…
kiwicopple Mar 18, 2021
bc9096d
fix some of the login styles
kiwicopple Mar 18, 2021
ea2f2af
Also save to the user data
kiwicopple Mar 18, 2021
96fe5c8
chore: clean up
phamhieu Mar 19, 2021
a6ed5fd
chore: extract avatars bucket to constant
phamhieu Mar 19, 2021
ad5aa33
refactor: improve storage search api
phamhieu Mar 19, 2021
2f989d2
Updating the example to use a view instead
kiwicopple Mar 19, 2021
e6d02bf
Merge branch 'feat/storage-api' of github.com:supabase/supabase-js in…
kiwicopple Mar 19, 2021
21a8d3c
chore: implement get/update file metadata api
phamhieu Mar 19, 2021
f9b8acd
Merge branch 'feat/storage-api' of github.com:supabase/supabase-js in…
phamhieu Mar 19, 2021
89344ed
chore: update comment
phamhieu Mar 19, 2021
b120b15
Update gotrue-js
kiwicopple Mar 19, 2021
4f99b6f
Save some more profile data
kiwicopple Mar 19, 2021
e652d46
Merge branch 'feat/storage-api' of github.com:supabase/supabase-js in…
kiwicopple Mar 19, 2021
7195600
chore: comment out metadata api, server hasn't implemented yet
phamhieu Mar 22, 2021
c1aa5a5
bumps gotrue-js
kiwicopple Mar 22, 2021
e3779e2
Merge branch 'feat/storage-api' of github.com:supabase/supabase-js in…
kiwicopple Mar 22, 2021
30b9078
New plan for the user profiles
kiwicopple Mar 22, 2021
08e6e7e
policies for user profiles
kiwicopple Mar 22, 2021
806b235
Moving everything over to a profiles table
kiwicopple Mar 22, 2021
744a40b
Final updates to the index page
kiwicopple Mar 22, 2021
460695e
Moves Account functions into it's own component so we don't have to g…
kiwicopple Mar 22, 2021
7f592a0
Makes the example a bit more interesting - with constraints
kiwicopple Mar 22, 2021
6580526
Sets up a column for profiles
kiwicopple Mar 22, 2021
201bb56
Adds some realtime, removes some styles
kiwicopple Mar 23, 2021
c8e301c
Removes more styles - just one CSS file now
kiwicopple Mar 23, 2021
e5f189a
Removed the final CSS module
kiwicopple Mar 23, 2021
7f51734
Fix Profile list
kiwicopple Mar 23, 2021
685b1ed
Adds updated times to the cards
kiwicopple Mar 23, 2021
a7e114a
Adds the profile list
kiwicopple Mar 23, 2021
4230ad6
final tidy up
kiwicopple Mar 23, 2021
f6fd8de
Merge branch 'master' into feat/storage-api
kiwicopple Mar 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions example/next-storage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
34 changes: 34 additions & 0 deletions example/next-storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Supabase storage example

- Create a file `.env.local`
- Add a `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_KEY`
- Run `npm run dev`

## Database schema

```sql
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,

primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by everyone." on profiles for select using (true);
create policy "Users can insert their own profile." on profiles for insert with check (auth.uid() = id);
create policy "Users can update own profile." on profiles for update using (auth.uid() = id);


-- Set up Realtime!
begin;
drop publication if exists supabase_realtime;
create publication supabase_realtime;
commit;

alter publication supabase_realtime add table profiles;
```
171 changes: 171 additions & 0 deletions example/next-storage/components/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { useState, useEffect, ChangeEvent } from 'react'
import { supabase } from '../lib/api'
import UploadButton from '../components/UploadButton'
import Avatar from './Avatar'
import { AuthSession } from '../../../dist/main'
import { DEFAULT_AVATARS_BUCKET, Profile } from '../lib/constants'

export default function Account({ session }: { session: AuthSession }) {
const [loading, setLoading] = useState<boolean>(true)
const [uploading, setUploading] = useState<boolean>(false)
const [avatar, setAvatar] = useState<string | null>(null)
const [username, setUsername] = useState<string | null>(null)
const [website, setWebsite] = useState<string | null>(null)

useEffect(() => {
getProfile()
}, [session])

async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
}

async function uploadAvatar(event: ChangeEvent<HTMLInputElement>) {
try {
setUploading(true)

if (!event.target.files || event.target.files.length == 0) {
throw 'You must select an image to upload.'
}

const user = supabase.auth.user()
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${session?.user.id}${Math.random()}.${fileExt}`
const filePath = `${DEFAULT_AVATARS_BUCKET}/${fileName}`

let { error: uploadError } = await supabase.storage.uploadFile(filePath, file)

if (uploadError) {
throw uploadError
}

let { error: updateError } = await supabase.from('profiles').upsert({
id: user.id,
avatar_url: filePath,
})

if (updateError) {
throw updateError
}

setAvatar(null)
setAvatar(filePath)
} catch (error) {
alert(error.message)
} finally {
setUploading(false)
}
}

function setProfile(profile: Profile) {
setAvatar(profile.avatar_url)
setUsername(profile.username)
setWebsite(profile.website)
}

async function getProfile() {
try {
setLoading(true)
const user = supabase.auth.user()

let { data, error } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()

if (error) {
throw error
}

setProfile(data)
} catch (error) {
console.log('error', error.message)
} finally {
setLoading(false)
}
}

async function updateProfile() {
try {
setLoading(true)
const user = supabase.auth.user()

const updates = {
id: user.id,
username,
website,
updated_at: new Date(),
}

let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})

if (error) {
throw error
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}

return (
<div
style={{
minWidth: 250,
maxWidth: 600,
margin: 'auto',
display: 'flex',
flexDirection: 'column',
gap: 20,
}}
>
<div className="card">
<div>
<Avatar url={avatar} size={270} />
</div>
<UploadButton onUpload={uploadAvatar} loading={uploading} />
</div>

<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>

<div>
<button className="button block primary" onClick={() => updateProfile()} disabled={loading}>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>

<div>
<button className="button block" onClick={() => signOut()}>
Sign Out
</button>
</div>
</div>
)
}
54 changes: 54 additions & 0 deletions example/next-storage/components/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState } from 'react'
import { supabase } from '../lib/api'
// import styles from '../styles/Auth.module.css'

export default function Auth({}) {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')

const handleLogin = async (email: string) => {
try {
setLoading(true)
const { error, user } = await supabase.auth.signIn({ email })

if (error) {
throw error
}

console.log('user', user)
alert('Check your email for the login link!')
} catch (error) {
console.log('Error thrown:', error.message)
alert(error.error_description || error.message)
} finally {
setLoading(false)
}
}

return (
<div style={{ display: 'flex', gap: 20, flexDirection: 'column' }}>
<div>
<label>Email</label>
<input
type="email"
placeholder="Your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>

<div>
<button
onClick={(e) => {
e.preventDefault()
handleLogin(email)
}}
className={'button block'}
disabled={loading}
>
{loading ? 'Loading ..' : 'Send magic link'}
</button>
</div>
</div>
)
}
29 changes: 29 additions & 0 deletions example/next-storage/components/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, useState } from 'react'
import { supabase } from '../lib/api'

export default function Avatar({ url, size }: { url: string | null; size: number }) {
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)

useEffect(() => {
if (url) downloadImage(url)
}, [url])

async function downloadImage(path: string) {
try {
const { data, error } = await supabase.storage.downloadFile(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error.message)
}
}

return avatarUrl ? (
<img src={avatarUrl} className="avatar image" style={{ height: size, width: size }} />
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)
}
21 changes: 21 additions & 0 deletions example/next-storage/components/ProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Profile } from '../lib/constants'
import Avatar from './Avatar'

export default function ProfileCard({ profile }: { profile: Profile }) {
const lastUpdated = profile.updated_at ? new Date(profile.updated_at) : null
return (
<div className="card">
<Avatar url={profile.avatar_url} size={50} />
<p>Username: {profile.username}</p>
<p>Website: {profile.website}</p>
<p>
<small>
Last updated{' '}
{lastUpdated
? `${lastUpdated.toLocaleDateString()} ${lastUpdated.toLocaleTimeString()}`
: 'Never'}
</small>
</p>
</div>
)
}
Loading