diff --git a/p2p/10.IPFS_or_HTTP/README.md b/p2p/10.IPFS_or_HTTP/README.md new file mode 100644 index 00000000..4d8fc92a --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/README.md @@ -0,0 +1,190 @@ +# 10. Learn the differences between HTTP and IPFS + +## πŸ’« Table of contents + +* [Step 0 - Setup](README.md#πŸ”§-step-0---setup) +* [Step 1 - HTTP](README.md#step-1---http) + * [Discover the basics](README.md#✏️-10-discover-the-basics) + * [Storage](README.md#πŸ’Ύ-11-storage) +* [Step 2 - IPFS](README.md#step-2---ipfs) + * [Improve the storage](README.md#πŸ•ΈοΈ-20-improve-the-storage) + * [Retrieve](README.md#πŸ“₯-22-retrieve) +* [Going further](README.md#πŸš€-going-further) + +In this Workshop, you will learn : + +βœ”οΈ The basics of HTTP + +βœ”οΈ The basics of IPFS, and why in some cases it is better than HTTP + +βœ”οΈ How to change a centralized storage into a distributed one via IPFS with Infura ! + +## πŸ”§ Step 0 - Setup + +Please follow each instruction on the [SETUP.md](SETUP.md) file. + +## Step 1 - HTTP + +### ✏️ 1.0 Discover the basics + +Wanna launch the back-end? It's very simple, just run the container you pulled in the setup: + +``` +docker run -p 8080:8080 sacharbon/workshop-ipfs +``` + +you should have this log : + +```bash +[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. + +[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. + - using env: export GIN_MODE=release + - using code: gin.SetMode(gin.ReleaseMode) + +[GIN-debug] GET /images --> main.getImages (4 handlers) +[GIN-debug] GET /images/:id --> main.getImageByID (4 handlers) +[GIN-debug] POST /upload --> main.uploadImage (4 handlers) +[GIN-debug] GET /uploads/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (4 handlers) +[GIN-debug] HEAD /uploads/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (4 handlers) +[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. +Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details. +[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080 +``` + +As you can see, there is different route for the back-end of our website. You can open your favorite browser like Firefox or Chrome (or Opera, +no discrimination here) and go to this URL: [http://0.0.0.0:8080/images](http://0.0.0.0:8080/images). + +You should see an empty array !! + +If you want to shut down the server, use `Ctrl` + `C` and then run `docker stop ${id}`. + +Now it's time to wake the front-end up. + +Go to [the source of the front-end](./openocean/frontend/) and type `bun i` to download the dependencies. Then, type `bun dev` to launch the front-side of our web-app. + +Now visit this URL: [localhost:5173](localhost:5173). You should see a pretty UI made by two genius. + +### πŸ’Ύ 1.1 Storage + +Go to [Unsplash](https://unsplash.com/photos/a-woman-sitting-at-a-table-using-a-cell-phone-nplkFSNschY) website and download the image. +By the way, you can look for any other image you prefer on this website, it is free and open source. For the example, we are going to stick with this image. +Go back to [http://localhost:5173/](http://localhost:5173), scroll down and click on the button on the `up right`. +Fill the form correctly and validates it. + +Now, look at your terminal, you should see those strange logs appear: + +```bash +[GIN] 2024/12/02 - 19:10:29 | 200 | 85.347Β΅s | 172.17.0.1 | GET "/images" +[GIN] 2024/12/02 - 19:10:29 | 200 | 140.871Β΅s | 172.17.0.1 | GET "/images" +[GIN] 2024/12/02 - 19:10:37 | 200 | 2.93712ms | 172.17.0.1 | POST "/upload" +[GIN] 2024/12/02 - 19:10:37 | 200 | 30.521Β΅s | 172.17.0.1 | GET "/images/08245a6c-0843-4f68-bb41-c1dea72369c7" +[GIN] 2024/12/02 - 19:10:37 | 200 | 15.784Β΅s | 172.17.0.1 | GET "/images/08245a6c-0843-4f68-bb41-c1dea72369c7" +[GIN] 2024/12/02 - 19:10:37 | 200 | 5.895332ms | 172.17.0.1 | GET "/uploads/65fb793f-a8b5-4c88-9fbc-6432d40961ba.png" + +``` + +Let me explain: +The first part of the message is obviously the date-time. The second one is two numbers. The `200` is the most interesting : it is a status, preview code. `200` means that the server is OK to give us that page from the `/images` route, and it has been delivered correctly. +Then, the time it took to respond to the request. Afterward, the address which requested. And finally, the method, `GET`, because we want to just get the page ; we are asking the server to give us the `/images` route which is the home page. + +![](http_request_flowchart.png) +*Scheme of a HTTP request* + +>πŸ’‘ What does `POST` means ? + +`POST` is another **HTTP method** than `GET`. When you fill the form earlier, it was **you** that was giving the server +some information :that is the main difference between `POST` and `GET`. + +> πŸ’‘ Learn more about HTTP methods [here](https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol). + +Then, go to look at our `uploads` folder : you have the file you just downloaded in here ! +>πŸ’‘ This is how HTTP works. When retrieving data, HTTP focuses on **location**. + +## Step 2 - IPFS + +> πŸ’‘ HTTP is cool but has its limits : if the server is down, you won't be able to retrieve the data stored. Furthermore, your government can easily block access to certain servers by their IPs that host particular website for censure purposes. +Let's see how IPFS answers this issues. + +At its core, IPFS is a [distributed system](https://en.wikipedia.org/wiki/Distributed_computing) for storing and accessing files, websites, applications, and data. +Instead of referring to data (photos, articles, videos) by **location**, or which server they are stored on, IPFS refers +to everything by that data’s [hash](https://docs.ipfs.io/concepts/hashing/#hashes-are-important), meaning the **content itself.** + +The idea is that if you want to access a particular page from your browser, IPFS will ask the entire network, β€œdoes anyone +have the data that corresponds to this hash?” A node on IPFS that contains the corresponding hash will return the data, allowing you to access it from anywhere (and potentially even offline). + +If this is not enough clear for you, I strongly advise you to refer to this [video (Simply Explained IPFS)](https://www.youtube.com/watch?v=5Uj6uR3fp-U). + +### πŸ•ΈοΈ 2.0 Improve the storage + +Here is what we are going to do : We are going to upload our files directly on IPFS and not locally anymore. +Instead of having the file locally, let's pin it with [Pinata](https://pinata.cloud/). Which is a pinning service. + +1. Go to `frontend/src/hooks/` and create a new hook `usePinFileToIPFS.ts` as a manner of `usePostImage.ts` It should call the [list file Pinata API route](https://docs.pinata.cloud/api-reference/endpoint/list-files). +> πŸ’‘ Don't forget to create an [Pinata API & Gateway key](https://app.pinata.cloud/developers/api-keys) and write it down into your a `.env`. You can create one by typing in your terminal in `frontend/` folder: + + ``` +cp .env.dist .env +``` + +2. Go to `frontend/src/pages/Upload.tsx` and modify the code of the upload view to communicate with your new hook in order to upload the file there. +3. Go https://app.pinata.cloud/pinmanager and make sure the hash of the song appears. + +
+Some Trouble with IPFS API ? + Here is some links that could help you: +
  • + What is an API ? +
  • +
  • + Infura IPFS API +
  • +
  • + ipfs-Api python package +
  • +
    + +### πŸ“₯ 2.2 Retrieve + +Last step : if anyone wants to see from our website some images, we need to get it from IPFS. +Since you did the previous step, this one would seem easy : in your `src/page` + on the `index.tsx`, do the same thing as previously but instead of adding a file, call the get method to retrieve your image. + +## πŸš€ Going further + +A very cool feature with IPFS is that if someone is having an IPFS node running on its machine and download your image then you deleted it, you will be able to retrieve it from its node ! + +* Learn [how](https://docs.ipfs.io/how-to/command-line-quick-start) you can deploy and configure your own IPFS node. +* Want to store a lot of data on IPFS but being the only one that can access it? Look at [OrbitDB](https://orbitdb.org/). + +## Authors + +| [
    Sacha Dujardin](https://github.com/Sacharbon) | +| :---: | + +

    +Organization +

    +
    +

    + + LinkedIn logo + + + Instagram logo + + + Twitter logo + + + Discord logo + +

    +

    + + Website logo + +

    + +> πŸš€ Don't hesitate to follow us on our different networks, and put a star 🌟 on `PoC's` repositories. diff --git a/p2p/10.IPFS_or_HTTP/SETUP.md b/p2p/10.IPFS_or_HTTP/SETUP.md new file mode 100644 index 00000000..d776181b --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/SETUP.md @@ -0,0 +1,10 @@ +# Setup πŸ”§ + +In order to launch our image platform, you have to download Docker and Bun. + +* [Docker](https://docs.docker.com/get-started/get-docker/) + Once docker is downloaded, pull this docker repo `sacharbon/workshop-ipfs` + +* [Bun](https://bun.sh/) + +[Go back to the exercise](README.md#step-1---http) diff --git a/p2p/old/3.IPFS_or_HTTP/http_request_flowchart.png b/p2p/10.IPFS_or_HTTP/http_request_flowchart.png similarity index 100% rename from p2p/old/3.IPFS_or_HTTP/http_request_flowchart.png rename to p2p/10.IPFS_or_HTTP/http_request_flowchart.png diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/.env.dist b/p2p/10.IPFS_or_HTTP/openocean/frontend/.env.dist new file mode 100644 index 00000000..3f3c2a64 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/.env.dist @@ -0,0 +1,2 @@ +VITE_PINATA_API_KEY= +VITE_PINATA_GATEWAY= diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/.eslintrc.cjs b/p2p/10.IPFS_or_HTTP/openocean/frontend/.eslintrc.cjs new file mode 100644 index 00000000..d6c95379 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/.gitignore b/p2p/10.IPFS_or_HTTP/openocean/frontend/.gitignore new file mode 100644 index 00000000..50c8dda2 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/bun.lockb b/p2p/10.IPFS_or_HTTP/openocean/frontend/bun.lockb new file mode 100755 index 00000000..de7b05c3 Binary files /dev/null and b/p2p/10.IPFS_or_HTTP/openocean/frontend/bun.lockb differ diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/index.html b/p2p/10.IPFS_or_HTTP/openocean/frontend/index.html new file mode 100644 index 00000000..6101660e --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + OpenOcean + + +
    + + + diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/package.json b/p2p/10.IPFS_or_HTTP/openocean/frontend/package.json new file mode 100644 index 00000000..869faad7 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/package.json @@ -0,0 +1,40 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@tanstack/react-query": "^5.51.11", + "axios": "^1.7.2", + "env-var": "^7.5.0", + "framer-motion": "^11.2.11", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.52.0", + "react-icons": "^5.2.1", + "react-router-dom": "^6.23.1", + "vite-plugin-node-polyfills": "^0.22.0", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/public/_redirects b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/_redirects new file mode 100644 index 00000000..78f7f206 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/public/openocean.png b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/openocean.png new file mode 100644 index 00000000..db253a00 Binary files /dev/null and b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/openocean.png differ diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/public/vite.svg b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/App.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/App.tsx new file mode 100644 index 00000000..d38365e4 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/App.tsx @@ -0,0 +1,55 @@ +import Header from "./organisms/Header"; +import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; +import theme, { colors } from "./theme"; +import { ChakraProvider, VStack } from "@chakra-ui/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import Home from "./pages"; +import { FC } from "react"; +import ImageDetailsPage from "./pages/images/:id"; +import ImagePage from "./pages/images"; +import UploadPage from "./pages/Upload"; + +const Layout: FC = () => ( + +
    + + + + +); + +const queryClient = new QueryClient; + +const App: FC = () => { + return ( + + + + + }> + } /> + } /> + + } /> + } /> + + + + + + + ); +}; + +export default App; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/assets/react.svg b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/HeaderContainer.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/HeaderContainer.tsx new file mode 100644 index 00000000..655d43cf --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/HeaderContainer.tsx @@ -0,0 +1,19 @@ +import { HStack } from "@chakra-ui/react"; +import { FC, PropsWithChildren } from "react"; + +const HeaderContainer: FC = ({ children }) => ( + + {children} + +); + +export default HeaderContainer; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/IMGCard.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/IMGCard.tsx new file mode 100644 index 00000000..923b24f1 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/IMGCard.tsx @@ -0,0 +1,34 @@ +import { AspectRatio, Box } from "@chakra-ui/react"; +import { FC } from "react"; +import { urlFromFileName } from "../utils"; + +interface ImageCardProps { + url?: string; + ratio?: number; +} +const IMGCard: FC = ({ url, ratio = 16 / 9 }) => ( + + + +); + +export default IMGCard; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/Logo.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/Logo.tsx new file mode 100644 index 00000000..6ce3e9a5 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/atoms/Logo.tsx @@ -0,0 +1,14 @@ +import { Icon, Text, VStack } from "@chakra-ui/react"; +import { FC } from "react"; +import { BsEasel } from "react-icons/bs"; + +const Logo: FC = () => ( + + + + OpenOcean + + +); + +export default Logo; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/constants.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/constants.ts new file mode 100644 index 00000000..6aa56907 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/constants.ts @@ -0,0 +1,13 @@ +import { from } from "env-var"; + +const vars = { + VITE_PINATA_API_KEY: import.meta.env.VITE_PINATA_API_KEY, + VITE_PINATA_GATEWAY: import.meta.env.VITE_PINATA_GATEWAY, +}; + +const env = from(vars, {}); + +export const constants = { + pinataAPIKey: env.get("VITE_PINATA_API_KEY").asString(), + pinataGateway: env.get("VITE_PINATA_GATEWAY").asString(), +}; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImageByID.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImageByID.ts new file mode 100644 index 00000000..2de8c108 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImageByID.ts @@ -0,0 +1,11 @@ +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; + +const useGetImageByID = () => + useMutation({ + mutationFn: (id: string | undefined) => { + return axios.get(`http://localhost:8080/images/${id}`); + }, + }); + +export default useGetImageByID; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImages.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImages.ts new file mode 100644 index 00000000..31058195 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/useGetImages.ts @@ -0,0 +1,11 @@ +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; + +const useGetImages = () => + useMutation({ + mutationFn: () => { + return axios.get("http://localhost:8080/images"); + }, + }); + +export default useGetImages; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/usePostImage.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/usePostImage.ts new file mode 100644 index 00000000..c1aa8b4b --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/hooks/usePostImage.ts @@ -0,0 +1,15 @@ +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; + +const usePostImage = () => + useMutation({ + mutationFn: (params: { image: File, name: string }) => { + const formData = new FormData(); + formData.append("image", params.image); + formData.append("name", params.name); + console.log(formData); + return axios.post("http://localhost:8080/upload", formData); + }, + }); + +export default usePostImage; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/index.css b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/index.css new file mode 100644 index 00000000..54aa67d4 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/index.css @@ -0,0 +1,8 @@ +html { + background-color: rgb(41, 41, 41); +} + +::-webkit-scrollbar { + width: 0px; + background: transparent; /* make scrollbar transparent */ +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/main.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/main.tsx new file mode 100644 index 00000000..966f17a4 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/HomeButton.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/HomeButton.tsx new file mode 100644 index 00000000..aa10c916 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/HomeButton.tsx @@ -0,0 +1,16 @@ +import { FC } from "react"; +import Logo from "../atoms/Logo"; +import { Box } from "@chakra-ui/react"; +import { useNavigate } from "react-router-dom"; + +const HomeButton: FC = () => { + const navigate = useNavigate(); + + return ( + navigate("/")}> + + + ); +}; + +export default HomeButton; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/IMGCardWithDetails.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/IMGCardWithDetails.tsx new file mode 100644 index 00000000..61af1ab2 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/IMGCardWithDetails.tsx @@ -0,0 +1,61 @@ +import { Box, Card, Icon, Text, Tooltip } from "@chakra-ui/react"; +import { FC } from "react"; +import IMGCard from "../atoms/IMGCard"; +import { FaQuestion } from "react-icons/fa"; + +interface IMGCardWithDetailsProps { + name: string; + description: string; + img?: string; + onClick: () => void; +} +const IMGCardWithDetails: FC = ({ + name, + description, + img, + onClick, +}) => ( + + + {description} + + + } + > + + + {!img && ( + + )} + + + {name} + + + + +); + +export default IMGCardWithDetails; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/NavButton.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/NavButton.tsx new file mode 100644 index 00000000..5fc68c30 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/NavButton.tsx @@ -0,0 +1,27 @@ +import { Button, Icon } from "@chakra-ui/react"; +import { FC } from "react"; +import { IconType } from "react-icons"; +import { useLocation, useNavigate } from "react-router-dom"; + +interface NavButtonProps { + path: string; + icon: IconType; + label: string; +} +const NavButton: FC = ({ path, icon, label }) => { + const navigate = useNavigate(); + const location = useLocation(); + + return ( + + ); +}; + +export default NavButton; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SectionTitle.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SectionTitle.tsx new file mode 100644 index 00000000..d2d8610e --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SectionTitle.tsx @@ -0,0 +1,24 @@ +import { Button, HStack, Text } from "@chakra-ui/react"; +import { FC } from "react"; + +interface SectionTitleProps { + title: string; + subtitle?: string; + onClick: () => void; +} +const SectionTitle: FC = ({ + onClick, + title, + subtitle = "View all", +}) => ( + + + {title} + + + +); + +export default SectionTitle; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SingleUploadImage.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SingleUploadImage.tsx new file mode 100644 index 00000000..e60a580b --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/molecules/SingleUploadImage.tsx @@ -0,0 +1,76 @@ +import { + Center, + chakra, + VStack, + ScaleFade, + Text, + Icon, + Image, + ChakraProps, + CenterProps, +} from "@chakra-ui/react"; +import { FC, useState } from "react"; +import { MdAdd } from "react-icons/md"; + +interface SingleUploadImageProps { + size?: ChakraProps["boxSize"]; + rounded?: ChakraProps["rounded"]; + onUpdateFile: (file: File) => void; +} +const SingleUploadImage: FC = ({ + onUpdateFile, + ...props +}) => { + const [uploadedFile, setUploadedFile] = useState(null); + + const handleFileChange = (event: { target: { files: FileList | null } }) => { + const file = event.target.files?.[0]; + if (!file) return; + setUploadedFile(file); + onUpdateFile(file); + }; + + return ( +
    + {uploadedFile ? ( + + Uploaded + + ) : ( +
    + + + Upload + +
    + )} + + +
    + ); +}; + +export default SingleUploadImage; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/AvailableImages.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/AvailableImages.tsx new file mode 100644 index 00000000..1a758f08 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/AvailableImages.tsx @@ -0,0 +1,40 @@ +import { HStack, Box } from "@chakra-ui/react"; +import { motion } from "framer-motion"; +import { FC } from "react"; +import IMGCard from "../atoms/IMGCard"; +import { useNavigate } from "react-router-dom"; + +const AvailableImages: FC = ({ images }) => { + const navigate = useNavigate(); + + return ( + + + {images.map((image: any) => ( + navigate(`/images/${image.id}`)} + w={`${33}vw`} + px="24px" + flexShrink={0} + > + + + ))} + + + ); +}; + +export default AvailableImages; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/Header.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/Header.tsx new file mode 100644 index 00000000..54818640 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/organisms/Header.tsx @@ -0,0 +1,17 @@ +import { FC } from "react"; +import { HStack } from "@chakra-ui/react"; +import HomeButton from "../molecules/HomeButton"; +import NavButton from "../molecules/NavButton"; +import HeaderContainer from "../atoms/HeaderContainer"; +import { RiImageAddFill } from "react-icons/ri"; + +const Header: FC = () => ( + + + + + + +); + +export default Header; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/Upload.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/Upload.tsx new file mode 100644 index 00000000..2b27afe5 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/Upload.tsx @@ -0,0 +1,148 @@ +import { + FormControl, + Button, + FormErrorMessage, + FormLabel, + Icon, + Input, + Stack, + Text, + VStack, + chakra, + useToast, +} from "@chakra-ui/react"; +import { FC } from "react"; +import SingleUploadImage from "../molecules/SingleUploadImage"; +import { colors } from "../theme"; +import { useForm } from "react-hook-form"; +import { RiImageAddFill } from "react-icons/ri"; +import usePostImage from "../hooks/usePostImage"; +import { useNavigate } from "react-router-dom"; + +interface MintForm { + name: string; + description: string; + file: File; + collection: string; + price: number; +} + +const UploadPage: FC = () => { + const { + handleSubmit, + register, + setValue, + clearErrors, + setError, + formState: { errors }, + } = useForm(); + + const toast = useToast(); + + const navigate = useNavigate(); + + const { mutate: postImage, isPending: isPendingPostImage } = + usePostImage(); + + const onSubmit = handleSubmit((data) => { + if (!data.file) { + setError("file", { message: "This field is required" }); + return; + } + postImage({ image: data.file, name: data.name }, { + onSuccess: (res) => { + toast({ + colorScheme: "purple", + title: "Image uploaded", + description: "Your image has been uploaded successfully!", + status: "success", + duration: 5000, + isClosable: true, + }); + navigate(`/images/${res.data.id}`) + }, + onError: (err) => { + console.log(err) + toast({ + colorScheme: "red", + title: "Error", + description: "There was an error uploading the image to IPFS", + status: "error", + duration: 5000, + isClosable: true, + }); + }, + }); + }); + + return ( + + + Upload new picture + + + + { + setValue("file", file); + clearErrors("file"); + }} + /> + {errors.file && ( + An image is required + )} + + + + + + Name + + {errors.name && ( + This field is required + )} + + + + + + + + ); +}; + +export default UploadPage; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/:id.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/:id.tsx new file mode 100644 index 00000000..8e47588e --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/:id.tsx @@ -0,0 +1,51 @@ +import { + Box, + HStack, + Text, + VStack, +} from "@chakra-ui/react"; +import { FC, useState, useEffect } from "react"; +import IMGCard from "../../atoms/IMGCard"; +import { useParams } from "react-router-dom"; +import useGetImageByID from "../../hooks/useGetImageByID"; + +const ImageDetailsPage: FC = () => { + const { id } = useParams(); + const [img, setImg] = useState([]); + + const { mutate: getImageByID } = useGetImageByID(); + + useEffect(() => { + getImageByID(id, { + onSuccess: (data) => { + setImg(data.data); + console.log(data.data) + }, + onError: (error) => { + console.error("Erreur :", error); + }, + }); + }, [getImageByID]); + + if (!img) return null; + + return ( + + + + + #{img.id} + + {img.name} + + + + + + + + + ); +}; + +export default ImageDetailsPage; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/index.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/index.tsx new file mode 100644 index 00000000..bde36098 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/images/index.tsx @@ -0,0 +1,62 @@ +import { + Card, + Divider, + SimpleGrid, + Text, + VStack, +} from "@chakra-ui/react"; +import { FC, useState, useEffect } from "react"; +import IMGCard from "../../atoms/IMGCard"; +import { useNavigate } from "react-router-dom"; +import useGetImages from "../../hooks/useGetImages"; + +const ImageCard: FC<{ img: any }> = ({ img }) => { + const navigate = useNavigate(); + return ( + navigate(`/images/${img.id}`)} + cursor="pointer" + role="group" + gap="12px" + p="12px" + > + + {img.metadata?.name} + + + + + ); +}; + +const ImagePage: FC = () => { + const [images, setImages] = useState([]); + + const { mutate: getImages } = useGetImages(); + + useEffect(() => { + getImages(undefined, { + onSuccess: (data) => { + setImages(data.data); + }, + onError: (error) => { + console.error("Erreur :", error); + }, + }); + }, [getImages]); + + return ( + + + All images + + + {images.map((img) => ( + + ))} + + + ); +}; + +export default ImagePage; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/index.tsx b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/index.tsx new file mode 100644 index 00000000..7264fcae --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/pages/index.tsx @@ -0,0 +1,39 @@ +import { FC } from "react"; +import { VStack } from "@chakra-ui/react"; +import SectionTitle from "../molecules/SectionTitle"; +import { useNavigate } from "react-router-dom"; +import AvailableImages from "../organisms/AvailableImages"; +import useGetImages from "../hooks/useGetImages"; +import { useEffect, useState } from "react"; + +const Home: FC = () => { + const navigate = useNavigate(); + const [images, setImages] = useState([]); + + const { mutate: getImages } = useGetImages(); + + useEffect(() => { + getImages(undefined, { + onSuccess: (data) => { + setImages(data.data); + }, + onError: (error) => { + console.error("Erreur :", error); + }, + }); + }, [getImages]); + + + return ( + <> + {images && ( + < VStack align="start" w="100%" h="100%" spacing="24px"> + navigate("/images")} /> + + + )} + + ); +}; + +export default Home; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/theme.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/theme.ts new file mode 100644 index 00000000..ba351736 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/theme.ts @@ -0,0 +1,113 @@ +import { defineStyleConfig, extendTheme } from "@chakra-ui/react"; + +export const colors = { + gray: { + 50: "#f7fafc", + 100: "#edf2f7", + 200: "#e2e8f0", + 300: "#cbd5e0", + 400: "#a0aec0", + 500: "#718096", + 600: "#4a5568", + 700: "#2d3748", + 800: "#1a202c", + 900: "#171923", + }, +}; + +const Button = defineStyleConfig({ + baseStyle: { + fontWeight: "bold", + borderRadius: "base", + }, + variants: { + primary: { + p: "0px", + fontFamily: "heading", + color: "gray.400", + _hover: { + color: "gray.50", + }, + transitionDuration: "0.5s", + }, + secondary: { + p: "0px", + fontFamily: "heading", + background: "purple.600", + boxShadow: "lg", + color: "gray.300", + _hover: { + border: `1px solid ${colors.gray[300]}`, + color: "gray.200", + background: "purple.400", + }, + border: `1px solid ${colors.gray[900]}`, + transitionDuration: "0.5s", + }, + tertiary: { + p: "0px", + fontFamily: "heading", + background: "gray.800", + boxShadow: "lg", + color: "gray.200", + _hover: { + border: `1px solid ${colors.gray[500]}`, + color: "gray.100", + background: "gray.700", + }, + border: `1px solid ${colors.gray[700]}`, + transitionDuration: "0.5s", + }, + }, + defaultProps: { + size: "md", + variant: "primary", + }, +}); + +const Card = { + parts: ["container"], + baseStyle: { + container: { + border: `1px solid ${colors.gray[700]}`, + boxShadow: "lg", + bg: "gray.800", + }, + }, +}; + +const theme = extendTheme({ + colors, + fonts: { + heading: "Poppins, sans-serif", + body: "Whyte, sans-serif", + }, + components: { + Table: { + variants: { + simple: { + th: { + borderColor: "gray.700", + }, + td: { + borderColor: "gray.800", + }, + }, + }, + }, + Text: { + baseStyle: { + color: "gray.50", + }, + }, + Heading: { + baseStyle: { + color: "gray.50", + }, + }, + Button, + Card, + }, +}); + +export default theme; diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/utils/index.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/utils/index.ts new file mode 100644 index 00000000..b5774093 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/utils/index.ts @@ -0,0 +1,30 @@ +import { constants } from "../constants"; + +export const displayUgnot = (ugnot: number) => { + return ugnot >= 1000 + ? `${Math.floor(ugnot / 1000).toLocaleString("fr-FR")} GNOT` + : `${ugnot.toLocaleString("fr-FR")} ugnot`; +}; + +export const displayGnot = (gnot: number) => { + return `${gnot.toLocaleString("fr-FR", { + style: "currency", + currency: "USD", + })} GNOT`; +}; + +export const parseDataJson = (data: string): T => { + const content = data.slice(2, -'" string)'.length).replaceAll("\\", ""); + return JSON.parse(content); +}; + +export const urlFromIpfsHash = (hash: string) => + hash.includes("https://") + ? hash + : `https://${constants.pinataGateway}/ipfs/${hash}`; + +export const urlFromFileName = (filename: string): string => { + const url = `http://localhost:8080/uploads/${filename}`; + console.log(url); + return url; +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/src/vite-env.d.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.json b/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.json new file mode 100644 index 00000000..d4aba321 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.node.json b/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.node.json new file mode 100644 index 00000000..97ede7ee --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/p2p/10.IPFS_or_HTTP/openocean/frontend/vite.config.ts b/p2p/10.IPFS_or_HTTP/openocean/frontend/vite.config.ts new file mode 100644 index 00000000..634dc411 --- /dev/null +++ b/p2p/10.IPFS_or_HTTP/openocean/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), nodePolyfills()], +}); diff --git a/p2p/old/3.IPFS_or_HTTP/README.md b/p2p/old/3.IPFS_or_HTTP/README.md deleted file mode 100644 index 5aea7da3..00000000 --- a/p2p/old/3.IPFS_or_HTTP/README.md +++ /dev/null @@ -1,212 +0,0 @@ -# 3. Learn the differences between HTTP and IPFS ! - -## πŸ’« Table of contents -* [Step 0 - Setup](README.md#πŸ”§-step-0---setup) -* [Step 1 - HTTP](README.md#step-1---http) - * [Discover the basics](README.md#✏️-10-discover-the-basics) - * [Storage](README.md#πŸ’Ύ-11-storage) -* [Step 2 - IPFS](README.md#step-2---ipfs) - * [Improve the storage](README.md#πŸ•ΈοΈ-20-improve-the-storage) - * [Retrieve](README.md#πŸ“₯-22-retrieve) -* [Going further](README.md#πŸš€-going-further) - - -In this Workshop, you will learn : - -βœ”οΈ Run a web application with `docker-compose` - -βœ”οΈ The basics of HTTP - -βœ”οΈ The basics of IPFS, and why in some cases it is better than HTTP - -βœ”οΈ How to change a centralized storage into a distributed one via IPFS with Infura ! - -## πŸ”§ Step 0 - Setup -Please follow each instruction on the [SETUP.md](SETUP.md) file. - -## Step 1 - HTTP -### ✏️ 1.0 Discover the basics -Wanna launch the platform? Alright, make sure you are on the [sources](./sources.zip) directory where the `Dockerfile` and -`docker-compose.yml` are. - -Build the multi service container : -```bash -sudo docker-compose build -``` -You have to only use this command once. After several steps, you should have this output: -```bash -Successfully built a998a543950c -Successfully tagged music-share_web:latest -``` -> πŸ’‘ The number `a998a543950c` is the id of your container, it's ok if yours is different. - -Then, each time you want to launch your website, run : -```bash -docker-compose up -``` -you should have this log : -```bash -Starting music-share_db_1 ... done -Starting music-share_web_1 ... done -Attaching to music-share_db_1, music-share_web_1 -db_1 | -db_1 | PostgreSQL Database directory appears to contain a database; Skipping initialization -db_1 | -db_1 | 2021-08-02 20:48:37.281 UTC [1] LOG: starting PostgreSQL 13.2 (Debian 13.2-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit -db_1 | 2021-08-02 20:48:37.281 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432 -db_1 | 2021-08-02 20:48:37.281 UTC [1] LOG: listening on IPv6 address "::", port 5432 -db_1 | 2021-08-02 20:48:37.283 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" -db_1 | 2021-08-02 20:48:37.286 UTC [25] LOG: database system was shut down at 2021-08-02 20:48:32 UTC -db_1 | 2021-08-02 20:48:37.289 UTC [1] LOG: database system is ready to accept connections -web_1 | No changes detected -web_1 | Operations to perform: -web_1 | Apply all migrations: admin, auth, contenttypes, musicshare, sessions -web_1 | Running migrations: -web_1 | No migrations to apply. -web_1 | Watching for file changes with StatReloader -web_1 | Performing system checks... -web_1 | -web_1 | System check identified no issues (0 silenced). -web_1 | August 02, 2021 - 20:48:39 -web_1 | Django version 3.1.6, using settings 'music_share_project.settings' -web_1 | Starting development server at http://0.0.0.0:8000/ -web_1 | Quit the server with CONTROL-C. - -``` -As you can see, `db_1` is our database which seems okay and ready to accept new connections, -and `web_1` is the backend of our website. You can open your favorite browser like Firefox or Chrome (or Opera, -no discrimination here) and go to this url: [http://0.0.0.0:8000/](http://0.0.0.0:8000/). - -You should see our beautiful website !! - -If you want to shut down the server, use `Ctrl` + `C` and then run `docker-compose down -v`. - -### πŸ’Ύ 1.1 Storage - -Go to the [freearchivemusic](https://freemusicarchive.org/music/Scott_Holmes/rock-background-music/country-road-drive) -website and download the song. -By the way, you can look for any other genres of music you prefer on this website, it is free and open source. For the example, we are going to -stick with this song. -Go back to [http://0.0.0.0:8000/](http://0.0.0.0:8000/), scroll down and click on the button `upload a song`. -Fill the form correctly and validates it. - -You can find the code of the form in `musichare/forms.py`, and the associate model object in -`musicshare/models.py`. - -Now, look at your terminal, you should see those strange logs appear: -```bash -web_1 | [06/Aug/2021 11:02:33] "GET / HTTP/1.1" 200 4650 -web_1 | [06/Aug/2021 11:02:37] "GET /upload/ HTTP/1.1" 200 4640 -web_1 | [06/Aug/2021 11:02:57] "POST /upload/ HTTP/1.1" 302 0 -web_1 | [06/Aug/2021 11:02:57] "GET /success/ HTTP/1.1" 200 4360 -web_1 | [06/Aug/2021 11:02:59] "POST / HTTP/1.1" 200 5120 -web_1 | [06/Aug/2021 11:02:59] "GET /media/static/Scott_Holmes_Music_-_Country_Road_Drive.mp3 HTTP/1.1" 200 8983053 -``` -Let me explain: -The first part of the message is obviously the datetime. The second one is the method, `GET`, because we want to just -get the page ; we are asking the server to give us the `/` route which is the home page. -Then, the `HTTP` protocol and finally two numbers. The `200` is the most interesting : it is a status, preview code. `200` means -that the server is ok to give us that page from the `/` route, and it has been delivered correctly. - -![](http_request_flowchart.png) -*Scheme of a HTTP request* - ->πŸ’‘ What does `POST` means ? - -`POST` is another **HTTP method** than `GET`. When you fill the form earlier, it was **you** that was giving the server -some information :that is the main difference between `POST` and `GET`. - -> πŸ’‘ Learn more about HTTP methods [here](https://www.restapitutorial.com/lessons/httpmethods.html). - -Now, look at our `media/static` folder : you have the mp3 file you just downloaded in here ! ->πŸ’‘ This is how HTTP works. When retrieving data, HTTP focuses on **location**. Have you noticed the url ? -First, it is `0.0.0.0` which is your local IP; then `:8000` to signify the port. Finally, the routes or file you want to -get, joined by a `/`. That format does not ring a bell to you ? It is like a path ! - -## Step 2 - IPFS - -> πŸ’‘ HTTP is cool but has its limits : if the server is down, you won't be able to retrieve the data stored. Furthermore, -your government can easily block access to certain servers by their IPs that host particular website for censure purposes. -Let's see how IPFS answers this issues. - -At its core, IPFS is a [distributed system](https://blog.stackpath.com/distributed-system/) for storing and accessing files, websites, applications, and data. -Instead of referring to data (photos, articles, videos) by **location**, or which server they are stored on, IPFS refers -to everything by that data’s [hash](https://docs.ipfs.io/concepts/hashing/#hashes-are-important), meaning the **content itself.** - -The idea is that if you want to access a particular page from your browser, IPFS will ask the entire network, β€œdoes anyone -have the data that corresponds to this hash?” A node on IPFS that contains the corresponding hash will return the data, allowing you to access it from anywhere (and potentially even offline). - -If this is not enough clear for you, I strongly advise you to refer to this [video (Simply Explained IPFS)](https://www.youtube.com/watch?v=5Uj6uR3fp-U). - -### πŸ•ΈοΈ 2.0 Improve the storage - -Here is what we are going to do : We are going to upload our files directly on IPFS and not locally anymore. -Instead of having the file locally, let's have its corresponding hash in our database. - -1. Go to `musicshare/models.py` and add a CharField for the hash. -2. Go to `musicshare/views.py` and modify the code of the upload view to communicate with IPFS API in order to upload the file there. - Please use port `5001` for the connection. -3. Open `musicshare/templates/musicshare/index.html` to line `179` - and make sure the hash of the song appears. - -
    -Some Trouble with IPFS API ? - Here is some links that could help you: -
  • - What is an API ? -
  • -
  • - Infura IPFS API -
  • -
  • - ipfs-Api python package -
  • -
    - -To apply your migrations, shut down the server and relaunch it. - -Test another time to upload your music, copy past the hash of the newly added song and go to `https://ipfs.infura.io:5001/api/v0/cat?arg={your_hash_here}`. -You should see your song play, even if the server has been shut down ! - -### πŸ“₯ 2.2 Retrieve -Last step : if anyone wants to download from our website some mp3 songs, we need to get it from IPFS. -Since you did the previous step, this one would seem easy : in your `musicshare/views.py` - on the `download` view, do the same thing as previously but instead of adding a file, call the `cat` (or `get`) method. - -## πŸš€ Going further -A very cool feature with IPFS is that if someone is having an IPFS node running on its machine and download your mp3 audio -then you deleted it, you will be able to retrieve it from its node ! - -* Learn [how](https://docs.ipfs.io/how-to/command-line-quick-start) you can deploy and configure your own IPFS node. -* Want to store a lot of data on IPFS but being the only one that can access it? Look at [OrbitDB](https://orbitdb.org/). - -## Authors - -| [
    Adina Cazalens](https://github.com/NaadiQmmr) | -| :---: | -

    -Organization -

    -
    -

    - - LinkedIn logo - - - Instagram logo - - - Twitter logo - - - Discord logo - -

    -

    - - Website logo - -

    - -> πŸš€ Don't hesitate to follow us on our different networks, and put a star 🌟 on `PoC's` repositories. diff --git a/p2p/old/3.IPFS_or_HTTP/SETUP.md b/p2p/old/3.IPFS_or_HTTP/SETUP.md deleted file mode 100644 index 3a31e09a..00000000 --- a/p2p/old/3.IPFS_or_HTTP/SETUP.md +++ /dev/null @@ -1,9 +0,0 @@ -# Setup πŸ”§ - -In order to launch our music platform, you have to download Docker and the `source.zip` -which contains the code of our web application. -* [Docker](https://github.com/PoCInnovation/Workshops/blob/master/software/04.Docker/SETUP.md) -* Download the [source.zip](https://github.com/PoCInnovation/Workshops/raw/p2p/digital_ipfs/p2p/3.IPFS_or_HTTP/sources.zip), move it to your working directory `mv ~/Downloads/sources.zip ; cd ` and unzip it `unzip ./sources.zip`. - - -[Go back to the exercise](README.md#step-1---http) diff --git a/p2p/old/3.IPFS_or_HTTP/sources.zip b/p2p/old/3.IPFS_or_HTTP/sources.zip deleted file mode 100644 index a268e4d7..00000000 Binary files a/p2p/old/3.IPFS_or_HTTP/sources.zip and /dev/null differ