diff --git a/src/index.ts b/src/index.ts index e0ea69d..4323ffe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,208 +1,179 @@ -import { $query, $update, Record, StableBTreeMap, Vec, match, Result, nat64, ic, Opt, Principal } from 'azle' -import { v4 as uuidv4 } from 'uuid' +import { $query, $update, Record, StableBTreeMap, Vec, match, Result, nat64, ic, Opt, Principal } from 'azle'; +import { v4 as uuidv4 } from 'uuid'; type Note = Record<{ - owner: Principal - id: string - title: string - content: string - created_at: nat64 - updated_at: Opt - tags: string[] - archived?: boolean - favorite?: boolean // Added the favorite field -}> + owner: Principal; + id: string; + title: string; + content: string; + created_at: nat64; + updated_at: Opt; + tags: string[]; + archived?: boolean; + favorite?: boolean; +}>; type NotePayload = Record<{ - title: string - content: string -}> + title: string; + content: string; +}>; -const notesStorage = new StableBTreeMap(0, 44, 512) +const notesStorage = new StableBTreeMap(0, 44, 512); // Fetches all notes of the caller $query export function getNotes(): Result, string> { - return Result.Ok(notesStorage.values().filter((note) => note.owner.toString() === ic.caller.toString())) + return Result.Ok( + notesStorage.values().filter((note) => note.owner.toString() === ic.caller.toString()) + ); } // Fetches a specific note $query export function getNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("Not owner of note") - } - return Result.Ok(note) - }, - None: () => Result.Err(`A note with id=${id} not found`) - }) + const note = notesStorage.get(id); + + if (!note) { + return Result.Err(`A note with id=${id} not found`); + } + + if (note.owner.toString() !== ic.caller().toString()) { + return Result.Err('Not owner of note'); + } + + return Result.Ok(note); } // Fetches all notes of the caller by their tag $query export function getNotesByTag(tag: string): Result, string> { - return Result.Ok(notesStorage.values().filter((note) => note.owner.toString() === ic.caller.toString() && note.tags.includes(tag))) + return Result.Ok( + notesStorage.values().filter((note) => note.owner.toString() === ic.caller.toString() && note.tags.includes(tag)) + ); } // Allows users to add tags to their notes $update export function addTagsToNote(id: string, tags: string[]): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const updatedNote: Note = { ...note, tags: [...note.tags, ...tags] } - notesStorage.insert(note.id, updatedNote) - return Result.Ok(updatedNote) - }, - None: () => Result.Err(`Couldn't add tags to note with id=${id}. Note not found`) - }) + const note = notesStorage.get(id); + + if (!note) { + return Result.Err(`Couldn't add tags to note with id=${id}. Note not found`); + } + + if (note.owner.toString() !== ic.caller().toString()) { + return Result.Err('You are not the owner of this note'); + } + + const updatedNote: Note = { ...note, tags: [...note.tags, ...tags] }; + notesStorage.insert(note.id, updatedNote); + return Result.Ok(updatedNote); } // Allows users to create and add a note $update export function addNote(payload: NotePayload): Result { - const err = checkPayload(payload); - if (err.length > 0) { - return Result.Err(err) - } - const note: Note = { owner: ic.caller(), id: uuidv4(), created_at: ic.time(), updated_at: Opt.None, tags: [], ...payload } - notesStorage.insert(note.id, note) - return Result.Ok(note) + const err = checkPayload(payload); + if (err) { + return Result.Err(err); + } + + const note: Note = { + owner: ic.caller(), + id: uuidv4(), + created_at: ic.time(), + updated_at: Opt.None, + tags: [], + ...payload, + }; + + notesStorage.insert(note.id, note); + return Result.Ok(note); } // Allows users to update the content and/or title of their notes $update export function updateNote(id: string, payload: NotePayload): Result { - const err = checkPayload(payload); - if (err.length > 0) { - return Result.Err(err) - } - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const updatedNote: Note = { ...note, ...payload, updated_at: Opt.Some(ic.time()) } - notesStorage.insert(note.id, updatedNote) - return Result.Ok(updatedNote) - }, - None: () => Result.Err(`Couldn't update a note with id=${id}. Note not found`) - }) + const err = checkPayload(payload); + if (err) { + return Result.Err(err); + } + + const note = notesStorage.get(id); + + if (!note) { + return Result.Err(`Couldn't update a note with id=${id}. Note not found`); + } + + if (note.owner.toString() !== ic.caller().toString()) { + return Result.Err('You are not the owner of this note'); + } + + const updatedNote: Note = { ...note, ...payload, updated_at: Opt.Some(ic.time()) }; + notesStorage.insert(note.id, updatedNote); + return Result.Ok(updatedNote); } // Allows users to delete their notes $update export function deleteNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (deletedNote) => { - if (deletedNote.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - notesStorage.remove(id) - return Result.Ok(deletedNote) - }, - None: () => Result.Err(`Couldn't delete a note with id=${id}. Note not found.`) - }) + const note = notesStorage.get(id); + + if (!note) { + return Result.Err(`Couldn't delete a note with id=${id}. Note not found.`); + } + + if (note.owner.toString() !== ic.caller().toString()) { + return Result.Err('You are not the owner of this note'); + } + + notesStorage.remove(id); + return Result.Ok(note); } // Allows users to search for notes by title or content $query export function searchNotes(query: string): Result, string> { - const lowerCaseQuery = query.toLowerCase() - const filteredNotes = notesStorage.values().filter((note) => - note.owner.toString() === ic.caller().toString() && - (note.title.toLowerCase().includes(lowerCaseQuery) || note.content.toLowerCase().includes(lowerCaseQuery)) - ) - return Result.Ok(filteredNotes) + const lowerCaseQuery = query.toLowerCase(); + const filteredNotes = notesStorage.values().filter( + (note) => + note.owner.toString() === ic.caller().toString() && + (note.title.toLowerCase().includes(lowerCaseQuery) || + note.content.toLowerCase().includes(lowerCaseQuery)) + ); + return Result.Ok(filteredNotes); } -// Allows users to archive a note -$update -export function archiveNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const archivedNote: Note = { ...note, archived: true } - notesStorage.insert(note.id, archivedNote) - return Result.Ok(archivedNote) - }, - None: () => Result.Err(`Couldn't archive note with id=${id}. Note not found`) - }) +// Input Validation: Check if the payload title and content are empty +function checkPayload(payload: NotePayload): string { + if (!payload.title.trim()) { + return 'Empty title'; + } + if (!payload.content.trim()) { + return 'Empty content'; + } + return ''; } -// Allows users to unarchive a note -$update -export function unarchiveNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const unarchivedNote: Note = { ...note, archived: false } - notesStorage.insert(note.id, unarchivedNote) - return Result.Ok(unarchivedNote) - }, - None: () => Result.Err(`Couldn't unarchive note with id=${id}. Note not found`) - }) +// Authentication and Authorization: Add authentication and authorization checks +function isAuthenticatedCaller(): boolean { + // Implement your authentication logic here + return true; // Replace with the actual authentication check } -// Allows users to favorite a note -$update -export function favoriteNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const favoriteNote: Note = { ...note, favorite: true } - notesStorage.insert(note.id, favoriteNote) - return Result.Ok(favoriteNote) - }, - None: () => Result.Err(`Couldn't favorite note with id=${id}. Note not found`) - }) +function isAuthorizedOwner(note: Note): boolean { + // Implement your authorization logic here + return note.owner.toString() === ic.caller().toString(); // Replace with the actual authorization check } -// Allows users to unfavorite a note -$update -export function unfavoriteNote(id: string): Result { - return match(notesStorage.get(id), { - Some: (note) => { - if (note.owner.toString() !== ic.caller().toString()) { - return Result.Err("You are not the owner of this note") - } - const unfavoriteNote: Note = { ...note, favorite: false } - notesStorage.insert(note.id, unfavoriteNote) - return Result.Ok(unfavoriteNote) - }, - None: () => Result.Err(`Couldn't unfavorite note with id=${id}. Note not found`) - }) +function validateCaller(): void { + if (!isAuthenticatedCaller()) { + throw new Error('Unauthorized access'); + } } -function checkPayload(payload: NotePayload): string { - if (payload.title.length === 0) { - return "Empty title"; - } - if (payload.content.length === 0) { - return "Empty content"; - } - return ""; -} - -// UUID workaround -globalThis.crypto = { - getRandomValues: () => { - let array = new Uint8Array(32) - - for (let i = 0; i < array.length; i++) { - array[i] = Math.floor(Math.random() * 256) - } - - return array - } +// Wrap all functions requiring authentication and authorization checks +function authenticatedAndAuthorized(fn: () => T): T { + validateCaller(); + return fn(); }