diff --git a/dbschema/default.esdl b/dbschema/default.esdl index 22d152f..5753829 100644 --- a/dbschema/default.esdl +++ b/dbschema/default.esdl @@ -165,6 +165,9 @@ module default { default := EditScope.Self; }; + creator: User { + on target delete allow; + }; multi updates: Change { on target delete allow; on source delete delete target; diff --git a/dbschema/migrations/00020.edgeql b/dbschema/migrations/00020.edgeql new file mode 100644 index 0000000..1ecf097 --- /dev/null +++ b/dbschema/migrations/00020.edgeql @@ -0,0 +1,12 @@ +CREATE MIGRATION m17fqphiq6jr5unbqqolhaopjqdcgklmjowirlpgkejf2qsqocypsq + ONTO m1mzn3ehkgkjrnujdwfxjhe5optsk3cmyqkdagwev6vq2p5a4xxbsa +{ + ALTER TYPE default::Note { + CREATE LINK creator: default::User { + ON TARGET DELETE ALLOW; + }; + ALTER PROPERTY editScope { + SET REQUIRED USING (default::EditScope.Self); + }; + }; +}; diff --git a/src/routes/notes/create.ts b/src/routes/notes/create.ts index eade8c3..41be9c0 100644 --- a/src/routes/notes/create.ts +++ b/src/routes/notes/create.ts @@ -44,6 +44,7 @@ export const createNote = new Elysia() })), editScope: body.editScope, + creator: userByUsername(auth.username), updates: e.insert(e.Change, { user: userByUsername(auth.username) }), }); const result = await promiseResult(() => query.run(client)); diff --git a/src/routes/notes/delete.ts b/src/routes/notes/delete.ts new file mode 100644 index 0000000..eef332d --- /dev/null +++ b/src/routes/notes/delete.ts @@ -0,0 +1,64 @@ +import e from "@edgedb"; +import { DATABASE_DELETE_FAILED, UNAUTHORIZED } from "constants/responses"; +import Elysia from "elysia"; +import { HttpStatusCode } from "elysia-http-status-code"; +import { client } from "index"; +import { auth } from "plugins/auth"; +import { promiseResult } from "utils/errors"; +import { responseBuilder } from "utils/response"; + +export const deleteNote = new Elysia() + .use(auth) + .use(HttpStatusCode()) + .delete("/:id", async ({ auth, set, httpStatus, params }) => { + if (!auth.isAuthorized) { + set.status = httpStatus.HTTP_401_UNAUTHORIZED; + return UNAUTHORIZED; + } + + const deletionQuery = e.delete(e.Note, (n) => { + const selfFilter = e.op( + e.op(n.editScope, "=", e.cast(e.EditScope, "Self")), + "and", + e.op(n.creator.username, "=", auth.username), + ); + const classFilter = e.op( + e.op(n.editScope, "=", e.cast(e.EditScope, "Class")), + "and", + e.op(auth.username, "in", n.class.students.username), + ); + const schoolFilter = e.op( + e.op(n.editScope, "=", e.cast(e.EditScope, "School")), + "and", + e.op(auth.username, "in", n.class.school.classes.students.username), + ); + + const editScopeFilter = e.op( + e.op(selfFilter, "or", classFilter), + "or", + schoolFilter, + ); + + const paramId = e.cast(e.uuid, params.id); + const idFilter = e.op(n.id, "=", paramId); + + return { filter_single: e.op(editScopeFilter, "and", idFilter) }; + }); + const result = await promiseResult(() => deletionQuery.run(client)); + + if (result.isError) { + set.status = httpStatus.HTTP_500_INTERNAL_SERVER_ERROR; + return DATABASE_DELETE_FAILED; + } + if (!result.data) { + set.status = httpStatus.HTTP_404_NOT_FOUND; + return responseBuilder("error", { + error: "This note doesn't exist or you don't have rights to delete it", + }); + } + + return responseBuilder("success", { + message: "Successfully deleted note", + data: null, + }); + }); diff --git a/src/routes/notes/index.ts b/src/routes/notes/index.ts index 4a97718..2d2c435 100644 --- a/src/routes/notes/index.ts +++ b/src/routes/notes/index.ts @@ -1,7 +1,9 @@ import Elysia from "elysia"; import { createNote } from "./create"; +import { deleteNote } from "./delete"; import { listNotes } from "./list"; export const noteRouter = new Elysia({ prefix: "/notes" }) .use(createNote) + .use(deleteNote) .use(listNotes); diff --git a/src/routes/notes/list.ts b/src/routes/notes/list.ts index f8b3944..b073969 100644 --- a/src/routes/notes/list.ts +++ b/src/routes/notes/list.ts @@ -84,22 +84,27 @@ export const listNotes = new Elysia() id: true, }; }); - const result = await promiseResult(() => - dbQuery(query.limit, query.offset).run(client), - ); + const result = await promiseResult(async () => { + const [notes, count] = await Promise.all([ + dbQuery(query.limit, query.offset).run(client), + e.count(dbQuery(-1, 0)).run(client), + ]); + return { notes, count }; + }); if (result.isError) { set.status = httpStatus.HTTP_500_INTERNAL_SERVER_ERROR; return DATABASE_READ_FAILED; } - const formatted = result.data.map((i) => + const formatted = result.data.notes.map((i) => replaceDateDeep(i, normalDateToCustom), ); return responseBuilder("success", { message: "Successfully retrieved data", data: { + totalCount: result.data.count, notes: formatted, }, });