Skip to content

Commit

Permalink
Refactored video cloud function and wrote test for delete video
Browse files Browse the repository at this point in the history
  • Loading branch information
Travis544 committed Jan 7, 2024
1 parent 25451d7 commit 38a2faa
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 131 deletions.
91 changes: 14 additions & 77 deletions video-func/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,82 +9,25 @@

import { onRequest } from "firebase-functions/v2/https";
import * as logger from "firebase-functions/logger";
import { GetSignedUrlConfig } from '@google-cloud/storage';
import { Storage } from '@google-cloud/storage';
import { initializeApp } from "firebase-admin/app";
import { Timestamp, getFirestore } from "firebase-admin/firestore";

initializeApp();
const firestore = getFirestore()
export type VideoMetadata = {
userId: string
videoName: string,
description: string,
timestamp: Timestamp,
contentType: string,
status: string
};
import { VideoService } from "./service/VideoService";
import { FirebaseVideoService } from "./service/FirebaseVideoService";
import { Timestamp } from "firebase-admin/firestore";


const FRONTEND_URL = "https://our-compound-407022.web.app/"
const firebaseVideoService: VideoService = new FirebaseVideoService()

// Creates a client, loading the service account key.
const storage = new Storage({ keyFilename: 'key.json' });
const UPLOADED_VIDEO_BUCKET_NAME = 'uploaded-video-bucket';
const TRANSCODED_VIDEO_BUCKET_NAME = "transcoded-videos-bucket"

const videoCollection = firestore.collection("Video")

async function generateV4UploadSignedUrl(fileName: string, contentType: string) {
// These options will allow temporary uploading of the file with outgoing
const options: GetSignedUrlConfig = {
version: 'v4',
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: contentType,
};

// Get a v4 signed URL for uploading file
const url = await storage
.bucket(UPLOADED_VIDEO_BUCKET_NAME)
.file(fileName)
.getSignedUrl(options);

console.log('Generated PUT signed URL:');
console.log(url);
return url
}

async function saveVideoMetadata(fileName: string, videoMetadata: VideoMetadata) {
await videoCollection.doc(fileName).set(videoMetadata, { merge: true })
}


