Smart Contract
Alexandria
A.fed1adffd14ea9d0.Alexandria
1// The Alexandria Library - Preserving Knowledge Forever
2//
3// The ancient Library of Alexandria was one of the largest and most significant libraries
4// of the ancient world, dedicated to the Muses and housing the greatest collection of
5// knowledge in human history. Its tragic destruction represented an immeasurable loss
6// to humanity's collective wisdom.
7//
8// This smart contract honors that legacy by creating a decentralized, immutable library
9// on the Flow blockchain. By leveraging blockchain technology, we ensure that knowledge
10// and literature can never be lost, censored, or destroyed - preserving humanity's
11// cultural heritage and making it freely accessible to all, forever.
12//
13// Written by: @NoahOverflow
14//
15// License: MIT
16
17access(all)
18contract Alexandria {
19 // -----------------------------------------------------------------------
20 // Alexandria Contract-level information
21 // -----------------------------------------------------------------------
22 access(self) let libraryInfo: {String: AnyStruct}
23 access(self) let titles: {String: String}
24 access(self) let authors: {String: [String]}
25 access(self) let genres: {String: [String]}
26 // Paths
27 access(all) let AdminStoragePath: StoragePath
28 access(all) let LibrarianStoragePath: StoragePath
29 access(all) let UserPreferenceStorage: StoragePath
30 access(all) let UserPreferencePublic: PublicPath
31 // Events
32 access(all) event ContractInitialized()
33 access(all) event BookAdded(title: String, author: String, genre: String)
34 access(all) event ChapterNameAdded(bookTitle: String, chapterTitle: String)
35 access(all) event ChapterAdded(bookTitle: String, chapterTitle: String)
36 access(all) event ChapterRemoved(bookTitle: String, chapterTitle: String)
37 access(all) event ChapterSubmitted(bookTitle: String, chapterTitle: String, librarian: Address)
38 access(all) event ParagraphAdded(bookTitle: String, chapterTitle: String)
39 access(all) event ParagraphRemoved(bookTitle: String, chapterTitle: String)
40
41 // Entitlements
42 access(all) entitlement LibrarianActions
43 access(all) entitlement AdminActions
44 // -----------------------------------------------------------------------
45 // Alexandria Book Attachments
46 // -----------------------------------------------------------------------
47 access(all) attachment Keeper for Book {
48 access(all) var keeper: Address
49 init(keeper: Address) {
50 self.keeper = keeper
51 }
52
53 access(all) fun updateKeeper(keeper: Address) {
54 self.keeper = keeper
55 }
56 }
57 // Add a keeper to a book
58 access(account) fun addKeeper(bookTitle: String, keeper: Address) {
59 pre {
60 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
61 }
62 let identifier = "Alexandria_Library_\(Alexandria.account.address)_\(bookTitle)"
63 let book <- Alexandria.account.storage.load<@Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
64 // create new keeper attachment
65 let entitledBook <- attach Keeper(keeper: keeper) to <- book
66 // save entitled book to storage
67 Alexandria.account.storage.save(<- entitledBook, to: StoragePath(identifier: identifier)!)
68 }
69 // Get a book's keepers
70 access(all) fun getKeeper(bookTitle: String): Address? {
71 pre {
72 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
73 }
74 let identifier = "Alexandria_Library_\(Alexandria.account.address)_\(bookTitle)"
75 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
76 let keeperAttachment = book[Alexandria.Keeper]
77 ?? panic("No keeper set for this book")
78
79 return keeperAttachment.keeper
80 }
81 // -----------------------------------------------------------------------
82 // Alexandria Book Resource
83 // -----------------------------------------------------------------------
84 access(all)
85 resource Book {
86 access(all) let Title: String
87 access(all) let Author: String
88 access(all) let Genre: String
89 access(all) let Summary: String
90 access(all) let Edition: String
91 // This is a mapping of chapterName ->
92 access(all) let Chapters: {String: Chapter?}
93 // Dictionary of chapter names
94 access(all) let chapterNames: {String: Bool}
95 // Dictionary of Chapters submitted by Librarians
96 // these Chapters are not automatically added to the book
97 // They go through a review process and then, if approved, they get
98 // added to the book
99 // This is a mapping from chapterName -> Librarian.address -> Chapter
100 access(all) let pendingReview: {String: {Address: Chapter?}}
101 access(all) let extra: {String: AnyStruct}
102
103 init(
104 _ title: String,
105 _ author: String,
106 _ genre: String,
107 _ edition: String,
108 _ summary: String,
109 ) {
110 self.Title = title
111 self.Author = author
112 self.Genre = genre
113 self.Summary = summary
114 self.Edition = edition
115 self.Chapters = {}
116 self.chapterNames = {}
117 self.pendingReview = {}
118 self.extra = {}
119 }
120 // Add a chapter name to the list of chapters
121 // this will be helpful to guide Librarians on what has been submitted
122 // or is still missing
123 access(all)
124 fun addChapterName(chapterName: String): [String] {
125 pre {
126 self.chapterNames[chapterName] == nil: "This chapter already exists"
127 }
128 self.chapterNames[chapterName] = true
129
130 emit ChapterNameAdded(bookTitle: self.Title, chapterTitle: chapterName)
131
132 return self.Chapters.keys
133 }
134 // Add a Chapter directly into the book
135 // this function is used only by the Admin
136 access(all)
137 fun addChapter(chapterName: String, chapter: Chapter): [String] {
138 pre {
139 // Check that the
140 self.chapterNames[chapterName] != nil : "This chapter doesn't exists"
141 }
142 self.Chapters[chapterName] = chapter
143
144 return self.Chapters.keys
145 }
146 // This is the function that a Librarian uses
147 // in order to submit a Chapter for review
148 access(all)
149 fun submitChapter(chapterName: String, chapter: Chapter, librarian: Address) {
150 pre {
151 self.chapterNames[chapterName] != nil: "This chapter doesn't exists"
152 }
153
154 self.pendingReview[chapterName] = {librarian : chapter}
155 }
156 // This function is used by the Admin to
157 // approve Chapter submissions
158 access(all)
159 fun approveChapter(chapterName: String, librarian: Address) {
160 pre {
161 self.Chapters[chapterName] != nil: "This chapter doesn't exists"
162 self.pendingReview[chapterName]![librarian] != nil: "There are no Chapters submitted by this Librarian: \(librarian.toString())"
163 }
164 // Remove chapter from the Pending list
165 let chapter = self.pendingReview[chapterName]!.remove(key: librarian)!
166 // Add the Chapter to the approved mapping
167 self.Chapters[chapterName] = chapter
168 // Send Karma and FlowToken to the Librarian's account
169
170 }
171 // Get Chapter
172 access(all)
173 fun getChapter(chapterTitle: String): Chapter? {
174 return self.Chapters[chapterTitle]!
175 }
176 access(all)
177 fun removeChapter(chapterTitle: String): String {
178 pre {
179 self.Chapters[chapterTitle] != nil: "This chapter doesn't exists"
180 }
181 let chapter = self.Chapters.remove(key: chapterTitle)!
182 return chapterTitle
183 }
184
185 // Function to add a paragraph to a chapter
186 access(all)
187 fun addParagraph(chapterTitle: String, paragraph: String) {
188 pre {
189 self.Chapters[chapterTitle] != nil: "This chapter doesn't exists"
190 }
191 post {
192 self.Chapters[chapterTitle]!!.paragraphs.length > 1: "The chapter doesn't have any paragraphs"
193 }
194 let chap = self.Chapters[chapterTitle]!!.paragraphs.append(paragraph)
195 emit ParagraphAdded(bookTitle: self.Title, chapterTitle: chapterTitle)
196 }
197 // Function to remove the last paragraph from a chapter
198 access(all)
199 fun removeLastParagraph(chapterTitle: String) {
200 pre {
201 self.Chapters[chapterTitle] != nil: "This chapter doesn't exists"
202 }
203 post {
204 self.Chapters[chapterTitle]!!.paragraphs.length > 1: "The chapter doesn't have any paragraphs"
205 }
206 let chapter = self.Chapters[chapterTitle]!!.removeLastParagraph()
207 emit ParagraphRemoved(bookTitle: self.Title, chapterTitle: chapterTitle)
208 }
209
210 // Function to get a paragraph from a chapter
211 access(all) view fun getParagraph(chapterTitle: String, paragraphIndex: Int): String {
212 return self.Chapters[chapterTitle]!!.getParagraph(paragraphIndex: paragraphIndex)
213 }
214 // Function to get all chapter titles
215 access(all) view fun getChapterTitles(): [String] {
216 return self.chapterNames.keys
217 }
218 }
219
220 access(all)
221 struct Chapter {
222 access(all) let bookTitle: String
223 access(all) let chapterTitle: String
224 access(all) let index: Int
225 access(all) let paragraphs: [String]
226 access(all) let extra: {String: AnyStruct}
227
228 init(
229 _ bookTitle: String,
230 _ chapterTitle: String,
231 _ index: Int,
232 _ paragraphs: [String]
233 ) {
234 self.bookTitle = bookTitle
235 self.chapterTitle = chapterTitle
236 self.index = index
237 self.paragraphs = paragraphs
238 self.extra = {}
239 }
240
241 // Function to add to a chapter's paragraphs
242 access(all)
243 fun addParagraph(paragraph: String) {
244 self.paragraphs.append(paragraph)
245 }
246
247 access(all)
248 fun removeLastParagraph(): String {
249 let paragraph = self.paragraphs.removeLast()
250 return paragraph
251 }
252
253 access(all) view fun getParagraph(paragraphIndex: Int): String {
254 return self.paragraphs[paragraphIndex]
255 }
256 }
257 // -----------------------------------------------------------------------
258 // User Preferences Resource
259 // -----------------------------------------------------------------------
260
261 // Public interface to get a user's favorites and bookmarks
262 access(all) resource interface UserPreferencesPublic {
263 access(all) fun getFavorites(): [String]
264 access(all) fun getBookmarks(): [String]
265 }
266
267 access(all)
268 resource UserPreferences {
269 access(all) var favorites: {String: Bool}
270 access(all) var bookmarks: {String: Bool}
271 access(all) var extra: {String: AnyStruct}
272
273 init() {
274 self.favorites = {}
275 self.bookmarks = {}
276 self.extra = {}
277 }
278 // Add a favorite book
279 access(all)
280 fun addFavorite(bookName: String) {
281 pre {
282 self.favorites[bookName] == nil: "This book is already in your favorites"
283 Alexandria.titles[bookName] != nil: "This book is not part of the library"
284 }
285
286 self.favorites[bookName] = true
287 }
288 // Remove a favorite book
289 access(all)
290 fun removeFavorite(bookName: String) {
291 pre {
292 self.favorites[bookName] == nil: "This book is not in your favorites"
293 Alexandria.titles[bookName] != nil: "This book is not part of the library"
294 }
295
296 let bookName = self.favorites.remove(key: bookName)
297 }
298 // Bookmark a book
299 access(all)
300 fun addBookmark(bookName: String) {
301 pre {
302 self.bookmarks[bookName] == nil: "This book is already in your bookmarks"
303 Alexandria.titles[bookName] != nil: "This book is not part of the library"
304 }
305
306 self.bookmarks[bookName] = true
307 }
308 // Remove a bookmark
309 access(all)
310 fun removeBookmark(bookName: String) {
311 pre {
312 self.bookmarks[bookName] == nil: "This book is not in your bookmarks"
313 Alexandria.titles[bookName] != nil: "This book is not part of the library"
314 }
315
316 let bookName = self.bookmarks.remove(key: bookName)
317 }
318 // Function to get all favorites
319 access(all)
320 fun getFavorites(): [String] {
321 return self.favorites.keys
322 }
323 // Function to get all bookmarks
324 access(all)
325 fun getBookmarks(): [String] {
326 return self.bookmarks.keys
327 }
328 }
329
330 // -----------------------------------------------------------------------
331 // Alexandria Admin Resource
332 // -----------------------------------------------------------------------
333
334 access(all)
335 resource Admin {
336 // Function to add a book to the library
337 access(AdminActions)
338 fun addBook(
339 title: String,
340 author: String,
341 genre: String,
342 edition: String,
343 summary: String,
344 ) {
345 pre {
346 Alexandria.titles[title] == nil: "This book is already in the Library."
347 }
348 // create new book resource
349 let newBook <- create Book(title, author, genre, edition, summary)
350 // create new path identifier for book
351 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(title)"
352 // Add the book's details to the library's catalog
353 Alexandria.titles[title] = newBook.Title
354 // Check if this Author already exists
355 if Alexandria.authors[author] == nil {
356 // create new list of titles under this author
357 Alexandria.authors[author] = []
358 // add this title to the list
359 Alexandria.authors[author]!.append(title)
360 } else {
361 // Add title to the list of titles under this author
362 Alexandria.authors[author]!.append(title)
363 }
364 Alexandria.titles[author] = newBook.Author
365 // Check if this Genre already exists
366 if Alexandria.genres[genre] == nil {
367 Alexandria.genres[genre] = []
368 Alexandria.genres[genre]!.append(title)
369 } else {
370 Alexandria.genres[genre]!.append(title)
371 }
372 // add new book to the library
373 Alexandria.account.storage.save(<- newBook, to: StoragePath(identifier: identifier)!)
374 // Emit book added event
375 emit BookAdded(title: title, author: author, genre: genre)
376
377 }
378 // Add a chapter to a book
379 access(AdminActions)
380 fun addChapter(bookTitle: String, chapter: Chapter): [String] {
381 pre {
382 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
383 }
384 // create book path identifier based on title
385 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
386 // fetch book
387 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
388 // Emit event
389 emit ChapterAdded(bookTitle: bookTitle, chapterTitle: chapter.chapterTitle)
390 // add chapter to book and
391 // return the current number of chapters in this book
392 return book.addChapter(chapterName: chapter.chapterTitle, chapter: chapter)
393 }
394 // Add a chapter title to a book
395 access(AdminActions)
396 fun addChapterName(bookTitle: String, chapterName: String) {
397 // create book path identifier based on title
398 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
399 // fetch book
400 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
401 // Emit event
402 // emit ChapterAdded(bookTitle: bookTitle, chapterTitle: chapter.chapterTitle)
403 // add chapter name to book
404 let newChapterTitle = book.addChapterName(chapterName: chapterName)
405 // return newChapterTitle
406 }
407 // Remove the last chapter from a book
408 access(AdminActions)
409 fun removeChapter(bookTitle: String, chapterTitle: String) {
410 pre {
411 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
412 }
413 // create book path identifier based on title
414 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
415 // fetch book
416 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
417 // remove last chapter from book
418 let chapterTitle = book.removeChapter(chapterTitle: chapterTitle)
419 // Emit event
420 emit ChapterRemoved(bookTitle: bookTitle, chapterTitle: chapterTitle)
421 }
422 // Add a genre to the library
423 access(AdminActions)
424 fun addGenre(genre: String) {
425 pre {
426 Alexandria.genres[genre] == nil: "This genre already exists"
427 }
428 Alexandria.genres[genre] = []
429 }
430 // create a new Admin resource
431 access(AdminActions)
432 fun createAdmin(): @Admin {
433 return <- create Admin()
434 }
435 // change Alexandria of library info
436 access(AdminActions)
437 fun changeField(key: String, value: AnyStruct) {
438 Alexandria.libraryInfo[key] = value
439 }
440
441
442 }
443 // -----------------------------------------------------------------------
444 // Alexandria Librarian Resource
445 // -----------------------------------------------------------------------
446
447 // Librarians are users that are contributing to the creation of the Library
448 // by submitting book chapters to be reviewed, and then added to the storage
449 // upon approval
450
451 access(all)
452 resource Librarian {
453 // The reputation of this librarian
454 access(all) let karma: UFix64
455 // A list of chapters submitted by this Librarian
456 // that have been approved, and the book they belong to
457 access(all) let approvedChapters: {String: [Chapter]}
458 // A list of chapters that pending for approval
459 access(all) let pendingChapters: {String: [Chapter]}
460 access(all) let extra: {String: AnyStruct}
461
462 init() {
463 self.karma = 0.0
464 self.approvedChapters = {}
465 self.pendingChapters = {}
466 self.extra = {}
467 }
468 // Function used to submit a Chapter to a book
469 access(LibrarianActions)
470 fun submitChapter(bookTitle: String, chapter: Chapter) {
471 pre {
472 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
473 }
474 // create book path identifier based on title
475 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
476 // fetch book
477 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
478 // Emit event
479 emit ChapterSubmitted(bookTitle: bookTitle, chapterTitle: chapter.chapterTitle, librarian: self.owner!.address)
480 // return the current number of chapters in this book
481 book.submitChapter(chapterName: chapter.chapterTitle, chapter: chapter, librarian: self.owner!.address)
482 }
483
484 access(LibrarianActions)
485 fun addGenre(genre: String) {
486 pre {
487 Alexandria.genres[genre] == nil: "This genre already exists"
488 }
489 Alexandria.genres[genre] = []
490 }
491 }
492
493 // -----------------------------------------------------------------------
494 // Alexandria Public Services
495 // -----------------------------------------------------------------------
496
497 access(all)
498 fun createEmptyPreferences(): @Alexandria.UserPreferences {
499 return <- create UserPreferences()
500 }
501 // Fetch one book
502 access(all)
503 fun getBook(bookTitle: String): &Book {
504 pre {
505 Alexandria.titles[bookTitle] != nil: "This book doesn't exist in the Library."
506 }
507 // create book path identifier based on title
508 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
509 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
510 return book
511 }
512 // Fetch a book's chapter titles
513 access(all)
514 fun getBookChapterTitles(bookTitle: String): [String] {
515 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
516 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
517 let chapterTitles = book.getChapterTitles()
518 return chapterTitles
519 }
520 // Fetch a book's chapter
521 access(all)
522 fun getBookChapter(bookTitle: String, chapterTitle: String): Chapter? {
523 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
524 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
525 return book.getChapter(chapterTitle: chapterTitle)
526 }
527 // Fetch a book's paragraph
528 access(all)
529 fun getBookParagraph(bookTitle: String, chapterTitle: String, paragraphIndex: Int): String {
530 let identifier = "Alexandria_Library_\(Alexandria.account.address.toString())_\(bookTitle)"
531 let book = Alexandria.account.storage.borrow<&Alexandria.Book>(from: StoragePath(identifier: identifier)!)!
532 return book.getParagraph(chapterTitle: chapterTitle, paragraphIndex: paragraphIndex)
533 }
534 // Fetch all registered authors
535 access(all)
536 fun getAuthors(): [String]? {
537 return self.authors.keys
538 }
539 // Fetch all registered genres
540 access(all)
541 fun getAllGenres(): [String] {
542 return self.genres.keys
543 }
544 // Fetch all titles under a genre
545 access(all)
546 fun getGenre(genre: String): [String]? {
547 return self.genres[genre]
548 }
549 // Fetch all titles under an author
550 access(all)
551 fun getAuthor(author: String): [String]? {
552 return self.authors[author]
553 }
554 init() {
555 let identifier = "Alexandria_Library_\(self.account.address)_"
556
557 self.AdminStoragePath = StoragePath(identifier: "\(identifier)Admin")!
558 self.LibrarianStoragePath = StoragePath(identifier: "\(identifier)Librarian")!
559 self.UserPreferenceStorage = StoragePath(identifier: "\(identifier)User_Preferences")!
560 self.UserPreferencePublic = PublicPath(identifier: "\(identifier)User_Preferences_Public")!
561
562 self.libraryInfo = {}
563 self.titles = {}
564 self.authors = {}
565 self.genres = {
566 "Adventure": [],
567 "Biography": [],
568 "Realism": [],
569 "Dystopian": [],
570 "Fantasy": [],
571 "Horror": [],
572 "Mistery": [],
573 "History": [],
574 "Romance": [],
575 "Thriller": [],
576 "Fiction": [],
577 "Science Fiction": [],
578 "Western": [],
579 "Philosophy": [],
580 "Psychology": [],
581 "Literature": [],
582 "Feminist Literature": []
583 }
584
585 // Create a Admin resource and save it to Alexandria account storage
586 let Admin <- create Admin()
587 self.account.storage.save(<- Admin, to: self.AdminStoragePath)
588
589 // Create a UserPreference resource and save it to Alexandria account storage
590 let UserPreference <- create UserPreferences()
591 self.account.storage.save(<- UserPreference, to: self.UserPreferenceStorage)
592
593 // create a public capability for the user preferences
594 let storageCap = self.account.capabilities.storage.issue<&{Alexandria.UserPreferencesPublic}>(self.UserPreferenceStorage)
595 self.account.capabilities.publish(storageCap, at: self.UserPreferencePublic)
596
597 emit ContractInitialized()
598 }
599}