Skip to content

Commit

Permalink
Feat: add import image features to move all images to OpenPlanner sto…
Browse files Browse the repository at this point in the history
…rage (#135)

* Add import images feature when opening the sessions

* Review
  • Loading branch information
HugoGresse authored Jul 24, 2024
1 parent 95db078 commit 8bcf0e9
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 13 deletions.
11 changes: 11 additions & 0 deletions src/events/actions/speakers/updateSpeaker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { doc, updateDoc } from 'firebase/firestore'
import { Speaker } from '../../../types'
import { collections } from '../../../services/firebase'

export const updateSpeaker = async (eventId: string, speaker: Partial<Speaker>): Promise<void> => {
const ref = doc(collections.speakers(eventId), speaker.id)

return await updateDoc(ref, {
...speaker,
})
}
175 changes: 175 additions & 0 deletions src/events/actions/speakers/useMoveAllImagesToOpenPlannerStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { Event, Session, Speaker } from '../../../types'
import { baseStorageUrl } from '../../../services/firebase'
import { uploadImage } from '../../../utils/images/uploadImage'
import { updateSpeaker } from './updateSpeaker'
import { useCallback, useEffect, useState } from 'react'

export const useMoveAllImagesToOpenPlannerStorage = (event: Event, sessions: Session[]) => {
const [state, setState] = useState<{
shouldUpdateImageStorage: boolean
isLoading: boolean
error: string[]
isError: boolean
progress: number
total: number
isTotalCalculated: boolean
}>({
shouldUpdateImageStorage: false,
isLoading: false,
error: [],
isError: false,
progress: 0,
total: 0,
isTotalCalculated: false,
})

useEffect(() => {
if (!state.isTotalCalculated && sessions.length > 0) {
const total = getTotalImagesToChange(sessions)
setState((newState) => ({
...newState,
total: total,
shouldUpdateImageStorage: total > 0,
isTotalCalculated: true,
}))
}
}, [sessions])

const moveAllImagesToOpenPlannerStorage = useCallback(async () => {
setState((newState) => ({
...newState,
isLoading: true,
error: [],
isError: false,
progress: 0,
}))
const errors: string[] = []
for (const session of sessions) {
if (session.speakersData?.length) {
for (const speaker of session.speakersData) {
let i = 0
const shouldUpdatePhotoUrl = speaker.photoUrl && !speaker.photoUrl.startsWith(baseStorageUrl)
const shouldUpdateCompanyLogoUrl =
speaker.companyLogoUrl && !speaker.companyLogoUrl.startsWith(baseStorageUrl)

if (shouldUpdatePhotoUrl) {
const result = await downloadAndUpdateSpeakerImage(event, speaker, 'photoUrl')
if (result.success) {
i++
} else {
errors.push(result.error)
}
}

if (shouldUpdateCompanyLogoUrl) {
const result = await downloadAndUpdateSpeakerImage(event, speaker, 'companyLogoUrl')
if (result.success) {
i++
} else {
errors.push(result.error)
}
}

setState((newState) => ({
...newState,
progress: newState.progress + i,
error: errors,
isError: errors.length > 0,
}))
}
}
}

setState((newState) => ({
...newState,
isLoading: false,
}))
}, [sessions])

return {
...state,
moveAllImagesToOpenPlannerStorage,
}
}

const getTotalImagesToChange = (sessions: Session[]) => {
return sessions.reduce((acc, session) => {
if (session.speakersData?.length) {
return session.speakersData.reduce((acc, speaker) => {
const shouldUpdatePhotoUrl = speaker.photoUrl && !speaker.photoUrl.startsWith(baseStorageUrl)
const shouldUpdateCompanyLogoUrl =
speaker.companyLogoUrl && !speaker.companyLogoUrl.startsWith(baseStorageUrl)

if (shouldUpdatePhotoUrl && !shouldUpdateCompanyLogoUrl) {
return acc + 1
}
if (shouldUpdateCompanyLogoUrl && !shouldUpdatePhotoUrl) {
return acc + 1
}
if (shouldUpdatePhotoUrl && shouldUpdateCompanyLogoUrl) {
return acc + 2
}
return acc
}, acc)
}

return acc
}, 0)
}

const downloadAndUpdateSpeakerImage = async (
event: Event,
speaker: Speaker,
fieldName: 'photoUrl' | 'companyLogoUrl'
): Promise<
| {
success: true
error: null
}
| {
success: false
error: string
}
> => {
try {
const imageToDownload = speaker[fieldName]
if (!imageToDownload) {
return {
success: true,
error: null,
}
}
const imageFetchResult = await fetch(imageToDownload)
if (!imageFetchResult.ok) {
console.warn(
'Error downloading image',
imageFetchResult.statusText,
imageFetchResult.status,
imageToDownload
)
return {
success: false,
error: `Error downloading image for speaker ${speaker.name}, error: ${imageFetchResult.statusText} (${imageFetchResult.status})`,
}
}

const imageBlob = await imageFetchResult.blob()
const newImageUrl = await uploadImage(event, imageBlob)

await updateSpeaker(event.id, {
id: speaker.id,
[fieldName]: newImageUrl,
})

return {
success: true,
error: null,
}
} catch (error) {
console.error('error downloading or uploading image', error)
return {
success: false,
error: `Error downloading or uploading image for speaker ${speaker.name}, error: ${error}`,
}
}
}
13 changes: 6 additions & 7 deletions src/events/actions/updateWebsiteActions/getFilesNames.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Event, EventFiles } from '../../../types'
import { doc, updateDoc } from 'firebase/firestore'
import { collections, storageBucket } from '../../../services/firebase'
import { baseStorageUrl, collections } from '../../../services/firebase'
import { v4 as uuidv4 } from 'uuid'

