diff --git a/litelist-api/dub.json b/litelist-api/dub.json index 41adfc7..97b373e 100644 --- a/litelist-api/dub.json +++ b/litelist-api/dub.json @@ -7,7 +7,7 @@ "botan": "~>1.13.5", "d-properties": "~>1.0.5", "d2sqlite3": "~>1.0.0", - "handy-httpd": "~>7.10.4", + "handy-httpd": "~>7.13.0", "jwt": "~>0.4.0", "resusage": "~>0.3.2", "slf4d": "~>2.4.3" diff --git a/litelist-api/dub.selections.json b/litelist-api/dub.selections.json index 22c3754..b94ee22 100644 --- a/litelist-api/dub.selections.json +++ b/litelist-api/dub.selections.json @@ -1,14 +1,15 @@ { "fileVersion": 1, "versions": { - "botan": "1.13.5", + "botan": "1.13.6", "botan-math": "1.0.4", "d-properties": "1.0.5", "d2sqlite3": "1.0.0", - "handy-httpd": "7.10.4", + "handy-httpd": "7.13.0", "httparsed": "1.2.1", "jwt": "0.4.0", "memutils": "1.0.9", + "path-matcher": "1.1.3", "resusage": "0.3.2", "slf4d": "2.4.3", "streams": "3.5.0" diff --git a/litelist-api/source/app.d b/litelist-api/source/app.d index 1887416..73fb864 100644 --- a/litelist-api/source/app.d +++ b/litelist-api/source/app.d @@ -16,7 +16,7 @@ void main() { * Returns: The HTTP server to use. */ private HttpServer initServer() { - import handy_httpd.handlers.path_delegating_handler; + import handy_httpd.handlers.path_handler; import handy_httpd.handlers.filtered_handler; import d_properties; import endpoints.auth; @@ -59,7 +59,7 @@ private HttpServer initServer() { immutable string API_PATH = "/api"; - PathDelegatingHandler mainHandler = new PathDelegatingHandler(); + PathHandler mainHandler = new PathHandler(); mainHandler.addMapping(Method.GET, API_PATH ~ "/status", &handleStatus); mainHandler.addMapping(Method.POST, API_PATH ~ "/register", &createNewUser); mainHandler.addMapping(Method.POST, API_PATH ~ "/login", &handleLogin); @@ -74,22 +74,23 @@ private HttpServer initServer() { mainHandler.addMapping(Method.OPTIONS, API_PATH ~ "/**", optionsHandler); // Separate handler for authenticated paths, protected by a TokenFilter. - PathDelegatingHandler authHandler = new PathDelegatingHandler(); + PathHandler authHandler = new PathHandler(); authHandler.addMapping(Method.GET, API_PATH ~ "/me", &getMyUser); authHandler.addMapping(Method.DELETE, API_PATH ~ "/me", &deleteMyUser); authHandler.addMapping(Method.GET, API_PATH ~ "/renew-token", &renewToken); authHandler.addMapping(Method.GET, API_PATH ~ "/lists", &getNoteLists); authHandler.addMapping(Method.POST, API_PATH ~ "/lists", &createNoteList); - authHandler.addMapping(Method.GET, API_PATH ~ "/lists/{id}", &getNoteList); - authHandler.addMapping(Method.DELETE, API_PATH ~ "/lists/{id}", &deleteNoteList); - authHandler.addMapping(Method.POST, API_PATH ~ "/lists/{listId}/notes", &createNote); - authHandler.addMapping(Method.DELETE, API_PATH ~ "/lists/{listId}/notes/{noteId}", &deleteNote); + authHandler.addMapping(Method.GET, API_PATH ~ "/lists/:id:ulong", &getNoteList); + authHandler.addMapping(Method.DELETE, API_PATH ~ "/lists/:id:ulong", &deleteNoteList); + authHandler.addMapping(Method.POST, API_PATH ~ "/lists/:listId:ulong/notes", &createNote); + authHandler.addMapping(Method.DELETE, API_PATH ~ "/lists/:listId:ulong/notes/:noteId:ulong", &deleteNote); + authHandler.addMapping(Method.DELETE, API_PATH ~ "/lists/:listId:ulong/notes", &deleteAllNotes); HttpRequestFilter tokenFilter = new TokenFilter(loadTokenSecret()); HttpRequestFilter adminFilter = new AdminFilter(); // Separate handler for admin paths, protected by an AdminFilter. - PathDelegatingHandler adminHandler = new PathDelegatingHandler(); + PathHandler adminHandler = new PathHandler(); adminHandler.addMapping(Method.GET, API_PATH ~ "/admin/users", &getAllUsers); mainHandler.addMapping(API_PATH ~ "/admin/**", new FilteredRequestHandler(adminHandler, [tokenFilter, adminFilter])); diff --git a/litelist-api/source/data/impl/list.d b/litelist-api/source/data/impl/list.d index 3b4da46..5c86763 100644 --- a/litelist-api/source/data/impl/list.d +++ b/litelist-api/source/data/impl/list.d @@ -72,6 +72,13 @@ class SqliteNoteListDataSource : NoteListDataSource { db.commit(); } + void deleteAllNotes(string username, ulong id) { + Database db = getDb(username); + db.begin(); + db.execute("DELETE FROM note WHERE note_list_id = ?", id); + db.commit(); + } + NoteList updateNoteList(string username, ulong id, NoteList newData) { Database db = getDb(username); ResultRange result = db.execute("SELECT * FROM note_list WHERE id = ?", id); diff --git a/litelist-api/source/data/list.d b/litelist-api/source/data/list.d index 3b33791..e919369 100644 --- a/litelist-api/source/data/list.d +++ b/litelist-api/source/data/list.d @@ -9,6 +9,7 @@ interface NoteListDataSource { Nullable!NoteList getList(string username, ulong id); NoteList createNoteList(string username, string name, string description = null); void deleteNoteList(string username, ulong id); + void deleteAllNotes(string username, ulong id); NoteList updateNoteList(string username, ulong id, NoteList newData); ulong countLists(string username); } diff --git a/litelist-api/source/endpoints/lists.d b/litelist-api/source/endpoints/lists.d index 0054845..5911471 100644 --- a/litelist-api/source/endpoints/lists.d +++ b/litelist-api/source/endpoints/lists.d @@ -81,6 +81,12 @@ void deleteNote(ref HttpRequestContext ctx) { noteDataSource.deleteNote(auth.user.username, noteId); } +void deleteAllNotes(ref HttpRequestContext ctx) { + AuthContext auth = getAuthContextOrThrow(ctx); + ulong listId = ctx.request.getPathParamAs!ulong("listId"); + noteListDataSource.deleteAllNotes(auth.user.username, listId); +} + private JSONValue serializeList(NoteList list) { JSONValue listObj = JSONValue(string[string].init); listObj.object["id"] = JSONValue(list.id); diff --git a/litelist-app/src/api/lists.ts b/litelist-app/src/api/lists.ts index 1a1d772..0af6502 100644 --- a/litelist-app/src/api/lists.ts +++ b/litelist-app/src/api/lists.ts @@ -77,3 +77,10 @@ export async function deleteNote(token: string, listId: number, id: number): Pro headers: {"Authorization": "Bearer " + token} }) } + +export async function deleteAllNotes(token: string, listId: number): Promise { + await fetch(API_URL + "/lists/" + listId + "/notes", { + method: "DELETE", + headers: {"Authorization": "Bearer " + token} + }) +} diff --git a/litelist-app/src/views/SingleListView.vue b/litelist-app/src/views/SingleListView.vue index 990da81..edc0ed5 100644 --- a/litelist-app/src/views/SingleListView.vue +++ b/litelist-app/src/views/SingleListView.vue @@ -2,7 +2,7 @@ import type {NoteList} from "@/api/lists"; import {nextTick, onMounted, ref, type Ref} from "vue"; import {useAuthStore} from "@/stores/auth"; -import {createNote, deleteNote, deleteNoteList, getNoteList} from "@/api/lists"; +import {createNote, deleteAllNotes, deleteNote, deleteNoteList, getNoteList} from "@/api/lists"; import {useRoute, useRouter} from "vue-router"; import PageContainer from "@/components/PageContainer.vue"; import LogOutButton from "@/components/LogOutButton.vue"; @@ -64,6 +64,22 @@ function toggleCreatingNewNote() { } } +async function clearList() { + if (!list.value) return + const dialog = document.getElementById("list-clear-notes-dialog") as HTMLDialogElement + dialog.showModal() + const confirmButton = document.getElementById("clear-notes-confirm-button") as HTMLButtonElement + confirmButton.onclick = async () => { + dialog.close() + await deleteAllNotes(authStore.token, list.value.id) + list.value.notes = [] + } + const cancelButton = document.getElementById("clear-notes-cancel-button") as HTMLButtonElement + cancelButton.onclick = async () => { + dialog.close() + } +} + async function createNoteAndRefresh() { if (!list.value) return await createNote(authStore.token, list.value.id, newNoteText.value) @@ -71,6 +87,32 @@ async function createNoteAndRefresh() { newNoteText.value = "" list.value = await getNoteList(authStore.token, list.value.id) } + +function printList() { + if (!list.value) return + const l: NoteList = list.value + const header = `

${l.name}

` + const description = `

${l.description}

` + let checkboxList = `
` + for (let i = 0; i < l.notes.length; i++) { + const note = l.notes[i] + checkboxList += `
` + } + checkboxList += `
` + const w = window.open() + const html = ` + + + +${header} +${description} +${checkboxList} + + +` + w.document.write(html) + w.window.print() +} @@ -134,6 +191,8 @@ h1 { .buttons-list button { margin-right: 1rem; + margin-top: 0.25rem; + margin-bottom: 0.25rem; font-size: medium; }