Skip to content

Commit d7cd63d

Browse files
dominguesgmterichadbourne
authored andcommitted
feat: Non MFS Files API tutorial (#303)
* feat: first draft of first lesson * feat: draft of the second lesson done. also bumped ipfs version * feat: first draft of the 3rd lesson * chore: first draft of the 4th lesson * chore: first draft of the 5th lesson * feat: first draft of 6th and last lesson * chore: revisit lesson 2: change information on add method argument * Er suggestions nonmfs (#304) * Title renaming for parallelism * Copy suggestions - lesson 1 * Lesson 1 edits * Lesson 2 edits * Lesson 3 edits * Lessons 4-6 edits * chore: update to review * chore: restore section on file upload in lesson 2 * chore: worked on last comment left * chore: made several revisions from feedback. missing discussion on directories and simplifying lesson 7 * copy edits to lesson 1 * copy edits to lesson 2 * copy edits to lesson 3 * fix typos * remove old comments from Vue files * add MFS tutorial to resources page * update featured courses * fix typo * copy edits to lesson 4 * chore: update approach to lesson 5 * chore: update lesson 6 to match lesson 5 * chore: added content to lesson 5 * copy edits to Lesson 5k * fix tutorial path * add IPFS Camp course to resources * fix: fixed bug causing race conditions on fast executing user code * copy edits to lesson 6 * add resulting code block sample to L6 * chore: added lesson on cat file inside directory * copy edits to lesson 7 * feat: improved lessons 7 and 8 Note: success.txt is now inside a subdirectory * feat: made some improvements to lesson 5 * lesson 7 copy edits * copy edit to lesson 5 success msg * lesson 8 copy edits, adding ls comparison * fix merge issue * update references to Regular Files API * update tutorial description * fix: lesson 1: add format for mfs files API method calls * fix copy paste errors * mention IPLD when describing DAG API * change tutorial url * chore: minor changes * chore: update with most of the changes suggested by Alan * Use ipfs in path in src/tutorials/0005/07.md Co-Authored-By: Marcin Rataj <[email protected]> * Apply suggestions from code review Co-Authored-By: Marcin Rataj <[email protected]> * add /ipfs/ to paths * incorporate feedback from Alan & Marcin * chore: change main solution in lesson 05 * chore: updated file structure and contents * chore: update src/tutorials/0005/07-exercise.md Co-Authored-By: Marcin Rataj <[email protected]> * chore: updated validation for lessons 3, 4, and 5. Fixed a bug in validation of lesson 10 in the MFS tutorial * chore: update to lesson 6 validation * chore: update feedback on lesson 7 * chore: update lesson 8 validation * remove all references to 'root' directory * imrpove catch-all error msg * tweaks to error messages * move javascript hints from solution to hints in exercise * chore: update validation for lessons 5 and 7 according to comments * validation tweaks + new override * rename variable * minor copy edit
1 parent 0d41bfb commit d7cd63d

31 files changed

+6438
-4363
lines changed

package-lock.json

Lines changed: 5185 additions & 4346 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
},
1616
"dependencies": {
1717
"async": "^2.6.1",
18-
"cids": "^0.5.6",
18+
"cids": "^0.7.1",
1919
"highlight.js": "^9.12.0",
20-
"ipfs": "^0.36.4",
20+
"ipfs": "^0.39.0",
2121
"ipfs-css": "^0.6.0",
2222
"marked": "^0.4.0",
2323
"monaco-editor": "^0.6.1",
24+
"p-timeout": "^3.2.0",
2425
"raw-loader": "^0.5.1",
2526
"shallow-equal": "^1.0.0",
2627
"tachyons": "^4.11.1",

src/components/Lesson.vue

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,6 @@ marked.setOptions({
132132
})
133133
134134
const _eval = async (text, ipfs, modules = {}, args = []) => {
135-
await new Promise(resolve => ipfs.on('ready', resolve))
136-
137135
let fn
138136
let result
139137
try {
@@ -212,6 +210,7 @@ export default {
212210
lessonKey: 'passed' + self.$route.path,
213211
lessonPassed: !!localStorage['passed' + self.$route.path],
214212
createTestFile: self.$attrs.createTestFile,
213+
createTestTree: self.$attrs.createTestTree,
215214
output: self.output,
216215
showUploadInfo: false,
217216
expandExercise: false,
@@ -279,9 +278,15 @@ export default {
279278
}
280279
let output = this.output
281280
let ipfs = await this.createIPFS()
281+
282+
await ipfs.ready
282283
if (this.createTestFile) {
283284
await this.createFile(ipfs)
284285
}
286+
if (this.createTestTree) {
287+
await this.createTree(ipfs)
288+
}
289+
285290
let code = this.editor.getValue()
286291
let modules = {}
287292
@@ -347,12 +352,24 @@ export default {
347352
},
348353
createFile: function (ipfs) {
349354
/* eslint-disable no-new */
350-
new Promise((resolve, reject) => {
351-
ipfs.on('ready', async () => {
352-
await ipfs.add(this.ipfsConstructor.Buffer.from('You did it!'))
353-
resolve()
354-
})
355-
})
355+
return ipfs.add(this.ipfsConstructor.Buffer.from('You did it!'))
356+
},
357+
createTree: function (ipfs) {
358+
/* eslint-disable no-new */
359+
return ipfs.add([
360+
{
361+
content: this.ipfsConstructor.Buffer.from('¯\\_(ツ)_/¯'),
362+
path: 'shrug.txt'
363+
},
364+
{
365+
content: this.ipfsConstructor.Buffer.from(':)'),
366+
path: 'smile.txt'
367+
},
368+
{
369+
content: this.ipfsConstructor.Buffer.from('You did it!'),
370+
path: 'fun/success.txt'
371+
}
372+
], { wrapWithDirectory: true })
356373
},
357374
resetCode: function () {
358375
// TRACK? User chose to reset code

src/main.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ import T0004L08 from './tutorials/0004/08.vue'
4646
import T0004L09 from './tutorials/0004/09.vue'
4747
import T0004L10 from './tutorials/0004/10.vue'
4848
import T0004L11 from './tutorials/0004/11.vue'
49+
import T0005L01 from './tutorials/0005/01.vue'
50+
import T0005L02 from './tutorials/0005/02.vue'
51+
import T0005L03 from './tutorials/0005/03.vue'
52+
import T0005L04 from './tutorials/0005/04.vue'
53+
import T0005L05 from './tutorials/0005/05.vue'
54+
import T0005L06 from './tutorials/0005/06.vue'
55+
import T0005L07 from './tutorials/0005/07.vue'
56+
import T0005L08 from './tutorials/0005/08.vue'
4957

5058
Vue
5159
.use(VueRouter)
@@ -96,7 +104,18 @@ const routes = [
96104
{ path: '/mutable-file-system/08', component: T0004L08 },
97105
{ path: '/mutable-file-system/09', component: T0004L09 },
98106
{ path: '/mutable-file-system/10', component: T0004L10 },
99-
{ path: '/mutable-file-system/11', component: T0004L11 },
107+
{ path: '/mutable-file-system/01', component: T0004L11 },
108+
// Tutorial 0005
109+
{ path: '/regular-files-api', component: Landing, props: { tutorialId: '0005' } },
110+
{ path: '/regular-files-api/resources', component: ResourcesLesson, props: { tutorialId: '0005' } },
111+
{ path: '/regular-files-api/01', component: T0005L01 },
112+
{ path: '/regular-files-api/02', component: T0005L02 },
113+
{ path: '/regular-files-api/03', component: T0005L03 },
114+
{ path: '/regular-files-api/04', component: T0005L04 },
115+
{ path: '/regular-files-api/05', component: T0005L05 },
116+
{ path: '/regular-files-api/06', component: T0005L06 },
117+
{ path: '/regular-files-api/07', component: T0005L07 },
118+
{ path: '/regular-files-api/08', component: T0005L08 },
100119
// 404
101120
{ path: '*', name: '404', component: NotFound, props: { notFound: true } }
102121
]

src/pages/Home.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export default {
5555

5656
<style scoped>
5757
.tutorial-tile {
58-
max-width: 49%
58+
max-width: 49%;
59+
min-height: 167px;
5960
}
6061
.tutorial-tile a {
6162
text-decoration: none;

src/static/courses.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"all": ["0004", "0001", "0002", "0003"],
3-
"featured": ["0004", "0001", "0002", "0003"]
2+
"all": ["0004", "0005", "0001", "0002", "0003"],
3+
"featured": ["0001", "0004", "0005", "0002"]
44
}

src/static/tutorials.json

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"url": "basics",
6060
"project": "IPFS",
6161
"title": "P2P data links with content addressing",
62-
"description": "Store, fetch, and create verifiable links between peer-hosted datasets with IPFS and CIDs. It’s graphs with friends!",
62+
"description": "Store, fetch, and create verifiable links between peer-hosted datasets using the IPFS DAG API and CIDs. It’s graphs with friends!",
6363
"lessons": [
6464
"Create a node and return a Content Identifier (CID)",
6565
"Create a new node that's linked to an old one",
@@ -143,5 +143,47 @@
143143
"description": "You've seen the IPFS Files API. Now explore the IPFS DAG API, where you'll use CIDs to create verifiable links between datasets."
144144
}
145145
]
146+
},
147+
"0005": {
148+
"url": "regular-files-api",
149+
"project": "IPFS",
150+
"title": "IPFS: Regular Files API",
151+
"description": "The IPFS Regular Files API provides a way to store and share files in a peer-to-peer storage system.",
152+
"lessons": [
153+
"Introducing the Files API",
154+
"Working with files in ProtoSchool",
155+
"Add a file",
156+
"Read the contents of a file",
157+
"Add files in a directory",
158+
"List the files in a directory",
159+
"Read a file in a directory",
160+
"Get all of the files in a directory"
161+
],
162+
"resources": [
163+
{
164+
"title": "Understanding How IPFS Deals with Files",
165+
"link": "https://youtu.be/Z5zNPwMDYGg",
166+
"type": "video",
167+
"description": "This course from IPFS Camp 2019 offers a deep exploration of how IPFS deals with files, including key concepts like immutability, content addressing, hashing, the anatomy of CIDs, what the heck a Merkle DAG is, and how chunk size affects file imports. It also introduces some the joys and pitfalls of the Mutable File System (MFS)."
168+
},
169+
{
170+
"title": "JS-IPFS Files API",
171+
"link": "https://github.com/ipfs/interface-js-ipfs-core/blob/master/SPEC/FILES.md",
172+
"type": "docs",
173+
"description": "Notice that these docs contain two sections, one for top-level methods relevant to files, which we learned about in this tutorial, and one for the Mutable File System (MFS)."
174+
},
175+
{
176+
"title": "IPFS: Mutable File System (MFS)",
177+
"link": "https://proto.school/#/mutable-file-system",
178+
"type": "tutorial",
179+
"description": "Wish adding files to IPFS felt more like using a traditional name-based file system? Explore the Mutable File System (MFS)."
180+
},
181+
{
182+
"title": "P2P Data Links with Content Addressing",
183+
"link": "https://proto.school/#/basics/",
184+
"type": "tutorial",
185+
"description": "You've seen the IPFS Files API. Now explore the IPFS DAG API, where you'll use CIDs to create verifiable links between datasets."
186+
}
187+
]
146188
}
147189
}

src/tutorials/0004/10.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const validate = async (result, ipfs) => {
2020
2121
if (!result) {
2222
return { fail: 'You forgot to return a result. Did you accidentally edit the return statement?' }
23+
} else if (result.error) {
24+
return { error: result.error }
2325
} else if (result && typeof result !== 'string') {
2426
return { fail: 'Oops. `secretMessage` should be a string. Did you forget to convert the buffer to a string?' }
2527
} else if (result !== correctMessage) {
@@ -34,8 +36,6 @@ const validate = async (result, ipfs) => {
3436
logDesc: "Here's the secret message you discovered in the file:",
3537
log: result
3638
}
37-
} else if (result.error) {
38-
return { error: result.error }
3939
}
4040
}
4141

src/tutorials/0005/01.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
## IPFS: The InterPlanetary File System
3+
4+
[IPFS](https://ipfs.io/) is a peer-to-peer (P2P) networking protocol used to share data on the distributed web. You can think of it as a file system with some unique characteristics that make it ideal for safe, decentralized sharing.
5+
6+
If you haven't yet done so, we encourage you to check out our [Decentralized Data Structures](https://proto.school/#/data-structures/) tutorial to learn all about the decentralized web and how it compares to the web you're accustomed to. There you'll learn all about content addressing, cryptographic hashing, Content Identifiers (CIDs), and sharing with peers, all of which you'll need to understand to make the most of this tutorial.
7+
8+
## The Files API vs the DAG API
9+
10+
You can store multiple types of data with IPFS. If you've explored our [P2P Data Links with Content Addressing](https://proto.school/#/basics) or [Blogging on the Decentralized Web](https://proto.school/#/blog) tutorial, you've already seen how you can store primitives, objects and arrays on the network using the DAG API.
11+
12+
The DAG API allows you to use the unique and versatile primitive data structures offered by [IPLD](https://github.com/ipld/ipld) (InterPlanetary Linked Data) within IPFS. You can recognize its methods in js-ipfs (the JavaScript implementation of IPFS) because they take the following format: `ipfs.dag.someMethod()`
13+
14+
The DAG API is the most generic and flexible approach to adding data to the IPFS node, but it's not the most efficient when it comes to the very common use case of sharing files. What if you wanted to share a picture of a kitten, or a larger file like a funny video of your favorite celebrity dog? How would you add these files to the network and provide a way for your friends to see them? How should each file be placed in the Directed Acyclic Graph (DAG), in a single block or split into chunks? These are optimization details that fall beyond the scope of the DAG API. Though it would be possible to use the DAG API to add files to an IPFS node, it would be a labor-intensive task.
15+
16+
The Files API, on the other hand, is custom-built for a more specific use case. The Files API prepares files to be placed in the network and ensures that IPFS knows how to access them and handle them efficiently. The Files API is made of two parts: the Regular Files API, which we'll cover in this tutorial, and the Mutable File System (MFS).
17+
18+
## The Regular Files API vs the MFS Files API
19+
20+
If you've read our [Mutable File System tutorial](https://proto.school/#/mutable-file-system), you may be thinking, "I've already learned how to work with files on IPFS. How will this be any different?"
21+
22+
The Mutable File System (MFS) provides an API designed to replicate familiar file system operations such as `mkdir`, `ls`, `cp`, and others, mimicking the way you organize files and directories on a computer. However, the way that content is addressed in IPFS makes it an immutable file system. The address to a file or directory depends on its contents, so any change to a file or directory will result in an entirely new address. The MFS Files API works on a familiar-looking file system with regular paths — like `/some/stuff` — in the local IPFS node, which hides the complexity of immutable content addressing.
23+
24+
Although MFS is very useful, the abstraction it provides hides some of the inner workings of IPFS. The Regular Files API we will discuss here is instead a bare bones approach to managing files in IPFS. It trades the powerful abstractions of MFS for a scheme which helps you understand what is actually happening in the file system. In the Regular Files API you'll find methods like `add`, `cat`, `get` and `ls`.
25+
26+
Unlike the MFS API, which can be recognized in js-ipfs by the format `ipfs.files.someMethod()`, the Regular Files API uses top-level methods that take the format `ipfs.someMethod()`.
27+
28+
The distinction between these two versions of the Files APIs is a bit confusing the moment, but the IPFS team is currently working on revamping them to make the whole Files API more user-friendly. We'll be sure to update our tutorials when things change!

src/tutorials/0005/01.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<Lesson :text="text" />
3+
</template>
4+
5+
<script>
6+
import Lesson from '../../components/Lesson'
7+
import text from './01.md'
8+
9+
export default {
10+
components: {
11+
Lesson
12+
},
13+
data: () => {
14+
return { text }
15+
}
16+
}
17+
</script>

src/tutorials/0005/02-exercise.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
First, upload one or more files by dragging and dropping below or clicking to make a selection from your file explorer. Next, in the code editor, remove the comment markers (`//`) that precede `return files` within the `run` function.

src/tutorials/0005/02.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
As a matter of security, web browsers don't let us directly change files that live in your computer's file system. Therefore, you'll need to upload one or more files to the browser that you can use throughout this tutorial.
2+
3+
Within each exercise, you'll see that you can upload files from your computer either by dragging and dropping or selecting them from your file explorer. If you look closely at the `run` function in the code editor, you'll notice that it now takes an argument `files`. When you upload files from your computer, we'll make sure they're passed into the function as the `files` array. As long as you don't refresh your browser, these files will remain accessible for the next lesson in the tutorial, but you'll also have the option to upload different files to work with for each lesson.
4+
5+
Each element in this `files` array will be a `File` object, as defined by the browser's [File API](https://developer.mozilla.org/en-US/docs/Web/API/File) (not to be confused with the IPFS Files API). Aside from the contents of the uploaded file, a `File` object also contains the following attributes:
6+
7+
* **name** (name of the uploaded file)
8+
* **lastModified** (date the uploaded file was last modified)
9+
* **size** (size of the uploaded file)
10+
* **type** (type of the uploaded file)
11+
12+
To practice, let's upload one or more files from your computer and take a look at what's been received by the browser as the `files` array.

src/tutorials/0005/02.vue

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<template>
2+
<FileLesson
3+
:text="text"
4+
:code="code"
5+
:validate="validate"
6+
:modules="modules"
7+
:exercise="exercise"
8+
:solution="solution"/>
9+
</template>
10+
11+
<script>
12+
import FileLesson from '../../components/FileLesson.vue'
13+
import text from './02.md'
14+
import exercise from './02-exercise.md'
15+
import { logFiles } from '../../utils/files'
16+
17+
const validate = async (result, ipfs) => {
18+
if (!result) {
19+
return { fail: 'Looks like you forgot to return a result. Did you forget to remove the `//` before `return files`?' }
20+
} else if (typeof result.length === 'number') {
21+
const fileCount = result.length > 1 ? `${result.length} files` : '1 file'
22+
return {
23+
success: `You successfully uploaded ${fileCount}!`,
24+
logDesc: "Check out the data below to see the data now accessible in the `files` array. Note that these files are only in the browser right now. In the next lesson we'll see how to add them to IPFS.",
25+
log: logFiles(result)
26+
}
27+
} else {
28+
return { fail: `Something seems to be wrong. Please click "Reset Code" and try again, taking another look at the instructions and editing only the portion of code indicated. Feeling really stuck? You can click "View Solution" to see our suggested code.` }
29+
}
30+
}
31+
32+
const code = `const run = async (files) => {
33+
/* remove the '//' on the line below to complete this challenge */
34+
// return files
35+
}
36+
return run
37+
`
38+
39+
const solution = `const run = async (files) => {
40+
return files
41+
}
42+
43+
return run
44+
`
45+
46+
const modules = { cids: require('cids') }
47+
48+
export default {
49+
components: {
50+
FileLesson
51+
},
52+
data: () => {
53+
return { text, validate, code, modules, exercise, solution }
54+
}
55+
}
56+
</script>

src/tutorials/0005/03-exercise.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add the files in your browser (available in the `files` array below) to your IPFS node using the `add` method.
2+
3+
**Hint:** You can pass either a single `File` object or an array of `File` objects to the `add` method.

0 commit comments

Comments
 (0)