Skip to content

Commit

Permalink
Meeting Creation Functionality (#102)
Browse files Browse the repository at this point in the history
* style: 🎨 rename groupMembers to groupAvailabilities

* feat: ✨ create meeting

* refactor(chrono): ♻️ use HourMinuteString instead of string

* fix(creation): 🐛 start and end times formats

* fix(schema): 🐛 regress to HH:MM format for start and end times

* refactor(get meeting): use existing util

* feat(create): ✨ add host_id to created meeting

* feat(availability): ✨ add member to members_in_meeting

* fix: 🐛 remove vestigial function import
  • Loading branch information
MinhxNguyen7 authored May 20, 2024
1 parent a2cb804 commit 47051c6
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 61 deletions.
6 changes: 3 additions & 3 deletions src/lib/components/availability/GroupAvailability.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {
availabilityDates,
availabilityTimeBlocks,
groupMembers,
groupAvailabilities,
} from "$lib/stores/availabilityStores";
import { ZotDate } from "$lib/utils/ZotDate";
import { cn } from "$lib/utils/utils";
Expand Down Expand Up @@ -47,10 +47,10 @@
[];
availableMembersOfSelection = availableMemberIndices.map(
(availableMemberIndex) => $groupMembers[availableMemberIndex].name,
(availableMemberIndex) => $groupAvailabilities[availableMemberIndex].name,
);
notAvailableMembersOfSelection = $groupMembers
notAvailableMembersOfSelection = $groupAvailabilities
.filter((_, index) => !availableMemberIndices.includes(index))
.map((notAvailableMember) => notAvailableMember.name);
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/availability/GroupAvailabilityBlock.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { groupMembers } from "$lib/stores/availabilityStores";
import { groupAvailabilities } from "$lib/stores/availabilityStores";
import { cn } from "$lib/utils/utils";
export let availableMemberIndices: number[] | null;
Expand All @@ -9,7 +9,7 @@
const calculateGroupBlockColor = (availableMemberIndices: number[] | null): string => {
if (availableMemberIndices) {
const opacity = availableMemberIndices.length / $groupMembers.length;
const opacity = availableMemberIndices.length / $groupAvailabilities.length;
return `rgba(55, 124, 251, ${opacity})`;
}
return "transparent";
Expand Down
35 changes: 35 additions & 0 deletions src/lib/components/creation/Creation.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Calendar from "$lib/components/creation/CalendarV2/Calendar.svelte";
import MeetingNameField from "$lib/components/creation/MeetingV2/MeetingNameField.svelte";
import MeetingTimeField from "$lib/components/creation/MeetingV2/MeetingTimeField.svelte";
import { selectedDays } from "$lib/stores/meetingSetupStores";
import { startTime, endTime } from "$lib/stores/meetingSetupStores";
import { meetingName } from "$lib/stores/meetingSetupStores";
import type { CreateMeetingPostParams } from "$lib/types/meetings";
import { cn } from "$lib/utils/utils";
const handleCreation = async () => {
const body: CreateMeetingPostParams = {
title: $meetingName,
fromTime: $startTime,
toTime: $endTime,
meetingDates: $selectedDays.map((zotDate) => zotDate.day.toISOString()),
description: "",
};
const response: Response = await fetch("/api/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
console.error("Failed to create meeting: ", response.statusText);
return;
}
const { meetingId } = await response.json();
if (!meetingId) {
console.error("Failed to create meeting. Meeting ID not found.");
return;
}
goto(`/availability/${meetingId}`);
};
</script>

<div class="px-4 pt-8 md:pl-[60px] md:pt-10">
Expand Down Expand Up @@ -37,6 +71,7 @@
"btn w-48 border-none bg-success font-montserrat text-xl font-medium text-gray-light sm:btn-wide",
)}
disabled={$selectedDays.length > 0 && $startTime && $endTime && $meetingName ? false : true}
on:click={handleCreation}
>
Continue →
</button>
Expand Down
47 changes: 24 additions & 23 deletions src/lib/db/databaseUtils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SuperValidated } from "sveltekit-superforms";
import type { ZodObject, ZodString } from "zod";

