Skip to content

Commit

Permalink
feat: event notifications (#656)
Browse files Browse the repository at this point in the history
* fix: remove old notifications stuff

* feat: new notifications for events

* fix: send notifications more efficient

* feat: use cursors

* fix: use now

* cursor handling

* remove unused fn

* explorer ulr as env var

* query names

* linter comments

---------

Co-authored-by: Mariano Goldman <[email protected]>
  • Loading branch information
lauti7 and Mariano Goldman authored Mar 28, 2024
1 parent 79f78d7 commit f800500
Show file tree
Hide file tree
Showing 22 changed files with 320 additions and 422 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ GATSBY_LAND_URL=https://api.decentraland.org/v1
GATSBY_PROFILE_URL=https://peer-lb.decentraland.org
GATSBY_EVENTS_URL=https://localhost:8000/api
GATSBY_SSO_URL=https://id.decentraland.zone

NOTIFICATION_SERVICE_URL=
NOTIFICATION_SERVICE_TOKEN=
EXPLORER_URL=
20 changes: 0 additions & 20 deletions src/api/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export default class Events extends API {
duration,
recurrent: Boolean(event.recurrent),
attending: Boolean(event.attending),
notify: Boolean(event.notify),
live: Boolean(event.live),
} as SessionEventAttributes
}
Expand Down Expand Up @@ -271,13 +270,6 @@ export default class Events extends API {
)
}

async deleteEvent(eventId: string) {
return this.fetch<{}>(
`/events/${eventId}`,
this.options({ method: "DELETE" }).authorization({ sign: true })
)
}

async notifyEvent(eventId: string) {
return this.fetch<{}>(
`/events/${eventId}/notifications`,
Expand All @@ -300,18 +292,6 @@ export default class Events extends API {
)
}

async updateEventAttendee(
eventId: string,
attendee: Partial<Pick<EventAttendeeAttributes, "notify">> = {}
) {
return this.fetch<EventAttendeeAttributes[]>(
`/events/${eventId}/attendees`,
this.options({ method: "PATCH" })
.json(attendee)
.authorization({ sign: true })
)
}

async deleteEventAttendee(eventId: string) {
return this.fetch<EventAttendeeAttributes[]>(
`/events/${eventId}/attendees`,
Expand Down
145 changes: 145 additions & 0 deletions src/api/Notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import API from "decentraland-gatsby/dist/utils/api/API"
import env from "decentraland-gatsby/dist/utils/env"

import { EventAttributes } from "../entities/Event/types"
import { EventAttendeeAttributes } from "../entities/EventAttendee/types"

type DCLNotification<T, M> = {
eventKey: string
type: T
address?: string
metadata: M & { title: string; description: string }
timestamp: number
}

export enum EventsNotifications {
EVENT_STARTS_SOON = "events_starts_soon",
EVENT_STARTED = "events_started",
}

export type EventStartsSoonNotification = DCLNotification<
EventsNotifications.EVENT_STARTS_SOON,
{
name: string
image: string
link: string
startsAt: string
endsAt: string
}
>

export type EventStartedNotification = DCLNotification<
EventsNotifications.EVENT_STARTED,
{
name: string
image: string
link: string
}
>

export default class Notifications extends API {
static Url = env(
"NOTIFICATION_SERVICE_URL",
`https://notifications-processor.decentraland.io`
)
static Token = env("NOTIFICATION_SERVICE_TOKEN", "")
static ExplorerURL = env("EXPLORER_URL", "https://play.decentraland.org/")

static Cache = new Map<string, Notifications>()

static from(url: string) {
if (!this.Cache.has(url)) {
this.Cache.set(url, new Notifications(url))
}

return this.Cache.get(url)!
}

static get() {
return this.from(this.Url)
}

private async sendNotification<T>(notifications: T[]) {
return this.fetch(
"/notifications",
this.options()
.method("POST")
.headers({
Authorization: `Bearer ${env("NOTIFICATION_SERVICE_TOKEN", "")}`,
})
.json(notifications)
)
}

async sendEventStartsSoon(
event: EventAttributes,
attendees: EventAttendeeAttributes[]
) {
const link = new URL(Notifications.ExplorerURL)
link.searchParams.append("position", `${event.x},${event.y}`)

if (event.server) {
link.searchParams.append("realm", event.server)
}

const common = {
eventKey: event.id,
type: EventsNotifications.EVENT_STARTS_SOON,
timestamp: Date.now(),
metadata: {
title: "Event starts in an hour",
description: `The event ${event.name} starts in an hour.`,
link: link.toString(),
startsAt: event.start_at.toISOString(),
endsAt: event.finish_at.toISOString(),
image: event.image || "",
name: event.name,
},
} as const

const notifications: EventStartsSoonNotification[] = attendees.map(
(attendee) => ({
...common,
metadata: common.metadata,
address: attendee.user,
})
)

return this.sendNotification<EventStartsSoonNotification>(notifications)
}

async sendEventStarted(
event: EventAttributes,
attendees: EventAttendeeAttributes[]
) {
const link = new URL("https://play.decentraland.org/")
link.searchParams.append("position", `${event.x},${event.y}`)

if (event.server) {
link.searchParams.append("realm", event.server)
}

const common = {
eventKey: event.id,
type: EventsNotifications.EVENT_STARTED,
timestamp: Date.now(),
metadata: {
title: "Event started",
description: `The event ${event.name} has begun!`,
link: link.toString(),
name: event.name,
image: event.image || "",
},
} as const

const notifications: EventStartedNotification[] = attendees.map(
(attendee) => ({
...common,
metadata: common.metadata,
address: attendee.user,
})
)

return this.sendNotification<EventStartedNotification>(notifications)
}
}
25 changes: 2 additions & 23 deletions src/context/Event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ const defaultProfileSettings = [
rejecting: [] as string[],
restoring: [] as string[],
attending: [] as string[],
notifying: [] as string[],
modifying: new Set<string>(),
reload: (() => null) as () => void,
add: (() => null) as (newEvent: SessionEventAttributes) => void,
approve: (() => null) as (id: string) => void,
reject: (() => null) as (id: string) => void,
restore: (() => null) as (id: string) => void,
attend: (() => null) as (id: string, attending: boolean) => void,
notify: (() => null) as (id: string, attending: boolean) => void,
},
] as const

Expand Down Expand Up @@ -128,7 +126,6 @@ export function useEvents() {
const newEvent = {
...event,
attending: !!newAttendee,
notify: !!(newAttendee && newAttendee.notify),
total_attendees: newAttendees.length,
latest_attendees: newAttendees
.slice(0, 10)
Expand Down Expand Up @@ -165,25 +162,9 @@ export function useEvents() {
[updateAttendee]
)

const [notifying, notify] = useAsyncTasks(
async (id, notify: boolean) => {
await updateAttendee(id, () =>
Events.get().updateEventAttendee(id, { notify })
)
},
[updateAttendee]
)

const modifying = useMemo(
() =>
new Set([
...approving,
...rejecting,
...restoring,
...attending,
...notifying,
]),
[approving, rejecting, restoring, attending, notifying]
() => new Set([...approving, ...rejecting, ...restoring, ...attending]),
[approving, rejecting, restoring, attending]
)

return [
Expand All @@ -195,14 +176,12 @@ export function useEvents() {
rejecting,
restoring,
attending,
notifying,
modifying,
reload: eventsState.reload,
add,
approve,
reject,
restore,
notify,
attend,
},
] as const
Expand Down
Loading

0 comments on commit f800500

Please sign in to comment.