Skip to content

Commit

Permalink
Create an endpoint to add calendara events
Browse files Browse the repository at this point in the history
  • Loading branch information
Dlurak committed Apr 9, 2024
1 parent b0e4810 commit 395edc9
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Classical homework books have a lot of problems. These are some of them and how

This is the second Version of the backend. Here is a brief roadmap until it will be integrated into the Frontend:

- [ ] Improvements to the authentication system
- [ ] Notes
- [ ] Calendar
- [ ] Tags
- [ ] Notes
- [ ] A SDK for TypeScript
- [ ] Migrating the Frontend from the old to the new API
- [ ] Migrating the data from MongoDB to EdgeDB
Expand Down
35 changes: 35 additions & 0 deletions dbschema/default.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,40 @@ module default {
};
}

scalar type Priority extending enum<Critical, High, Medium, Low>;

type Calendar {
required class: Class {
readonly := true;
};


required title: str;
summary: str;

required beginning: datetime;
# It may sound stupid, but in school we don't
# always know how long something will take, so it is optional
ending: datetime;

location: str;


multi tags: Tag;
priority: Priority;

multi updates: Change {
on target delete allow;
on source delete delete target;
};
}

type Tag {
required tag: str;
color: str;
required class: Class;
}

type Change {
required user: User {
on target delete delete source;
Expand All @@ -125,5 +159,6 @@ module default {
};

assignments := .<updates[is Assignment];
calendar := .<updates[is Calendar];
}
}
29 changes: 29 additions & 0 deletions dbschema/migrations/00017.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
CREATE MIGRATION m1sty7i6q5winkndbyoecud6k7wkx6cd7hvlg4yx5d3ncwdjlbba3q
ONTO m1k5kk6wjhcyjkqblwsla7tml2hbmfohxsdafccqr45wapyvwfwhva
{
CREATE TYPE default::Tag {
CREATE REQUIRED LINK class: default::Class;
CREATE PROPERTY color: std::str;
CREATE REQUIRED PROPERTY tag: std::str;
};
CREATE SCALAR TYPE default::Priority EXTENDING enum<Critical, High, Medium, Low>;
CREATE TYPE default::Calendar {
CREATE REQUIRED LINK class: default::Class {
SET readonly := true;
};
CREATE MULTI LINK tags: default::Tag;
CREATE MULTI LINK updates: default::Change {
ON SOURCE DELETE DELETE TARGET;
ON TARGET DELETE ALLOW;
};
CREATE REQUIRED PROPERTY beginning: std::datetime;
CREATE PROPERTY ending: std::datetime;
CREATE PROPERTY location: std::str;
CREATE PROPERTY priority: default::Priority;
CREATE PROPERTY summary: std::str;
CREATE REQUIRED PROPERTY title: std::str;
};
ALTER TYPE default::Change {
CREATE LINK calendar := (.<updates[IS default::Calendar]);
};
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { assignmentsRouter } from "routes/assignments";
import { accessTokenRouter } from "routes/auth/accessToken";
import { refreshTokenRouter } from "routes/auth/refreshToken";
import { registerRouter } from "routes/auth/register";
import { calendarRouter } from "routes/calendar";
import { classRouter } from "routes/classes";
import { moderationRouter } from "routes/moderation";
import { schoolRouter } from "routes/school";
Expand All @@ -22,6 +23,7 @@ const app = new Elysia()
.use(classRouter)
.use(moderationRouter)
.use(assignmentsRouter)
.use(calendarRouter)
.get(
"/",
() => ({
Expand Down
110 changes: 110 additions & 0 deletions src/routes/calendar/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import e from "@edgedb";
import { DATABASE_WRITE_FAILED, UNAUTHORIZED } from "constants/responses";
import Elysia, { t } from "elysia";
import { HttpStatusCode } from "elysia-http-status-code";
import { client } from "index";
import { auth } from "plugins/auth";
import { isIncreasing } from "utils/arrays/increasing";
import { promiseResult } from "utils/errors";
import { classBySchoolAndName } from "utils/queries/class";
import { responseBuilder } from "utils/response";
import { savePredicate } from "utils/undefined";

export const createCalendar = new Elysia()
.use(HttpStatusCode())
.use(auth)
.post(
"/",
async ({ body, auth, set, httpStatus }) => {
if (!auth.isAuthorized) {
set.status = httpStatus.HTTP_401_UNAUTHORIZED;
return UNAUTHORIZED;
}

if (!isIncreasing([body.beginning, body.ending ?? Infinity])) {
set.status = httpStatus.HTTP_422_UNPROCESSABLE_ENTITY;
return responseBuilder("error", {
error: "Ending must be later then beginning"
})
}

const isUserInClassQuery = e.count(
e.select(e.Class, (c) => ({
filter_single: e.op(
e.op(
e.op(c.name, "=", body.class),
"and",
e.op(c.school.name, "=", body.school),
),
"and",
e.op(auth.username, "in", c.students.username),
),
})),
);

const updateQuery = e.insert(e.Calendar, {
title: body.title,
summary: body.summary,
class: classBySchoolAndName({
className: body.class,
schoolName: body.school,
}),
beginning: new Date(body.beginning),
ending: savePredicate(body.ending, (ending) => new Date(ending)),
updates: e.insert(e.Change, {
user: e.select(e.User, (u) => ({
filter_single: e.op(u.username, "=", auth.username),
})),
}),
location: body.location,
priority: body.priority,
});

const result = await promiseResult(() => {
return client.transaction(async (tx) => {
const isInClass = await isUserInClassQuery
.run(tx)
.then((c) => c === 1);
if (!isInClass) return "NOT_IN_CLASS";

return updateQuery.run(tx);
});
});

if (result.isError) {
set.status = httpStatus.HTTP_500_INTERNAL_SERVER_ERROR;
return DATABASE_WRITE_FAILED;
}

if (result.data === "NOT_IN_CLASS") {
set.status = httpStatus.HTTP_404_NOT_FOUND;
return responseBuilder("error", {
error:
"This class doesn't exist or you don't have rights to create data for it",
});
}

set.status = httpStatus.HTTP_201_CREATED;
return responseBuilder("success", {
message: "Successfully created calendar event",
data: result.data,
});
},
{
body: t.Object({
title: t.String({ minLength: 1 }),
school: t.String({ minLength: 1 }),
class: t.String({ minLength: 1 }),
beginning: t.Number({ minimum: 0 }),
ending: t.Optional(t.Number({ minimum: 1 })),
summary: t.Optional(t.String({ minLength: 1 })),
location: t.Optional(t.String({ minLength: 1 })),
priority: t.Optional(t.Union([
t.Literal("Critical"),
t.Literal("High"),
t.Literal("Medium"),
t.Literal("Low"),
]))
}),
},
);
6 changes: 6 additions & 0 deletions src/routes/calendar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Elysia from "elysia";
import { createCalendar } from "./create";

export const calendarRouter = new Elysia({ prefix: "/calendar" }).use(
createCalendar,
);
8 changes: 8 additions & 0 deletions src/utils/arrays/increasing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const isIncreasing = (arr: number[]): boolean => {
if (arr.length <= 1) return true

const [first, second] = arr.slice(0, 2)
if (second < first) return false

return isIncreasing(arr.slice(1))
}
6 changes: 6 additions & 0 deletions src/utils/queries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# EdgeDB Queries

The functions in this directory should only make it simpler to create queries.
These functions are not supposed to execute those queries. This makes it way
clearer what does what and makes it possible to use these queries in
transactions.
19 changes: 19 additions & 0 deletions src/utils/queries/class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import e from "@edgedb";

interface ClassBySchoolAndNameProps {
schoolName: string;
className: string;
}

export const classBySchoolAndName = ({
schoolName,
className,
}: ClassBySchoolAndNameProps) => {
return e.select(e.Class, (c) => ({
filter_single: e.op(
e.op(c.name, "=", className),
"and",
e.op(c.school.name, "=", schoolName),
),
}));
};
20 changes: 20 additions & 0 deletions tests/array.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from "bun:test";
import { removeDuplicates } from "utils/arrays/duplicates";
import { areSameValue } from "utils/arrays/general";
import { isIncreasing } from "utils/arrays/increasing";
import { merge } from "utils/arrays/merge";

describe("merge", () => {
Expand Down Expand Up @@ -70,3 +71,22 @@ describe("same value", () => {
}
});
});

describe("is increasing", () => {
it("works for short arrays", () => {
expect(isIncreasing([])).toBeTrue()
expect(isIncreasing([1])).toBeTrue()
expect(isIncreasing([-1])).toBeTrue()
})

it("works for increasing long arrays", () => {
expect(isIncreasing([1,2,3,4,60,70])).toBeTrue()
expect(isIncreasing([-Infinity, -2.5,300, Infinity])).toBeTrue()
})

it("works for non increasing long arrays", () => {
expect(isIncreasing([-Infinity, -200, 0, 1, 2, 4, 3, 5])).toBeFalse()
expect(isIncreasing([1,0,2,3,4])).toBeFalse()
expect(isIncreasing([0,1,2,4,3])).toBeFalse()
})
})

0 comments on commit 395edc9

Please sign in to comment.