Skip to content

Commit

Permalink
Merge pull request #643 from podverse/develop
Browse files Browse the repository at this point in the history
Release v4.13.6
  • Loading branch information
mitchdowney authored Jul 24, 2023
2 parents 8b6b392 + 323df33 commit eb8337b
Show file tree
Hide file tree
Showing 21 changed files with 4,066 additions and 48 deletions.
3,456 changes: 3,456 additions & 0 deletions docs/postman/podverse-api_v4-13-4.postman_collection.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions migrations/0047_upDevices.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Create upDevices table

CREATE TABLE public."upDevices" (
"upEndpoint" character varying,
"upPublicKey" character varying NOT NULL,
"upAuthKey" character varying NOT NULL,
"userId" character varying(14) references users(id) ON DELETE CASCADE,
"createdAt" timestamp without time zone DEFAULT now() NOT NULL,
"updatedAt" timestamp without time zone DEFAULT now() NOT NULL,
PRIMARY KEY ("upEndpoint")
);

ALTER TABLE public."upDevices" OWNER TO postgres;

CREATE INDEX "IDX_upDevices_userId" ON public."upDevices" USING btree ("userId");
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "podverse-api",
"version": "4.13.5",
"version": "4.13.6",
"description": "Data API, database migration scripts, and backend services for all Podverse models.",
"contributors": [
"Mitch Downey"
Expand Down Expand Up @@ -133,6 +133,7 @@
"@types/passport-local": "1.0.33",
"@types/shelljs": "0.8.6",
"@types/validator": "12.0.1",
"@types/web-push": "^3.3.2",
"koa2-swagger-ui": "2.15.4",
"nodemon": "2.0.1",
"prettier": "^2.5.1",
Expand Down Expand Up @@ -211,6 +212,7 @@
"uuid": "3.3.3",
"valid-url": "1.0.9",
"validator": "13.7.0",
"web-push": "^3.6.3",
"webpack": "4.41.2",
"winston": "3.2.1",
"ws": "^8.5.0"
Expand Down
4 changes: 4 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
podcastRouter,
podcastIndexRouter,
podpingRouter,
upDeviceRouter,
userHistoryItemRouter,
userNowPlayingItemRouter,
userQueueItemRouter,
Expand Down Expand Up @@ -198,6 +199,9 @@ export const createApp = async (conn: Connection) => {
app.use(toolsRouter.routes())
app.use(toolsRouter.allowedMethods())

app.use(upDeviceRouter.routes())
app.use(upDeviceRouter.allowedMethods())

app.use(userHistoryItemRouter.routes())
app.use(userHistoryItemRouter.allowedMethods())

Expand Down
177 changes: 177 additions & 0 deletions src/controllers/upDevice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { getRepository } from 'typeorm'
import { UPDevice, Notification } from '~/entities'
import { getLoggedInUser } from './user'
import { UPEndpointData } from '~/entities/upDevice'
const createError = require('http-errors')

export const createUPDevice = async ({
upEndpoint,
upPublicKey,
upAuthKey,
loggedInUserId
}: {
upEndpoint: string
upPublicKey: string
upAuthKey: string
loggedInUserId: string
}) => {
if (!upEndpoint) {
throw new createError.BadRequest('An upEndpoint must be provided.')
}

if (!upPublicKey) {
throw new createError.BadRequest('An upPublicKey must be provided.')
}

if (!upAuthKey) {
throw new createError.BadRequest('An upAuthKey must be provided.')
}

if (!loggedInUserId) {
throw new createError.BadRequest('A user id must be provided.')
}

const user = await getLoggedInUser(loggedInUserId)
if (!user) {
throw new createError.NotFound(`User for id ${loggedInUserId} not found.`)
}

const newUPDevice = new UPDevice()
newUPDevice.upEndpoint = upEndpoint
newUPDevice.upPublicKey = upPublicKey
newUPDevice.upAuthKey = upAuthKey
newUPDevice.user = user

const repository = getRepository(UPDevice)
await repository.save(newUPDevice)
}

export const updateUPDevice = async ({
previousUPEndpoint,
nextUPEndpoint,
upPublicKey,
upAuthKey,
loggedInUserId
}: {
previousUPEndpoint
nextUPEndpoint
upPublicKey
upAuthKey
loggedInUserId
}) => {
if (!previousUPEndpoint) {
throw new createError.BadRequest('A previous upEndpoint must be provided.')
}

if (!nextUPEndpoint) {
throw new createError.BadRequest('A new upEndpoint must be provided.')
}

if (!upPublicKey) {
throw new createError.BadRequest('A new upPublicKey must be provided.')
}

if (!upAuthKey) {
throw new createError.BadRequest('A new upAuthKey must be provided.')
}

if (!loggedInUserId) {
throw new createError.BadRequest('A user id must be provided.')
}

const user = await getLoggedInUser(loggedInUserId)
if (!user) {
throw new createError.NotFound(`User for id ${loggedInUserId} not found.`)
}

const existingUPDevice = await getUPDevice(previousUPEndpoint, loggedInUserId)
const repository = getRepository(UPDevice)
if (!existingUPDevice) {
const newUPDevice = new UPDevice()
newUPDevice.upEndpoint = nextUPEndpoint
newUPDevice.upPublicKey = upPublicKey
newUPDevice.upAuthKey = upAuthKey
newUPDevice.user = user
await repository.save(newUPDevice)
} else {
const newData = { upEndpoint: nextUPEndpoint, upPublicKey, upAuthKey }
await repository.update(previousUPEndpoint, newData)
}
}

export const deleteUPDevice = async (upEndpoint: string, loggedInUserId: string) => {
if (!upEndpoint) {
throw new createError.BadRequest('An upEndpoint must be provided.')
}

if (!loggedInUserId) {
throw new createError.BadRequest('A user id must be provided.')
}

const user = await getLoggedInUser(loggedInUserId)
if (!user) {
throw new createError.NotFound(`User for id ${loggedInUserId} not found.`)
}

const upDevice = await getUPDevice(upEndpoint, loggedInUserId)

if (!upDevice) {
throw new createError.NotFound(`upDevice for upEndpoint ${upEndpoint} not found.`)
}

const repository = getRepository(UPDevice)
await repository.remove(upDevice)
}

const getUPDevice = async (upEndpoint: string, loggedInUserId: string) => {
if (!upEndpoint) {
throw new createError.BadRequest('An upEndpoint must be provided.')
}

if (!loggedInUserId) {
throw new createError.BadRequest('A user id must be provided.')
}

const repository = getRepository(UPDevice)
return repository
.createQueryBuilder('upDevices')
.select('"upDevices"."upEndpoint"', 'upEndpoint')
.where('"upEndpoint" = :upEndpoint', { upEndpoint })
.andWhere('user.id = :loggedInUserId', { loggedInUserId })
.leftJoin('upDevices.user', 'user')
.getRawOne()
}

export const getUPEndpointsForPodcastId = async (podcastId: string) => {
if (!podcastId) {
throw new createError.BadRequest('A podcastId but be provided.')
}

const repository = getRepository(Notification)
const notifications = await repository
.createQueryBuilder('notifications')
.select('"upDevices"."upEndpoint", "upEndpoint"')
.innerJoin(UPDevice, 'upDevices', 'notifications."userId" = "upDevices"."userId"')
.where('notifications."podcastId" = :podcastId', { podcastId })
.getRawMany()

const upEndpoints = notifications.map((upDevice: UPDevice) => upDevice.upEndpoint)

return upEndpoints
}

export const getUPDevicesForPodcastId = async (podcastId: string): Promise<UPEndpointData[]> => {
if (!podcastId) {
throw new createError.BadRequest('A podcastId but be provided.')
}

const repository = getRepository(Notification)
return await repository
.createQueryBuilder('notifications')
.select(
'"upDevices"."upEndpoint" AS "upEndpoint", "upDevices"."upPublicKey" AS "upPublicKey", "upDevices"."upAuthKey" AS "upAuthKey"'
)
.innerJoin(UPDevice, 'upDevices', 'notifications."userId" = "upDevices"."userId"')
.where('notifications."podcastId" = :podcastId', { podcastId })
.getRawMany()
}
1 change: 1 addition & 0 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export { Playlist } from './playlist'
export { Podcast } from './podcast'
export { RecentEpisodeByCategory } from './recentEpisodeByCategory'
export { RecentEpisodeByPodcast } from './recentEpisodeByPodcast'
export { UPDevice } from './upDevice'
export { User } from './user'
export { UserHistoryItem } from './userHistoryItem'
export { UserNowPlayingItem } from './userNowPlayingItem'
Expand Down
36 changes: 36 additions & 0 deletions src/entities/upDevice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'
import { User } from '~/entities'

export interface UPEndpointData {
upEndpoint: string
upPublicKey: string
upAuthKey: string
}

@Entity('upDevices')
export class UPDevice {
@PrimaryColumn()
upEndpoint: string

// E2EE
@Column()
upPublicKey: string

// E2EE
@Column()
upAuthKey: string

@ManyToOne((type) => User, (user) => user.upDevices, {
nullable: false,
onDelete: 'CASCADE'
})
user: User

@CreateDateColumn()
createdAt: Date

@UpdateDateColumn()
updatedAt: Date
}
4 changes: 4 additions & 0 deletions src/entities/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Notification,
PayPalOrder,
Playlist,
UPDevice,
UserHistoryItem,
UserQueueItem
} from '~/entities'
Expand Down Expand Up @@ -184,6 +185,9 @@ export class User {
@OneToMany((type) => Playlist, (playlist) => playlist.owner)
playlists: Playlist[]

@OneToMany((type) => UPDevice, (upDevice) => upDevice.user)
upDevices: UPDevice[]

@OneToMany((type) => UserHistoryItem, (userHistoryItem) => userHistoryItem.owner)
userHistoryItems: UserHistoryItem[]

Expand Down
2 changes: 2 additions & 0 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Podcast,
RecentEpisodeByCategory,
RecentEpisodeByPodcast,
UPDevice,
User,
UserHistoryItem,
UserNowPlayingItem,
Expand Down Expand Up @@ -47,6 +48,7 @@ const entities = [
Podcast,
RecentEpisodeByCategory,
RecentEpisodeByPodcast,
UPDevice,
User,
UserHistoryItem,
UserNowPlayingItem,
Expand Down
29 changes: 9 additions & 20 deletions src/lib/notifications/fcmGoogleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@
import { request } from '../request'
import { config } from '~/config'
import { getFCMTokensForPodcastId } from '~/controllers/fcmDevice'
import { SendNotificationOptions } from './sendNotificationOptions'
const { fcmGoogleApiAuthToken } = config

const fcmGoogleApiPath = 'https://fcm.googleapis.com/fcm/send'

export const sendNewEpisodeDetectedNotification = async (
podcastId: string,
podcastTitle?: string,
episodeTitle?: string,
podcastImage?: string,
episodeImage?: string,
episodeId?: string
) => {
export const sendFcmNewEpisodeDetectedNotification = async (options: SendNotificationOptions) => {
const { podcastId, podcastImage, episodeImage, episodeId } = options
const fcmTokens = await getFCMTokensForPodcastId(podcastId)
podcastTitle = podcastTitle || 'Untitled Podcast'
episodeTitle = episodeTitle || 'Untitled Episode'
const podcastTitle = options.podcastTitle || 'Untitled Podcast'
const episodeTitle = options.episodeTitle || 'Untitled Episode'
const title = podcastTitle
const body = episodeTitle
return sendFCMGoogleApiNotification(
Expand All @@ -33,17 +28,11 @@ export const sendNewEpisodeDetectedNotification = async (
)
}

export const sendLiveItemLiveDetectedNotification = async (
podcastId: string,
podcastTitle?: string,
episodeTitle?: string,
podcastImage?: string,
episodeImage?: string,
episodeId?: string
) => {
export const sendFcmLiveItemLiveDetectedNotification = async (options: SendNotificationOptions) => {
const { podcastId, podcastImage, episodeImage, episodeId } = options
const fcmTokens = await getFCMTokensForPodcastId(podcastId)
podcastTitle = podcastTitle || 'Untitled Podcast'
episodeTitle = episodeTitle || 'Livestream starting'
const podcastTitle = options.podcastTitle || 'Untitled Podcast'
const episodeTitle = options.episodeTitle || 'Livestream starting'
const title = `LIVE: ${podcastTitle}`
const body = episodeTitle
return sendFCMGoogleApiNotification(
Expand Down
14 changes: 14 additions & 0 deletions src/lib/notifications/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SendNotificationOptions } from './sendNotificationOptions'
import { sendFcmLiveItemLiveDetectedNotification, sendFcmNewEpisodeDetectedNotification } from './fcmGoogleApi'
import { sendUpLiveItemLiveDetectedNotification, sendUpNewEpisodeDetectedNotification } from './unifiedPush'

export const sendNewEpisodeDetectedNotification = async (options: SendNotificationOptions) => {
return Promise.all([sendFcmNewEpisodeDetectedNotification(options), sendUpNewEpisodeDetectedNotification(options)])
}

export const sendLiveItemLiveDetectedNotification = async (options: SendNotificationOptions) => {
return Promise.all([
sendFcmLiveItemLiveDetectedNotification(options),
sendUpLiveItemLiveDetectedNotification(options)
])
}
8 changes: 8 additions & 0 deletions src/lib/notifications/sendNotificationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface SendNotificationOptions {
podcastId: string
podcastTitle?: string
episodeTitle?: string
podcastImage?: string
episodeImage?: string
episodeId?: string
}
Loading

0 comments on commit eb8337b

Please sign in to comment.