diff --git a/auth.config.mjs b/auth.config.mjs index e8ea219..1071847 100644 --- a/auth.config.mjs +++ b/auth.config.mjs @@ -9,6 +9,17 @@ const logsnag = new LogSnag({ project: "magicsnap", }); +function generateRandomString() { + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < 16; i++) { + const randomIndex = Math.floor(Math.random() * chars.length); + result += chars.charAt(randomIndex); + } + return result; +} + export default defineConfig({ providers: [ Slack({ @@ -50,6 +61,7 @@ export default defineConfig({ if (users.length === 0) { await db.insert(User).values({ userId: profile["https://slack.com/user_id"], + hash: generateRandomString(), name: profile.name, email: profile.email, image: profile.picture, @@ -97,6 +109,7 @@ export default defineConfig({ } else { await db.insert(User).values({ userId: profile["https://slack.com/user_id"], + hash: generateRandomString(), name: profile.name, email: profile.email, image: profile.picture, diff --git a/db/config.ts b/db/config.ts index 01cfc63..d5c4328 100644 --- a/db/config.ts +++ b/db/config.ts @@ -15,6 +15,7 @@ const Organization = defineTable({ const User = defineTable({ columns: { userId: column.text({ primaryKey: true }), + hash: column.text(), team: column.text(), name: column.text(), email: column.text(), diff --git a/src/pages/api/remind.ts b/src/pages/api/remind.ts index 4feeb07..d90005d 100644 --- a/src/pages/api/remind.ts +++ b/src/pages/api/remind.ts @@ -1,11 +1,5 @@ import type { APIRoute } from "astro" import { db, User, Organization, Event } from "astro:db"; -import { LogSnag } from "logsnag"; - -const logsnag = new LogSnag({ - token: process.env.LOGSNAG_TOKEN || "", - project: "magicsnap", -}); export const POST: APIRoute = async ({ request }) => { // get authorization header @@ -26,13 +20,6 @@ export const POST: APIRoute = async ({ request }) => { return diffHours < 24 && diffHours > 0 }) - await logsnag.track({ - channel: "api", - event: "reminder-sent", - description: `Sent reminder to ${users.length} users in ${organizations.length} different organizations about ${eventsHappeningToday.length} events happening today`, - icon: "📬", - }); - return new Response(JSON.stringify({ ok: true, eventsHappeningToday: eventsHappeningToday, users: users, organizations: organizations }), { status: 200 }) diff --git a/src/pages/messages.astro b/src/pages/messages.astro index 1a5b6b2..1955263 100644 --- a/src/pages/messages.astro +++ b/src/pages/messages.astro @@ -88,12 +88,15 @@ if (Astro.request.method === "POST") { .map((user) => ({ email: user.email, name: user.name })); } + message += " \n\n-----\n\n"; + message += `*This email was sent by MagicSnap because you are a member of ${session.teamName}. If you have any questions or need assistance, please contact us at spellcheck@magicsnap.org.*`; + const email = { to: "annoucements@magicsnap.org", bcc: userList, from: "magic.broadcast@magicsnap.org", subject: subject, - text: message, + markdown: message, }; const lastEmailHash = ( @@ -102,7 +105,7 @@ if (Astro.request.method === "POST") { .from(Organization) .where( and( - like(Organization.lastMessageHash, Md5.hashStr(email.text)), + like(Organization.lastMessageHash, Md5.hashStr(email.markdown)), like(Organization.team, session.team) ) ) @@ -139,7 +142,7 @@ if (Astro.request.method === "POST") { await db .update(Organization) .set({ - lastMessageHash: Md5.hashStr(email.text), + lastMessageHash: Md5.hashStr(email.markdown), }) .where(like(Organization.team, session.team)); diff --git a/src/pages/update/[team]/[eventID]/[userID].astro b/src/pages/update/[team]/[eventID]/[userID].astro new file mode 100644 index 0000000..4cc0e71 --- /dev/null +++ b/src/pages/update/[team]/[eventID]/[userID].astro @@ -0,0 +1,158 @@ +--- +import Base from "../../../../Layouts/Base.astro"; +import ThreeWayToggle from "../../../../components/ThreeWayToggle.astro"; + +import { LogSnag } from "logsnag"; +import { db, like, and, Event, User } from "astro:db"; + +const logsnag = new LogSnag({ + token: process.env.LOGSNAG_TOKEN || "", + project: "magicsnap", +}); + +const { team, eventID, userID } = Astro.params as { + team: string; + eventID: string; + userID: string; +}; + +const user = ( + await db + .select() + .from(User) + .where(and(like(User.team, team), like(User.userId, userID))) + .all() +)[0]; + +const hash = Astro.url.searchParams.get("hash"); + +if (Astro.request.method === "POST" && user && hash === user.hash) { + try { + const data = await Astro.request.formData(); + + if (data.get("availability") != null) { + const eventID = data.get("eventID") as string; + const status = data.get("selected") as string; + + const event = ( + await db.select().from(Event).where(like(Event.id, eventID)) + )?.[0]; + + if (event) { + let statusGoing = event.statusGoing + .split(",") + .filter((id) => id !== ""); + let statusMaybe = event.statusMaybe + .split(",") + .filter((id) => id !== ""); + let statusNotGoing = event.statusNotGoing + .split(",") + .filter((id) => id !== ""); + + const currentStatus = statusGoing.includes(user.userId) + ? "yes" + : statusMaybe.includes(user.userId) + ? "maybe" + : "no"; + + if (currentStatus !== status) { + if (status === "yes") { + statusGoing.push(user.userId); + statusMaybe = statusMaybe.filter((id) => id !== user.userId); + statusNotGoing = statusNotGoing.filter((id) => id !== user.userId); + } else if (status === "maybe") { + statusMaybe.push(user.userId); + statusGoing = statusGoing.filter((id) => id !== user.userId); + statusNotGoing = statusNotGoing.filter((id) => id !== user.userId); + } else if (status === "no" && currentStatus !== "no") { + statusNotGoing.push(user.userId); + statusGoing = statusGoing.filter((id) => id !== user.userId); + statusMaybe = statusMaybe.filter((id) => id !== user.userId); + } + } + + await db + .update(Event) + .set({ + statusGoing: statusGoing.join(","), + statusMaybe: statusMaybe.join(","), + statusNotGoing: statusNotGoing.join(","), + }) + .where(like(Event.id, eventID)); + + await logsnag.track({ + channel: "actions", + event: "event_status_change", + icon: "📅", + user_id: user.userId, + }); + } + } + } catch (error) { + if (error instanceof Error) { + await logsnag.track({ + channel: "errors", + event: "event_status_change_error", + icon: "📅", + user_id: user.userId, + tags: { + error: error.message, + }, + }); + } + } +} + +const event = ( + await db + .select() + .from(Event) + .where(and(like(Event.team, team), like(Event.id, eventID))) + .all() +)[0]; +--- + + +
+ {user && user.hash === hash && event && ( +

{event.name}

+

+ {event.name} is being held at {event.location} on { + event.date.toLocaleDateString() + + " at " + + event.date.toLocaleTimeString([], { + hour: "numeric", + minute: "2-digit", + }) + } +

+

{event.comments}

+

Are you going?

+
+ +
+ )} + {!user &&

Sorry, we couldn't find your user account.

} + {user && user.hash !== hash &&

Sorry, the link you used is invalid.

} + {!event &&

Sorry, we couldn't find the event you're looking for.

} +
+ + +