/**
* Delete video from cloud storage and video metadata from firestore if it exists
* @param videoId id of the video being deleted.
* @returns none
*/
export async function deleteVideo(videoId: string) {
let docReference = videoCollection.doc(videoId)
let doc = await docReference.get()
if (!doc.exists) {
return
}
let data = doc.data()!
console.log("DATA")
console.log(data)
let transcodedVideoBucket = await storage.bucket(TRANSCODED_VIDEO_BUCKET_NAME)

let resolutionToVideoURI: Map<string, string> = data.resolutionToVideoURI
resolutionToVideoURI.forEach(async (key, value) => {
console.log(firebaseVideoService)
let videoMetadata = await firebaseVideoService.getVideoMetatadata(videoId)
let resolutionToVideoURI: Map<string, string> = videoMetadata.resolutionToVideoId
resolutionToVideoURI.forEach(async (value, key) => {
let transcodedFileName = key + "_" + videoId
let file = transcodedVideoBucket.file(transcodedFileName)
if (await file.exists()) {
await file.delete()
}
console.log(value)
await firebaseVideoService.deleteVideoFile(transcodedFileName)
})
await docReference.delete()

await firebaseVideoService.deleteVideoMetadata(videoId)
}

exports.delete_video = onRequest({ cors: [FRONTEND_URL] }, async (req: any, res: any) => {
Expand All @@ -94,7 +37,6 @@ exports.delete_video = onRequest({ cors: [FRONTEND_URL] }, async (req: any, res:
logger.info("video id not provided")
return
}
await deleteVideo(videoId)
})


Expand All @@ -114,21 +56,16 @@ exports.create_signed_url_for_video_upload = onRequest({ cors: true }, async (re
return
}

logger.info(UPLOADED_VIDEO_BUCKET_NAME)
logger.info(req.body)
logger.info(fileName)

saveVideoMetadata(fileName, {
firebaseVideoService.saveVideoMetadata(fileName, {
userId: userId,
videoName: videoName,
description: description,
timestamp: Timestamp.now(),
contentType: contentType,
status: status

}).then(() => {
console.log("FINISH Putting video metadata into firestore")
generateV4UploadSignedUrl(fileName, contentType).then((url) => {
firebaseVideoService.generateSignedUrlForUpload(fileName, contentType).then((url) => {
res.set('Access-Control-Allow-Origin', "*");
res.status(200).json({ result: url })
}).catch((err) => {
Expand Down
74 changes: 74 additions & 0 deletions video-func/functions/src/service/FirebaseVideoService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { VideoMetadata, VideoService } from './VideoService';

import { GetSignedUrlConfig } from '@google-cloud/storage';
import { Storage } from '@google-cloud/storage';
import { initializeApp } from "firebase-admin/app";
import { CollectionReference, Firestore, getFirestore } from "firebase-admin/firestore";

const UPLOADED_VIDEO_BUCKET_NAME = 'uploaded-video-bucket';
const TRANSCODED_VIDEO_BUCKET_NAME = "transcoded-videos-bucket"
initializeApp();
export class FirebaseVideoService implements VideoService {
private storage: Storage
private firestore: Firestore
private videoCollection: CollectionReference


constructor() {
// Creates a client, loading the service account key.
this.storage = new Storage({ keyFilename: 'key.json' });
this.firestore = getFirestore()
this.videoCollection = this.firestore.collection("Video")
}

async generateSignedUrlForUpload(videoFileName: string, contentType: string): Promise<string> {
const options: GetSignedUrlConfig = {
version: 'v4',
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: contentType,
};

// Get a v4 signed URL for uploading file
const url = await this.storage
.bucket(UPLOADED_VIDEO_BUCKET_NAME)
.file(videoFileName)
.getSignedUrl(options);


return url.toString()
}

async saveVideoMetadata(videoFileName: string, videoMetadata: VideoMetadata): Promise<void> {
await this.videoCollection.doc(videoFileName).set(videoMetadata, { merge: true })
}


async deleteVideoMetadata(videoId: string): Promise<void> {
let docReference = this.videoCollection.doc(videoId)
let doc = await docReference.get()
if (!doc.exists) {
return
}
await docReference.delete()
}

async deleteVideoFile(videoFileName: string): Promise<void> {
let transcodedVideoBucket = this.storage.bucket(TRANSCODED_VIDEO_BUCKET_NAME)
let file = transcodedVideoBucket.file(videoFileName)
if (await file.exists()) {
await file.delete()
}
}

async getVideoMetatadata(videoId: string) {
let docReference = this.videoCollection.doc(videoId)
let doc = await docReference.get()
if (!doc.exists) {
return
}
let data = doc.data()!
return data
}

}
50 changes: 50 additions & 0 deletions video-func/functions/src/service/VideoService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Timestamp } from "firebase-admin/firestore";

export type VideoMetadata = {
userId: string
videoName: string,
description: string,
timestamp: Timestamp,
contentType: string,
status: string
};

export interface VideoService {
/**
* Delete video metadata in the db.
* @param videoId videoId of the video to delete
* @returns void
*/
deleteVideoMetadata(videoId: string): Promise<void>;

/**
* Deletes the stored video file
* @param videoFileName videoId of the video file to delete
* @returns void
*/
deleteVideoFile(videoFileName: string): Promise<void>;

/**
* Retrieve video metadata from db
* @param videoId videoId of the video to retrieve metadata for
* @returns video metadata as an object
*/
getVideoMetatadata(videoId: string): Promise<any>

/**
* Save video metadata to storage.
* @param videoFileName video file name
* @param videoMetadata object containing video metadata
*/
saveVideoMetadata(videoFileName: string, videoMetadata: VideoMetadata): Promise<void>

/**
* Create a signed url for uploading video file to storage bucket
* @param videoFileName video file name to create signed url for
* @param contentType content type of video file
* @returns signed url as a string
*/
generateSignedUrlForUpload(videoFileName: string, contentType: string): Promise<string>

}

51 changes: 51 additions & 0 deletions video-func/functions/src/test/cloudFunc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as sinon from 'sinon';
import * as mocha from "mocha"
import * as ServiceImpl from '../service/FirebaseVideoService';
import { VideoMetadata } from '../service/VideoService';
import { Timestamp } from 'firebase-admin/firestore';

const videoServiceStub = sinon.createStubInstance(ServiceImpl.FirebaseVideoService)

sinon.stub(ServiceImpl, "FirebaseVideoService").callsFake((args) => {
return videoServiceStub
})

const cloudFunctions = require('../index');
describe("Delete video function", function () {

beforeEach(() => {
const fakeResolutionToVideoId = new Map()
fakeResolutionToVideoId.set("360", "www.test.com")
const fakeVideoData = {
userId: "user",
videoName: "Cool video",
description: "This is a cool video",
timestamp: Timestamp.now(),
contentType: "application/mp3",
status: "Processed",
resolutionToVideoId: fakeResolutionToVideoId
}

videoServiceStub.getVideoMetatadata.withArgs("ABC").returns(Promise.resolve(fakeVideoData))
});

afterEach(() => {
sinon.resetHistory()
});


it("deletes video metadata and video file", async function () {
const VIDEO_ID = "ABC"
await cloudFunctions.deleteVideo(VIDEO_ID)
assert(videoServiceStub.deleteVideoMetadata.called)
assert(videoServiceStub.deleteVideoFile.calledOnce)
assert(videoServiceStub.deleteVideoFile.calledWith("360_" + VIDEO_ID), "called with wrong arg")
});

})

function assert(condition: boolean, message = "Assertion failed") {
if (!condition) {
throw new Error(message);
}
}
54 changes: 0 additions & 54 deletions video-func/functions/src/test/index.test.ts

This file was deleted.

0 comments on commit 38a2faa

Please sign in to comment.