Fix calendar caching

This commit is contained in:
Fergal Moran
2023-02-28 01:34:24 +00:00
parent cc94bbf488
commit 0ad595d4fd
25 changed files with 480 additions and 175 deletions

View File

@@ -9,4 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Cache outstanding events
run: curl -X GET https://otherway.fergl.ie/api/cron/cache
uses: wei/curl@master
with:
args: https://otherway.fergl.ie/api/cron/cache

View File

@@ -9,4 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Cache outstanding events
run: curl -X GET https://otherway.fergl.ie/api/cron/reminders
uses: wei/curl@master
with:
args: https://otherway.fergl.ie/api/cron/reminders

View File

@@ -1,11 +1,11 @@
"use client";
import React from "react";
import { themeChange } from "theme-change";
import "./globals.css";
import { Raleway } from "@next/font/google";
import { NavBar } from "@/components/layout";
import { AuthUserProvider } from "@/lib/auth/authUserContext";
import { Toaster } from "react-hot-toast";
import { defaults } from "@/lib/constants";
const font = Raleway({
weight: ["400", "700"],
@@ -19,7 +19,10 @@ export default function RootLayout({
children: React.ReactNode;
}) {
React.useEffect(() => {
themeChange(false);
const theme = localStorage.getItem("theme") || defaults.defaultTheme;
if (theme && !document.body.dataset.theme) {
document.body.dataset.theme = theme;
}
}, []);
return (
<html lang="en">
@@ -28,10 +31,8 @@ export default function RootLayout({
<Toaster />
<AuthUserProvider>
<div className="flex flex-col min-h-screen bg-base-100">
<div className="sticky top-0 z-30 flex justify-center flex-none w-full h-16 transition-all duration-100 bg-opacity-90 text-primary-content backdrop-blur">
<NavBar />
</div>
<div className="-mt-[4rem] grow place-items-center items-end bg-gradient-to-br from-primary to-secondary pt-20 text-primary-content ">
<div className="items-end grow place-items-center bg-base-200 text-primary-content">
<main className=" text-base-content">{children}</main>
</div>
</div>

View File

@@ -1,6 +1,8 @@
import React from "react";
import { useState } from "react";
const Loading = () => {
return (
<div role="status">
<svg

View File

@@ -2,12 +2,11 @@ import React from "react";
import HomePageComponent from "@/components/pages/home";
const getData = async () => {
// const res = await fetch(
// `${process.env.NEXT_PUBLIC_API_URL}/api/shows/upcoming`
// );
const res = await fetch(
"https://otherway.dev.fergl.ie:3000/api/shows/upcoming"
`${process.env.NEXT_PUBLIC_API_URL}/api/shows/upcoming`,
{ cache: "no-store" }
);
return await res.json();
};

View File

@@ -4,14 +4,8 @@ import Link from "next/link";
import { useAuthUserContext } from "@/lib/auth/authUserContext";
import Image from "next/image";
import { LogIn, LogOut, PlusSquare, Menu, User } from "react-feather";
import dynamic from "next/dynamic";
import { ThemeSelector } from "../widgets/ui/themes";
const ThemeToggle = dynamic(
() => import("@/components/widgets/ui/theme/ThemeToggle"),
{
ssr: false,
}
);
const Navbar = () => {
const { profile, loading, logOut } = useAuthUserContext();
const NavMenu = profile ? (
@@ -64,14 +58,14 @@ const Navbar = () => {
);
return (
<nav className="w-full mb-2 navbar">
<nav className="w-full navbar bg-secondary-content text-accent-focus">
<Link href="/">
<Image src="/logo.png" alt="Otherway" width={48} height={48} />
<Image src="/logo.png" alt="Otherway" width={42} height={42} />
</Link>
<div className="flex-col hidden ml-auto text-sm text-center font-body md:flex md:flex-row md:space-x-10">
{!loading && NavMenu}
</div>
<ThemeToggle />
<ThemeSelector />
<div className="ml-auto lg:hidden">
<div className="dropdown-end dropdown" data-cy="dropdown">

View File

@@ -6,6 +6,9 @@ import db, { users } from "@/lib/db";
import { debug } from "console";
import { doc, setDoc } from "firebase/firestore";
import React from "react";
import ToastService from "@/components/widgets/toast";
import logger from "@/lib/util/logging";
const ProfilePageComponentProfile = () => {
const { loading, profile } = useAuthUserContext();
const [sendReminders, setSendReminders] = React.useState(false);
@@ -25,6 +28,7 @@ const ProfilePageComponentProfile = () => {
}, [profile]);
const _submitProfileForm = async ($event: React.SyntheticEvent) => {
$event.preventDefault();
try {
const result = await setDoc(
doc(users, profile?.id),
Object.assign(
@@ -33,12 +37,17 @@ const ProfilePageComponentProfile = () => {
email,
displayName,
about: about || "",
lastSeen: new Date(),
lastSeen: new Date()
}
),
{ merge: true }
);
console.log("ProfilePageComponentProfile", "_submitProfileForm", result);
ToastService.success("Successfully updated your profile", "Success");
} catch (err) {
logger.error("ProfilePageComponentProfile", "_submitProfileForm", err);
ToastService.error("Failed to update your profile.");
}
};
return (
<form className="space-y-8 divide-y" onSubmit={_submitProfileForm}>
@@ -106,10 +115,6 @@ const ProfilePageComponentProfile = () => {
updateFormValue={(v) => setAbout(v)}
showLabel={false}
/>
<p className="mt-2 text-sm ">
Write a few sentences about yourself.
</p>
</div>
</div>
@@ -126,7 +131,8 @@ const ProfilePageComponentProfile = () => {
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
<path
d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
<button

View File

@@ -3,29 +3,29 @@ import useFirebaseAuth from "@/lib/auth/useFirebaseAuth";
import { Show } from "@/models";
import React from "react";
import { MdAddAlarm } from "react-icons/md";
import { error, success, warning } from "./toast/toastService";
import ToastService from "./toast/toastService";
const RemindMeButton = ({ showId }: { showId: string }) => {
const { profile } = useFirebaseAuth();
const createShowReminder = async () => {
if (profile?.id) {
var response = await fetch(
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/reminders`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
userId: profile?.id,
showId: profile?.id,
}),
showId: profile?.id
})
}
);
if (response.status === 201) {
success("Reminder created successfully");
ToastService.success("Reminder created successfully");
} else {
error("Unable to create reminder at this time");
ToastService.error("Unable to create reminder at this time");
}
}
};

View File

@@ -1,3 +1,3 @@
import { error, success, warning } from "./toastService";
import ToastService from "./toastService";
export { success, warning, error };
export default ToastService;

View File

@@ -4,12 +4,12 @@ import ToastComponent, { ToastType } from "./ToastComponent";
import {
RiAlarmWarningLine,
RiErrorWarningLine,
RiShieldCheckLine
RiShieldCheckLine,
} from "react-icons/ri";
const success = (message: string, title?: string) => {
toast.custom(
(t) => (
const ToastService = {
success: (message: string, title?: string) => {
toast.custom((t) => (
<ToastComponent
title={title ?? "Success"}
body={message}
@@ -19,12 +19,10 @@ const success = (message: string, title?: string) => {
toast.dismiss(t.id);
}}
/>
)
);
};
const warning = (message: string, title?: string) => {
toast.custom(
(t) => (
));
},
warning: (message: string, title?: string) => {
toast.custom((t) => (
<ToastComponent
title={title ?? "Warning"}
body={message}
@@ -34,12 +32,10 @@ const warning = (message: string, title?: string) => {
toast.dismiss(t.id);
}}
/>
)
);
};
const error = (message: string, title?: string) => {
toast.custom(
(t) => (
));
},
error: (message: string, title?: string) => {
toast.custom((t) => (
<ToastComponent
title={title ?? "Error"}
body={message}
@@ -49,7 +45,7 @@ const error = (message: string, title?: string) => {
toast.dismiss(t.id);
}}
/>
)
);
));
},
};
export { success, warning, error };
export default ToastService;

View File

@@ -0,0 +1,83 @@
"use client";
import React from "react";
import { themes } from "./themes";
import { useState, useEffect } from "react";
import { IoColorPaletteOutline } from "react-icons/io5";
import { BiDownArrow } from "react-icons/bi";
import { defaults } from "@/lib/constants";
const ThemeSelector = () => {
const [activeTheme, setActiveTheme] = useState(
document?.body.dataset.theme ||
localStorage.getItem("theme") ||
defaults.defaultTheme
);
useEffect(() => {
if (document) {
document.body.dataset.theme = activeTheme;
window.localStorage.setItem("theme", activeTheme);
}
}, [activeTheme]);
const _switchTheme = (theme: string) => {
setActiveTheme(theme);
const elem = document.activeElement as HTMLElement;
elem?.blur();
};
return (
<div title="Change Theme" className="dropdown-end dropdown">
<div tabIndex={0} className="gap-1 normal-case btn-ghost btn">
<IoColorPaletteOutline className="inline-block w-5 h-5 stroke-current md:h-6 md:w-6" />
<span className="hidden md:inline">Theme</span>
<BiDownArrow className="hidden w-3 h-3 ml-1 fill-current opacity-60 sm:inline-block" />
</div>
<div
className="dropdown-content rounded-t-box rounded-b-box top-px h-[70vh] max-h-96 w-52 overflow-y-auto bg-base-200 text-base-content shadow-2xl">
<div className="grid grid-cols-1 gap-3 p-3" tabIndex={0}>
{themes.map((theme) => (
<button
key={theme.id}
className="overflow-hidden text-left rounded-lg outline-base-content"
onClick={() => _switchTheme(theme.id)}
>
<div
data-theme={theme.id}
className="w-full font-sans cursor-pointer bg-base-100 text-base-content"
>
<div className="grid grid-cols-5 grid-rows-3">
<div className="flex items-center col-span-5 row-span-3 row-start-1 gap-2 px-4 py-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
className="invisible w-3 h-3"
>
<path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z" />
</svg>
<div className="flex-grow text-sm font-bold">
{theme.id}
</div>
<div className="flex flex-wrap flex-shrink-0 h-full gap-1">
<div className="w-2 rounded bg-primary">
<div className="w-2 rounded bg-secondary">
<div className="w-2 rounded bg-accent">
<div className="w-2 rounded bg-neutral"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</button>
))}
</div>
</div>
</div>
);
};
export default ThemeSelector;

View File

@@ -0,0 +1,4 @@
import ThemeSelector from "./ThemeSelector";
import ThemeToggle from "./ThemeToggle";
export { ThemeSelector, ThemeToggle };

View File

@@ -0,0 +1,118 @@
export let themes = [
{
name: "🌝 light",
id: "light",
},
{
name: "🌚 dark",
id: "dark",
},
{
name: "🧁 cupcake",
id: "cupcake",
},
{
name: "🐝 bumblebee",
id: "bumblebee",
},
{
name: "✳️ Emerald",
id: "emerald",
},
{
name: "🏢 Corporate",
id: "corporate",
},
{
name: "🌃 synthwave",
id: "synthwave",
},
{
name: "👴 retro",
id: "retro",
},
{
name: "🤖 cyberpunk",
id: "cyberpunk",
},
{
name: "🌸 valentine",
id: "valentine",
},
{
name: "🎃 halloween",
id: "halloween",
},
{
name: "🌷 garden",
id: "garden",
},
{
name: "🌲 forest",
id: "forest",
},
{
name: "🐟 aqua",
id: "aqua",
},
{
name: "👓 lofi",
id: "lofi",
},
{
name: "🖍 pastel",
id: "pastel",
},
{
name: "🧚‍♀️ fantasy",
id: "fantasy",
},
{
name: "📝 Wireframe",
id: "wireframe",
},
{
name: "🏴 black",
id: "black",
},
{
name: "💎 luxury",
id: "luxury",
},
{
name: "🧛‍♂️ dracula",
id: "dracula",
},
{
name: "🖨 CMYK",
id: "cmyk",
},
{
name: "🍁 Autumn",
id: "autumn",
},
{
name: "💼 Business",
id: "business",
},
{
name: "💊 Acid",
id: "acid",
},
{
name: "🍋 Lemonade",
id: "lemonade",
},
{
name: "🌙 Night",
id: "night",
},
{
name: "☕️ Coffee",
id: "coffee",
},
{
name: "❄️ Winter",
id: "winter",
},
];

View File

@@ -11,13 +11,14 @@ import {
signInWithPopup,
signOut,
TwitterAuthProvider,
UserCredential,
UserCredential
} from "firebase/auth";
import { app } from "./firebase";
import { useRouter } from "next/navigation";
import { Profile } from "@/models";
import { users } from "../db";
import { doc, setDoc } from "firebase/firestore";
import { doc, getDoc, setDoc } from "firebase/firestore";
import { servicesVersion } from "@ts-morph/common/lib/typescript";
export default function useFirebaseAuth() {
const [profile, setProfile] = useState<Profile | undefined>();
@@ -26,30 +27,30 @@ export default function useFirebaseAuth() {
const [loading, setLoading] = useState(true);
const getUserProfile = useCallback(() => {
const getUserProfile = useCallback(async () => {
if (auth.currentUser !== null) {
// The user object has basic properties such as display name, email, etc.
// Going forward we may look this up from the user table in firestore
const profile = new Profile(
const savedProfileRef = await getDoc(doc(users, auth.currentUser.uid));
const savedProfile = savedProfileRef.data();
const profile: Profile = new Profile(
auth.currentUser.uid,
auth.currentUser.email,
auth.currentUser.displayName,
auth.currentUser.photoURL,
auth.currentUser.emailVerified,
new Date()
(savedProfile?.email || auth.currentUser.email) as string,
(savedProfile?.displayName || auth.currentUser.email) as string,
(savedProfile?.photoURL || auth.currentUser.email) as string,
savedProfile?.about as string
);
setProfile(profile);
setDoc(doc(users, auth.currentUser.uid), Object.assign({}, profile), {
merge: true,
await setDoc(doc(users, auth.currentUser.uid), Object.assign({}, profile), {
merge: true
});
return profile;
}
}, [auth.currentUser]);
const authStateChanged = useCallback(
(user: any) => {
const authStateChanged = useCallback(async (user: any) => {
if (user) {
setLoading(true);
const profile = getUserProfile();
const profile = await getUserProfile();
setProfile(profile);
}
setLoading(false);
@@ -108,7 +109,7 @@ export default function useFirebaseAuth() {
const result = await _processSignIn(provider);
if (result) {
const credential = GoogleAuthProvider.credentialFromResult(result);
const profile = getUserProfile();
const profile = await getUserProfile();
setProfile(profile);
router.push("/");
}
@@ -118,7 +119,7 @@ export default function useFirebaseAuth() {
const result = await _processSignIn(provider);
if (result) {
const credential = TwitterAuthProvider.credentialFromResult(result);
const profile = getUserProfile();
const profile = await getUserProfile();
setProfile(profile);
router.push("/");
}
@@ -142,6 +143,6 @@ export default function useFirebaseAuth() {
signInWithGoogle,
signInWithTwitter,
signInWithFacebook,
getUserProfile,
getUserProfile
};
}

View File

@@ -7,7 +7,7 @@ import {
WithFieldValue,
QueryDocumentSnapshot,
SnapshotOptions,
Timestamp,
Timestamp
} from "firebase/firestore";
const firebaseConfig = {
@@ -17,7 +17,7 @@ const firebaseConfig = {
storageBucket: "radio-otherway.appspot.com",
messagingSenderId: "47147490249",
appId: "1:47147490249:web:a84515b3ce1c481826e618",
measurementId: "G-12YB78EZM4",
measurementId: "G-12YB78EZM4"
};
export const firebaseApp = initializeApp(firebaseConfig);
const firestore = getFirestore();
@@ -30,7 +30,7 @@ const showConverter = {
...show,
date: show.date
? Timestamp.fromDate(new Date(show.date as string))
: new Date(),
: new Date()
};
},
fromFirestore(
@@ -39,7 +39,7 @@ const showConverter = {
): Show {
const data = snapshot.data(options)!;
return new Show(snapshot.id, data.title, data.date.toDate(), data.creator);
},
}
};
// Import all your model types
@@ -52,4 +52,7 @@ export const shows =
export const reminders = createCollection<Reminder>("reminders");
export const remindersProcessed =
createCollection<RemindersProcessed>("reminders");
export default firestore;
export { createCollection };

26
src/lib/db/settings.ts Normal file
View File

@@ -0,0 +1,26 @@
import { Show } from "@/models";
import { createCollection } from "@/lib/db/index";
import { doc, query, setDoc, where } from "@firebase/firestore";
import { getDoc } from "firebase/firestore";
interface Setting {
value: string;
updated: Date;
}
const settingsCollection = createCollection<Setting>("settings");
const Settings = {
read: async (key: string): Promise<string | undefined> => {
const value = (await getDoc(doc(settingsCollection, key))).data();
return value?.value || undefined;
},
write: async (key: string, value: string) => {
await setDoc(doc(settingsCollection, key), {
value,
updated: new Date()
});
}
};
export default Settings;

View File

@@ -0,0 +1,22 @@
const { google } = require("googleapis");
const GOOGLE_PRIVATE_KEY = process.env.GOOGLE_CALENDAR_CREDENTIALS_PRIVATE_KEY;
const GOOGLE_CLIENT_EMAIL =
process.env.GOOGLE_CALENDAR_CREDENTIALS_CLIENT_EMAIL;
const GOOGLE_PROJECT_NUMBER = process.env.GOOGLE_CALENDAR_PROJECT_ID;
const GOOGLE_CALENDAR_ID = process.env.GOOGLE_CALENDAR_ID;
const SCOPES = ["https://www.googleapis.com/auth/calendar"];
const jwtClient = new google.auth.JWT(
GOOGLE_CLIENT_EMAIL,
null,
GOOGLE_PRIVATE_KEY,
SCOPES
);
const calendar = google.calendar({
version: "v3",
project: GOOGLE_PROJECT_NUMBER,
auth: jwtClient
});
export default calendar;
export {GOOGLE_CALENDAR_ID}

View File

@@ -1,44 +1,31 @@
import { Show } from "@/models";
import calendar, { GOOGLE_CALENDAR_ID } from "@/lib/util/google/calendar";
import logger from "../logging";
const { google } = require("googleapis");
const GOOGLE_PRIVATE_KEY = process.env.GOOGLE_CALENDAR_CREDENTIALS_PRIVATE_KEY;
const GOOGLE_CLIENT_EMAIL =
process.env.GOOGLE_CALENDAR_CREDENTIALS_CLIENT_EMAIL;
const GOOGLE_PROJECT_NUMBER = process.env.GOOGLE_CALENDAR_PROJECT_ID;
const GOOGLE_CALENDAR_ID = process.env.GOOGLE_CALENDAR_ID;
const SCOPES = ["https://www.googleapis.com/auth/calendar"];
const jwtClient = new google.auth.JWT(
GOOGLE_CLIENT_EMAIL,
null,
GOOGLE_PRIVATE_KEY,
SCOPES
);
const calendar = google.calendar({
version: "v3",
project: GOOGLE_PROJECT_NUMBER,
auth: jwtClient
});
const getCalendarEntries = async () => {
const getCalendarEntries = async (syncToken?: string) => {
try {
const events = await calendar.events.list({
const e = await calendar.events.list({
calendarId: GOOGLE_CALENDAR_ID,
timeMin: new Date().toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: "startTime"
syncToken: syncToken
});
return events.data.items.map((r: any) => {
return {
const events = _mapEvents(e);
return events;
} catch (err) {
logger.error("calendarReader", "Unable to read events", err);
}
return null;
};
const _mapEvents = (events: any) => {
const mapped = events.data.items.map((r: any) => ({
id: r.id,
title: r.summary,
date: r.start.dateTime,
creator: r.creator.email
}));
return {
syncToken: events.data.nextSyncToken,
events: mapped
};
});
} catch (err) {
}
return null;
};
export { getCalendarEntries };

View File

@@ -0,0 +1,14 @@
import calendar, { GOOGLE_CALENDAR_ID } from "@/lib/util/google/calendar";
import { uuidv4 } from "@firebase/util";
const setupCalendarWebhook = async () => {
calendar.events.watch({
resource: {
id: uuidv4(),
type: "web_hook",
address: `https://external.fergl.ie/api/shows/calendar`
},
calendarId: GOOGLE_CALENDAR_ID
});
};
export { setupCalendarWebhook };

View File

@@ -3,22 +3,23 @@ export default class Profile {
email: string | null;
displayName: string | null;
photoURL: string | null;
emailVerified: boolean;
emailVerified: boolean = false;
about?: String;
lastSeen: Date;
constructor(
id: string,
email: string | null,
displayName: string | null,
photoURL: string | null,
emailVerified: boolean,
about?: string,
lastSeen?: Date
) {
this.id = id;
this.email = email;
this.displayName = displayName;
this.photoURL = photoURL;
this.emailVerified = emailVerified;
this.about = about || "";
this.lastSeen = lastSeen || new Date();
}

View File

@@ -4,11 +4,16 @@ import logger from "@/lib/util/logging";
import { doc, setDoc } from "@firebase/firestore";
import { shows } from "@/lib/db";
import { Show } from "@/models";
import Settings from "@/lib/db/settings";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const e = await getCalendarEntries();
const entries = e.map((r: any) => Show.fromJson(r));
const syncToken = await Settings.read("CalendarSyncToken");
const e = await getCalendarEntries(syncToken);
if (!e?.events) {
res.status(204).json({ result: "No calendar entries found" });
} else {
const entries = e?.events.map((r: any) => Show.fromJson(r));
for (const entry of entries) {
logger.debug("Storing show", entry);
const showRef = doc(shows, entry.id);
@@ -19,7 +24,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}, { merge: true });
}
logger.debug("Stored show", res);
if (e?.syncToken) {
await Settings.write("CalendarSyncToken", e?.syncToken);
}
res.status(200).json({ status: "OK", entries });
}
} catch (err) {
logger.error(err);
res.status(500).json({ status: "Error" });

View File

@@ -1,6 +1,8 @@
import { NextApiRequest, NextApiResponse } from "next";
import { setupCalendarWebhook } from "@/lib/util/google/calendarWatcher";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await setupCalendarWebhook();
res.status(200).json({ hello: process.env });
};
export default handler;

View File

@@ -0,0 +1,33 @@
import { NextApiRequest, NextApiResponse } from "next";
import { doc, getDocs, query, setDoc, where } from "@firebase/firestore";
import { shows } from "@/lib/db";
import logger from "@/lib/util/logging";
import { Show } from "@/models";
import { getCalendarEntries } from "@/lib/util/google/calendarReader";
import Settings from "@/lib/db/settings";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const resourceId = req.headers["x-goog-resource-id"];
const channelToken = req.headers["x-goog-channel-token"];
const channelId = req.headers["x-goog-channel-id"];
const resourceState = req.headers["x-goog-resource-state"];
logger.debug("Webhook callback",
resourceId,
channelToken,
channelId,
resourceState
);
const changed = await getCalendarEntries();
const entries = changed?.events.map((r: any) => Show.fromJson(r));
for (const entry of entries) {
const showRef = doc(shows, entry.id);
await setDoc(showRef, {
title: entry.title,
date: entry.date,
creator: entry.creator
}, { merge: true });
}
res.status(200).json({ result: "We got pinged" });
res.end();
};

View File

@@ -830,47 +830,47 @@
"@types/node" "*"
"@typescript-eslint/parser@^5.42.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52"
integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.0.tgz#def186eb1b1dbd0439df0dacc44fb6d8d5c417fe"
integrity sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==
dependencies:
"@typescript-eslint/scope-manager" "5.53.0"
"@typescript-eslint/types" "5.53.0"
"@typescript-eslint/typescript-estree" "5.53.0"
"@typescript-eslint/scope-manager" "5.54.0"
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/typescript-estree" "5.54.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef"
integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==
"@typescript-eslint/scope-manager@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz#74b28ac9a3fc8166f04e806c957adb8c1fd00536"
integrity sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==
dependencies:
"@typescript-eslint/types" "5.53.0"
"@typescript-eslint/visitor-keys" "5.53.0"
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/visitor-keys" "5.54.0"
"@typescript-eslint/types@5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f"
integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==
"@typescript-eslint/types@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.0.tgz#7d519df01f50739254d89378e0dcac504cab2740"
integrity sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==
"@typescript-eslint/typescript-estree@5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690"
integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==
"@typescript-eslint/typescript-estree@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz#f6f3440cabee8a43a0b25fa498213ebb61fdfe99"
integrity sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==
dependencies:
"@typescript-eslint/types" "5.53.0"
"@typescript-eslint/visitor-keys" "5.53.0"
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/visitor-keys" "5.54.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/visitor-keys@5.53.0":
version "5.53.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f"
integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==
"@typescript-eslint/visitor-keys@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz#846878afbf0cd67c19cfa8d75947383d4490db8f"
integrity sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==
dependencies:
"@typescript-eslint/types" "5.53.0"
"@typescript-eslint/types" "5.54.0"
eslint-visitor-keys "^3.3.0"
"@upstash/qstash@^0.3.6":
@@ -1409,9 +1409,9 @@ csstype@^3.0.2:
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
daisyui@^2.49.0:
version "2.51.1"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-2.51.1.tgz#20fe01f3f3caa8628ebb30fcc1b99341183d1702"
integrity sha512-rMtdB8Rh8Ghd3+FDlMe63Caw+IopKIf7tviJGEx8gkjj5H+RCvU1bEbjx6DHsMsu477Gv5aafrLIdoUL+iMRrw==
version "2.51.2"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-2.51.2.tgz#831440e02ea55f773f519a69b7f746824f4f5f84"
integrity sha512-Jnfknn9HrOBNzj1kUI9g/kqNSA5oP5B/r6X4Ff/cdMO27glisO2Oi9QAO8hKwMN9LzfWGdfyqvJGvc3Ji78CXA==
dependencies:
color "^4.2"
css-selector-tokenizer "^0.8.0"
@@ -1573,9 +1573,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.284:
version "1.4.311"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz#953bc9a4767f5ce8ec125f9a1ad8e00e8f67e479"
integrity sha512-RoDlZufvrtr2Nx3Yx5MB8jX3aHIxm8nRWPJm3yVvyHmyKaRvn90RjzB6hNnt0AkhS3IInJdyRfQb4mWhPvUjVw==
version "1.4.312"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.312.tgz#e70a5b46252814ffc576b2c29032e1a559b9ad53"
integrity sha512-e7g+PzxzkbiCD1aNhdj+Tx3TLlfrQF/Lf+LAaUdoLvB1kCxf9wJimqXdWEqnoiYjFtxIR1hGBmoHsBIcCBNOMA==
emoji-regex@^8.0.0:
version "8.0.0"