Skip to content

Commit

Permalink
πŸͺπŸ₯Ž ↝ #8 Add file upload feature & auth handler
Browse files Browse the repository at this point in the history
1. Completed authentication header for web client. To the end user it is 100% offchain, with user profiles being stored on a postgresql database. However, I've taken a dive into the Magic sdk to create wallet addresses for each user, as well as a Flask-based authentication handler for future metamask/wallet interaction. I've made this decision for a few reasons (like simplicity), but the main reason is for the client to seem like a regular journal platform and not be confusing, as well as follow the 'web3-agnostic' design language I favour for projects like this due to confusion and/or distrust of web3 products/teams. However, since each user will have a wallet address, they'll be able to interact with smart contracts and IPFS just fine. Further discussion will need to take place to discuss long-term suitability of this model, including things like gas fees (currently everything regarding transactions is occurring on Goerli [testnet] and gas fees will be processed by a "superuser" so that there's no restrictions or huge expenses) and how we go about getting users to trust the web3 nature (which I've got a lot of experience with). However, I don't fully know the exact demographics we'll be targeting & also I understand that that's quite a while away, so I'll raise it now but won't spend any time thinking about it until the time comes
          2. I've continued with the contracts for proposals/publications & updating the metadata standards. I favour a lazy minting approach with data processing being handled by a Flask blueprint (which is a formula my team have developed on signal-k/sytizen). Right now I'm using Thirdweb & Moralis for the contract interactions and I have also, with much difficulty, succeeded in getting Moralis to self-host on my Postgres server. Finally, I've begun the process of optimising the base layer contracts so that the gas fees (which are already reduced post-merge) are essentially negligible at this time.
          3. File upload for posts/articles feature on the web client is complete, and the smart contracts now receive all file upload metadata from this.
          4. Begun a new flask blueprint (forked from point #2) to generate dataset previews based on which modules (e.g. lightkurve) are used and to add interactive nature to the 'sandbox' feature discussed earlier
          5. Reluctantly continued some documentation

(above message from the Desci discord, see Signal-K/sytizen#16 for more info)
  • Loading branch information
Gizmotronn committed Feb 8, 2023
1 parent 91bee1c commit 69497a1
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 47 deletions.
33 changes: 8 additions & 25 deletions components/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { UserContext } from "../context/UserContext";

type Profiles = Database['public']['Tables']['profiles']['Row'];

export default function PostCard ( { content, created_at, profiles:authorProfile } ) {
export default function PostCard ( { content, created_at, media, profiles:authorProfile } ) {
const [loading, setLoading] = useState(false);
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>();
const { profile: myProfile } = useContext(UserContext);
Expand All @@ -31,33 +31,13 @@ export default function PostCard ( { content, created_at, profiles:authorProfile
setDropdownOpen(false);
}

/*useEffect(() => {
supabase.from('profiles') // Fetch profile from user id matching session
.select()
.eq('id', session.user.id)
.then(result => {
if (result.data.length) {
setProfiles(result.data[0]);
}
});
}, []); */

/*useEffect(() => {
supabase.from('profiles')
.select(`avatar_url`)
.eq('id', session.user.id)
.then(result => {
setAvatarUrl(result.data[0].avatar_url) //console.log(result.data[0].avatar_url)
})
}, []);*/

return (
<Card noPadding={false}>
<div className="flex gap-3">
<div>
<Link href={'/posts/profile'}>
<span className="cursor-pointer">
<PostCardAvatar url={profile.avatar_url}
<PostCardAvatar url={authorProfile.avatar_url}
size={50} />
</span>
</Link>
Expand All @@ -66,7 +46,7 @@ export default function PostCard ( { content, created_at, profiles:authorProfile
<p>
<Link href={'/posts/profile'}>
<span className="mr-1 font-semibold cursor-pointer hover:underline">
{profile?.username}
{authorProfile?.username}
</span>
</Link>
shared a <a className="text-socialBlue">post</a>
Expand Down Expand Up @@ -120,8 +100,11 @@ export default function PostCard ( { content, created_at, profiles:authorProfile
</div>
<div>
<p className="my-3 text-sm">{content}</p>
{media?.length > 0 && media.map(media => (
<div><img src={media} /></div>
))}
<div className="rounded-md overflow-hidden">
<img src="https://images.unsplash.com/photo-1530841377377-3ff06c0ca713?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" alt=""/>
<img src="" alt=""/>
</div>
</div>
<div className="mt-5 flex gap-8">
Expand All @@ -147,7 +130,7 @@ export default function PostCard ( { content, created_at, profiles:authorProfile
<div className="flex mt-4 gap-3">
<div className="mt-1">
<AccountAvatar uid={session.user!.id}
url={profile.avatar_url}
url={authorProfile.avatar_url}
size={45} />
</div>
<div className="border grow rounded-full relative">
Expand Down
66 changes: 45 additions & 21 deletions components/PostFormCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useContext, useEffect, useState } from "react";
import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react";
import { Database } from "../utils/database.types";
import { UserContext } from "../context/UserContext";
import { ClimbingBoxLoader } from "react-spinners";

type Profiles = Database['public']['Tables']['profiles']['Row'];

Expand All @@ -12,18 +13,22 @@ export default function PostFormCard ( { onPost } ) {
const [content, setContent] = useState('');
const session = useSession();
const { profile } = useContext(UserContext);

const [loading, setLoading] = useState(false);

const [uploads, setUploads] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>();

function createPost () {
supabase.from('posts').insert({
author: session.user.id, // This is validated via RLS so users can't pretend to be other user
content, // : content,
media: uploads, // This should be changed to the user path `storage/userId/post/media...` like in the image gallery
// File upload -> show an icon depending on what type of file.
}).then(response => {
if (!response.error) {
alert(`Post ${content} created`);
setContent('');
setUploads([]);
if ( onPost ) {
onPost();
}
Expand All @@ -40,30 +45,30 @@ export default function PostFormCard ( { onPost } ) {
})
}, []);

async function getProfile() {
try {
setLoading(true);
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url, address`)
.eq('id', session.user.id)
.single()

if (error && status !== 406) {
throw error;
}
async function addMedia (e: { target: { files: any; }; }) {
const files = e.target.files;
if (files.length > 0) {
setIsUploading(true);
for (const file of files) { // To-Do: List of user's photos from the image gallery in wb3-10
const fileName = Date.now() + session?.user?.id + file.name; // Generate a random string to make the file unique
const result = await supabase.storage
.from('media') // Upload the file/media
.upload(fileName, file);

if (data) {
console.log('Received')
setAvatarUrl(data.avatar_url);
if (result.data) {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL + '/storage/v1/object/public/media/' + result.data.path;
setUploads(prevUploads => [...prevUploads, url]); // Add the most recently uploaded image to an array of images that are in state
} else {
console.log(result);
}
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
setIsUploading(false);
}
}

// https://qwbufbmxkjfaikoloudl.supabase.co/storage/v1/object/public/media/1675853386903cebdc7a2-d8af-45b3-b37f-80f328ff54d6image-asset.jpg
// https://qwbufbmxkjfaikoloudl.supabase.co/storage/v1/object/public/media1675853386903cebdc7a2-d8af-45b3-b37f-80f328ff54d6image-asset.jpg

return (
<Card noPadding={false}>
<div className="flex gap-2">
Expand All @@ -74,7 +79,26 @@ export default function PostFormCard ( { onPost } ) {
</div> { profile && (
<textarea value={content} onChange={e => setContent(e.target.value)} className="grow p-3 h-14" placeholder={`What's on your mind, ${profile?.username}?`} /> )}
</div>
{isUploading && (
<div><ClimbingBoxLoader /></div>
)}
{uploads.length > 0 && (
<div className="flex gap-2 mt-4">
{uploads.map(upload => (
<div className=""><img src={upload} className='w-auto h-48 rounded-md' /></div>
))}
</div>
)}
<div className="flex gap-5 items-center mt-2">
<div>
<label className="flex gap-1">
<input type='file' className="hidden" onChange={addMedia} />
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13" />
</svg>
<span className="hidden md:block">Media / File</span>
</label>
</div>
<div>
<button className="flex gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react": "18.2.0",
"react-clickout-handler": "^1.2.1",
"react-dom": "18.2.0",
"react-spinners": "^0.13.8",
"react-time-ago": "^7.2.1",
"tailwindcss": "^3.2.2"
},
Expand Down
2 changes: 1 addition & 1 deletion pages/posts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function SocialGraphHome() {

function fetchPosts () {
supabase.from('posts')
.select('id, content, created_at, profiles(id, avatar_url, username)') // Reset id on testing playground server later
.select('id, content, created_at, media, profiles(id, avatar_url, username)') // Reset id on testing playground server later
.order('created_at', { ascending: false })
.then(result => { setPosts(result.data); })
}
Expand Down
1 change: 1 addition & 0 deletions supabase/rls/media.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE POLICY "Logged in users can upload photos 1ps738_0" ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id = 'media' && auth.uid() != null); /* Also applies to 'photos'/'media' bucket(s) */
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4489,6 +4489,11 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==

react-spinners@^0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.8.tgz#5262571be0f745d86bbd49a1e6b49f9f9cb19acc"
integrity sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==

react-time-ago@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/react-time-ago/-/react-time-ago-7.2.1.tgz#101a549a8d7f51c225c82c74abc517ecef647b24"
Expand Down

0 comments on commit 69497a1

Please sign in to comment.