import { db } from "./drizzle";
import { members, users, guests, meetings, meetingDates } from "./schema";
import { members, users, guests, meetings, meetingDates, sessions } from "./schema";
import type {
UserInsertSchema,
MemberInsertSchema,
Expand Down Expand Up @@ -89,6 +89,15 @@ export const getExistingUser = async (
return existingUser;
};

export async function getUserIdFromSession(sessionId: string): Promise<string> {
const [{ userId }] = await db
.select({ userId: sessions.userId })
.from(sessions)
.where(eq(sessions.id, sessionId));

return userId;
}

export const getExistingGuest = async (username: string, meeting: MeetingSelectSchema) => {
const [existingGuest] = await db
.select()
Expand All @@ -99,21 +108,15 @@ export const getExistingGuest = async (username: string, meeting: MeetingSelectS
};

/**
* To create a meeting, call this function with:
* 1. A title
* 2. A start time; I used: 2024-01-31T16:00:00.000Z
* 3. An end time: I used: 2024-02-06T16:00:00.000Z
*
* NOTE:
* `generateSampleDates()` is called whenever no availability is found
* If you use dates other than the ones above, generateSampleDates() will return dates
* other than the ones your meeting may *actually* be of
*
* @param meeting
* @param meeting The meeting object to insert. `from_time` and `to_time` represent the start and end times
* and are only used for the times of day.
* @param meetingDates The dates to insert for the meeting. Only the date portion of the date object is used.
* @returns The id of the inserted meeting.
*/
export const insertMeeting = async (meeting: MeetingInsertSchema) => {
export const insertMeeting = async (meeting: MeetingInsertSchema, meetingDates: Date[]) => {
const [dbMeeting] = await db.insert(meetings).values(meeting).returning();
await insertMeetingDates(dbMeeting);
await insertMeetingDates(meetingDates, dbMeeting.id);
return dbMeeting.id;
};

export const getExistingMeeting = async (meetingId: string) => {
Expand All @@ -122,16 +125,14 @@ export const getExistingMeeting = async (meetingId: string) => {
return dbMeeting;
};

export const insertMeetingDates = async (meeting: MeetingSelectSchema) => {
const currentDate = meeting.from_time;
currentDate.setDate(currentDate.getDate());

for (let i = 0; currentDate <= meeting.to_time; i++) {
const value: MeetingDateInsertSchema = { date: currentDate, meeting_id: meeting.id };
await db.insert(meetingDates).values(value);
export const insertMeetingDates = async (dates: Date[], meeting_id: string) => {
const dbMeetingDates: MeetingDateInsertSchema[] = dates.map((date) => {
// Get the start of the date to better standardize
const startOfDay = new Date(date.toDateString());
return { meeting_id, date: startOfDay };
});

currentDate.setDate(currentDate.getDate() + 1);
}
await db.insert(meetingDates).values(dbMeetingDates);
};

export const getExistingMeetingDates = async (meetingId: string) => {
Expand Down
10 changes: 7 additions & 3 deletions src/lib/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import {
boolean,
json,
pgTable,
char,
} from "drizzle-orm/pg-core";

export const attendanceEnum = pgEnum("attendance", ["accepted", "maybe", "declined"]);
export const attendanceValues = ["accepted", "maybe", "declined"] as const;
export type AttendanceValue = (typeof attendanceValues)[number];

export const attendanceEnum = pgEnum("attendance", attendanceValues);
export const memberEnum = pgEnum("member_type", ["guest", "user"]);

// Members encompasses anyone who uses ZotMeet, regardless of guest or user status.
Expand Down Expand Up @@ -54,8 +58,8 @@ export const meetings = pgTable("meetings", {
description: text("description"),
location: text("location"),
scheduled: boolean("scheduled"),
from_time: timestamp("from_time").notNull(),
to_time: timestamp("to_time").notNull(),
from_time: char("from_time", { length: 5 }).notNull(),
to_time: char("to_time", { length: 5 }).notNull(),
group_id: uuid("group_id").references(() => groups.id, { onDelete: "cascade" }),
host_id: text("host_id").references(() => members.id),
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/stores/availabilityStores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const availabilityDates = writable<ZotDate[]>(
generateSampleDates(earliestTime, latestTime, sampleMembers),
);
export const availabilityTimeBlocks = writable<number[]>(defaultTimeBlocks);
export const groupMembers = readable<MemberAvailability[]>(sampleMembers);
export const groupAvailabilities = readable<MemberAvailability[]>(sampleMembers);

export const isEditingAvailability = writable<boolean>(false);
export const isStateUnsaved = writable<boolean>(false);
Expand Down
9 changes: 5 additions & 4 deletions src/lib/stores/meetingSetupStores.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { writable } from "svelte/store";

import type { HourMinuteString } from "$lib/types/chrono";
import type { MeetingTime } from "$lib/types/meetings";
import { ZotDate } from "$lib/utils/ZotDate";

Expand Down Expand Up @@ -40,9 +41,9 @@ export const DEFAULT_MEETING_NAME = "";
export const meetingName = writable<string>(DEFAULT_MEETING_NAME);

export const DEFAULT_MEETING_TIMES: MeetingTime = {
startTime: "08:00",
endTime: "17:00",
startTime: "09:00",
endTime: "16:00",
};

export const startTime = writable<string>(DEFAULT_MEETING_TIMES.startTime);
export const endTime = writable<string>(DEFAULT_MEETING_TIMES.endTime);
export const startTime = writable<HourMinuteString>(DEFAULT_MEETING_TIMES.startTime);
export const endTime = writable<HourMinuteString>(DEFAULT_MEETING_TIMES.endTime);
2 changes: 2 additions & 0 deletions src/lib/types/chrono.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ export enum CalendarConstants {
export enum TimeConstants {
MINUTES_PER_HOUR = 60,
}

export type HourMinuteString = `${string}:${string}`;
15 changes: 13 additions & 2 deletions src/lib/types/meetings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { HourMinuteString } from "./chrono";

export type Group = { name: string; id: number; img: string; link: string };

export type Meeting = ScheduledMeeting | UnscheduledMeeting;
Expand Down Expand Up @@ -28,6 +30,15 @@ export type UnscheduledMeeting = {
};

export type MeetingTime = {
startTime: string;
endTime: string;
startTime: HourMinuteString;
endTime: HourMinuteString;
};

export interface CreateMeetingPostParams {
title: string;
description: string;
fromTime: HourMinuteString;
toTime: HourMinuteString;
meetingDates: string[]; // ISO date strings
sessionId?: string;
}
47 changes: 47 additions & 0 deletions src/routes/api/create/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { error, json } from "@sveltejs/kit";

import { getUserIdFromSession, insertMeeting } from "$lib/db/databaseUtils.server";
import type { MeetingInsertSchema } from "$lib/db/schema";
import type { CreateMeetingPostParams } from "$lib/types/meetings";

export async function POST({ request }) {
const { title, description, fromTime, toTime, meetingDates, sessionId }: CreateMeetingPostParams =
await request.json();

console.log("Creating meeting:", title, description, fromTime, toTime, meetingDates);

if (fromTime >= toTime) {
error(400, "From time must be before to time");
}

if (meetingDates.length === 0) {
error(400, "At least one date must be provided");
}

// Just so we don't get flooded too easily
if (meetingDates.length > 100) {
error(400, "Too many dates provided");
}

const sortedDates = meetingDates
.map((dateString) => new Date(dateString))
.sort((a, b) => a.getUTCMilliseconds() - b.getUTCMilliseconds());

const host_id = sessionId ? await getUserIdFromSession(sessionId) : undefined;

const meeting: MeetingInsertSchema = {
title,
description: description || "",
from_time: fromTime,
to_time: toTime,
host_id,
};

try {
const meetingId = await insertMeeting(meeting, sortedDates);
return json({ meetingId });
} catch (err) {
console.log("Error creating meeting:", err);
error(500, "Error creating meeting");
}
}
5 changes: 2 additions & 3 deletions src/routes/auth/guest/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { fail } from "@sveltejs/kit";
import { generateId } from "lucia";
import { setError, superValidate } from "sveltekit-superforms/client";

import { _getMeeting } from "../../availability/[slug]/+page.server";

import { guestSchema } from "$lib/config/zod-schemas";
import {
checkIfGuestUsernameExists,
getExistingMeeting,
// insertMeeting,
insertNewGuest,
insertNewMember,
Expand All @@ -32,7 +31,7 @@ async function createGuest({ request }: { request: Request }) {
try {
const isGuestUsernameAlreadyRegistered = await checkIfGuestUsernameExists(
form.data.username,
await _getMeeting(form.data.meetingId),
await getExistingMeeting(form.data.meetingId),
);

if (isGuestUsernameAlreadyRegistered === true) {
Expand Down
Loading

0 comments on commit 47051c6

Please sign in to comment.