@uppy/companion: improve Zoom folder structure (#5739)

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
This commit is contained in:
Merlijn Vos 2025-05-20 11:26:52 +02:00 committed by GitHub
parent 0b8b5ee9e8
commit d8d1e9b5fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 261 additions and 3081 deletions

View file

@ -6,8 +6,7 @@
"body-parser": "^1.20.3",
"express": "^4.19.2",
"express-session": "^1.15.6",
"light-server": "^2.4.0",
"upload-server": "^1.1.6"
"light-server": "^2.4.0"
},
"license": "ISC",
"main": "index.js",

View file

@ -59,6 +59,7 @@
"morgan": "1.10.0",
"ms": "2.1.3",
"node-schedule": "2.1.1",
"p-map": "^7.0.3",
"prom-client": "15.1.2",
"serialize-error": "^11.0.0",
"serialize-javascript": "^6.0.0",

View file

@ -1,7 +1,5 @@
const moment = require('moment-timezone')
const DEFAULT_RANGE_MOS = 23
const MIMETYPES = {
MP4: 'video/mp4',
M4A: 'audio/mp4',
@ -28,40 +26,10 @@ const ICONS = {
TIMELINE: 'file',
}
const getDateName = (start, end) => {
return `${start.format('YYYY-MM-DD')} - ${end.format('YYYY-MM-DD')}`
}
const getAccountCreationDate = (results) => {
return moment.utc(results.created_at)
}
const getUserEmail = (results) => {
return results.email
}
const getDateFolderId = (start, end) => {
return `${start.format('YYYY-MM-DD')}_${end.format('YYYY-MM-DD')}`
}
const getDateFolderRequestPath = (start, end) => {
return `?from=${start.format('YYYY-MM-DD')}&to=${end.format('YYYY-MM-DD')}`
}
const getDateFolderModified = (end) => {
return end.format('YYYY-MM-DD')
}
const getDateNextPagePath = (end) => {
return `?cursor=${end.format('YYYY-MM-DD')}`
}
const getNextPagePath = (results) => {
if (results.next_page_token) {
return `?cursor=${results.next_page_token}&from=${results.from}&to=${results.to}`
}
return null
}
// we rely on the file_type attribute to differentiate a recording file from other items
const getIsFolder = (item) => {
return !item.file_type
@ -135,67 +103,31 @@ const getItemTopic = (item) => {
return item.topic
}
exports.initializeData = (body, initialEnd = null) => {
let end = initialEnd || moment.utc().tz(body.timezone || 'UTC')
const accountCreation = getAccountCreationDate(body).tz(body.timezone || 'UTC').startOf('day')
const defaultLimit = end.clone().subtract(DEFAULT_RANGE_MOS, 'months').date(1).startOf('day')
const allResultsShown = accountCreation > defaultLimit
const limit = allResultsShown ? accountCreation : defaultLimit
// if the limit is mid-month, keep that exact date
let start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
const data = {
items: [],
username: getUserEmail(body),
}
while (end.isAfter(limit)) {
data.items.push({
isFolder: true,
icon: 'folder',
name: getDateName(start, end),
mimeType: null,
id: getDateFolderId(start, end),
thumbnail: null,
requestPath: getDateFolderRequestPath(start, end),
modifiedDate: getDateFolderModified(end),
size: null,
})
end = start.clone().subtract(1, 'days').endOf('day')
start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
}
data.nextPagePath = allResultsShown ? null : getDateNextPagePath(end)
return data
}
exports.adaptData = (userResponse, results, query) => {
exports.adaptData = (userResponse, results) => {
if (!results) {
return { items: [] }
}
// we query the zoom api by date (from 00:00 - 23:59 UTC) which may include
// extra results for 00:00 - 23:59 local time that we want to filter out.
const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
const data = {
nextPagePath: getNextPagePath(results),
nextPagePath: null,
items: [],
username: getUserEmail(userResponse),
}
let items = []
let itemsToProcess = []
if (results.meetings) {
items = results.meetings
.map(item => { return { ...item, utcStart: moment.utc(item.start_time) } })
.filter(item => moment.utc(item.start_time).isAfter(utcFrom) && moment.utc(item.start_time).isBefore(utcTo))
} else {
items = results.recording_files
itemsToProcess = results.meetings
.filter(meeting =>
meeting.recording_files &&
meeting.recording_files.some(file => !file.deleted_time && file.status === 'completed' && file.download_url)
)
} else if (results.recording_files) {
itemsToProcess = results.recording_files
.map(item => { return { ...item, topic: results.topic } })
.filter(file => file.file_type !== 'TIMELINE')
.filter(file => file.file_type !== 'TIMELINE' && !file.deleted_time && file.status === 'completed' && file.download_url)
}
items.forEach(item => {
itemsToProcess.forEach(item => {
data.items.push({
isFolder: getIsFolder(item),
icon: getIcon(item),

View file

@ -1,12 +1,14 @@
const moment = require('moment-timezone')
const Provider = require('../Provider')
const { initializeData, adaptData } = require('./adapter')
const { adaptData } = require('./adapter')
const { withProviderErrorHandling } = require('../providerErrors')
const { prepareStream, getBasicAuthHeader } = require('../../helpers/utils')
const got = require('../../got')
const pMap = import('p-map')
const BASE_URL = 'https://zoom.us/v2'
const PAGE_SIZE = 300
const DEAUTH_EVENT_NAME = 'app_deauthorized'
@ -34,50 +36,78 @@ class Zoom extends Provider {
return 'zoom'
}
/*
- returns list of months by default
- drill down for specific files in each month
*/
async list (options) {
return this.#withErrorHandling('provider.zoom.list.error', async () => {
const { token } = options
const query = options.query || {}
const { cursor, from, to } = query
const meetingId = options.directory || ''
const requestedYear = query.year ? parseInt(query.year, 10) : null
const client = await getClient({ token })
const user = await client.get('users/me', { responseType: 'json' }).json()
const { timezone } = user
if (!from && !to && !meetingId) {
const end = cursor && moment.utc(cursor).endOf('day').tz(timezone || 'UTC')
return initializeData(user, end)
}
if (from && to) {
/* we need to convert local datetime to UTC date for Zoom query
eg: user in PST (UTC-08:00) wants 2020-08-01 (00:00) to 2020-08-31 (23:59)
=> in UTC, that's 2020-07-31 (16:00) to 2020-08-31 (15:59)
*/
const searchParams = {
page_size: PAGE_SIZE,
from: moment.tz(from, timezone || 'UTC').startOf('day').tz('UTC').format('YYYY-MM-DD'),
to: moment.tz(to, timezone || 'UTC').endOf('day').tz('UTC').format('YYYY-MM-DD'),
}
if (cursor) searchParams.next_page_token = cursor
const meetingsInfo = await client.get('users/me/recordings', { searchParams, responseType: 'json' }).json()
return adaptData(user, meetingsInfo, query)
}
const userTz = timezone || 'UTC'
if (meetingId) {
const recordingInfo = await client.get(`meetings/${encodeURIComponent(meetingId)}/recordings`, { responseType: 'json' }).json()
return adaptData(user, recordingInfo, query)
return adaptData(user, recordingInfo)
}
throw new Error('Invalid list() arguments')
if (requestedYear) {
const now = moment.tz(userTz)
const numMonths = now.get('year') === requestedYear ? now.get('month') + 1 : 12
const monthsToCheck = Array.from({ length: numMonths }, (_, i) => i) // in moment, months are 0-indexed
// Run each month in parallel:
const allMeetingsInYear = (await (await pMap).default(monthsToCheck, async (month) => {
const startDate = moment.tz({ year: requestedYear, month, day: 1 }, userTz).startOf('month')
const endDate = startDate.clone().endOf('month')
const searchParams = {
page_size: PAGE_SIZE,
from: startDate.clone().tz('UTC').format('YYYY-MM-DD'),
to: endDate.clone().tz('UTC').format('YYYY-MM-DD'),
}
const paginatedMeetings = []
do {
const currentChunkMeetingsInfo = await client.get('users/me/recordings', { searchParams, responseType: 'json' }).json()
paginatedMeetings.push(...(currentChunkMeetingsInfo.meetings ?? []))
searchParams.next_page_token = currentChunkMeetingsInfo.next_page_token
} while (searchParams.next_page_token)
return paginatedMeetings
}, { concurrency: 3 })).flat() // this is effectively a flatMap
// concurrency 3 seems like a sensible number...
const finalResult = { meetings: allMeetingsInYear }
return adaptData(user, finalResult)
}
const accountCreationDate = moment.utc(user.created_at)
const startYear = accountCreationDate.year()
const currentYear = moment.tz(userTz).year()
const years = []
for (let year = currentYear; year >= startYear; year--) {
years.push({
isFolder: true,
icon: 'folder',
name: `${year}`,
mimeType: null,
id: `${year}`,
thumbnail: null,
requestPath: `?year=${year}`,
modifiedDate: `${year}-12-31`, // Representative date
size: null,
})
}
return {
username: user.email,
items: years,
nextPagePath: null,
}
})
}

3154
yarn.lock

File diff suppressed because it is too large Load diff