Skip to content

Commit

Permalink
testing webcal link (#257)
Browse files Browse the repository at this point in the history
* testing webcal link

* type-gen
  • Loading branch information
jongrim authored Feb 27, 2024
1 parent e6eb4c5 commit 5a3e15a
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 26 deletions.
103 changes: 103 additions & 0 deletions netlify/functions/webcal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Handler } from "@netlify/functions";
import * as ics from "ics";
import { format } from "date-fns";
import { logError, supabase } from "../utils";

export const handler: Handler = async (event) => {
const { id } = event.queryStringParameters;

const { data, error } = await supabase
.from("user_calendars")
.select("user_id")
.eq("webcal_id", id)
.single();

if (error) {
return {
statusCode: 404,
};
}

const { data: playingSessions, error: playingError } = await supabase
.from("sessions")
.select("*, game_id(*)")
.is("deleted_at", null)
.contains("rsvps", [data.user_id])
.gte("start_time", new Date().getTime());

if (playingError) {
logError({
message: `Error loading playing sessions in webcal: ${playingError}`,
});
}

const { data: managingSessions, error: managingError } = await supabase
.from("sessions")
.select("*, game_id(*)")
.is("deleted_at", null)
.eq("creator_id", data.user_id)
.gte("start_time", new Date().getTime());

if (managingError) {
logError({
message: `Error loading managing sessions in webcal: ${managingError}`,
});
}

function sessionToCalFormat(session) {
return {
startTime: session.start_time,
endTime: session.end_time,
title: session.game_id.title,
gameId: session.game_id.id,
description: session.game_id.description_as_flat_text,
};
}
const events = playingSessions
.map(sessionToCalFormat)
.concat(managingSessions.map(sessionToCalFormat));
const ics = createIcs(events);

return {
statusCode: 200,
body: JSON.stringify(ics),
};
};