export const getFilesNames = async (event: Event): Promise<EventFiles> => {
Expand Down Expand Up @@ -40,12 +40,11 @@ export const getUploadFilePathFromEvent = async (event: Event) => {
}

export const getUploadFilePath = (files: EventFiles) => {
const base = 'https://storage.googleapis.com/'
return {
public: `${base}${storageBucket}/${files.public}`,
private: `${base}${storageBucket}/${files.private}`,
imageFolder: `${base}${storageBucket}/${files.imageFolder}`,
openfeedback: `${base}${storageBucket}/${files.openfeedback}`,
voxxrin: files.voxxrin ? `${base}${storageBucket}/${files.voxxrin}` : null,
public: `${baseStorageUrl}/${files.public}`,
private: `${baseStorageUrl}/${files.private}`,
imageFolder: `${baseStorageUrl}/${files.imageFolder}`,
openfeedback: `${baseStorageUrl}/${files.openfeedback}`,
voxxrin: files.voxxrin ? `${baseStorageUrl}/${files.voxxrin}` : null,
}
}
47 changes: 47 additions & 0 deletions src/events/page/sessions/components/MoveImagesAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Alert, Button, Typography } from '@mui/material'
import * as React from 'react'
import { useMoveAllImagesToOpenPlannerStorage } from '../../../actions/speakers/useMoveAllImagesToOpenPlannerStorage'
import { Event, Session } from '../../../../types'

export const MoveImagesAlert = ({ event, sessionsData }: { event: Event; sessionsData: Session[] }) => {
const moveAllImagesToOpenPlannerStorageState = useMoveAllImagesToOpenPlannerStorage(event, sessionsData)

return (
<>
{moveAllImagesToOpenPlannerStorageState.shouldUpdateImageStorage && (
<Alert variant="filled" severity="info" sx={{ marginBottom: 2 }}>
{moveAllImagesToOpenPlannerStorageState.total} session/speaker images are NOT stored in OpenPlanner.
This may cause issues due to the image not being accessible in some code (ShortVid.io) or being
moved later and making it unavailable in the long term.
<br />
You can migrate the images to OpenPlanner at once now:
<Button
variant="contained"
color="secondary"
disabled={moveAllImagesToOpenPlannerStorageState.isLoading}
onClick={() => {
moveAllImagesToOpenPlannerStorageState.moveAllImagesToOpenPlannerStorage()
}}>
Migrate images{' '}
{moveAllImagesToOpenPlannerStorageState.isLoading
? `${moveAllImagesToOpenPlannerStorageState.progress}/${moveAllImagesToOpenPlannerStorageState.total}`
: ''}
</Button>
{moveAllImagesToOpenPlannerStorageState.error.length > 0 && (
<>
<Typography>Error(s):</Typography>
<ul>
{moveAllImagesToOpenPlannerStorageState.error.map((error) => (
<li key={error}>{error}</li>
))}
</ul>
<Typography>
Common errors could be linked to CORS issues, please check the console for more details.
</Typography>
</>
)}
</Alert>
)}
</>
)
}
9 changes: 4 additions & 5 deletions src/events/page/sessions/list/EventSessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@ import {
Card,
Container,
Divider,
FormControl,
FormControlLabel,
FormGroup,
Grid,
IconButton,
InputAdornment,
InputLabel,
Menu,
MenuItem,
Select,
SelectChangeEvent,
Switch,
TextField,
Typography,
Checkbox,
} from '@mui/material'
import * as React from 'react'
import { ChangeEvent, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import { Event, Session } from '../../../../types'
import { useSessions } from '../../../../services/hooks/useSessions'
import { FirestoreQueryLoaderAndErrorDisplay } from '../../../../components/FirestoreQueryLoaderAndErrorDisplay'
Expand All @@ -37,6 +33,7 @@ import { useSearchParams } from '../../../../hooks/useSearchParams'
import { GenerateSessionsVideoDialog } from '../components/GenerateSessionsVideoDialog'
import { exportSessionsAction, SessionsExportType } from './actions/exportSessionsActions'
import { TeasingPostSocials } from '../../../actions/sessions/generation/generateSessionTeasingContent'
import { MoveImagesAlert } from '../components/MoveImagesAlert'

export type EventSessionsProps = {
event: Event
Expand Down Expand Up @@ -103,6 +100,8 @@ export const EventSessions = ({ event }: EventSessionsProps) => {

return (
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<MoveImagesAlert event={event} sessionsData={sessionsData} />

<Box display="flex" justifyContent="space-between" alignItems="center" flexWrap="wrap" marginBottom={1}>
<Typography>
{isFiltered
Expand Down
1 change: 1 addition & 0 deletions src/services/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const config = {
}

export const storageBucket = config.storageBucket
export const baseStorageUrl = `https://storage.googleapis.com/${storageBucket}`

let instanceApp: FirebaseApp = initializeApp(config)
export const instanceFirestore: Firestore = getFirestore(instanceApp)
Expand Down
2 changes: 1 addition & 1 deletion src/utils/images/uploadImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { storage } from '../../services/firebase'
import { Event } from '../../types'
import { v4 as uuidv4 } from 'uuid'

export const uploadImage = async (event: Event, image: Blob) => {
export const uploadImage = async (event: Event, image: Blob): Promise<string> => {
const fileNames = await getFilesNames(event)
const uploadPaths = await getUploadFilePathFromEvent(event)
const fileName = uuidv4()
Expand Down

0 comments on commit 8bcf0e9

Please sign in to comment.