Skip to content

Commit 914f8f0

Browse files
authored
Scrape Single function to firebase v2 (#2042)
Implemented a v2 version of the scrape single hearing function. Changed the frontend to call the v2 version. Updated the check auth and check admin functions to be able to take a request object for both v1 and v2 firebase functino
1 parent 6e580b8 commit 914f8f0

File tree

5 files changed

+61
-6
lines changed

5 files changed

+61
-6
lines changed

components/moderation/ScrapeHearing.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const scrapeSingleHearing = httpsCallable<
1616
ScrapeHearingResponse
1717
>(functions, "scrapeSingleHearing")
1818

19+
const scrapeSingleHearingv2 = httpsCallable<
20+
ScrapeHearingRequest,
21+
ScrapeHearingResponse
22+
>(functions, "scrapeSingleHearingv2")
23+
1924
export const ScrapeHearingForm = () => {
2025
const [eventId, setEventId] = useState("")
2126
const [loading, setLoading] = useState(false)
@@ -39,7 +44,7 @@ export const ScrapeHearingForm = () => {
3944

4045
setLoading(true)
4146
try {
42-
const response = await scrapeSingleHearing({ eventId: parsedEventId })
47+
const response = await scrapeSingleHearingv2({ eventId: parsedEventId })
4348
setResult({
4449
type: "success",
4550
message: `${response.data.message} (ID: ${response.data.hearingId})`

functions/src/common.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FieldValue } from "@google-cloud/firestore"
22
import axios from "axios"
33
import { https, logger } from "firebase-functions"
4+
import { CallableRequest } from "firebase-functions/v2/https"
45
import {
56
Null,
67
Nullish,
@@ -38,7 +39,7 @@ export function checkRequestZod<T extends ZodTypeAny>(
3839

3940
/** Return the authenticated user's id or fail if they are not authenticated. */
4041
export function checkAuth(
41-
context: https.CallableContext,
42+
context: https.CallableContext | CallableRequest,
4243
checkEmailVerification = false
4344
) {
4445
const uid = context.auth?.uid
@@ -61,7 +62,7 @@ export function checkAuth(
6162
/**
6263
* Checks that the caller is an admin.
6364
*/
64-
export function checkAdmin(context: https.CallableContext) {
65+
export function checkAdmin(context: https.CallableContext | CallableRequest) {
6566
const callerRole = context.auth?.token.role
6667
if (callerRole !== "admin") {
6768
throw fail("permission-denied", "You must be an admin")

functions/src/events/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./scrapeEvents"
22
export { scrapeSingleHearing } from "./scrapeEvents"
3+
export { scrapeSingleHearingv2 } from "./scrapeEvents"

functions/src/events/scrapeEvents.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import * as functions from "firebase-functions"
2-
import { RuntimeOptions, runWith } from "firebase-functions"
1+
import * as functions from "firebase-functions/v1"
2+
import { RuntimeOptions, runWith } from "firebase-functions/v1"
3+
import { onCall, CallableRequest } from "firebase-functions/v2/https"
34
import { DateTime } from "luxon"
45
import { JSDOM } from "jsdom"
56
import { AssemblyAI } from "assemblyai"
@@ -476,6 +477,52 @@ export const scrapeSingleHearing = functions
476477
}
477478
})
478479

480+
export const scrapeSingleHearingv2 = onCall(
481+
{ timeoutSeconds: 480, memory: "4GiB", secrets: ["ASSEMBLY_API_KEY"] },
482+
async (request: CallableRequest) => {
483+
// Require admin authentication
484+
// Check how to integrate the new object with these helper functions
485+
checkAuth(request, false)
486+
checkAdmin(request)
487+
488+
const { eventId } = request.data
489+
490+
if (!eventId || typeof eventId !== "number") {
491+
throw new functions.https.HttpsError(
492+
"invalid-argument",
493+
"The function must be called with a valid eventId (number)."
494+
)
495+
}
496+
497+
try {
498+
// Create a temporary scraper instance to reuse the existing logic
499+
const scraper = new HearingScraper()
500+
const hearing = await scraper.getEvent(
501+
{ EventId: eventId },
502+
{ ignoreCutoff: true }
503+
)
504+
505+
// Save the hearing to Firestore
506+
await db.doc(`/events/${hearing.id}`).set(hearing, { merge: true })
507+
508+
console.log(`Successfully scraped hearing ${eventId}`, hearing)
509+
510+
return {
511+
status: "success",
512+
message: `Successfully scraped hearing ${eventId}`,
513+
hearingId: hearing.id
514+
}
515+
} catch (error: any) {
516+
console.error(`Failed to scrape hearing ${eventId}:`, error)
517+
throw new functions.https.HttpsError(
518+
"internal",
519+
`Failed to scrape hearing ${eventId}`,
520+
{ details: error.message }
521+
)
522+
}
523+
}
524+
)
525+
479526
export const scrapeSpecialEvents = new SpecialEventsScraper().function
480527
export const scrapeSessions = new SessionScraper().function
481528
export const scrapeHearings = new HearingScraper().function

functions/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export {
1919
scrapeHearings,
2020
scrapeSessions,
2121
scrapeSpecialEvents,
22-
scrapeSingleHearing
22+
scrapeSingleHearing,
23+
scrapeSingleHearingv2
2324
} from "./events"
2425
export {
2526
syncHearingToSearchIndex,

0 commit comments

Comments
 (0)