function createIcs(
events: {
startTime: number;
endTime: number;
title: string;
gameId: number | string;
description: string;
}[]
) {
const { value } = ics.createEvents(
events.map((event) => ({
start: format(event.startTime, "yyyy-M-d-H-m")
.split("-")
.map((val) => parseInt(val)) as [
number,
number,
number,
number,
number,
],
end: format(event.endTime, "yyyy-M-d-H-m")
.split("-")
.map((val) => parseInt(val)) as [
number,
number,
number,
number,
number,
],
title: event.title,
url: `https://app.playabl.io/games/${event.gameId}`,
busyStatus: "BUSY",
description: `Game URL - https://app.playabl.io/games/${event.gameId}`,
}))
);
return value;
}
3 changes: 2 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { computed, onMounted, onUnmounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { store } from "./store";
import { triggerUserAccessLoad } from "./storeActions";
import { loadWebCal, triggerUserAccessLoad } from "./storeActions";
import { supabase } from "./supabase";
import ToasterManager from "./components/Toast/ToasterManager.vue";
import NewProfileModal from "./components/Modals/NewProfileModal.vue";
Expand Down Expand Up @@ -53,6 +53,7 @@ async function setupUserProfile(id: string) {
removeUser();
return;
}
loadWebCal(profile.id);
store.user = profile;
store.userSettings = profile?.user_settings;
loadInProgress.value = false;
Expand Down
18 changes: 9 additions & 9 deletions src/api/gamesAndSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export async function loadUpcomingJoinedGames(userId: string) {
.select("*, community_id (id, name), sessions!inner(*)")
.contains("sessions.rsvps", [userId])
.gte("sessions.start_time", today.getTime())
.order("start_time", { foreignTable: "sessions", ascending: true });
.order("start_time", { referencedTable: "sessions", ascending: true });
if (error) {
log({ error });
}
Expand Down Expand Up @@ -149,8 +149,8 @@ export async function loadBrowsableGames({
system?: string;
}) {
const today = new Date();
const sort: { foreignTable: string; ascending: boolean } = {
foreignTable: [sortKeys.startTime, sortKeys.endTime].includes(sortKey)
const sort: { referencedTable: string; ascending: boolean } = {
referencedTable: [sortKeys.startTime, sortKeys.endTime].includes(sortKey)
? "sessions"
: "",
ascending: sortDir === sortDirs.asc,
Expand Down Expand Up @@ -207,7 +207,7 @@ export async function loadChronologicalCommunityGames(communityIds: string[]) {
.is("deleted_at", null)
.gte("sessions.start_time", today.getTime())
.in("community_id", communityIds)
.order("start_time", { foreignTable: "sessions", ascending: true });
.order("start_time", { referencedTable: "sessions", ascending: true });
if (error) {
log({ error });
}
Expand All @@ -227,7 +227,7 @@ export async function loadGamesWithOpenings() {
.is("deleted_at", null)
.eq("sessions.has_openings", true)
.gte("sessions.start_time", today.getTime())
.order("start_time", { foreignTable: "sessions", ascending: true });
.order("start_time", { referencedTable: "sessions", ascending: true });
if (error) {
log({ error });
}
Expand All @@ -248,7 +248,7 @@ export async function loadCommunityGamesWithOpenings(communityIds: string[]) {
.eq("sessions.has_openings", true)
.gte("sessions.start_time", today.getTime())
.in("community_id", communityIds)
.order("start_time", { foreignTable: "sessions", ascending: true });
.order("start_time", { referencedTable: "sessions", ascending: true });
if (error) {
log({ error });
}
Expand All @@ -262,10 +262,10 @@ export async function loadManagedGames(userId: string) {
const today = new Date();
const { data, error } = await supabase
.from("games")
.select("*, community_id (id, name), sessions (*)")
.select("*, community_id (id, name), sessions!inner(*)")
.gte("sessions.start_time", today.getTime())
.eq("creator_id", userId)
.order("start_time", { foreignTable: "sessions" });
.order("start_time", { referencedTable: "sessions" });
if (error) {
log({ error });
}
Expand All @@ -282,7 +282,7 @@ export async function loadPastManagedGames(userId: string) {
.select("*, community_id (id, name), sessions!inner(*)")
.lt("sessions.start_time", today.getTime())
.eq("creator_id", userId)
.order("start_time", { foreignTable: "sessions" });
.order("start_time", { referencedTable: "sessions" });
if (error) {
log({ error });
}
Expand Down
28 changes: 28 additions & 0 deletions src/api/profiles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { supabase } from "@/supabase";
import { Profile } from "@/typings/Profile";
import { log } from "@/util/logger";
import { v4 as uuidv4 } from "uuid";

export async function loadProfile(userId: string) {
const { data } = await supabase
Expand Down Expand Up @@ -32,3 +33,30 @@ export async function updateProfile({
}
if (data) return data;
}

export async function createWebCalForUser(userId: string) {
const uuid = uuidv4();
const { data, error } = await supabase
.from("user_calendars")
.insert({ user_id: userId, webcal_id: uuid })
.select()
.single();
if (error) {
log(error);
throw error;
}
if (data) return data;
}

export async function loadWebCalForUser(userId: string) {
const { data, error } = await supabase
.from("user_calendars")
.select("*")
.eq("user_id", userId)
.single();

if (error && error.code !== "PGRST116") {
log(error);
}
if (data) return data;
}
20 changes: 10 additions & 10 deletions src/pages/Game/GameBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ const isLoading = ref(true);
const userIsInTheGame = computed(() =>
gameStore.sessions.some((session) =>
session.rsvps.includes(store.user?.id ?? ""),
),
session.rsvps.includes(store.user?.id ?? "")
)
);
const membership = computed(() => {
Expand All @@ -145,7 +145,7 @@ const membership = computed(() => {
const canManage = computed(
() =>
membership.value?.communityMembership?.role_id === ROLES.admin ||
gameStore.game.creator_id === store.user?.id,
gameStore.game.creator_id === store.user?.id
);
const hasAccess = computed(() => {
return userIsInTheGame.value || canManage.value;
Expand Down Expand Up @@ -192,11 +192,11 @@ async function getGameData() {
const { data, error } = await supabase
.from("games")
.select(
"*, creator_id (*), sessions (*), community_id (*), community_events (*)",
"*, creator_id (*), sessions (*), community_id (*), community_events (*)"
)
.eq("id", id as string)
.is("community_events.deleted_at", null)
.order("start_time", { foreignTable: "sessions" })
.order("start_time", { referencedTable: "sessions" })
.single();
if (error) {
Expand All @@ -207,7 +207,7 @@ async function getGameData() {
const game = {
...R.omit(
["creator_id", "sessions", "community_id"],
data as GameWithCommunityAndSessions,
data as GameWithCommunityAndSessions
),
creator_id: data.creator_id.id,
community_id: data.community_id.id,
Expand All @@ -218,7 +218,7 @@ async function getGameData() {
setSessionDataInStore(data.sessions);
loadAndSetAttendeesInStore(
// @ts-expect-error crazy ramda stuff that is too complicated
R.compose(R.uniq, R.flatten, R.pluck("rsvps"))(data.sessions) as string[],
R.compose(R.uniq, R.flatten, R.pluck("rsvps"))(data.sessions) as string[]
);
gameData.value = data;
Expand Down Expand Up @@ -275,7 +275,7 @@ function setSubscription(gameId: number) {
return session;
});
const attendeesToLoad = payload.new.rsvps.filter(
(rsvp: string) => !gameStore.attendees[rsvp],
(rsvp: string) => !gameStore.attendees[rsvp]
);
attendeesToLoad.forEach((member: string) =>
supabase
Expand All @@ -285,9 +285,9 @@ function setSubscription(gameId: number) {
.single()
.then(({ data }) => {
gameStore.attendees[member] = data;
}),
})
);
},
}
)
.subscribe();
}
Expand Down
52 changes: 46 additions & 6 deletions src/pages/Profile/SettingsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@
Update user preferences
</PrimaryButton>
</div>
<div v-if="store.userEnabledFlags[flags.webcal]" class="flex flex-col">
<Heading level="h6" as="h3" class="mb-6">Web calendar</Heading>
<Well>
<p>
Create a web calendar link that you can use with other programs to
subscribe to your RSVPs and managed games.
</p>
</Well>
<p v-if="store.userWebCalId" class="text-sm mt-3">
{{
`webcal://app.playabl.io/.netlify/functions/webcal?id=${store.userWebCalId}`
}}
</p>
<PrimaryButton
v-else
class="mt-4 mr-auto"
:is-loading="creatingCal"
@click="createWebCal"
>
Create your web calendar
</PrimaryButton>
</div>
</div>
</ProfileTemplate>
</template>
Expand All @@ -83,30 +105,32 @@ import FormCheckbox from "@/components/Forms/FormCheckbox.vue";
import FormInput from "@/components/Forms/FormInput.vue";
import PrimaryButton from "@/components/Buttons/PrimaryButton.vue";
import ProfileTemplate from "@/layouts/ProfileTemplate.vue";
import { updateProfile } from "@/api/profiles";
import { createWebCalForUser, updateProfile } from "@/api/profiles";
import Heading from "@/components/Heading.vue";
import { store } from "@/store";
import useToast from "@/components/Toast/useToast";
import Well from "@/components/Well.vue";
import flags from "@/util/flags";
const { showSuccess, showError } = useToast();
const starttime = ref(store.user?.user_settings?.starttime);
const endtime = ref(store.user?.user_settings?.endtime);
const emailsEnabled = ref(
store.user?.email_preferences?.email_enabled ?? false,
store.user?.email_preferences?.email_enabled ?? false
);
const unreadNotificationEmailsEnabled = ref(
store.user?.email_preferences?.unread_notifications_enabled ?? false,
store.user?.email_preferences?.unread_notifications_enabled ?? false
);
const rsvpToMyGameEmailsEnabled = ref(
store.user?.email_preferences?.rsvp_to_my_game_enabled ?? false,
store.user?.email_preferences?.rsvp_to_my_game_enabled ?? false
);
const communityAdminEmailsEnabled = ref(
store.user?.email_preferences?.send_community_admin_messages ?? false,
store.user?.email_preferences?.send_community_admin_messages ?? false
);
const saving = ref(false);
const creatingCal = ref(false);
watch(
() => emailsEnabled.value,
Expand All @@ -116,7 +140,7 @@ watch(
rsvpToMyGameEmailsEnabled.value = false;
communityAdminEmailsEnabled.value = false;
}
},
}
);
async function updateEmailSettings() {
Expand Down Expand Up @@ -168,4 +192,20 @@ async function setUserSettings() {
}
}
}
async function createWebCal() {
if (!store.user?.id) {
return;
}
creatingCal.value = true;
try {
const data = await createWebCalForUser(store.user.id);
store.userWebCalId = data.webcal_id;
showSuccess({ message: "Web calendar created" });
} catch (error) {
showError({ message: "Unable to create web calendar" });
} finally {
creatingCal.value = false;
}
}
</script>
Loading

0 comments on commit 5a3e15a

Please sign in to comment.